All files / memory-monitor/src/hooks useKeyboardShortcut.ts

100% Statements 36/36
88.88% Branches 24/27
100% Functions 11/11
100% Lines 36/36

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                                4x 4x 4x   4x                                                       64x     64x 27x     64x   4x     4x 4x 4x 4x   4x 2x 2x 2x           64x 47x 27x     20x   20x 20x                       64x   64x 27x     64x 46x 30x     16x 1x 1x 1x       16x   16x 16x        
import { useEffect, useCallback, useRef } from "react";
import { isSSR } from "../utils/environment";
 
/**
 * Parse a keyboard shortcut string into its components
 *
 * @param shortcut - Shortcut string like "ctrl+shift+m"
 * @returns Object with modifier keys and the main key
 */
function parseShortcut(shortcut: string): {
  ctrl: boolean;
  shift: boolean;
  alt: boolean;
  meta: boolean;
  key: string;
} {
  const parts = shortcut.toLowerCase().split("+");
  const key = parts.pop() || "";
  const modifiers = new Set(parts);
 
  return {
    ctrl: modifiers.has("ctrl") || modifiers.has("control"),
    shift: modifiers.has("shift"),
    alt: modifiers.has("alt"),
    meta: modifiers.has("meta") || modifiers.has("cmd") || modifiers.has("command"),
    key,
  };
}
 
/**
 * Hook to handle keyboard shortcuts
 *
 * @param shortcut - Keyboard shortcut string (e.g., "ctrl+shift+m")
 * @param callback - Function to call when shortcut is pressed
 * @param enabled - Whether the shortcut is enabled
 *
 * @example
 * ```tsx
 * useKeyboardShortcut("ctrl+shift+m", () => {
 *   setOpen(prev => !prev);
 * });
 * ```
 */
export function useKeyboardShortcut(
  shortcut: string,
  callback: () => void,
  enabled = true
): void {
  const callbackRef = useRef(callback);
 
  // Update callback ref when callback changes
  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);
 
  const handleKeyDown = useCallback(
    (event: KeyboardEvent) => {
      const parsed = parseShortcut(shortcut);
 
      // Check if all modifiers match
      const ctrlMatch = parsed.ctrl === (event.ctrlKey || event.metaKey);
      const shiftMatch = parsed.shift === event.shiftKey;
      const altMatch = parsed.alt === event.altKey;
      const keyMatch = event.key.toLowerCase() === parsed.key;
 
      if (ctrlMatch && shiftMatch && altMatch && keyMatch) {
        event.preventDefault();
        event.stopPropagation();
        callbackRef.current();
      }
    },
    [shortcut]
  );
 
  useEffect(() => {
    if (isSSR() || !enabled) {
      return;
    }
 
    document.addEventListener("keydown", handleKeyDown);
 
    return () => {
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [handleKeyDown, enabled]);
}
 
/**
 * Hook to handle Escape key press
 *
 * @param callback - Function to call when Escape is pressed
 * @param enabled - Whether the handler is enabled
 */
export function useEscapeKey(callback: () => void, enabled = true): void {
  const callbackRef = useRef(callback);
 
  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);
 
  useEffect(() => {
    if (isSSR() || !enabled) {
      return;
    }
 
    const handleKeyDown = (event: KeyboardEvent) => {
      Eif (event.key === "Escape") {
        event.preventDefault();
        callbackRef.current();
      }
    };
 
    document.addEventListener("keydown", handleKeyDown);
 
    return () => {
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [enabled]);
}