@inklu/keys 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -21
- package/dist/attributes.d.mts +1 -1
- package/dist/attributes.mjs +15 -18
- package/dist/{index-3d9f54ln.d.mts → index-DRXMJmbc.d.mts} +53 -36
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/react.d.mts +17 -34
- package/dist/react.mjs +36 -74
- package/dist/{src-B76YvObL.mjs → src-TaZw4V61.mjs} +624 -514
- package/dist/{types-QEXldPnL.d.mts → types-D-hUKGoN.d.mts} +101 -142
- package/package.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -1,53 +1,39 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as importKeymap, c as bindingTokens, d as isSingleKeyBinding, f as seq, i as exportKeymap, l as formatBinding, m as keyName, n as createCommands, o as webStorage, p as isModifierKey, r as createRebinder, s as ariaKeyShortcuts, t as matchCommands, u as spokenBinding } from "./src-TaZw4V61.mjs";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
4
4
|
//#region src/held.ts
|
|
5
5
|
/** Which keys are down right now. Listens only while something is subscribed. */
|
|
6
|
-
const NONE =
|
|
7
|
-
keys: [],
|
|
8
|
-
codes: {}
|
|
9
|
-
};
|
|
6
|
+
const NONE = [];
|
|
10
7
|
/** Held keys by physical key, so two keys producing one character stay apart. */
|
|
11
8
|
const down = /* @__PURE__ */ new Map();
|
|
12
9
|
const listeners = /* @__PURE__ */ new Set();
|
|
13
|
-
let
|
|
10
|
+
let held = NONE;
|
|
14
11
|
const identify = (event) => event.code || `key:${keyName(event.key)}`;
|
|
15
12
|
function publish() {
|
|
16
|
-
|
|
17
|
-
state = entries.length === 0 ? NONE : {
|
|
18
|
-
keys: [...new Set(entries.map((entry) => entry.key))],
|
|
19
|
-
codes: Object.fromEntries(entries.map((entry) => [entry.key, entry.code]))
|
|
20
|
-
};
|
|
13
|
+
held = down.size === 0 ? NONE : [...new Set(down.values())];
|
|
21
14
|
for (const listener of listeners) listener();
|
|
22
15
|
}
|
|
23
16
|
function onKeyDown(event) {
|
|
24
17
|
if (!event.key || down.has(identify(event))) return;
|
|
25
|
-
down.set(identify(event),
|
|
26
|
-
key: keyName(event.key),
|
|
27
|
-
code: event.code
|
|
28
|
-
});
|
|
18
|
+
down.set(identify(event), keyName(event.key));
|
|
29
19
|
publish();
|
|
30
20
|
}
|
|
31
21
|
function onKeyUp(event) {
|
|
32
22
|
down.delete(identify(event));
|
|
33
23
|
if (event.key === "Meta") {
|
|
34
|
-
for (const [identity,
|
|
24
|
+
for (const [identity, key] of down) if (!isModifierKey(key)) down.delete(identity);
|
|
35
25
|
}
|
|
36
26
|
publish();
|
|
37
27
|
}
|
|
38
|
-
/** Leaving the window swallows the keyups, so nothing is held any more. */
|
|
39
|
-
function
|
|
28
|
+
/** Leaving the window or hiding the page swallows the keyups, so nothing is held any more. */
|
|
29
|
+
function onLeave() {
|
|
40
30
|
if (down.size === 0) return;
|
|
41
31
|
down.clear();
|
|
42
32
|
publish();
|
|
43
33
|
}
|
|
44
|
-
/** The keys held right now. Empty unless something is subscribed. */
|
|
34
|
+
/** The keys held right now, by name. Empty unless something is subscribed. */
|
|
45
35
|
function heldKeys() {
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
/** Whether `key` is held, by the name `KeyboardEvent.key` gives it. */
|
|
49
|
-
function isHeld(key) {
|
|
50
|
-
return state.keys.includes(keyName(key));
|
|
36
|
+
return held;
|
|
51
37
|
}
|
|
52
38
|
/** Watches held keys until the returned function is called. */
|
|
53
39
|
function subscribeHeldKeys(listener) {
|
|
@@ -55,27 +41,26 @@ function subscribeHeldKeys(listener) {
|
|
|
55
41
|
if (listeners.size === 1) {
|
|
56
42
|
document.addEventListener("keydown", onKeyDown, true);
|
|
57
43
|
document.addEventListener("keyup", onKeyUp, true);
|
|
58
|
-
|
|
44
|
+
document.addEventListener("visibilitychange", onLeave);
|
|
45
|
+
window.addEventListener("blur", onLeave);
|
|
59
46
|
}
|
|
60
47
|
return () => {
|
|
61
48
|
listeners.delete(listener);
|
|
62
49
|
if (listeners.size > 0) return;
|
|
63
50
|
document.removeEventListener("keydown", onKeyDown, true);
|
|
64
51
|
document.removeEventListener("keyup", onKeyUp, true);
|
|
65
|
-
|
|
52
|
+
document.removeEventListener("visibilitychange", onLeave);
|
|
53
|
+
window.removeEventListener("blur", onLeave);
|
|
66
54
|
down.clear();
|
|
67
|
-
|
|
55
|
+
held = NONE;
|
|
68
56
|
};
|
|
69
57
|
}
|
|
70
58
|
//#endregion
|
|
71
59
|
//#region src/react/hooks.ts
|
|
72
60
|
const EMPTY_TOKENS = [];
|
|
73
|
-
const EMPTY_CODES = {};
|
|
74
61
|
const EMPTY_PENDING = [];
|
|
75
62
|
const getEmptyTokens = () => EMPTY_TOKENS;
|
|
76
|
-
const getEmptyCodes = () => EMPTY_CODES;
|
|
77
63
|
const getEmptyPending = () => EMPTY_PENDING;
|
|
78
|
-
const getFalse = () => false;
|
|
79
64
|
/**
|
|
80
65
|
* Mounts a commands registry for the lifecycle of the component.
|
|
81
66
|
*
|
|
@@ -90,20 +75,23 @@ const getFalse = () => false;
|
|
|
90
75
|
function useMountCommands(registry) {
|
|
91
76
|
useEffect(() => registry.mount(), [registry]);
|
|
92
77
|
}
|
|
93
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Returns all registered commands, re-rendering when the registry changes.
|
|
80
|
+
* Hydrates against `serverList()`, so a subtree that hydrates late still
|
|
81
|
+
* matches the server's HTML.
|
|
82
|
+
*/
|
|
83
|
+
function useCommands(registry) {
|
|
94
84
|
const subscribe = useCallback((onChange) => registry.subscribe(onChange), [registry]);
|
|
95
85
|
const getSnapshot = useCallback(() => registry.list(), [registry]);
|
|
96
86
|
const getServerSnapshot = useCallback(() => registry.serverList(), [registry]);
|
|
97
87
|
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
98
88
|
}
|
|
99
|
-
/** Returns
|
|
100
|
-
function useCommands(registry) {
|
|
101
|
-
return useCommandList(registry);
|
|
102
|
-
}
|
|
103
|
-
/** Returns a specific command snapshot by ID. */
|
|
89
|
+
/** Returns one command's snapshot, re-rendering only when that command changes. */
|
|
104
90
|
function useCommand(registry, id) {
|
|
105
|
-
const
|
|
106
|
-
|
|
91
|
+
const subscribe = useCallback((onChange) => registry.subscribe(onChange), [registry]);
|
|
92
|
+
const getSnapshot = useCallback(() => registry.get(id), [registry, id]);
|
|
93
|
+
const getServerSnapshot = useCallback(() => registry.serverList().find((command) => command.id === id), [registry, id]);
|
|
94
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
107
95
|
}
|
|
108
96
|
/**
|
|
109
97
|
* Returns display and accessibility shortcuts for a command's primary
|
|
@@ -121,9 +109,8 @@ function useShortcut(registry, id) {
|
|
|
121
109
|
}
|
|
122
110
|
/**
|
|
123
111
|
* Activates a scope for the lifecycle of the component. Scopes rank in the
|
|
124
|
-
* order
|
|
125
|
-
*
|
|
126
|
-
* the same render the child is pushed first.
|
|
112
|
+
* order React runs effects in: of a parent and a child opening in the same
|
|
113
|
+
* render, the child is pushed first.
|
|
127
114
|
*
|
|
128
115
|
* @example
|
|
129
116
|
* ```tsx
|
|
@@ -143,12 +130,10 @@ function useScope(registry, name, active = true, options = {}) {
|
|
|
143
130
|
]);
|
|
144
131
|
}
|
|
145
132
|
/**
|
|
146
|
-
* Attaches what a command does for the lifecycle of the component
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* The handler attached last wins, and React attaches a parent's after its
|
|
151
|
-
* children's when they mount together.
|
|
133
|
+
* Attaches what a command does for the lifecycle of the component. The latest
|
|
134
|
+
* `run` is always the one called, and the handler attached last wins. While
|
|
135
|
+
* `enabled` is `false` the command falls to the handler attached before it,
|
|
136
|
+
* or cannot run if there is none.
|
|
152
137
|
*
|
|
153
138
|
* @example
|
|
154
139
|
* ```tsx
|
|
@@ -221,38 +206,15 @@ function useRebind(registry, id, options = {}) {
|
|
|
221
206
|
id
|
|
222
207
|
]);
|
|
223
208
|
}
|
|
224
|
-
/** Returns
|
|
209
|
+
/** Returns the keys held down right now, by the name `KeyboardEvent.key` gives each. */
|
|
225
210
|
function useHeldKeys() {
|
|
226
|
-
return useSyncExternalStore(subscribeHeldKeys,
|
|
211
|
+
return useSyncExternalStore(subscribeHeldKeys, heldKeys, getEmptyTokens);
|
|
227
212
|
}
|
|
228
|
-
/**
|
|
229
|
-
* Checks whether a specific key is currently held down.
|
|
230
|
-
*
|
|
231
|
-
* @example
|
|
232
|
-
* ```tsx
|
|
233
|
-
* const shift = useKeyHold("Shift");
|
|
234
|
-
* ```
|
|
235
|
-
*/
|
|
236
|
-
function useKeyHold(key) {
|
|
237
|
-
const getSnapshot = useCallback(() => isHeld(key), [key]);
|
|
238
|
-
return useSyncExternalStore(subscribeHeldKeys, getSnapshot, getFalse);
|
|
239
|
-
}
|
|
240
|
-
/** Returns a map of held key identifiers to physical event codes. */
|
|
241
|
-
function useHeldKeyCodes() {
|
|
242
|
-
return useSyncExternalStore(subscribeHeldKeys, () => heldKeys().codes, getEmptyCodes);
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Returns currently active partially-typed sequences.
|
|
246
|
-
*
|
|
247
|
-
* @example
|
|
248
|
-
* ```tsx
|
|
249
|
-
* const pending = usePendingSequences(commands);
|
|
250
|
-
* ```
|
|
251
|
-
*/
|
|
213
|
+
/** Returns the half-typed sequences, for a hint such as "G …". */
|
|
252
214
|
function usePendingSequences(registry) {
|
|
253
215
|
const subscribe = useCallback((onChange) => registry.subscribePending(onChange), [registry]);
|
|
254
216
|
const getSnapshot = useCallback(() => registry.pending(), [registry]);
|
|
255
217
|
return useSyncExternalStore(subscribe, getSnapshot, getEmptyPending);
|
|
256
218
|
}
|
|
257
219
|
//#endregion
|
|
258
|
-
export { ariaKeyShortcuts, bindingTokens, createCommands, createRebinder, formatBinding, isSingleKeyBinding, matchCommands, seq, spokenBinding, useCommand, useCommandHandler, useCommands,
|
|
220
|
+
export { ariaKeyShortcuts, bindingTokens, createCommands, createRebinder, exportKeymap, formatBinding, importKeymap, isSingleKeyBinding, matchCommands, seq, spokenBinding, useCommand, useCommandHandler, useCommands, useHeldKeys, useMountCommands, usePendingSequences, useRebind, useScope, useShortcut, webStorage };
|