import * as React from "react"; export interface KeyboardShortcut { key: string; label?: string; description: string; group?: string; handler: (event: KeyboardEvent) => void; code?: string; altKey?: boolean; ctrlKey?: boolean; metaKey?: boolean; shiftKey?: boolean; preventDefault?: boolean; allowInInputs?: boolean; disabled?: boolean; } function isEditableTarget(target: EventTarget | null): boolean { if (!(target instanceof HTMLElement)) { return false; } const tagName = target.tagName.toLowerCase(); return ( target.isContentEditable || tagName === "input" || tagName === "textarea" || tagName === "select" ); } function matchesShortcut(event: KeyboardEvent, shortcut: KeyboardShortcut): boolean { const keyMatches = event.key.toLowerCase() === shortcut.key.toLowerCase() || Boolean(shortcut.code && event.code === shortcut.code); if (!keyMatches) { return false; } return ( Boolean(shortcut.altKey) === event.altKey && Boolean(shortcut.ctrlKey) === event.ctrlKey && Boolean(shortcut.metaKey) === event.metaKey && Boolean(shortcut.shiftKey) === event.shiftKey ); } export function formatShortcut(shortcut: Pick): string { if (shortcut.label) { return shortcut.label; } const keys = [ shortcut.ctrlKey ? "Ctrl" : null, shortcut.metaKey ? "⌘" : null, shortcut.altKey ? "Alt" : null, shortcut.shiftKey ? "Shift" : null, shortcut.key === " " ? "Space" : shortcut.key.toUpperCase(), ].filter(Boolean); return keys.join(" + "); } export function useKeyboardShortcuts(shortcuts: readonly KeyboardShortcut[]): void { const shortcutsRef = React.useRef(shortcuts); React.useEffect(() => { shortcutsRef.current = shortcuts; }, [shortcuts]); React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { for (const shortcut of shortcutsRef.current) { if (shortcut.disabled) { continue; } if (!shortcut.allowInInputs && isEditableTarget(event.target)) { continue; } if (!matchesShortcut(event, shortcut)) { continue; } if (shortcut.preventDefault !== false) { event.preventDefault(); } shortcut.handler(event); break; } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, []); }