import { useEffect } from "react"; export interface ShortcutDefinition { key: string; meta?: boolean; // Cmd on Mac, Ctrl on Windows/Linux shift?: boolean; alt?: boolean; ctrl?: boolean; handler: () => void; description: string; enabled?: boolean; // Allow dynamic enable/disable } interface ShortcutMap { [key: string]: ShortcutDefinition; } /** * Convert keyboard event to a shortcut key string * Format: "Meta+Shift+R" or "Space" or "ArrowLeft" */ export function getShortcutKey(e: KeyboardEvent): string { const parts: string[] = []; if (e.metaKey) parts.push("Meta"); if (e.ctrlKey) parts.push("Ctrl"); if (e.altKey) parts.push("Alt"); if (e.shiftKey) parts.push("Shift"); // Normalize key names let key = e.key; if (key === " ") key = "Space"; parts.push(key); return parts.join("+"); } /** * Check if a shortcut definition matches a keyboard event */ function matchesShortcut(e: KeyboardEvent, shortcut: ShortcutDefinition): boolean { // Check modifier keys if (shortcut.meta && !e.metaKey) return false; if (!shortcut.meta && e.metaKey) return false; if (shortcut.shift && !e.shiftKey) return false; if (!shortcut.shift && e.shiftKey) return false; if (shortcut.alt && !e.altKey) return false; if (!shortcut.alt && e.altKey) return false; if (shortcut.ctrl && !e.ctrlKey) return false; if (!shortcut.ctrl && e.ctrlKey) return false; // Check key (normalize space) let eventKey = e.key; if (eventKey === " ") eventKey = "Space"; return eventKey === shortcut.key; } /** * Hook for registering keyboard shortcuts * * @example * ```tsx * useKeyboardShortcuts({ * "play-pause": { * key: "Space", * handler: () => togglePlay(), * description: "Play/pause video" * }, * "help": { * key: "?", * meta: true, * shift: true, * handler: () => setShowHelp(true), * description: "Show keyboard shortcuts" * } * }); * ``` */ export function useKeyboardShortcuts(shortcuts: ShortcutMap, enabled: boolean = true) { useEffect(() => { if (!enabled) return; const handler = (e: KeyboardEvent) => { // Find matching shortcut for (const [_id, shortcut] of Object.entries(shortcuts)) { if (shortcut.enabled === false) continue; if (matchesShortcut(e, shortcut)) { e.preventDefault(); e.stopPropagation(); shortcut.handler(); break; } } }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [shortcuts, enabled]); }