import { KeyboardEventHandler, useCallback, useRef } from "react"; import { useToggle, useWindowEvent } from "hooks"; type KeyBindingToggle = (data: { pressed: boolean; event: React.KeyboardEvent; keys: string[] }) => void; type KeyBindingWindowToggle = (data: { pressed: boolean; event: KeyboardEvent; keys: string[] }) => void; /** * * @kind 05-Keyboard */ export const useKey = ( keys: string | string[] | ((keys: string[]) => boolean), options: { onKeyDown?: KeyBindingWindowToggle; onKeyUp?: KeyBindingWindowToggle; onToggle?: KeyBindingWindowToggle; dependencies?: unknown[]; }, ) => { const { onKeyDown, onKeyUp, onToggle, dependencies = [] } = options || {}; const pressedKeys = useRef([]); const [pressed, , setToggle] = useToggle(); const isPressed = useCallback(() => { const activeKeys = pressedKeys.current; if (Array.isArray(keys)) { return keys.every((key) => activeKeys.includes(key)); } if (typeof keys === "function") { return keys(activeKeys); } return activeKeys.includes(keys); }, [keys]); const handleKeyDown = useCallback( (event: KeyboardEvent) => { if (!pressedKeys.current.includes(event.key)) { pressedKeys.current.push(event.key); } if (isPressed()) { onToggle?.({ pressed: true, keys: pressedKeys.current, event }); onKeyDown?.({ pressed: true, keys: pressedKeys.current, event }); setToggle(true); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [isPressed, onKeyDown, onToggle, setToggle, ...dependencies], ); const handleKeyUp = useCallback( (event: KeyboardEvent) => { pressedKeys.current = pressedKeys.current.filter((pressedKey) => pressedKey !== event.key); if (!isPressed()) { onToggle?.({ pressed: false, keys: pressedKeys.current, event }); onKeyUp?.({ pressed: true, keys: pressedKeys.current, event }); setToggle(false); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [isPressed, onKeyDown, onToggle, setToggle, ...dependencies], ); useWindowEvent("keydown", handleKeyDown); useWindowEvent("keyup", handleKeyUp); return { pressed }; }; /** * * @kind 05-Keyboard */ export const useKeyBinding = ( keys: string | string[] | ((keys: string[]) => boolean), options: { onKeyDown?: KeyBindingToggle; onKeyUp?: KeyBindingToggle; onToggle?: KeyBindingToggle; dependencies?: unknown[]; }, ) => { const { onKeyDown, onKeyUp, onToggle, dependencies = [] } = options || {}; const pressedKeys = useRef([]); const [pressed, , setToggle] = useToggle(); const isPressed = useCallback(() => { const activeKeys = pressedKeys.current; if (Array.isArray(keys)) { return keys.every((key) => activeKeys.includes(key)); } if (typeof keys === "function") { return keys(activeKeys); } return activeKeys.includes(keys); }, [keys]); const handleKeyDown: KeyboardEventHandler = useCallback( (event) => { if (!pressedKeys.current.includes(event.key)) { pressedKeys.current.push(event.key); } if (isPressed()) { onToggle?.({ pressed: true, keys: pressedKeys.current, event }); onKeyDown?.({ pressed: true, keys: pressedKeys.current, event }); setToggle(true); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [isPressed, onKeyDown, onToggle, setToggle, ...dependencies], ); const handleKeyUp: KeyboardEventHandler = useCallback( (event) => { pressedKeys.current = pressedKeys.current.filter((pressedKey) => pressedKey !== event.key); if (!isPressed()) { onToggle?.({ pressed: false, keys: pressedKeys.current, event }); onKeyUp?.({ pressed: true, keys: pressedKeys.current, event }); setToggle(false); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [isPressed, onKeyUp, onToggle, setToggle, ...dependencies], ); return { pressed, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }; };