@hookies/key-bindings
Version:
A React hook library for adding keyboard shortcuts
67 lines (56 loc) • 2.13 kB
text/typescript
import { useEffect, useRef, useMemo } from "react";
type ShortcutKeys = string[];
interface UseShortcutOptions {
preventDefault?: boolean; // Optional: Prevent default behavior
}
/**
* `useShortcutExtended` - Detects any keyboard shortcut.
* Example: `A + S`, `A + 1 + M`, `X + Z`
*/
export function useShortcutExtended(
keys: ShortcutKeys,
callback: () => void,
options: UseShortcutOptions = {}
): void {
// Using a ref to hold pressed keys avoids re-rendering on every update.
const pressedKeysRef = useRef<Set<string>>(new Set());
const { preventDefault } = options;
// Pre-compute lowercase target keys for performance.
const keysLower = useMemo(() => keys.map((key) => key.toLowerCase()), [keys]);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
const key = event.key.toLowerCase();
// Create a new set based on the current ref value.
const newSet = new Set(pressedKeysRef.current);
newSet.add(key);
// If every target key is present in the new set, trigger the callback.
if (keysLower.every((k) => newSet.has(k))) {
if (preventDefault) event.preventDefault();
callback();
}
// Update the ref with the new set.
pressedKeysRef.current = newSet;
};
const handleKeyUp = (event: KeyboardEvent) => {
const key = event.key.toLowerCase();
const newSet = new Set(pressedKeysRef.current);
newSet.delete(key);
// If a modifier key is released, clear the set.
if (["meta", "control", "alt", "shift"].includes(key)) {
newSet.clear();
}
pressedKeysRef.current = newSet;
};
const handleBlur = () => {
pressedKeysRef.current.clear();
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
window.addEventListener("blur", handleBlur);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
window.removeEventListener("blur", handleBlur);
};
}, [keysLower, callback, preventDefault]);
}