import { useEffect, useCallback, useRef } from 'react'; import { useBuilder } from '../contexts/builder/BuilderContext'; import { useCanvasSetting } from './useCanvasSettings'; import { debugLog } from '../utils/debug'; /** * Hook pour gérer les raccourcis clavier du canvas * Implémente les raccourcis configurables via les paramètres canvas */ export const useKeyboardShortcuts = () => { const { state, dispatch } = useBuilder(); const keyboardShortcutsEnabled = useCanvasSetting('enable_keyboard_shortcuts', true) as boolean; // Références pour éviter les closures stale const stateRef = useRef(state); const dispatchRef = useRef(dispatch); // Mettre à jour les références useEffect(() => { stateRef.current = state; dispatchRef.current = dispatch; }, [state, dispatch]); // ---------- handlers par touche (utilisent stateRef/dispatchRef stables) ---------- const handleZKey = (event: Event, isCtrlOrCmd: boolean, shiftKey: boolean): void => { if (!isCtrlOrCmd) return; event.preventDefault(); if (shiftKey) { debugLog('[KeyboardShortcuts] Executing redo (Ctrl+Shift+Z)'); dispatchRef.current({ type: 'REDO' }); } else { debugLog('[KeyboardShortcuts] Executing undo (Ctrl+Z)'); dispatchRef.current({ type: 'UNDO' }); } }; const handleYKey = (event: Event, isCtrlOrCmd: boolean, shiftKey: boolean): void => { if (!isCtrlOrCmd || shiftKey) return; event.preventDefault(); debugLog('[KeyboardShortcuts] Executing redo (Ctrl+Y)'); dispatchRef.current({ type: 'REDO' }); }; const handleAKey = (event: Event, isCtrlOrCmd: boolean): void => { if (!isCtrlOrCmd) return; event.preventDefault(); const allElementIds = stateRef.current.elements.map(el => el.id); debugLog(`[KeyboardShortcuts] Selecting all elements (${allElementIds.length} elements)`); dispatchRef.current({ type: 'SET_SELECTION', payload: allElementIds }); }; const handleDeleteKey = (event: Event): void => { if (stateRef.current.selection.selectedElements.length === 0) return; event.preventDefault(); debugLog(`[KeyboardShortcuts] Deleting ${stateRef.current.selection.selectedElements.length} selected elements`); stateRef.current.selection.selectedElements.forEach(elementId => { dispatchRef.current({ type: 'REMOVE_ELEMENT', payload: elementId }); }); dispatchRef.current({ type: 'CLEAR_SELECTION' }); }; const handleCKey = (event: Event, isCtrlOrCmd: boolean): void => { if (!isCtrlOrCmd) return; event.preventDefault(); if (stateRef.current.selection.selectedElements.length > 0) { debugLog(`[KeyboardShortcuts] Copying ${stateRef.current.selection.selectedElements.length} selected elements`); // Copie non encore implémentée } }; const handleVKey = (event: Event, isCtrlOrCmd: boolean): void => { if (!isCtrlOrCmd) return; event.preventDefault(); debugLog('[KeyboardShortcuts] Pasting elements'); // Collage non encore implémenté }; const handleDKey = (event: Event, isCtrlOrCmd: boolean): void => { if (!isCtrlOrCmd) return; event.preventDefault(); if (stateRef.current.selection.selectedElements.length > 0) { debugLog(`[KeyboardShortcuts] Duplicating ${stateRef.current.selection.selectedElements.length} selected elements`); // Duplication non encore implémentée } }; /** * Gère les événements clavier */ const handleKeyDown = useCallback((event: Event) => { const keyboardEvent = event as unknown as { ctrlKey: boolean; metaKey: boolean; key: string; shiftKey: boolean; preventDefault: () => void; target: object; }; // Ne pas traiter si les raccourcis sont désactivés if (!keyboardShortcutsEnabled) { debugLog('[KeyboardShortcuts] Shortcuts disabled - ignoring key event'); return; } // Ne pas traiter si on est dans un champ de saisie const target = event.target as HTMLElement; if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.contentEditable === 'true') { debugLog('[KeyboardShortcuts] Ignoring key event in input field'); return; } const { ctrlKey, metaKey, key, shiftKey } = keyboardEvent; const isCtrlOrCmd = ctrlKey || metaKey; const ctrlOrCmdLabel = ctrlKey ? 'Ctrl' : 'Cmd'; const modifierPrefix = isCtrlOrCmd ? ctrlOrCmdLabel : ''; const shortcut = `${modifierPrefix}${shiftKey ? '+Shift' : ''}+${key.toUpperCase()}`; debugLog(`[KeyboardShortcuts] Key pressed: ${shortcut}`); switch (key.toLowerCase()) { case 'z': handleZKey(event, isCtrlOrCmd, shiftKey); break; case 'y': handleYKey(event, isCtrlOrCmd, shiftKey); break; case 'a': handleAKey(event, isCtrlOrCmd); break; case 'delete': case 'backspace': handleDeleteKey(event); break; case 'c': handleCKey(event, isCtrlOrCmd); break; case 'v': handleVKey(event, isCtrlOrCmd); break; case 'd': handleDKey(event, isCtrlOrCmd); break; default: debugLog(`[KeyboardShortcuts] Unhandled key: ${shortcut}`); break; } }, [keyboardShortcutsEnabled]); /** * Configure les écouteurs d'événements */ useEffect(() => { if (!keyboardShortcutsEnabled) { debugLog('[KeyboardShortcuts] Keyboard shortcuts disabled'); return; } debugLog('[KeyboardShortcuts] Initializing keyboard shortcuts'); // Ajouter l'écouteur d'événements document.addEventListener('keydown', handleKeyDown); // Nettoyer l'écouteur return () => { debugLog('[KeyboardShortcuts] Cleaning up keyboard shortcuts'); document.removeEventListener('keydown', handleKeyDown); }; }, [handleKeyDown, keyboardShortcutsEnabled]); // Retourner des informations sur l'état des raccourcis return { keyboardShortcutsEnabled, hasSelection: state.selection.selectedElements.length > 0, canUndo: state.history.past.length > 0, canRedo: state.history.future.length > 0, }; };