154 lines
5.1 KiB
TypeScript
154 lines
5.1 KiB
TypeScript
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();
|
|
}
|
|
}
|