All files / src/hooks useKeyboardShortcut.ts

95.45% Statements 21/22
96.66% Branches 29/30
100% Functions 4/4
100% Lines 15/15

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                                  15x   15x   15x     15x 15x             5x     12x 11x 10x   9x 9x         15x 15x 14x 14x      
import { useEffect, useCallback } from "react";
 
/**
 * Hook to register a keyboard shortcut.
 * @param key - The key to listen for (e.g., "s", "k", "Escape")
 * @param callback - Function to call when the shortcut is triggered
 * @param options - Configuration options
 */
export function useKeyboardShortcut(
	key: string,
	callback: () => void,
	options: {
		meta?: boolean;
		shift?: boolean;
		enabled?: boolean;
	} = {},
) {
	const { meta = false, shift = false, enabled = true } = options;
 
	const handler = useCallback(
		(e: KeyboardEvent) => {
			Iif (!enabled) return;
 
			// Don't trigger shortcuts when typing in inputs/textareas
			const target = e.target as HTMLElement;
			if (
				target.tagName === "INPUT" ||
				target.tagName === "TEXTAREA" ||
				target.tagName === "SELECT" ||
				target.isContentEditable
			) {
				// Allow Escape and meta shortcuts even in inputs
				if (key !== "Escape" && !meta) return;
			}
 
			if (meta && !(e.metaKey || e.ctrlKey)) return;
			if (shift && !e.shiftKey) return;
			if (e.key.toLowerCase() !== key.toLowerCase()) return;
 
			e.preventDefault();
			callback();
		},
		[key, callback, meta, shift, enabled],
	);
 
	useEffect(() => {
		if (!enabled) return;
		window.addEventListener("keydown", handler);
		return () => window.removeEventListener("keydown", handler);
	}, [handler, enabled]);
}