Replaces reka-ui's roving-tabindex/arrow-key navigation for RadioGroup and Select.
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import type { Ref } from 'vue'
|
|
|
|
export interface RovingFocusOptions {
|
|
orientation?: 'vertical' | 'horizontal'
|
|
loop?: boolean
|
|
}
|
|
|
|
export function useRovingFocus(itemsRef: Ref<HTMLElement[]>, options: RovingFocusOptions = {}) {
|
|
const orientation = options.orientation ?? 'vertical'
|
|
const loop = options.loop ?? true
|
|
|
|
function focusIndex(index: number) {
|
|
const items = itemsRef.value
|
|
if (items.length === 0) return
|
|
const clamped = loop
|
|
? ((index % items.length) + items.length) % items.length
|
|
: Math.max(0, Math.min(index, items.length - 1))
|
|
items[clamped]?.focus()
|
|
}
|
|
|
|
function handleKeydown(event: KeyboardEvent) {
|
|
const items = itemsRef.value
|
|
if (items.length === 0) return
|
|
const currentIndex = items.indexOf(document.activeElement as HTMLElement)
|
|
const nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'
|
|
const prevKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
|
|
|
|
if (event.key === nextKey) {
|
|
event.preventDefault()
|
|
focusIndex(currentIndex + 1)
|
|
} else if (event.key === prevKey) {
|
|
event.preventDefault()
|
|
focusIndex(currentIndex - 1)
|
|
} else if (event.key === 'Home') {
|
|
event.preventDefault()
|
|
focusIndex(0)
|
|
} else if (event.key === 'End') {
|
|
event.preventDefault()
|
|
focusIndex(items.length - 1)
|
|
}
|
|
}
|
|
|
|
return { handleKeydown, focusIndex }
|
|
}
|