import { useEffect, useRef, useCallback } from 'react'; export interface ShortcutBinding { keys: string; // e.g. "Control+k", " ", "m", "alt+1" description: string; action: () => void; } export const useKeyboardShortcuts = (bindings: ShortcutBinding[], enabled: boolean = true) => { const bindingsRef = useRef(bindings); useEffect(() => { bindingsRef.current = bindings; }, [bindings]); const handleKeyDown = useCallback((e: KeyboardEvent) => { if (!enabled) return; // Avoid hijacking keystrokes when editing inputs const active = document.activeElement; if (active) { const name = active.tagName.toLowerCase(); if (name === 'input' || name === 'textarea' || active.getAttribute('contenteditable') === 'true') { return; } } const pressedKey = e.key.toLowerCase(); const isCtrl = e.ctrlKey || e.metaKey; const isAlt = e.altKey; const isShift = e.shiftKey; for (const binding of bindingsRef.current) { const keys = binding.keys.toLowerCase().split('+'); const requiresCtrl = keys.includes('control') || keys.includes('ctrl'); const requiresAlt = keys.includes('alt'); const requiresShift = keys.includes('shift'); const baseKey = keys.filter(k => !['control', 'ctrl', 'alt', 'shift'].includes(k))[0]; const matchesCtrl = requiresCtrl ? isCtrl : !isCtrl; const matchesAlt = requiresAlt ? isAlt : !isAlt; const matchesShift = requiresShift ? isShift : !isShift; const normalizedBaseKey = baseKey === 'space' ? ' ' : baseKey; const matchesBase = pressedKey === normalizedBaseKey; if (matchesCtrl && matchesAlt && matchesShift && matchesBase) { e.preventDefault(); binding.action(); break; } } }, [enabled]); useEffect(() => { window.addEventListener('keydown', handleKeyDown); return () => { window.removeEventListener('keydown', handleKeyDown); }; }, [handleKeyDown]); }; export default useKeyboardShortcuts;