Spaces:
Configuration error
Configuration error
| 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<T extends HTMLElement>(ref: RefObject<T | null>, active: boolean) { | |
| useEffect(() => { | |
| if (!active || !ref.current) return; | |
| const focusableElements = ref.current.querySelectorAll<HTMLElement>( | |
| '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); | |
| } | |