import { useEffect, useState } from "react"; export interface ShortcutModifierState { metaKey: boolean; ctrlKey: boolean; altKey: boolean; shiftKey: boolean;} const EMPTY_SHORTCUT_MODIFIER_STATE: ShortcutModifierState = { metaKey: false, ctrlKey: false, altKey: false, shiftKey: false,}; export function areShortcutModifierStatesEqual( left: ShortcutModifierState, right: ShortcutModifierState,): boolean { return ( left.metaKey === right.metaKey && left.ctrlKey === right.ctrlKey && left.altKey === right.altKey && left.shiftKey === right.shiftKey );} export function useShortcutModifierState(): ShortcutModifierState { const [state, setState] = useState(EMPTY_SHORTCUT_MODIFIER_STATE); useEffect(() => { const onKeyboardEvent = (event: KeyboardEvent) => { setState((current) => shortcutModifierStateAfterKeyboardEvent(current, event)); }; const onWindowBlur = () => { setState((current) => areShortcutModifierStatesEqual(current, EMPTY_SHORTCUT_MODIFIER_STATE) ? current : EMPTY_SHORTCUT_MODIFIER_STATE, ); }; window.addEventListener("keydown", onKeyboardEvent, true); window.addEventListener("keyup", onKeyboardEvent, true); window.addEventListener("blur", onWindowBlur); return () => { window.removeEventListener("keydown", onKeyboardEvent, true); window.removeEventListener("keyup", onKeyboardEvent, true); window.removeEventListener("blur", onWindowBlur); }; }, []); return state;} function normalizeModifierKey(key: string): keyof ShortcutModifierState | null { switch (key) { case "Meta": case "OS": case "Command": return "metaKey"; case "Control": return "ctrlKey"; case "Alt": case "Option": return "altKey"; case "Shift": return "shiftKey"; default: return null; }} export function shortcutModifierStateAfterKeyboardEvent( currentState: ShortcutModifierState, event: KeyboardEvent,): ShortcutModifierState { const normalizedModifierKey = normalizeModifierKey(event.key); let nextState: ShortcutModifierState; if (normalizedModifierKey) { nextState = { ...currentState, [normalizedModifierKey]: event.type === "keydown", }; } else { nextState = { metaKey: event.metaKey, ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, }; } return areShortcutModifierStatesEqual(currentState, nextState) ? currentState : nextState;}
import { useEffect, useState } from "react";
export interface ShortcutModifierState {
metaKey: boolean;
ctrlKey: boolean;
altKey: boolean;
shiftKey: boolean;
}
const EMPTY_SHORTCUT_MODIFIER_STATE: ShortcutModifierState = {
metaKey: false,
ctrlKey: false,
altKey: false,
shiftKey: false,
};
export function areShortcutModifierStatesEqual(
left: ShortcutModifierState,
right: ShortcutModifierState,
): boolean {
return (
left.metaKey === right.metaKey &&
left.ctrlKey === right.ctrlKey &&
left.altKey === right.altKey &&
left.shiftKey === right.shiftKey
);
}
export function useShortcutModifierState(): ShortcutModifierState {
const [state, setState] = useState(EMPTY_SHORTCUT_MODIFIER_STATE);
useEffect(() => {
const onKeyboardEvent = (event: KeyboardEvent) => {
setState((current) => shortcutModifierStateAfterKeyboardEvent(current, event));
};
const onWindowBlur = () => {
setState((current) =>
areShortcutModifierStatesEqual(current, EMPTY_SHORTCUT_MODIFIER_STATE)
? current
: EMPTY_SHORTCUT_MODIFIER_STATE,
);
};
window.addEventListener("keydown", onKeyboardEvent, true);
window.addEventListener("keyup", onKeyboardEvent, true);
window.addEventListener("blur", onWindowBlur);
return () => {
window.removeEventListener("keydown", onKeyboardEvent, true);
window.removeEventListener("keyup", onKeyboardEvent, true);
window.removeEventListener("blur", onWindowBlur);
};
}, []);
return state;
}
function normalizeModifierKey(key: string): keyof ShortcutModifierState | null {
switch (key) {
case "Meta":
case "OS":
case "Command":
return "metaKey";
case "Control":
return "ctrlKey";
case "Alt":
case "Option":
return "altKey";
case "Shift":
return "shiftKey";
default:
return null;
}
}
export function shortcutModifierStateAfterKeyboardEvent(
currentState: ShortcutModifierState,
event: KeyboardEvent,
): ShortcutModifierState {
const normalizedModifierKey = normalizeModifierKey(event.key);
let nextState: ShortcutModifierState;
if (normalizedModifierKey) {
nextState = {
...currentState,
[normalizedModifierKey]: event.type === "keydown",
};
} else {
nextState = {
metaKey: event.metaKey,
ctrlKey: event.ctrlKey,
altKey: event.altKey,
shiftKey: event.shiftKey,
};
}
return areShortcutModifierStatesEqual(currentState, nextState) ? currentState : nextState;
}