All files / src/hooks useKeyboardShortcuts.ts

93.75% Statements 30/32
94.73% Branches 18/19
100% Functions 6/6
93.75% Lines 30/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130                                                                                              54x     54x 54x     54x 34x 34x           54x   8x 1x       7x     7x 8x 1x       7x           7x       7x 5x           5x 4x   5x       5x 5x                     54x 34x 1x     33x   33x   33x 33x 33x        
/**
 * Custom hook for keyboard shortcut handling
 * 
 * @module hooks/useKeyboardShortcuts
 */
 
import { useEffect, useCallback, useRef } from 'react';
import { ShortcutMap, UseKeyboardShortcutsOptions } from '../types/keyboard';
import {
  getKeyCombination,
  shortcutsMatch,
  shouldIgnoreKeyboardEvent,
  getPlatformShortcut,
  areKeyboardShortcutsSupported,
} from '../utils/keyboardUtils';
import logger from '../utils/logger';
 
/**
 * Custom hook for registering and handling keyboard shortcuts
 * 
 * @param options - Configuration options for keyboard shortcuts
 * 
 * @example
 * ```tsx
 * const shortcuts = {
 *   'save': {
 *     id: 'save',
 *     keys: 'ctrl+s',
 *     description: 'Save document',
 *     category: 'Actions',
 *     handler: () => handleSave(),
 *   }
 * };
 * 
 * useKeyboardShortcuts({
 *   shortcuts,
 *   enabled: true,
 *   preventDefault: true,
 * });
 * ```
 */
export function useKeyboardShortcuts(options: UseKeyboardShortcutsOptions): void {
  const {
    shortcuts,
    enabled = true,
    preventDefault = true,
    stopPropagation = false,
  } = options;
 
  // Use ref to avoid recreating handler on every render
  const shortcutsRef = useRef<ShortcutMap>(shortcuts);
  const optionsRef = useRef({ preventDefault, stopPropagation });
 
  // Update refs when values change
  useEffect(() => {
    shortcutsRef.current = shortcuts;
    optionsRef.current = { preventDefault, stopPropagation };
  }, [shortcuts, preventDefault, stopPropagation]);
 
  /**
   * Handle keyboard event and match against registered shortcuts
   */
  const handleKeyDown = useCallback((event: KeyboardEvent): void => {
    // Skip if shortcuts are not supported or event should be ignored
    if (!areKeyboardShortcutsSupported() || shouldIgnoreKeyboardEvent(event)) {
      return;
    }
 
    // Get the key combination from the event
    const keyCombination = getKeyCombination(event);
 
    // Find matching shortcut
    const matchingShortcut = Object.values(shortcutsRef.current).find(shortcut => {
      if (shortcut.enabled === false) {
        return false;
      }
 
      // Get platform-specific keys if available
      const shortcutKeys = getPlatformShortcut(
        shortcut.keys,
        shortcut.platformKeys
      );
 
      // Check if keys match
      return shortcutsMatch(keyCombination, shortcutKeys);
    });
 
    // Execute handler if match found
    if (matchingShortcut) {
      logger.debug('Keyboard shortcut triggered', {
        id: matchingShortcut.id,
        keys: keyCombination,
        description: matchingShortcut.description,
      });
 
      if (optionsRef.current.preventDefault) {
        event.preventDefault();
      }
      Iif (optionsRef.current.stopPropagation) {
        event.stopPropagation();
      }
 
      try {
        matchingShortcut.handler();
      } catch (error) {
        logger.error('Error executing keyboard shortcut handler', {
          id: matchingShortcut.id,
          error,
        });
      }
    }
  }, []);
 
  // Register and cleanup event listener
  useEffect(() => {
    if (!enabled || !areKeyboardShortcutsSupported()) {
      return;
    }
 
    logger.debug('Registering keyboard shortcuts');
 
    window.addEventListener('keydown', handleKeyDown);
 
    return () => {
      logger.debug('Unregistering keyboard shortcuts');
      window.removeEventListener('keydown', handleKeyDown);
    };
  }, [enabled, handleKeyDown]);
}