-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathuse-wasd.ts
87 lines (73 loc) · 2.36 KB
/
use-wasd.ts
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { MutableRefObject, useEffect, useRef, useState } from "react";
export interface UseWASDOptions {
allowed?: string[];
blocked?: string[];
combos?: Record<string, string[]>;
initialValue?: Record<string, boolean>;
preventDefault?: boolean | string[];
ref?: MutableRefObject<Element>;
}
function shouldTrack(options: UseWASDOptions, key: string) {
const allowAll = !options.allowed;
const isBlocked = options.blocked?.includes(key);
const isAllowed = allowAll || options.allowed?.includes(key);
return !(isBlocked || !isAllowed);
}
function shouldPreventDefault(
preventDefault: UseWASDOptions["preventDefault"],
key: string
) {
if (Array.isArray(preventDefault)) {
return preventDefault?.includes(key);
}
return Boolean(preventDefault);
}
const initialValue = {};
export default function useWASD(options: UseWASDOptions = initialValue) {
const [keyboard, setKeyboard] = useState(
options.initialValue || initialValue
);
const keys = useRef({ ...keyboard });
useEffect(() => {
function update() {
const matchingCombos = options.combos
? Object.entries(options.combos).reduce(
(previousValue, [name, characters]) => ({
...previousValue,
[name]: characters.every((character) => keys.current[character]),
}),
{}
)
: {};
setKeyboard({ ...keys.current, ...matchingCombos });
}
function handleDown(event: KeyboardEvent) {
const key = event.key.toLowerCase().trim() || "space";
if (shouldPreventDefault(options.preventDefault, key)) {
event.preventDefault();
}
if (shouldTrack(options, key)) {
keys.current[key] = true;
update();
}
}
function handleUp(event: KeyboardEvent) {
const key = event.key.toLowerCase().trim() || "space";
if (shouldPreventDefault(options.preventDefault, key)) {
event.preventDefault();
}
if (shouldTrack(options, key)) {
keys.current[key] = false;
update();
}
}
const context = options.ref?.current ?? document;
context.addEventListener("keydown", handleDown);
context.addEventListener("keyup", handleUp);
return () => {
context.removeEventListener("keydown", handleDown);
context.removeEventListener("keyup", handleUp);
};
}, [options]);
return keyboard;
}