Skip to content

Commit

Permalink
fix(kselect): arrow down navigation [KHCP-12967] (#2358)
Browse files Browse the repository at this point in the history
fix(kselect): arrow down navigation [KHCP-12967]
  • Loading branch information
portikM authored Sep 4, 2024
1 parent 11842e3 commit 62fbe64
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 2 deletions.
28 changes: 27 additions & 1 deletion src/components/KSelect/KSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
data-propagate-clicks="false"
>
<KSelectItems
ref="kSelectItems"
:items="filteredItems"
@selected="handleItemSelect"
>
Expand Down Expand Up @@ -212,6 +213,7 @@ import { ChevronDownIcon, CloseIcon, ProgressIcon } from '@kong/icons'
import { ResizeObserverHelper } from '@/utilities/resizeObserverHelper'
import { sanitizeInput } from '@/utilities/sanitizeInput'
import useUniqueId from '@/composables/useUniqueId'
import { useEventListener } from '@vueuse/core'
defineOptions({
inheritAttrs: false,
Expand Down Expand Up @@ -347,6 +349,8 @@ const emit = defineEmits<{
const attrs = useAttrs()
const slots = useSlots()
const isDropdownOpen = ref<boolean>(false)
const resizeObserver = ref<ResizeObserverHelper>()
const hasLabelTooltip = computed((): boolean => !!(props.labelAttributes?.info || slots['label-tooltip']))
Expand Down Expand Up @@ -524,7 +528,9 @@ const clearSelection = (): void => {
emit('update:modelValue', null)
}
const triggerFocus = (evt: any, isToggled: Ref<boolean>):void => {
const kSelectItems = ref<InstanceType<typeof KSelectItems> | null>(null)
const triggerFocus = (evt: any, isToggled: Ref<boolean>): void => {
// Ignore `esc` key
if (evt.keyCode === 27) {
isToggled.value = false
Expand All @@ -535,6 +541,10 @@ const triggerFocus = (evt: any, isToggled: Ref<boolean>):void => {
if (!isToggled.value && inputElem) { // simulate click to trigger dropdown open
inputElem.click()
}
if ((evt.code === 'ArrowDown' || evt.code === 'ArrowUp') && isToggled.value) {
kSelectItems.value?.setFocus()
}
}
const onQueryChange = (query: string) => {
Expand Down Expand Up @@ -579,18 +589,24 @@ const onPopoverClick = (toggle: () => void) => {
}
const onClose = (toggle: () => void, isToggled: boolean) => {
isDropdownOpen.value = false
if (selectedItem.value) {
filterQuery.value = selectedItem.value.label
}
if (isToggled) {
toggle()
}
}
const onOpen = (toggle: () => void) => {
isDropdownOpen.value = true
if (props.enableFiltering) {
filterQuery.value = ''
}
toggle()
}
Expand Down Expand Up @@ -704,6 +720,16 @@ onMounted(() => {
}
setLabelAttributes()
useEventListener(document, 'keydown', (event: any) => {
// When enableFiltering is false, the KInput doesn't have focus so we need to handle arrow key events here
if (!props.enableFiltering && document.activeElement?.tagName === 'BODY' && !inputFocused.value && isDropdownOpen.value) {
if (event.code === 'ArrowDown' || event.code === 'ArrowUp') {
event.preventDefault()
kSelectItems.value?.setFocus()
}
}
})
})
onUnmounted(() => {
Expand Down
4 changes: 4 additions & 0 deletions src/components/KSelect/KSelectItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
type="button"
:value="item.value"
@click="handleClick"
@keydown.down.prevent="$emit('arrow-down')"
@keydown.up.prevent="$emit('arrow-up')"
>
<span class="select-item-label">
<slot name="content">{{ item.label }}</slot>
Expand All @@ -37,6 +39,8 @@ const props = defineProps({
const emit = defineEmits<{
(e: 'selected', value: SelectItem): void;
(e: 'arrow-down'): void;
(e: 'arrow-up'): void;
}>()
const handleClick = (e: MouseEvent): void => {
Expand Down
48 changes: 47 additions & 1 deletion src/components/KSelect/KSelectItems.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<KSelectItem
v-for="item in nonGroupedItems"
:key="item.key"
ref="kSelectItem"
:item="item"
@arrow-down="() => shiftFocus(item.key, 'down')"
@arrow-up="() => shiftFocus(item.key, 'up')"
@selected="handleItemSelect"
>
<template #content>
Expand All @@ -26,7 +29,10 @@
<KSelectItem
v-for="item in getGroupItems(group)"
:key="item.key"
ref="kSelectItem"
:item="item"
@arrow-down="() => shiftFocus(item.key, 'down')"
@arrow-up="() => shiftFocus(item.key, 'up')"
@selected="handleItemSelect"
>
<template #content>
Expand All @@ -40,8 +46,8 @@
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import type { PropType } from 'vue'
import { computed } from 'vue'
import type { SelectItem, SelectItemWithGroup } from '@/types'
import KSelectItem from '@/components/KSelect/KSelectItem.vue'
Expand All @@ -67,6 +73,46 @@ const groups = computed((): string[] =>
.map(item => item.group))].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())))
const getGroupItems = (group: string) => props.items?.filter(item => item.group === group)
const kSelectItem = ref<InstanceType<typeof KSelectItem>[] | null>(null)
const setFocus = (index: number = 0) => {
if (kSelectItem.value) {
if (!props.items[index].disabled) {
kSelectItem.value[index]?.$el?.querySelector('button').focus()
} else {
setFocus(index + 1)
}
}
}
const shiftFocus = (key: SelectItem['key'], direction: 'down' | 'up') => {
const index = props.items.findIndex(item => item.key === key)
if (index === -1) {
return // Exit if the item is not found
}
// determine step for navigation
const step = direction === 'down' ? 1 : -1
const isValidIndex = direction === 'down'
? index + step < props.items.length
: index + step >= 0
if (isValidIndex) {
const nextIndex = index + step
if (props.items[nextIndex].disabled) {
// find the next valid index if the current one is disabled
shiftFocus(props.items[nextIndex].key!, direction)
} else {
// focus the button
kSelectItem.value?.[nextIndex]?.$el?.querySelector('button')?.focus()
}
}
}
defineExpose({ setFocus })
</script>

<style lang="scss" scoped>
Expand Down

0 comments on commit 62fbe64

Please sign in to comment.