Skip to content

Commit 04d72e0

Browse files
authored
Create Vue 3 version of the custom LanguageSelector component (#773)
* Create Vue 3 version of the custom LanguageSelector component Bug: T345915 * Update indentation
1 parent 5dcac4a commit 04d72e0

File tree

3 files changed

+165
-184
lines changed

3 files changed

+165
-184
lines changed

resources/js/Components/LanguageSelector.vue

Lines changed: 73 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div class="mismatchfinder__language-selector">
33
<div class="languageSelector__mobile-header">
4-
<span>{{ $i18n( 'language-selector-mobile-header' ) }}</span>
4+
<span>{{ $i18n('language-selector-mobile-header') }}</span>
55
<button @click="onCloseMenu">
66
<img :src="closeUrl" :alt="$i18n( 'language-selector-close-button-label' )">
77
</button>
@@ -25,79 +25,84 @@
2525
@select="onSelect"
2626
>
2727
<template #no-results>
28-
<slot name="no-results" />
28+
<slot name="no-results"/>
2929
</template>
3030
</LanguageSelectorOptionsMenu>
3131
</div>
3232
</template>
3333

34-
<script lang="ts">
35-
import LanguageSelectorInput from '../Components/LanguageSelectorInput.vue';
36-
import LanguageSelectorOptionsMenu from '../Components/LanguageSelectorOptionsMenu.vue';
34+
<script setup lang="ts">
35+
import LanguageSelectorOptionsMenu from "./LanguageSelectorOptionsMenu.vue";
36+
import LanguageSelectorInput from "./LanguageSelectorInput.vue";
3737
import Language from '../types/Language';
38-
import { defineComponent } from 'vue';
39-
import languagedata from '@wikimedia/language-data';
40-
import closeUrl from '../../img/close.svg';
41-
42-
export default defineComponent( {
43-
name: 'LanguageSelector',
44-
components: {
45-
LanguageSelectorInput,
46-
LanguageSelectorOptionsMenu,
47-
},
48-
data: () => ( {
49-
searchInput: '',
50-
highlightedIndex: -1,
51-
closeUrl,
52-
} ),
53-
computed: {
54-
languages(): Language[] {
55-
const autonyms = languagedata.getAutonyms();
56-
const languageCodes = Object.keys( autonyms );
57-
languageCodes.sort( languagedata.sortByAutonym );
58-
return languageCodes.map( ( code ) => ( {
59-
code,
60-
autonym: autonyms[ code ],
61-
} ) );
62-
},
63-
shownLanguages(): Language[] {
64-
return this.languages.filter( ( language ) =>
65-
language.code.startsWith( this.searchInput.toLowerCase() ) ||
66-
language.autonym.toLowerCase().includes( this.searchInput.toLowerCase() ),
67-
);
68-
},
69-
},
70-
methods: {
71-
onInput( searchedLanguage: string ): void {
72-
this.searchInput = searchedLanguage;
73-
this.highlightedIndex = 0;
74-
},
75-
onSelect( languageCode: string ): void {
76-
this.$emit( 'select', languageCode );
77-
},
78-
onClearInputValue(): void {
79-
this.searchInput = '';
80-
},
81-
onCloseMenu(): void {
82-
this.$emit( 'close' );
83-
},
84-
// eslint-disable-next-line vue/no-unused-properties -- exported method
85-
focus(): void {
86-
const inputRef = this.$refs.input as InstanceType<typeof HTMLInputElement>
87-
inputRef.focus();
88-
},
89-
onArrowDown(): void {
90-
this.highlightedIndex = ( this.highlightedIndex + 1 ) % this.shownLanguages.length;
91-
},
92-
onArrowUp(): void {
93-
const length = this.shownLanguages.length;
94-
this.highlightedIndex = ( this.highlightedIndex + length - 1 ) % length;
95-
},
96-
onEnter(): void {
97-
this.onSelect( this.shownLanguages[ this.highlightedIndex ].code );
98-
},
99-
},
100-
} );
38+
import closeUrlSvg from '../../img/close.svg';
39+
40+
import {ref, computed} from "vue";
41+
import type {Ref} from 'vue';
42+
import languageData from "@wikimedia/language-data";
43+
44+
const searchInput: Ref<string> = ref('');
45+
const highlightedIndex: Ref<number> = ref(-1);
46+
const closeUrl = ref(closeUrlSvg);
47+
48+
const input = ref<InstanceType<typeof LanguageSelectorInput> | null>(null);
49+
50+
const emit = defineEmits(['select', 'close']);
51+
52+
const languages = computed<Language[]>(() => {
53+
const autonyms = languageData.getAutonyms();
54+
const languageCodes = Object.keys(autonyms);
55+
languageCodes.sort(languageData.sortByAutonym);
56+
return languageCodes.map((code) => ({
57+
code,
58+
autonym: autonyms[code],
59+
}));
60+
});
61+
62+
const shownLanguages = computed<Language[]>(() => {
63+
return languages.value.filter((language) =>
64+
language.code.startsWith(searchInput.value.toLowerCase()) ||
65+
language.autonym.toLowerCase().includes(searchInput.value.toLowerCase()),
66+
);
67+
});
68+
69+
function onInput(searchedLanguage: string): void {
70+
searchInput.value = searchedLanguage;
71+
highlightedIndex.value = 0;
72+
}
73+
74+
function onSelect(languageCode: string): void {
75+
emit('select', languageCode);
76+
}
77+
78+
function onClearInputValue(): void {
79+
searchInput.value = '';
80+
}
81+
82+
function onCloseMenu(): void {
83+
emit('close');
84+
}
85+
86+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
87+
function focus(): void {
88+
input.value?.focus();
89+
}
90+
91+
function onArrowDown(): void {
92+
highlightedIndex.value = (highlightedIndex.value + 1) % shownLanguages.value.length;
93+
}
94+
95+
function onArrowUp(): void {
96+
const length = shownLanguages.value.length;
97+
highlightedIndex.value = (highlightedIndex.value + length - 1) % length;
98+
}
99+
100+
function onEnter(): void {
101+
onSelect(shownLanguages.value[highlightedIndex.value].code)
102+
}
103+
104+
defineExpose({focus})
105+
101106
</script>
102107

103108
<style lang="scss">

resources/js/Components/LanguageSelectorInput.vue

Lines changed: 42 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
:value="value"
1212
:placeholder="placeholder"
1313
@input="onInput"
14-
@keydown.tab="onTab"
15-
@keydown.down.prevent="onArrowDown"
16-
@keydown.up.prevent="onArrowUp"
17-
@keydown.enter="onEnter"
18-
@keydown.esc.prevent="onEscape"
14+
@keydown.tab="$emit('tab')"
15+
@keydown.down.prevent="$emit('arrowDown')"
16+
@keydown.up.prevent="$emit('arrowUp')"
17+
@keydown.enter="$emit('enter')"
18+
@keydown.esc.prevent="$emit('escape')"
1919
>
2020
</div>
2121
<button
@@ -28,63 +28,41 @@
2828
</div>
2929
</template>
3030

31-
<script lang="ts">
32-
import { defineComponent } from 'vue';
33-
import searchUrl from '../../img/search.svg';
34-
import clearUrl from '../../img/clear.svg';
35-
36-
export default defineComponent( {
37-
name: 'LanguageSelectorInput',
38-
props: {
39-
value: {
40-
type: String,
41-
default: '',
42-
},
43-
placeholder: {
44-
type: String,
45-
default: '',
46-
},
47-
},
48-
data() {
49-
return {
50-
searchUrl,
51-
clearUrl,
52-
};
53-
},
54-
computed: {
55-
clearBtnVisible(): boolean {
56-
return this.value.length > 0;
57-
},
58-
},
59-
methods: {
60-
onClearInputValue(): void {
61-
this.$emit( 'clear' );
62-
this.focus();
63-
},
64-
65-
focus(): void {
66-
( this.$refs.input as HTMLInputElement ).focus();
67-
},
68-
onArrowDown() {
69-
this.$emit( 'arrowDown' );
70-
},
71-
onArrowUp() {
72-
this.$emit( 'arrowUp' );
73-
},
74-
onEnter() {
75-
this.$emit( 'enter' );
76-
},
77-
onEscape() {
78-
this.$emit( 'escape' );
79-
},
80-
onInput( event : Event ) {
81-
this.$emit( 'input', (event.target as HTMLInputElement).value );
82-
},
83-
onTab() {
84-
this.$emit( 'tab' );
85-
},
86-
},
87-
} );
31+
<script setup lang="ts">
32+
import {ref, computed} from 'vue';
33+
import searchUrlSvg from '../../img/search.svg';
34+
import clearUrlSvg from '../../img/clear.svg';
35+
36+
const props = defineProps<{
37+
value: string,
38+
placeholder: string
39+
}>();
40+
41+
const emit = defineEmits(['clear', 'arrowDown', 'arrowUp', 'enter', 'escape', 'input', 'tab'])
42+
43+
const searchUrl = ref(searchUrlSvg);
44+
const clearUrl = ref(clearUrlSvg);
45+
46+
const input = ref<HTMLInputElement | null>(null);
47+
48+
const clearBtnVisible = computed<boolean>(() => {
49+
return props.value.length > 0;
50+
})
51+
52+
function focus(): void {
53+
(input.value as HTMLInputElement).focus();
54+
}
55+
56+
function onClearInputValue(): void {
57+
emit('clear');
58+
focus();
59+
}
60+
61+
function onInput(event: Event) {
62+
emit('input', (event.target as HTMLInputElement).value);
63+
}
64+
65+
defineExpose({focus});
8866
</script>
8967

9068
<style lang="scss">
@@ -123,8 +101,8 @@ export default defineComponent( {
123101
}
124102
125103
&-left-side {
126-
display:flex;
127-
flex-grow:1;
104+
display: flex;
105+
flex-grow: 1;
128106
}
129107
130108
&__search-icon {

0 commit comments

Comments
 (0)