Map of hotkey action id -> handler.
Optional hooks, e.g. onTriggered for status-bar feedback.
useHotkeys(
{ showHotkeyHelp: () => setHelpOpen(true) },
{ onTriggered: (cfg) => cfg.flash && flashStatusText(cfg.flash) },
);
export function useHotkeys(handlers: HotkeyHandlers, options: UseHotkeysOptions = {}): void {
const compiled = useMemo(() => compile(hotkeysConfig as HotkeyConfig[]), []);
const { onTriggered } = options;
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (isEditableTarget(event)) return;
for (const { config, binding } of compiled) {
if (!matches(event, binding)) continue;
const handler = handlers[config.id];
if (!handler) continue;
event.preventDefault();
event.stopPropagation();
try {
const result = handler();
if (result instanceof Promise) {
result.catch((error) => {
console.error(`Hotkey handler "${config.id}" failed`, { error });
});
}
onTriggered?.(config);
} catch (error) {
console.error(`Hotkey handler "${config.id}" threw`, { error });
}
return;
}
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [compiled, handlers, onTriggered]);
}
Installs a single global
keydownlistener ondocumentthat dispatches to the supplied handler map based on the application's hotkey config.The hook is intentionally generic: it has no knowledge of the concrete actions. Callers pass a
handlersmap keyed by theidfield fromconfig.json, and the hook takes care of:ctrl+swon't fire onctrl+shift+s)preventDefault+stopPropagationwhen a binding fires