import { useBlockableInput } from "../../../hooks/useInputLayer.js"; import type { ActionWizardState } from "./useActionWizard.js"; /** * Keyboard input handler for the action wizard. * * Dispatches key presses to step-specific handlers based on the * current step type and focus area. Extracted from the screen * component to keep it purely presentational. */ export function useWizardKeyboard(wizard: ActionWizardState): void { useBlockableInput( (input, key) => { // ── Tab: cycle through content and button bar ───────── if (key.tab) { handleTab(wizard); return; } // ── Content-area: step-specific input handling ──────── if (wizard.focusArea === "content") { if (wizard.currentStepData?.type === "role-select") { handleRoleSelectInput(wizard, input, key); return; } if (wizard.currentStepData?.type === "inputs") { handleInputsStepInput(wizard, input, key); return; } } // ── Button bar navigation + activation ──────────────── if (wizard.focusArea === "buttons") { handleButtonBarInput(wizard, key); } // ── Global shortcuts ────────────────────────────────── if ( input === "c" && wizard.currentStepData?.type === "publish" && wizard.invitationId ) { wizard.copyId(); } if (input === "a" && wizard.currentStepData?.type === "inputs") { wizard.selectAll(); } if (input === "n" && wizard.currentStepData?.type === "inputs") { wizard.deselectAll(); } }, { isActive: !wizard.textInputHasFocus }, ); } // ── Tab cycling ───────────────────────────────────────────────── function handleTab(wizard: ActionWizardState): void { if (wizard.focusArea === "content") { // Within role-select, tab through roles before moving to buttons if ( wizard.currentStepData?.type === "role-select" && wizard.availableRoles.length > 0 && wizard.selectedRoleIndex < wizard.availableRoles.length - 1 ) { wizard.setSelectedRoleIndex((prev) => prev + 1); return; } // Within inputs, tab through UTXOs before moving to buttons if ( wizard.currentStepData?.type === "inputs" && wizard.availableUtxos.length > 0 && wizard.selectedUtxoIndex < wizard.availableUtxos.length - 1 ) { wizard.setSelectedUtxoIndex((prev) => prev + 1); return; } // Move to button bar wizard.setFocusArea("buttons"); wizard.setFocusedButton("next"); } else { // Cycle through buttons, then wrap back to content if (wizard.focusedButton === "back") { wizard.setFocusedButton("cancel"); } else if (wizard.focusedButton === "cancel") { wizard.setFocusedButton("next"); } else { wizard.setFocusArea("content"); wizard.setFocusedInput(0); wizard.setSelectedUtxoIndex(0); wizard.setSelectedRoleIndex(0); } } } // ── Role-select step ──────────────────────────────────────────── function handleRoleSelectInput( wizard: ActionWizardState, _input: string, key: { upArrow: boolean; downArrow: boolean }, ): void { if (key.upArrow) { wizard.setSelectedRoleIndex((p) => Math.max(0, p - 1)); } else if (key.downArrow) { wizard.setSelectedRoleIndex((p) => Math.min(wizard.availableRoles.length - 1, p + 1), ); } } // ── Inputs step (UTXO selection) ──────────────────────────────── function handleInputsStepInput( wizard: ActionWizardState, input: string, key: { upArrow: boolean; downArrow: boolean; return: boolean }, ): void { if (key.upArrow) { wizard.setSelectedUtxoIndex((p) => Math.max(0, p - 1)); } else if (key.downArrow) { wizard.setSelectedUtxoIndex((p) => Math.min(wizard.availableUtxos.length - 1, p + 1), ); } else if (key.return || input === " ") { wizard.toggleUtxoSelection(wizard.selectedUtxoIndex); } } // ── Button bar ────────────────────────────────────────────────── function handleButtonBarInput( wizard: ActionWizardState, key: { leftArrow: boolean; rightArrow: boolean; return: boolean }, ): void { if (key.leftArrow) { wizard.setFocusedButton((p) => p === "next" ? "cancel" : p === "cancel" ? "back" : "back", ); } else if (key.rightArrow) { wizard.setFocusedButton((p) => p === "back" ? "cancel" : p === "cancel" ? "next" : "next", ); } if (key.return) { if (wizard.focusedButton === "back") wizard.previousStep(); else if (wizard.focusedButton === "cancel") wizard.cancel(); else if (wizard.focusedButton === "next") wizard.nextStep(); } }