import { useEffect, useRef, type RefObject } from 'react' interface UseArrowNavigationParams { itemCount: number containerRef: RefObject enabled?: boolean } function handleArrowDown( e: KeyboardEvent, focusableElements: NodeListOf, getCurrentIndex: () => number, setCurrentIndex: (index: number) => void ): void { e.preventDefault() const currentIndex = getCurrentIndex() const nextIndex = currentIndex < focusableElements.length - 1 ? currentIndex + 1 : 0 setCurrentIndex(nextIndex) focusableElements[nextIndex]?.focus() } function handleArrowUp( e: KeyboardEvent, focusableElements: NodeListOf, getCurrentIndex: () => number, setCurrentIndex: (index: number) => void ): void { e.preventDefault() const currentIndex = getCurrentIndex() const prevIndex = currentIndex > 0 ? currentIndex - 1 : focusableElements.length - 1 setCurrentIndex(prevIndex) focusableElements[prevIndex]?.focus() } function handleHome(e: KeyboardEvent, focusableElements: NodeListOf, setCurrentIndex: (index: number) => void): void { e.preventDefault() setCurrentIndex(0) focusableElements[0]?.focus() } function handleEnd(e: KeyboardEvent, focusableElements: NodeListOf, setCurrentIndex: (index: number) => void): void { e.preventDefault() setCurrentIndex(focusableElements.length - 1) focusableElements[focusableElements.length - 1]?.focus() } function createKeyDownHandler( containerRef: RefObject, getCurrentIndex: () => number, setCurrentIndex: (index: number) => void ): (e: KeyboardEvent) => void { return (e: KeyboardEvent): void => { if (!containerRef.current) { return } const focusableElements = containerRef.current.querySelectorAll( 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])' ) if (focusableElements.length === 0) { return } if (e.key === 'ArrowDown') { handleArrowDown(e, focusableElements, getCurrentIndex, setCurrentIndex) } else if (e.key === 'ArrowUp') { handleArrowUp(e, focusableElements, getCurrentIndex, setCurrentIndex) } else if (e.key === 'Home') { handleHome(e, focusableElements, setCurrentIndex) } else if (e.key === 'End') { handleEnd(e, focusableElements, setCurrentIndex) } } } export function useArrowNavigation({ itemCount, containerRef, enabled = true }: UseArrowNavigationParams): void { const currentIndexRef = useRef(-1) useEffect(() => { if (!enabled || itemCount === 0) { return } const getCurrentIndex = (): number => currentIndexRef.current const setCurrentIndex = (index: number): void => { currentIndexRef.current = index } const handleKeyDown = createKeyDownHandler(containerRef, getCurrentIndex, setCurrentIndex) const container = containerRef.current if (!container) { return } container.addEventListener('keydown', handleKeyDown) return () => { container.removeEventListener('keydown', handleKeyDown) } }, [itemCount, containerRef, enabled]) }