@inklu/keys 2.1.0 → 3.0.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.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as SequenceBinding, D as Chord, E as SerializedOverride, O as Key, S as ScopeOptions, T as SerializedKeymap, _ as KeymapPersistence, a as CommandDefinition, b as RunBlockReason, c as CommandSource, d as Conflict, f as ConflictKind, g as KeyCapture, h as Json, i as CommandContext, k as Platform, l as Commands, m as DisplayOptions, n as BindingInput, o as CommandHandler, p as ConflictOptions, r as Command, s as CommandKeymap, t as Binding, u as CommandsOptions, v as KeymapStorage, w as SerializedBinding, x as RunOptions, y as PendingSequence } from "./types-QEXldPnL.mjs";
2
- import { a as RebindOptions, c as createRebinder, d as formatBinding, f as spokenBinding, g as validateBinding, h as seq, i as RebindMode, l as ariaKeyShortcuts, m as isSingleKeyBinding, n as CommandId, o as RebindState, p as BindingValidation, r as createCommands, s as Rebinder, t as matchCommands, u as bindingTokens } from "./index-3d9f54ln.mjs";
1
+ import { C as Chord, S as SerializedKeymap, T as Platform, _ as RunBlockReason, a as CommandDefinition, b as SequenceBinding, c as CommandSource, d as Conflict, f as ConflictOptions, g as PlatformBindings, h as KeymapStore, i as CommandContext, l as Commands, m as KeyCapture, n as BindingInput, o as CommandHandler, p as Json, r as Command, s as CommandKeymap, t as Binding, u as CommandsOptions, v as RunOptions, w as Key, x as SerializedBinding, y as ScopeOptions } from "./types-BxMI3b4a.mjs";
2
+ import { a as Rebinder, c as importKeymap, d as bindingTokens, f as formatBinding, i as RebindState, l as webStorage, m as seq, n as createCommands, o as createRebinder, p as isSingleKeyBinding, r as RebindOptions, s as exportKeymap, t as CommandId, u as ariaKeyShortcuts } from "./index-BnB38jCF.mjs";
3
3
  //#region src/react/hooks.d.ts
4
4
  /**
5
5
  * Mounts a commands registry for the lifecycle of the component.
@@ -13,26 +13,27 @@ import { a as RebindOptions, c as createRebinder, d as formatBinding, f as spoke
13
13
  * ```
14
14
  */
15
15
  export declare function useMountCommands<Id extends string>(registry: Commands<Id>): void;
16
- /** Returns all registered commands, re-rendering when the registry changes. */
16
+ /**
17
+ * Returns all registered commands, re-rendering when the registry changes.
18
+ * Hydrates against `serverList()`, so a subtree that hydrates late still
19
+ * matches the server's HTML.
20
+ */
17
21
  export declare function useCommands<Id extends string>(registry: Commands<Id>): ReadonlyArray<Command<Id>>;
18
- /** Returns a specific command snapshot by ID. */
22
+ /** Returns one command's snapshot, re-rendering only when that command changes. */
19
23
  export declare function useCommand<Id extends string>(registry: Commands<Id>, id: NoInfer<Id>): Command<Id> | undefined;
20
24
  /**
21
- * Returns display and accessibility shortcuts for a command's primary
22
- * binding: `tokens` for `<kbd>` elements, `aria` for `aria-keyshortcuts`
23
- * (chords only), and `spoken` for screen-reader text such as `"G then I"`.
25
+ * Returns a command's primary binding for people: `display` to read,
26
+ * `tokens` for `<kbd>` elements, and `aria` for `aria-keyshortcuts` (chords only).
24
27
  */
25
28
  export declare function useShortcut<Id extends string>(registry: Commands<Id>, id: NoInfer<Id>): {
26
29
  display: string;
27
30
  tokens: ReadonlyArray<string>;
28
31
  aria: string;
29
- spoken: string;
30
32
  };
31
33
  /**
32
34
  * Activates a scope for the lifecycle of the component. Scopes rank in the
33
- * order they are pushed, which here is the order React runs effects in: a
34
- * surface opened later ranks higher, but of a parent and a child opening in
35
- * the same render the child is pushed first.
35
+ * order React runs effects in: of a parent and a child opening in the same
36
+ * render, the child is pushed first.
36
37
  *
37
38
  * @example
38
39
  * ```tsx
@@ -41,12 +42,10 @@ export declare function useShortcut<Id extends string>(registry: Commands<Id>, i
41
42
  */
42
43
  export declare function useScope<Id extends string>(registry: Commands<Id>, name: string, active?: boolean, options?: ScopeOptions): void;
43
44
  /**
44
- * Attaches what a command does for the lifecycle of the component, so the
45
- * behaviour lives where its state does. The latest `run` is always the one
46
- * called. While `enabled` is `false` the component steps back: the command
47
- * falls to the handler attached before it, or cannot run if there is none.
48
- * The handler attached last wins, and React attaches a parent's after its
49
- * children's when they mount together.
45
+ * Attaches what a command does for the lifecycle of the component. The latest
46
+ * `run` is always the one called, and the handler attached last wins. While
47
+ * `enabled` is `false` the command falls to the handler attached before it,
48
+ * or cannot run if there is none.
50
49
  *
51
50
  * @example
52
51
  * ```tsx
@@ -64,35 +63,14 @@ interface UseRebindResult<Id extends string = string> extends RebindState<Id> {
64
63
  /** Whether the shortcut has been customized by the user. */
65
64
  overridden: boolean;
66
65
  start: () => void;
67
- stop: () => void;
68
66
  cancel: () => void;
69
67
  commit: () => void;
68
+ /** Unbinds the command. */
70
69
  clear: () => void;
70
+ /** Restores the default shortcut. */
71
71
  reset: () => void;
72
72
  }
73
73
  /** Hook for recording and configuring a command's keybindings. */
74
74
  export declare function useRebind<Id extends string>(registry: Commands<Id>, id: NoInfer<Id>, options?: RebindOptions<Id>): UseRebindResult<Id>;
75
- /** Returns an array of keys currently held down. */
76
- export declare function useHeldKeys(): ReadonlyArray<string>;
77
- /**
78
- * Checks whether a specific key is currently held down.
79
- *
80
- * @example
81
- * ```tsx
82
- * const shift = useKeyHold("Shift");
83
- * ```
84
- */
85
- export declare function useKeyHold(key: string): boolean;
86
- /** Returns a map of held key identifiers to physical event codes. */
87
- export declare function useHeldKeyCodes(): Readonly<Record<string, string>>;
88
- /**
89
- * Returns currently active partially-typed sequences.
90
- *
91
- * @example
92
- * ```tsx
93
- * const pending = usePendingSequences(commands);
94
- * ```
95
- */
96
- export declare function usePendingSequences<Id extends string>(registry: Commands<Id>): ReadonlyArray<PendingSequence<Id>>;
97
75
  //#endregion
98
- export { type Binding, type BindingInput, type BindingValidation, type Chord, type Command, type CommandContext, type CommandDefinition, type CommandHandler, type CommandId, type CommandKeymap, type CommandSource, type Commands, type CommandsOptions, type Conflict, type ConflictKind, type ConflictOptions, type DisplayOptions, type Json, type Key, type KeyCapture, type KeymapPersistence, type KeymapStorage, type PendingSequence, type Platform, type RebindMode, type RebindOptions, type RebindState, type Rebinder, type RunBlockReason, type RunOptions, type ScopeOptions, type SequenceBinding, type SerializedBinding, type SerializedKeymap, type SerializedOverride, type UseRebindResult, ariaKeyShortcuts, bindingTokens, createCommands, createRebinder, formatBinding, isSingleKeyBinding, matchCommands, seq, spokenBinding, validateBinding };
76
+ export { type Binding, type BindingInput, type Chord, type Command, type CommandContext, type CommandDefinition, type CommandHandler, type CommandId, type CommandKeymap, type CommandSource, type Commands, type CommandsOptions, type Conflict, type ConflictOptions, type Json, type Key, type KeyCapture, type KeymapStore, type Platform, type PlatformBindings, type RebindOptions, type RebindState, type Rebinder, type RunBlockReason, type RunOptions, type ScopeOptions, type SequenceBinding, type SerializedBinding, type SerializedKeymap, type UseRebindResult, ariaKeyShortcuts, bindingTokens, createCommands, createRebinder, exportKeymap, formatBinding, importKeymap, isSingleKeyBinding, seq, webStorage };
package/dist/react.mjs CHANGED
@@ -1,81 +1,8 @@
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 webStorage, c as formatBinding, i as importKeymap, l as isSingleKeyBinding, n as createRebinder, o as ariaKeyShortcuts, r as exportKeymap, s as bindingTokens, t as createCommands, u as seq } from "./src-CGKvYKjd.mjs";
3
3
  import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
4
- //#region src/held.ts
5
- /** Which keys are down right now. Listens only while something is subscribed. */
6
- const NONE = {
7
- keys: [],
8
- codes: {}
9
- };
10
- /** Held keys by physical key, so two keys producing one character stay apart. */
11
- const down = /* @__PURE__ */ new Map();
12
- const listeners = /* @__PURE__ */ new Set();
13
- let state = NONE;
14
- const identify = (event) => event.code || `key:${keyName(event.key)}`;
15
- 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
- };
21
- for (const listener of listeners) listener();
22
- }
23
- function onKeyDown(event) {
24
- if (!event.key || down.has(identify(event))) return;
25
- down.set(identify(event), {
26
- key: keyName(event.key),
27
- code: event.code
28
- });
29
- publish();
30
- }
31
- function onKeyUp(event) {
32
- down.delete(identify(event));
33
- if (event.key === "Meta") {
34
- for (const [identity, entry] of down) if (!isModifierKey(entry.key)) down.delete(identity);
35
- }
36
- publish();
37
- }
38
- /** Leaving the window swallows the keyups, so nothing is held any more. */
39
- function onBlur() {
40
- if (down.size === 0) return;
41
- down.clear();
42
- publish();
43
- }
44
- /** The keys held right now. Empty unless something is subscribed. */
45
- 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));
51
- }
52
- /** Watches held keys until the returned function is called. */
53
- function subscribeHeldKeys(listener) {
54
- listeners.add(listener);
55
- if (listeners.size === 1) {
56
- document.addEventListener("keydown", onKeyDown, true);
57
- document.addEventListener("keyup", onKeyUp, true);
58
- window.addEventListener("blur", onBlur);
59
- }
60
- return () => {
61
- listeners.delete(listener);
62
- if (listeners.size > 0) return;
63
- document.removeEventListener("keydown", onKeyDown, true);
64
- document.removeEventListener("keyup", onKeyUp, true);
65
- window.removeEventListener("blur", onBlur);
66
- down.clear();
67
- state = NONE;
68
- };
69
- }
70
- //#endregion
71
4
  //#region src/react/hooks.ts
72
5
  const EMPTY_TOKENS = [];
73
- const EMPTY_CODES = {};
74
- const EMPTY_PENDING = [];
75
- const getEmptyTokens = () => EMPTY_TOKENS;
76
- const getEmptyCodes = () => EMPTY_CODES;
77
- const getEmptyPending = () => EMPTY_PENDING;
78
- const getFalse = () => false;
79
6
  /**
80
7
  * Mounts a commands registry for the lifecycle of the component.
81
8
  *
@@ -90,40 +17,40 @@ const getFalse = () => false;
90
17
  function useMountCommands(registry) {
91
18
  useEffect(() => registry.mount(), [registry]);
92
19
  }
93
- function useCommandList(registry) {
20
+ /**
21
+ * Returns all registered commands, re-rendering when the registry changes.
22
+ * Hydrates against `serverList()`, so a subtree that hydrates late still
23
+ * matches the server's HTML.
24
+ */
25
+ function useCommands(registry) {
94
26
  const subscribe = useCallback((onChange) => registry.subscribe(onChange), [registry]);
95
27
  const getSnapshot = useCallback(() => registry.list(), [registry]);
96
28
  const getServerSnapshot = useCallback(() => registry.serverList(), [registry]);
97
29
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
98
30
  }
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. */
31
+ /** Returns one command's snapshot, re-rendering only when that command changes. */
104
32
  function useCommand(registry, id) {
105
- const commands = useCommandList(registry);
106
- return useMemo(() => commands.find((command) => command.id === id), [commands, id]);
33
+ const subscribe = useCallback((onChange) => registry.subscribe(onChange), [registry]);
34
+ const getSnapshot = useCallback(() => registry.get(id), [registry, id]);
35
+ const getServerSnapshot = useCallback(() => registry.serverList().find((command) => command.id === id), [registry, id]);
36
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
107
37
  }
108
38
  /**
109
- * Returns display and accessibility shortcuts for a command's primary
110
- * binding: `tokens` for `<kbd>` elements, `aria` for `aria-keyshortcuts`
111
- * (chords only), and `spoken` for screen-reader text such as `"G then I"`.
39
+ * Returns a command's primary binding for people: `display` to read,
40
+ * `tokens` for `<kbd>` elements, and `aria` for `aria-keyshortcuts` (chords only).
112
41
  */
113
42
  function useShortcut(registry, id) {
114
43
  const command = useCommand(registry, id);
115
44
  return useMemo(() => ({
116
45
  display: command?.display[0] ?? "",
117
46
  tokens: command?.tokens[0] ?? EMPTY_TOKENS,
118
- aria: command?.aria ?? "",
119
- spoken: command?.spoken ?? ""
47
+ aria: command?.aria ?? ""
120
48
  }), [command]);
121
49
  }
122
50
  /**
123
51
  * 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.
52
+ * order React runs effects in: of a parent and a child opening in the same
53
+ * render, the child is pushed first.
127
54
  *
128
55
  * @example
129
56
  * ```tsx
@@ -143,12 +70,10 @@ function useScope(registry, name, active = true, options = {}) {
143
70
  ]);
144
71
  }
145
72
  /**
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.
73
+ * Attaches what a command does for the lifecycle of the component. The latest
74
+ * `run` is always the one called, and the handler attached last wins. While
75
+ * `enabled` is `false` the command falls to the handler attached before it,
76
+ * or cannot run if there is none.
152
77
  *
153
78
  * @example
154
79
  * ```tsx
@@ -172,31 +97,21 @@ function useCommandHandler(registry, id, run, enabled = true) {
172
97
  function useRebind(registry, id, options = {}) {
173
98
  const latest = useRef(options);
174
99
  latest.current = options;
175
- const mode = options.mode ?? "chord";
100
+ const sequence = options.sequence === true;
176
101
  const rebinder = useMemo(() => createRebinder(registry, id, {
177
- mode,
102
+ sequence,
178
103
  get apply() {
179
104
  return latest.current.apply;
180
105
  },
181
- get add() {
182
- return latest.current.add;
183
- },
184
106
  get rejectConflicts() {
185
107
  return latest.current.rejectConflicts;
186
108
  },
187
- get ignoreInputs() {
188
- return latest.current.ignoreInputs;
189
- },
190
- get idleTimeout() {
191
- return latest.current.idleTimeout;
192
- },
193
109
  onRecord: (binding, conflicts) => latest.current.onRecord?.(binding, conflicts),
194
- onCancel: () => latest.current.onCancel?.(),
195
- onClear: () => latest.current.onClear?.()
110
+ onCancel: () => latest.current.onCancel?.()
196
111
  }), [
197
112
  registry,
198
113
  id,
199
- mode
114
+ sequence
200
115
  ]);
201
116
  useEffect(() => rebinder.destroy, [rebinder]);
202
117
  const getState = useCallback(() => rebinder.state, [rebinder]);
@@ -208,7 +123,6 @@ function useRebind(registry, id, options = {}) {
208
123
  tokens: command?.tokens[0] ?? EMPTY_TOKENS,
209
124
  overridden: command?.overridden ?? false,
210
125
  start: rebinder.start,
211
- stop: rebinder.stop,
212
126
  cancel: rebinder.cancel,
213
127
  commit: rebinder.commit,
214
128
  clear: () => registry.keymap.set(id, null),
@@ -221,38 +135,5 @@ function useRebind(registry, id, options = {}) {
221
135
  id
222
136
  ]);
223
137
  }
224
- /** Returns an array of keys currently held down. */
225
- function useHeldKeys() {
226
- return useSyncExternalStore(subscribeHeldKeys, () => heldKeys().keys, getEmptyTokens);
227
- }
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
- */
252
- function usePendingSequences(registry) {
253
- const subscribe = useCallback((onChange) => registry.subscribePending(onChange), [registry]);
254
- const getSnapshot = useCallback(() => registry.pending(), [registry]);
255
- return useSyncExternalStore(subscribe, getSnapshot, getEmptyPending);
256
- }
257
138
  //#endregion
258
- export { ariaKeyShortcuts, bindingTokens, createCommands, createRebinder, formatBinding, isSingleKeyBinding, matchCommands, seq, spokenBinding, useCommand, useCommandHandler, useCommands, useHeldKeyCodes, useHeldKeys, useKeyHold, useMountCommands, usePendingSequences, useRebind, useScope, useShortcut, validateBinding };
139
+ export { ariaKeyShortcuts, bindingTokens, createCommands, createRebinder, exportKeymap, formatBinding, importKeymap, isSingleKeyBinding, seq, useCommand, useCommandHandler, useCommands, useMountCommands, useRebind, useScope, useShortcut, webStorage };