@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/dist/react.mjs CHANGED
@@ -1,53 +1,39 @@
1
1
  "use client";
2
- import { a as bindingTokens, c as isSingleKeyBinding, d as isModifierKey, f as keyName, i as ariaKeyShortcuts, l as seq, n as createCommands, o as formatBinding, r as createRebinder, s as spokenBinding, t as matchCommands, u as validateBinding } from "./src-B76YvObL.mjs";
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 state = NONE;
10
+ let held = NONE;
14
11
  const identify = (event) => event.code || `key:${keyName(event.key)}`;
15
12
  function publish() {
16
- const entries = [...down.values()];
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, entry] of down) if (!isModifierKey(entry.key)) down.delete(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 onBlur() {
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 state;
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
- window.addEventListener("blur", onBlur);
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
- window.removeEventListener("blur", onBlur);
52
+ document.removeEventListener("visibilitychange", onLeave);
53
+ window.removeEventListener("blur", onLeave);
66
54
  down.clear();
67
- state = NONE;
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
- function useCommandList(registry) {
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 all registered commands, re-rendering when the registry changes. */
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 commands = useCommandList(registry);
106
- return useMemo(() => commands.find((command) => command.id === id), [commands, id]);
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 they are pushed, which here is the order React runs effects in: a
125
- * surface opened later ranks higher, but of a parent and a child opening in
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, so the
147
- * behaviour lives where its state does. The latest `run` is always the one
148
- * called. While `enabled` is `false` the component steps back: the command
149
- * falls to the handler attached before it, or cannot run if there is none.
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 an array of keys currently held down. */
209
+ /** Returns the keys held down right now, by the name `KeyboardEvent.key` gives each. */
225
210
  function useHeldKeys() {
226
- return useSyncExternalStore(subscribeHeldKeys, () => heldKeys().keys, getEmptyTokens);
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, useHeldKeyCodes, useHeldKeys, useKeyHold, useMountCommands, usePendingSequences, useRebind, useScope, useShortcut, validateBinding };
220
+ export { ariaKeyShortcuts, bindingTokens, createCommands, createRebinder, exportKeymap, formatBinding, importKeymap, isSingleKeyBinding, matchCommands, seq, spokenBinding, useCommand, useCommandHandler, useCommands, useHeldKeys, useMountCommands, usePendingSequences, useRebind, useScope, useShortcut, webStorage };