30 lines
1.4 KiB
TypeScript
30 lines
1.4 KiB
TypeScript
import { useEffect } from "react";
|
|
|
|
export function useShortcuts(spec: {keys: string[], action: () => void}[], ignoreInputs = true) {
|
|
// I don't know if this is efficient, but I decided to just register one event listener for every shortcut, rather than generating one big event listener for all shortcuts.
|
|
// The benefit is that we don't have to memoize anything: useEffect will only be called if the action updated, and React allows calling useEffect for every item in a list as long as the list doesn't change.
|
|
for (const {keys, action} of spec) {
|
|
useEffect(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
if (ignoreInputs) {
|
|
// @ts-ignore: don't steal keyboard events while the user is typing in a text box, etc.
|
|
if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target?.tagName)) return;
|
|
}
|
|
|
|
if (e.ctrlKey !== keys.includes("Ctrl")) return;
|
|
if (e.shiftKey !== keys.includes("Shift")) return;
|
|
if (!keys.includes(e.key)) return;
|
|
const remainingKeys = keys.filter(key => key !== "Ctrl" && key !== "Shift" && key !== e.key);
|
|
if (remainingKeys.length !== 0) {
|
|
console.warn("impossible shortcut sequence:", keys.join(' + '));
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
action();
|
|
};
|
|
window.addEventListener("keydown", handler);
|
|
return () => window.removeEventListener("keydown", handler);
|
|
}, [action]);
|
|
}
|
|
}
|