Files
openchamber/packages/ui/src/hooks/useKeybind.ts
T
Bohdan Triapitsyn f1c3870909 feat(ui): land the centralized shortcuts core from #2532 with review fixes
The schema/config/bindings/registry/dispatcher module, useKeybind hooks,
recording dialog, reworked shortcuts settings page, help dialog, and the
localized action labels — re-based onto current main rather than merged
(the branch predates 440+ commits including the session-tabs shortcuts).

Review fixes applied on top of the original:
- close_session_tab (alt+w) joins the schema with labels in every locale;
  it shipped on main after the PR's base and would otherwise silently die.
- switch_context_surface's special-case in conflict resolution is now a
  declared prefixStyle config property instead of a magic id string.
- Duplicate handler registration warns in dev builds.
- The risky-browser-shortcut warning inspects every chord and covers
  mod+q/d/h/j/o/u plus mod+shift+w/q.
- The dispatcher remembers which target armed a two-chord prefix so the
  window-level completion handler can distinguish a deliberate sequence
  from typing in an editable field (guard lands with the dispatch hook).
- Schema tests: unique normalized default bindings enforced, and the
  flat-file-era override format proven to keep resolving.
2026-08-26 10:47:44 +03:00

31 lines
1.3 KiB
TypeScript

import React from 'react';
import { shortcutRegistry, type ShortcutActionId, type ShortcutHandler } from '@/lib/shortcuts';
export function useKeybind(actionId: ShortcutActionId, handler: ShortcutHandler): void {
const handlerRef = React.useRef(handler);
handlerRef.current = handler;
React.useEffect(() => shortcutRegistry.register(actionId, (event) => handlerRef.current(event)), [actionId]);
}
export type ShortcutBindings<
Bindings extends Partial<Record<ShortcutActionId, ShortcutHandler>>,
> = Bindings & Record<Exclude<keyof Bindings, ShortcutActionId>, never>;
export function useKeybinds<
const Bindings extends Partial<Record<ShortcutActionId, ShortcutHandler>>,
>(bindings: ShortcutBindings<Bindings>): void {
const handlersRef = React.useRef(bindings);
handlersRef.current = bindings;
const actionIdsKey = Object.keys(bindings).sort().join('\0');
React.useEffect(() => {
const actionIds = (actionIdsKey ? actionIdsKey.split('\0') : []) as ShortcutActionId[];
const unregister = actionIds.map((actionId) => shortcutRegistry.register(actionId, (event) => {
const handler = handlersRef.current[actionId];
return handler ? handler(event) : false;
}));
return () => unregister.forEach((remove) => remove());
}, [actionIdsKey]);
}