Files
xo-cli/src/tui/screens/action-wizard/hooks/useWizardKeyboard.ts

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();
}
}