CommandBar, ResultList, ActionPage and useCommandBar over the same model docs/COMMAND-BAR.md specifies: sigils, empty-query session order, the drill-in action page, and the keys. The highlight now walks the rows in the order they render. The legacy bar indexed the highlight by display order but activated by rank order, so Enter could act on a different row than the one lit.
66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
// Opening and closing the bar (docs/COMMAND-BAR.md, "Opening"). `ctrl-k` is a
|
|
// registry command like any other, so the `?` overlay lists it without a
|
|
// second list; the touch entry point — the sidebar's search field — raises
|
|
// `OPEN_COMMAND_BAR_EVENT` and lands on the same open.
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
import { OPEN_COMMAND_BAR_EVENT } from "../../catalog.js";
|
|
import { CommandSet, defineCommands, isBarTypingTarget } from "../../commands.js";
|
|
|
|
export interface UseCommandBarOptions {
|
|
/** Run before the bar opens — the shell discards a preview here, since the
|
|
* bar is not previewed into (docs/PREVIEW.md). */
|
|
onOpen?(): void;
|
|
/** Off while another overlay owns the keyboard (the restart overlay, the
|
|
* help dialog). Defaults to on. */
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export interface CommandBarControls {
|
|
open: boolean;
|
|
openBar(): void;
|
|
closeBar(): void;
|
|
}
|
|
|
|
export function useCommandBar(opts: UseCommandBarOptions = {}): CommandBarControls {
|
|
const { onOpen, enabled = true } = opts;
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const openBar = useCallback(() => {
|
|
onOpen?.();
|
|
setOpen(true);
|
|
}, [onOpen]);
|
|
|
|
const closeBar = useCallback(() => setOpen(false), []);
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
const keys = new CommandSet(defineCommands({ "bar.open": openBar }));
|
|
|
|
// Capture phase: an attached xterm swallows every keystroke it is given, so
|
|
// the bar's binding has to be seen ahead of the terminal.
|
|
const onKeydown = (e: KeyboardEvent) => {
|
|
// The bar owns the keyboard while it is up; its own input handles the keys.
|
|
if (open) return;
|
|
// The pane's hidden textarea is not a typing target for the bar: ctrl-k
|
|
// is seen ahead of xterm and opens the bar. A real form field — the spawn
|
|
// form's inputs — keeps its own Ctrl+K.
|
|
if (isBarTypingTarget(e.target)) return;
|
|
keys.handle(e);
|
|
};
|
|
const onTouchEntry = () => {
|
|
if (!open) openBar();
|
|
};
|
|
|
|
window.addEventListener("keydown", onKeydown, true);
|
|
document.addEventListener(OPEN_COMMAND_BAR_EVENT, onTouchEntry);
|
|
return () => {
|
|
window.removeEventListener("keydown", onKeydown, true);
|
|
document.removeEventListener(OPEN_COMMAND_BAR_EVENT, onTouchEntry);
|
|
};
|
|
}, [enabled, open, openBar]);
|
|
|
|
return { open, openBar, closeBar };
|
|
}
|