Client - handle keyboard navigation on timezone selection in preferences
This commit is contained in:
parent
52964b4045
commit
07c8611304
@ -7,19 +7,35 @@
|
|||||||
:value="timezone"
|
:value="timezone"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
required
|
required
|
||||||
@keydown.esc="onUpdateTimezone(input)"
|
role="combobox"
|
||||||
|
aria-autocomplete="list"
|
||||||
|
aria-controls="tz-dropdown-list"
|
||||||
|
:aria-expanded="isOpen"
|
||||||
|
@keydown.esc="cancelUpdate()"
|
||||||
@keydown.enter="onEnter"
|
@keydown.enter="onEnter"
|
||||||
@input="openDropdown"
|
@input="openDropdown"
|
||||||
|
@blur="closeDropdown()"
|
||||||
|
@keydown.down="onKeyDown()"
|
||||||
|
@keydown.up="onKeyUp()"
|
||||||
/>
|
/>
|
||||||
<ul class="tz-dropdown-list" v-if="isOpen" ref="tzList">
|
<ul
|
||||||
|
class="tz-dropdown-list"
|
||||||
|
id="tz-dropdown-list"
|
||||||
|
v-if="isOpen"
|
||||||
|
role="listbox"
|
||||||
|
tabindex="-1"
|
||||||
|
:aria-label="$t('user.PROFILE.TIMEZONE', 0)"
|
||||||
|
>
|
||||||
<li
|
<li
|
||||||
v-for="(tz, index) in timeZones.filter((t) => matchTimezone(t))"
|
v-for="(tz, index) in filteredTimezones"
|
||||||
:key="tz"
|
:key="tz"
|
||||||
|
:id="`tz-dropdown-item-${index}`"
|
||||||
class="tz-dropdown-item"
|
class="tz-dropdown-item"
|
||||||
:class="{ focus: index === focusItemIndex }"
|
:class="{ focus: index === focusItemIndex }"
|
||||||
@click="onUpdateTimezone(tz)"
|
@click="onUpdateTimezone(index)"
|
||||||
@mouseover="onMouseOver(index)"
|
@mouseover="onMouseOver(index)"
|
||||||
:autofocus="index === focusItemIndex"
|
:autofocus="index === focusItemIndex"
|
||||||
|
role="option"
|
||||||
>
|
>
|
||||||
{{ tz }}
|
{{ tz }}
|
||||||
</li>
|
</li>
|
||||||
@ -28,8 +44,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, toRefs, watch } from 'vue'
|
import { computed, ref, toRefs, watch } from 'vue'
|
||||||
import type { Ref } from 'vue'
|
import type { ComputedRef, Ref } from 'vue'
|
||||||
|
|
||||||
import { timeZones } from '@/utils/timezone'
|
import { timeZones } from '@/utils/timezone'
|
||||||
|
|
||||||
@ -46,8 +62,10 @@
|
|||||||
const { input, disabled } = toRefs(props)
|
const { input, disabled } = toRefs(props)
|
||||||
const timezone: Ref<string> = ref(input.value)
|
const timezone: Ref<string> = ref(input.value)
|
||||||
const isOpen: Ref<boolean> = ref(false)
|
const isOpen: Ref<boolean> = ref(false)
|
||||||
const tzList: Ref<HTMLInputElement | null> = ref(null)
|
|
||||||
const focusItemIndex: Ref<number> = ref(0)
|
const focusItemIndex: Ref<number> = ref(0)
|
||||||
|
const filteredTimezones: ComputedRef<string[]> = computed(() =>
|
||||||
|
input.value ? timeZones.filter((t) => matchTimezone(t)) : timeZones
|
||||||
|
)
|
||||||
|
|
||||||
function matchTimezone(t: string): RegExpMatchArray | null {
|
function matchTimezone(t: string): RegExpMatchArray | null {
|
||||||
return t.toLowerCase().match(timezone.value.toLowerCase())
|
return t.toLowerCase().match(timezone.value.toLowerCase())
|
||||||
@ -55,15 +73,17 @@
|
|||||||
function onMouseOver(index: number) {
|
function onMouseOver(index: number) {
|
||||||
focusItemIndex.value = index
|
focusItemIndex.value = index
|
||||||
}
|
}
|
||||||
function onUpdateTimezone(value: string) {
|
function onUpdateTimezone(index: number) {
|
||||||
timezone.value = value
|
if (filteredTimezones.value.length > index) {
|
||||||
isOpen.value = false
|
timezone.value = filteredTimezones.value[index]
|
||||||
emit('updateTimezone', value)
|
emit('updateTimezone', timezone.value)
|
||||||
|
isOpen.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function onEnter(event: Event) {
|
function onEnter(event: Event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (tzList.value?.firstElementChild?.innerHTML) {
|
if (filteredTimezones.value.length > 0) {
|
||||||
onUpdateTimezone(tzList.value?.firstElementChild?.innerHTML)
|
onUpdateTimezone(focusItemIndex.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function openDropdown(event: Event) {
|
function openDropdown(event: Event) {
|
||||||
@ -71,6 +91,42 @@
|
|||||||
isOpen.value = true
|
isOpen.value = true
|
||||||
timezone.value = (event.target as HTMLInputElement).value.trim()
|
timezone.value = (event.target as HTMLInputElement).value.trim()
|
||||||
}
|
}
|
||||||
|
function closeDropdown() {
|
||||||
|
onUpdateTimezone(focusItemIndex.value)
|
||||||
|
}
|
||||||
|
function scrollIntoOption(index: number) {
|
||||||
|
const option = document.getElementById(`tz-dropdown-item-${index}`)
|
||||||
|
if (option) {
|
||||||
|
option.focus()
|
||||||
|
option.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onKeyDown() {
|
||||||
|
isOpen.value = true
|
||||||
|
focusItemIndex.value =
|
||||||
|
focusItemIndex.value === null ? 0 : (focusItemIndex.value += 1)
|
||||||
|
if (focusItemIndex.value >= filteredTimezones.value.length) {
|
||||||
|
focusItemIndex.value = 0
|
||||||
|
}
|
||||||
|
scrollIntoOption(focusItemIndex.value)
|
||||||
|
}
|
||||||
|
function onKeyUp() {
|
||||||
|
isOpen.value = true
|
||||||
|
focusItemIndex.value =
|
||||||
|
focusItemIndex.value === null
|
||||||
|
? filteredTimezones.value.length - 1
|
||||||
|
: (focusItemIndex.value -= 1)
|
||||||
|
if (focusItemIndex.value <= -1) {
|
||||||
|
focusItemIndex.value = filteredTimezones.value.length - 1
|
||||||
|
}
|
||||||
|
scrollIntoOption(focusItemIndex.value)
|
||||||
|
}
|
||||||
|
function cancelUpdate() {
|
||||||
|
if (isOpen.value) {
|
||||||
|
isOpen.value = false
|
||||||
|
timezone.value = input.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.input,
|
() => props.input,
|
||||||
|
@ -123,7 +123,7 @@
|
|||||||
"LIGHT": "Light"
|
"LIGHT": "Light"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TIMEZONE": "Timezone",
|
"TIMEZONE": "Timezone | Timezones",
|
||||||
"UNITS": {
|
"UNITS": {
|
||||||
"IMPERIAL": "Imperial system (ft, mi, mph, °F)",
|
"IMPERIAL": "Imperial system (ft, mi, mph, °F)",
|
||||||
"LABEL": "Units for distance",
|
"LABEL": "Units for distance",
|
||||||
|
@ -123,7 +123,7 @@
|
|||||||
"LIGHT": "Clair"
|
"LIGHT": "Clair"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TIMEZONE": "Fuseau horaire",
|
"TIMEZONE": "Fuseau horaire | Fuseaux horaires",
|
||||||
"UNITS": {
|
"UNITS": {
|
||||||
"IMPERIAL": "Système impérial (ft, mi, mph, °F)",
|
"IMPERIAL": "Système impérial (ft, mi, mph, °F)",
|
||||||
"LABEL": "Unités pour les distances",
|
"LABEL": "Unités pour les distances",
|
||||||
|
Loading…
Reference in New Issue
Block a user