import { useEffect } from 'react' import type { UserQuestionItem, UserQuestionOption } from './types' export interface UseUserQuestionKeyboardProps { isSubmitting: boolean currentQuestion: UserQuestionItem | undefined currentOptions: UserQuestionOption[] currentQuestionIndex: number questions: UserQuestionItem[] focusedOptionIndex: number setFocusedOptionIndex: (n: number) => void currentQuestionHasAnswer: boolean getSelectedOptionIndex: (i: number) => number setShouldScrollToQuestion: (v: boolean) => void setCurrentQuestionIndex: (i: number) => void handleOptionClick: (questionText: string, optionLabel: string, questionIndex: number) => void handleContinue: () => void } function isFocusInInput(activeEl: Element | null): boolean { return ( activeEl instanceof HTMLInputElement || activeEl instanceof HTMLTextAreaElement || activeEl?.getAttribute('contenteditable') === 'true' ) } export function useUserQuestionKeyboard({ isSubmitting, currentQuestion, currentOptions, currentQuestionIndex, questions, focusedOptionIndex, setFocusedOptionIndex, currentQuestionHasAnswer, getSelectedOptionIndex, setShouldScrollToQuestion, setCurrentQuestionIndex, handleOptionClick, handleContinue, }: UseUserQuestionKeyboardProps) { useEffect(() => { const keyHandlers: Record void> = { ArrowDown(e) { e.preventDefault() if (focusedOptionIndex < currentOptions.length - 1) { setFocusedOptionIndex(focusedOptionIndex + 1) } else if (currentQuestionIndex < questions.length - 1) { const nextIndex = currentQuestionIndex + 1 setShouldScrollToQuestion(true) setCurrentQuestionIndex(nextIndex) setFocusedOptionIndex(getSelectedOptionIndex(nextIndex)) } }, ArrowUp(e) { e.preventDefault() if (focusedOptionIndex > 0) { setFocusedOptionIndex(focusedOptionIndex - 1) } else if (currentQuestionIndex > 0) { const prevIndex = currentQuestionIndex - 1 setShouldScrollToQuestion(true) setCurrentQuestionIndex(prevIndex) setFocusedOptionIndex(getSelectedOptionIndex(prevIndex)) } }, Enter(e) { e.preventDefault() if (currentQuestionHasAnswer) { handleContinue() } else if (currentQuestion && currentOptions[focusedOptionIndex]) { handleOptionClick( currentQuestion.question, currentOptions[focusedOptionIndex].label, currentQuestionIndex ) } }, } const handleKeyDown = (e: KeyboardEvent) => { if (isSubmitting) return if (isFocusInInput(document.activeElement)) return const handler = keyHandlers[e.key] if (handler) { handler(e) return } if (e.key >= '1' && e.key <= '9') { const numberIndex = parseInt(e.key, 10) - 1 if ( numberIndex >= 0 && numberIndex < currentOptions.length && currentQuestion ) { e.preventDefault() handleOptionClick( currentQuestion.question, currentOptions[numberIndex].label, currentQuestionIndex ) setFocusedOptionIndex(numberIndex) } } } document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) }, [ isSubmitting, currentQuestion, currentOptions, currentQuestionIndex, questions.length, focusedOptionIndex, setFocusedOptionIndex, currentQuestionHasAnswer, getSelectedOptionIndex, setCurrentQuestionIndex, handleOptionClick, handleContinue, setShouldScrollToQuestion, ]) }