import { useEffect, useCallback, RefObject } from 'react'; export interface KeyboardShortcut { key: string; ctrl?: boolean; shift?: boolean; alt?: boolean; handler: () => void; description?: string; } export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[], enabled = true) { const handleKeyDown = useCallback((e: KeyboardEvent) => { if (!enabled) return; if ( e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || (e.target as HTMLElement).isContentEditable ) { return; } for (const shortcut of shortcuts) { const keyMatch = e.key.toLowerCase() === shortcut.key.toLowerCase() || e.code.toLowerCase() === shortcut.key.toLowerCase(); const ctrlMatch = shortcut.ctrl ? (e.ctrlKey || e.metaKey) : true; const shiftMatch = shortcut.shift ? e.shiftKey : !e.shiftKey; const altMatch = shortcut.alt ? e.altKey : !e.altKey; if (keyMatch && ctrlMatch && shiftMatch && altMatch) { e.preventDefault(); shortcut.handler(); break; } } }, [shortcuts, enabled]); useEffect(() => { window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handleKeyDown]); } export function useFocusTrap(ref: RefObject, active: boolean) { useEffect(() => { if (!active || !ref.current) return; const focusableElements = ref.current.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; const handleTabKey = (e: KeyboardEvent) => { if (e.key !== 'Tab') return; if (e.shiftKey) { if (document.activeElement === firstElement) { e.preventDefault(); lastElement?.focus(); } } else { if (document.activeElement === lastElement) { e.preventDefault(); firstElement?.focus(); } } }; ref.current.addEventListener('keydown', handleTabKey); firstElement?.focus(); return () => { ref.current?.removeEventListener('keydown', handleTabKey); }; }, [ref, active]); } export function announceToScreenReader(message: string, priority: 'polite' | 'assertive' = 'polite') { const el = document.createElement('div'); el.setAttribute('role', 'status'); el.setAttribute('aria-live', priority); el.setAttribute('aria-atomic', 'true'); el.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;'; document.body.appendChild(el); setTimeout(() => { el.textContent = message; setTimeout(() => document.body.removeChild(el), 1000); }, 100); }