@tanstack/hotkeys 0.18.0 → 0.20.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.
@@ -2,9 +2,10 @@ import { detectPlatform } from "./platform.js";
2
2
  import { isHotkeySequence, simplifyHotkey } from "./parse.js";
3
3
  import { validateHotkey } from "./validate.js";
4
4
  import { areHotkeysEqual } from "./match.js";
5
+ import { createStore } from "./_store.js";
5
6
  import { GLOBAL_SCOPE } from "./_registration.js";
6
7
  import { getHotkeyManager } from "./hotkey-manager.js";
7
- import { Store } from "@tanstack/store";
8
+ import { setAppHotkeysStoreFactory } from "./_app-hotkeys-factory.js";
8
9
 
9
10
  //#region src/app-hotkeys.ts
10
11
  /**
@@ -22,6 +23,7 @@ import { Store } from "@tanstack/store";
22
23
  * ```
23
24
  */
24
25
  function defineAppHotkeys(definitions) {
26
+ setAppHotkeysStoreFactory((appHotkeys, options) => new AppHotkeysStore(appHotkeys, options));
25
27
  const result = {};
26
28
  for (const [id, input] of Object.entries(definitions)) {
27
29
  const hotkey = input.hotkey === null ? null : simplifyHotkey(input.hotkey);
@@ -73,13 +75,13 @@ function sanitizeCustomHotkeys(appHotkeys, saved) {
73
75
  if (typeof saved !== "object" || saved === null) return result;
74
76
  for (const [id, value] of Object.entries(saved)) {
75
77
  if (value === void 0) continue;
76
- if (!(id in appHotkeys)) {
77
- console.warn(`Ignoring custom hotkey for unknown app hotkey '${id}'`);
78
+ if (!Object.hasOwn(appHotkeys, id)) {
79
+ if (process.env.NODE_ENV !== "production") console.warn(`Ignoring custom hotkey for unknown app hotkey '${id}'`);
78
80
  continue;
79
81
  }
80
82
  const hotkey = parseCustomHotkey(value);
81
83
  if (hotkey === void 0) {
82
- console.warn(`Ignoring invalid custom hotkey for app hotkey '${id}':`, value);
84
+ if (process.env.NODE_ENV !== "production") console.warn(`Ignoring invalid custom hotkey for app hotkey '${id}':`, value);
83
85
  continue;
84
86
  }
85
87
  result[id] = hotkey;
@@ -123,7 +125,7 @@ var AppHotkeysStore = class {
123
125
  this.#declarations = appHotkeys;
124
126
  this.#options = options;
125
127
  const customHotkeys = sanitizeCustomHotkeys(appHotkeys, options.customHotkeys);
126
- this.store = new Store({
128
+ this.store = createStore({
127
129
  appHotkeys: applyCustomHotkeys(appHotkeys, customHotkeys),
128
130
  customHotkeys
129
131
  });
@@ -161,7 +163,8 @@ var AppHotkeysStore = class {
161
163
  }
162
164
  /** Effective app hotkey for an id, or `undefined` when it is not declared. */
163
165
  get(id) {
164
- return this.store.state.appHotkeys[id];
166
+ const { appHotkeys } = this.store.state;
167
+ return Object.hasOwn(appHotkeys, id) ? appHotkeys[id] : void 0;
165
168
  }
166
169
  /**
167
170
  * Other app hotkeys that would collide if `id` used `hotkey`. App hotkeys
@@ -169,7 +172,7 @@ var AppHotkeysStore = class {
169
172
  * hotkeys are equal or one sequence starts with the other.
170
173
  */
171
174
  findConflicts(id, hotkey) {
172
- const self = this.store.state.appHotkeys[id];
175
+ const self = this.get(id);
173
176
  if (!self) return [];
174
177
  const candidate = simplifyHotkey(hotkey);
175
178
  return Object.values(this.store.state.appHotkeys).filter((other) => other.id !== id && other.hotkey !== null && scopesOverlap(self, other) && hotkeysOverlap(candidate, other.hotkey, this.#platform));
@@ -181,9 +184,9 @@ var AppHotkeysStore = class {
181
184
  * @returns Conflicting app hotkeys. When any exist, nothing changes.
182
185
  */
183
186
  setCustomHotkey(id, hotkey) {
184
- const appHotkey = this.store.state.appHotkeys[id];
187
+ const appHotkey = this.get(id);
185
188
  if (!appHotkey) {
186
- console.warn(`Cannot set a custom hotkey for unknown app hotkey '${id}'`);
189
+ if (process.env.NODE_ENV !== "production") console.warn(`Cannot set a custom hotkey for unknown app hotkey '${id}'`);
187
190
  return [];
188
191
  }
189
192
  const next = hotkey === null ? null : simplifyHotkey(hotkey);
@@ -107,9 +107,9 @@ export declare const PUNCTUATION_CODE_MAP: Record<string, string>;
107
107
  /**
108
108
  * Set of all valid non-modifier keys.
109
109
  *
110
- * This is the union of all key type sets (letters, numbers, function keys, navigation,
111
- * editing, and punctuation). Used primarily for validation to check if a key string
112
- * is recognized and will have type-safe autocomplete support.
110
+ * This is the union of all key type sets (letters, numbers, function keys,
111
+ * navigation, editing, punctuation, and other named keys): every key with
112
+ * type-safe autocomplete support.
113
113
  *
114
114
  * @see {@link LETTER_KEYS}
115
115
  * @see {@link NUMBER_KEYS}
@@ -122,16 +122,17 @@ export declare const ALL_KEYS: Set<"CapsLock" | "ContextMenu" | "Convert" | "Kan
122
122
  /** Whether a key is a single Unicode letter, such as `'a'` or `'é'`. */
123
123
  export declare function isSingleLetterKey(key: string): boolean;
124
124
  /**
125
- * Normalizes a key name to its canonical form.
125
+ * Normalizes a key name from a `KeyboardEvent` or a hotkey: single letters
126
+ * become uppercase, and the space character becomes `Space`. Other names are
127
+ * returned as written, because key names are case-sensitive.
126
128
  *
127
- * @param key - The key name to normalize (can be an alias, lowercase, etc.)
129
+ * @param key - The key name to normalize
128
130
  * @returns The canonical key name
129
131
  *
130
132
  * @example
131
133
  * ```ts
132
- * normalizeKeyName('esc') // 'Escape'
133
134
  * normalizeKeyName('a') // 'A'
134
- * normalizeKeyName('f1') // 'F1'
135
+ * normalizeKeyName(' ') // 'Space'
135
136
  * normalizeKeyName('ArrowUp') // 'ArrowUp' (already canonical)
136
137
  * ```
137
138
  */
package/dist/constants.js CHANGED
@@ -1,4 +1,4 @@
1
- import { LOGICAL_ONLY_NAMED_KEYS, SHARED_NAMED_KEYS } from "./_named-keys.js";
1
+ import { EDITING_KEY_NAMES, LOGICAL_ONLY_NAMED_KEYS, NAVIGATION_KEY_NAMES, SHARED_NAMED_KEYS } from "./_named-keys.js";
2
2
 
3
3
  //#region src/constants.ts
4
4
  /**
@@ -80,34 +80,7 @@ const MODIFIER_ALIASES = {
80
80
  * Used for validation and type checking. Letter keys are matched case-insensitively
81
81
  * in hotkey matching, but normalized to uppercase in canonical form.
82
82
  */
83
- const LETTER_KEYS = /* @__PURE__ */ new Set([
84
- "A",
85
- "B",
86
- "C",
87
- "D",
88
- "E",
89
- "F",
90
- "G",
91
- "H",
92
- "I",
93
- "J",
94
- "K",
95
- "L",
96
- "M",
97
- "N",
98
- "O",
99
- "P",
100
- "Q",
101
- "R",
102
- "S",
103
- "T",
104
- "U",
105
- "V",
106
- "W",
107
- "X",
108
- "Y",
109
- "Z"
110
- ]);
83
+ const LETTER_KEYS = /* @__PURE__ */ new Set("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
111
84
  /**
112
85
  * Set of all valid number keys (0-9).
113
86
  *
@@ -115,50 +88,14 @@ const LETTER_KEYS = /* @__PURE__ */ new Set([
115
88
  * so they're excluded from Shift-based hotkey combinations to avoid
116
89
  * layout-dependent behavior.
117
90
  */
118
- const NUMBER_KEYS = /* @__PURE__ */ new Set([
119
- "0",
120
- "1",
121
- "2",
122
- "3",
123
- "4",
124
- "5",
125
- "6",
126
- "7",
127
- "8",
128
- "9"
129
- ]);
91
+ const NUMBER_KEYS = /* @__PURE__ */ new Set("0123456789");
130
92
  /**
131
93
  * Set of all valid function keys (F1-F24).
132
94
  *
133
95
  * Function keys are commonly used for system shortcuts (e.g., F12 for DevTools,
134
96
  * Alt+F4 to close windows) and application-specific commands.
135
97
  */
136
- const FUNCTION_KEYS = /* @__PURE__ */ new Set([
137
- "F1",
138
- "F2",
139
- "F3",
140
- "F4",
141
- "F5",
142
- "F6",
143
- "F7",
144
- "F8",
145
- "F9",
146
- "F10",
147
- "F11",
148
- "F12",
149
- "F13",
150
- "F14",
151
- "F15",
152
- "F16",
153
- "F17",
154
- "F18",
155
- "F19",
156
- "F20",
157
- "F21",
158
- "F22",
159
- "F23",
160
- "F24"
161
- ]);
98
+ const FUNCTION_KEYS = /* @__PURE__ */ new Set(Array.from({ length: 24 }, (_, i) => `F${i + 1}`));
162
99
  /**
163
100
  * Set of all valid navigation keys for cursor movement and document navigation.
164
101
  *
@@ -166,16 +103,7 @@ const FUNCTION_KEYS = /* @__PURE__ */ new Set([
166
103
  * These keys are commonly combined with modifiers for selection (Shift+ArrowUp) or
167
104
  * navigation shortcuts (Alt+ArrowLeft for back).
168
105
  */
169
- const NAVIGATION_KEYS = /* @__PURE__ */ new Set([
170
- "ArrowUp",
171
- "ArrowDown",
172
- "ArrowLeft",
173
- "ArrowRight",
174
- "Home",
175
- "End",
176
- "PageUp",
177
- "PageDown"
178
- ]);
106
+ const NAVIGATION_KEYS = /* @__PURE__ */ new Set(NAVIGATION_KEY_NAMES);
179
107
  /**
180
108
  * Set of all valid editing and special keys.
181
109
  *
@@ -183,14 +111,7 @@ const NAVIGATION_KEYS = /* @__PURE__ */ new Set([
183
111
  * special actions (Escape, Space). These keys are frequently combined with modifiers
184
112
  * for editor shortcuts (Mod+Enter to submit, Shift+Tab to go back).
185
113
  */
186
- const EDITING_KEYS = /* @__PURE__ */ new Set([
187
- "Enter",
188
- "Escape",
189
- "Space",
190
- "Tab",
191
- "Backspace",
192
- "Delete"
193
- ]);
114
+ const EDITING_KEYS = /* @__PURE__ */ new Set(EDITING_KEY_NAMES);
194
115
  /**
195
116
  * Set of all valid punctuation keys commonly used in keyboard shortcuts.
196
117
  *
@@ -203,40 +124,7 @@ const EDITING_KEYS = /* @__PURE__ */ new Set([
203
124
  * Note: Punctuation keys are affected by Shift (Shift+',' → '<' on US layout),
204
125
  * so they're excluded from Shift-based hotkey combinations to avoid layout-dependent behavior.
205
126
  */
206
- const PUNCTUATION_KEYS = /* @__PURE__ */ new Set([
207
- "/",
208
- "[",
209
- "]",
210
- "\\",
211
- "=",
212
- "-",
213
- ",",
214
- ".",
215
- ";",
216
- "`",
217
- "'",
218
- "+",
219
- "?",
220
- "!",
221
- "@",
222
- "#",
223
- "$",
224
- "%",
225
- "^",
226
- "&",
227
- "*",
228
- "(",
229
- ")",
230
- "_",
231
- "{",
232
- "}",
233
- "|",
234
- ":",
235
- "\"",
236
- "<",
237
- ">",
238
- "~"
239
- ]);
127
+ const PUNCTUATION_KEYS = /* @__PURE__ */ new Set("/[]\\=-,.;`'+?!@#$%^&*()_{}|:\"<>~");
240
128
  /**
241
129
  * Maps `KeyboardEvent.code` values for punctuation keys to their canonical characters.
242
130
  *
@@ -259,12 +147,15 @@ const PUNCTUATION_CODE_MAP = {
259
147
  Semicolon: ";",
260
148
  Slash: "/"
261
149
  };
150
+ function union(...groups) {
151
+ return new Set(groups.flatMap((group) => [...group]));
152
+ }
262
153
  /**
263
154
  * Set of all valid non-modifier keys.
264
155
  *
265
- * This is the union of all key type sets (letters, numbers, function keys, navigation,
266
- * editing, and punctuation). Used primarily for validation to check if a key string
267
- * is recognized and will have type-safe autocomplete support.
156
+ * This is the union of all key type sets (letters, numbers, function keys,
157
+ * navigation, editing, punctuation, and other named keys): every key with
158
+ * type-safe autocomplete support.
268
159
  *
269
160
  * @see {@link LETTER_KEYS}
270
161
  * @see {@link NUMBER_KEYS}
@@ -273,52 +164,23 @@ const PUNCTUATION_CODE_MAP = {
273
164
  * @see {@link EDITING_KEYS}
274
165
  * @see {@link PUNCTUATION_KEYS}
275
166
  */
276
- const ALL_KEYS = /* @__PURE__ */ new Set([
277
- ...LETTER_KEYS,
278
- ...NUMBER_KEYS,
279
- ...FUNCTION_KEYS,
280
- ...NAVIGATION_KEYS,
281
- ...EDITING_KEYS,
282
- ...PUNCTUATION_KEYS,
283
- ...SHARED_NAMED_KEYS,
284
- ...LOGICAL_ONLY_NAMED_KEYS
285
- ]);
286
- /**
287
- * Maps lowercase key names and aliases to their canonical form, so lookups
288
- * are case-insensitive: `'esc'`, `'Esc'`, and `'ESCAPE'` all become `'Escape'`.
289
- * Canonical names come from {@link ALL_KEYS}, so validation and normalization
290
- * share one spelling for every key.
291
- */
292
- const CANONICAL_KEY_NAMES = /* @__PURE__ */ new Map([
293
- ...Array.from(ALL_KEYS, (key) => [key.toLowerCase(), key]),
294
- ["plus", "+"],
295
- ["esc", "Escape"],
296
- ["return", "Enter"],
297
- [" ", "Space"],
298
- ["spacebar", "Space"],
299
- ["del", "Delete"],
300
- ["up", "ArrowUp"],
301
- ["down", "ArrowDown"],
302
- ["left", "ArrowLeft"],
303
- ["right", "ArrowRight"],
304
- ["pgup", "PageUp"],
305
- ["pgdn", "PageDown"]
306
- ]);
167
+ const ALL_KEYS = /* @__PURE__ */ union(LETTER_KEYS, NUMBER_KEYS, FUNCTION_KEYS, NAVIGATION_KEYS, EDITING_KEYS, PUNCTUATION_KEYS, SHARED_NAMED_KEYS, LOGICAL_ONLY_NAMED_KEYS);
307
168
  /** Whether a key is a single Unicode letter, such as `'a'` or `'é'`. */
308
169
  function isSingleLetterKey(key) {
309
170
  return /^\p{Letter}$/u.test(key);
310
171
  }
311
172
  /**
312
- * Normalizes a key name to its canonical form.
173
+ * Normalizes a key name from a `KeyboardEvent` or a hotkey: single letters
174
+ * become uppercase, and the space character becomes `Space`. Other names are
175
+ * returned as written, because key names are case-sensitive.
313
176
  *
314
- * @param key - The key name to normalize (can be an alias, lowercase, etc.)
177
+ * @param key - The key name to normalize
315
178
  * @returns The canonical key name
316
179
  *
317
180
  * @example
318
181
  * ```ts
319
- * normalizeKeyName('esc') // 'Escape'
320
182
  * normalizeKeyName('a') // 'A'
321
- * normalizeKeyName('f1') // 'F1'
183
+ * normalizeKeyName(' ') // 'Space'
322
184
  * normalizeKeyName('ArrowUp') // 'ArrowUp' (already canonical)
323
185
  * ```
324
186
  */
@@ -328,7 +190,7 @@ function normalizeKeyName(key) {
328
190
  const upper = key.toUpperCase();
329
191
  return upper.length === 1 ? upper : key;
330
192
  }
331
- return CANONICAL_KEY_NAMES.get(key.toLowerCase()) ?? key;
193
+ return key === " " ? "Space" : key;
332
194
  }
333
195
 
334
196
  //#endregion
package/dist/format.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { CanonicalModifier } from "./key.types.js";
1
2
  import { ParsedHotkeyChord, RegisterableHotkeyChord } from "./hotkey.types.js";
2
3
  //#region src/format.d.ts
3
4
  /**
@@ -17,8 +18,11 @@ import { ParsedHotkeyChord, RegisterableHotkeyChord } from "./hotkey.types.js";
17
18
  export declare function formatHotkey(parsed: ParsedHotkeyChord): string;
18
19
  export declare function formatHotkey(parsed: ReadonlyArray<ParsedHotkeyChord>): Array<string>;
19
20
  export declare function formatHotkey(parsed: ParsedHotkeyChord | ReadonlyArray<ParsedHotkeyChord>): string | Array<string>;
20
- /** A single chord to display: a string, raw object, or parsed hotkey. */
21
- export type DisplayHotkeyChord = RegisterableHotkeyChord | ParsedHotkeyChord | (string & {});
21
+ /**
22
+ * A single chord to display: a hotkey string, which includes single keys, a
23
+ * modifier on its own, a raw object such as `{ key }`, or a parsed hotkey.
24
+ */
25
+ export type DisplayHotkeyChord = RegisterableHotkeyChord | ParsedHotkeyChord | CanonicalModifier;
22
26
  /** A hotkey to display: a chord or a sequence of chords. */
23
27
  export type DisplayHotkey = DisplayHotkeyChord | ReadonlyArray<DisplayHotkeyChord>;
24
28
  /**
@@ -35,7 +39,10 @@ export type DisplayHotkey = DisplayHotkeyChord | ReadonlyArray<DisplayHotkeyChor
35
39
  * Sequence steps are formatted individually and joined with `sequenceSeparator`
36
40
  * (a space by default).
37
41
  *
38
- * @param hotkey - A logical or physical string, raw object, ParsedHotkeyChord, or sequence
42
+ * A key reported by a `KeyboardEvent`, such as a held key from
43
+ * `KeyStateTracker`, can be outside the key types: pass it as `{ key }`.
44
+ *
45
+ * @param hotkey - A logical or physical hotkey string, raw object, ParsedHotkeyChord, or sequence
39
46
  * @param options - Formatting options
40
47
  * @returns Display text, or individual key labels when parts is true (one array per step for sequences)
41
48
  *
@@ -50,6 +57,12 @@ export type DisplayHotkey = DisplayHotkeyChord | ReadonlyArray<DisplayHotkeyChor
50
57
  * formatForDisplay('Escape')
51
58
  * // Returns: 'Esc' (on all platforms)
52
59
  *
60
+ * formatForDisplay('Shift', { platform: 'mac' })
61
+ * // Returns: '⇧'
62
+ *
63
+ * formatForDisplay({ key: heldKey })
64
+ * // A key from a KeyboardEvent, such as a held key
65
+ *
53
66
  * formatForDisplay(['G', 'H'])
54
67
  * // Returns: 'G H'
55
68
  * ```
package/dist/format.js CHANGED
@@ -45,20 +45,25 @@ function formatChordParts(hotkey, options) {
45
45
  };
46
46
  segments.sort((a, b) => modifierIndex(a) - modifierIndex(b));
47
47
  }
48
- return segments.map((segment) => {
49
- const modifier = modifierAlias(segment);
50
- if (modifier) return platform === "mac" ? modifierSymbols ? MAC_MODIFIER_SYMBOLS[modifier] : MAC_MODIFIER_LABELS[modifier] : platform === "windows" ? WINDOWS_MODIFIER_LABELS[modifier] : LINUX_MODIFIER_LABELS[modifier];
51
- const code = physicalCodeOf(segment);
52
- const label = options.keyLabels?.[code ?? segment];
53
- if (label !== void 0) return label;
54
- const numpad = code?.startsWith("Numpad") ?? false;
55
- let displayKey = code ?? segment;
56
- const mappedKey = code ? options.layoutMap?.get(code) : void 0;
57
- if (mappedKey) displayKey = normalizeKeyName(mappedKey);
58
- else if (code) displayKey = numpad ? code.slice(6) : codeToLogicalKey(code) ?? code;
59
- const display = (keySymbols ? KEY_DISPLAY_SYMBOLS : PUNCTUATION_KEY_DISPLAY_LABELS)[displayKey] ?? displayKey;
60
- return numpad ? `Numpad ${display.replace(/([a-z])([A-Z])/g, "$1 $2")}` : display;
61
- });
48
+ return segments.map((segment) => formatKey(segment, platform, modifierSymbols, keySymbols, options));
49
+ }
50
+ /** Own properties only, so names such as `toString` never read Object.prototype. */
51
+ function lookup(table, name) {
52
+ return table && Object.hasOwn(table, name) ? table[name] : void 0;
53
+ }
54
+ function formatKey(segment, platform, modifierSymbols, keySymbols, options) {
55
+ const modifier = modifierAlias(segment);
56
+ if (modifier) return platform === "mac" ? modifierSymbols ? MAC_MODIFIER_SYMBOLS[modifier] : MAC_MODIFIER_LABELS[modifier] : platform === "windows" ? WINDOWS_MODIFIER_LABELS[modifier] : LINUX_MODIFIER_LABELS[modifier];
57
+ const code = physicalCodeOf(segment);
58
+ const label = lookup(options.keyLabels, code ?? segment);
59
+ if (label !== void 0) return label;
60
+ const numpad = code?.startsWith("Numpad") ?? false;
61
+ let displayKey = code ?? segment;
62
+ const mappedKey = code ? options.layoutMap?.get(code) : void 0;
63
+ if (mappedKey) displayKey = normalizeKeyName(mappedKey);
64
+ else if (code) displayKey = numpad ? code.slice(6) : codeToLogicalKey(code) ?? code;
65
+ const display = lookup(keySymbols ? KEY_DISPLAY_SYMBOLS : PUNCTUATION_KEY_DISPLAY_LABELS, displayKey) ?? displayKey;
66
+ return numpad ? `Numpad ${display.replace(/([a-z])([A-Z])/g, "$1 $2")}` : display;
62
67
  }
63
68
 
64
69
  //#endregion
@@ -1,6 +1,6 @@
1
+ import { Store } from "./_store.js";
1
2
  import { ConflictBehavior, HotkeyCallback, HotkeyCallbackContextFor, HotkeyChord, HotkeyMeta, HotkeySequence, ParsedHotkeyChord, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
2
3
  import { AppHotkey, AppHotkeysStore } from "./app-hotkeys.js";
3
- import { Store } from "@tanstack/store";
4
4
  //#region src/hotkey-manager.d.ts
5
5
  /** An element, document, or window that hotkeys can listen on. */
6
6
  export type Target = HTMLElement | Document | Window;
@@ -366,15 +366,6 @@ export declare class HotkeyManager {
366
366
  * Resets the progress of every in-progress sequence.
367
367
  */
368
368
  resetSequenceProgress(): void;
369
- /**
370
- * Triggers a registration's callback programmatically from devtools.
371
- * Creates a synthetic KeyboardEvent from the chord (or a sequence's last step)
372
- * and invokes the callback.
373
- *
374
- * @param id - The registration ID to trigger
375
- * @returns True if the registration was found and triggered
376
- */
377
- triggerRegistration(id: string): boolean;
378
369
  /**
379
370
  * Gets the number of registered hotkeys, chords and sequences.
380
371
  */
@@ -1,16 +1,16 @@
1
1
  import { normalizeKeyName } from "./constants.js";
2
2
  import { detectPlatform } from "./platform.js";
3
- import { keysEqual, normalizeKeyboardEvent } from "./_keyboard-event.js";
3
+ import { isKeyNameShape, keysEqual, normalizeKeyboardEvent } from "./_keyboard-event.js";
4
4
  import { isHotkeySequence, isModifierKey, parseHotkey, simplifyHotkey } from "./parse.js";
5
5
  import { formatHotkey } from "./format.js";
6
6
  import { matchKeyboardEvent, parsedChordsEqual } from "./_match.js";
7
7
  import { DEFAULT_SEQUENCE_TIMEOUT, areHotkeysEqual } from "./match.js";
8
- import { getTargetDocument, isEventForTarget, shouldIgnoreInputEvent, targetHasFocus } from "./_event-target.js";
8
+ import { getTargetDocument, isDocumentLevel, shouldIgnoreInputEvent, targetHasFocus } from "./_event-target.js";
9
+ import { createStore } from "./_store.js";
9
10
  import { TargetListeners } from "./_target-listeners.js";
10
11
  import { managerRef } from "./_manager-ref.js";
11
12
  import { isRecordingEvent } from "./_recording-guard.js";
12
13
  import { GLOBAL_SCOPE, defaultHotkeyOptions, getDefaultIgnoreInputs, handleConflict, optionsEqual } from "./_registration.js";
13
- import { Store } from "@tanstack/store";
14
14
 
15
15
  //#region src/hotkey-manager.ts
16
16
  /** Whether a registration can fire now. Without a manager, scopes don't apply. */
@@ -127,14 +127,14 @@ var HotkeyManager = class HotkeyManager {
127
127
  * }
128
128
  * ```
129
129
  */
130
- registrations = new Store(/* @__PURE__ */ new Map());
130
+ registrations = createStore(/* @__PURE__ */ new Map());
131
131
  /**
132
132
  * The app hotkeys store published by a framework provider, so tools such as
133
133
  * devtools can list declared hotkeys that are not registered.
134
134
  */
135
- appHotkeys = new Store(null);
135
+ appHotkeys = createStore(null);
136
136
  /** Active scopes. Subscribe to show which hotkeys are suspended. */
137
- scopes = new Store({
137
+ scopes = createStore({
138
138
  active: [],
139
139
  exclusive: null,
140
140
  allowed: [],
@@ -194,7 +194,7 @@ var HotkeyManager = class HotkeyManager {
194
194
  if (scope === "global") return () => {};
195
195
  const { exclusive } = options;
196
196
  const allow = exclusive ? exclusive === true ? [] : exclusive.allow : null;
197
- if (allow && [...this.#scopeActivations].some((other) => other.allow && other.scope === scope)) console.warn(`Exclusive hotkey scope '${scope}' is already active. Hotkeys from both activations will fire; give each a unique scope.`);
197
+ if (process.env.NODE_ENV !== "production" && allow && [...this.#scopeActivations].some((other) => other.allow && other.scope === scope)) console.warn(`Exclusive hotkey scope '${scope}' is already active. Hotkeys from both activations will fire; give each a unique scope.`);
198
198
  const activation = {
199
199
  scope,
200
200
  parent: options.parent ?? "global",
@@ -286,7 +286,7 @@ var HotkeyManager = class HotkeyManager {
286
286
  #handledByLiveHotkey(event, eventType) {
287
287
  const path = event.composedPath();
288
288
  for (const registration of this.registrations.state.values()) {
289
- if (!this.#isLive(registration) || registration.options.eventType !== eventType || !path.includes(registration.target) || ignoresInputs(registration) && shouldIgnoreInputEvent(event, registration.target, registration.target)) continue;
289
+ if (!this.#isLive(registration) || registration.options.eventType !== eventType || !path.includes(registration.target) || ignoresInputs(registration) && shouldIgnoreInputEvent(event, registration.target)) continue;
290
290
  if ((registration.kind === "chord" ? [registration.parsedChord] : [registration.parsedSequence[registration.currentIndex], registration.parsedSequence[0]]).some((step) => matchKeyboardEvent(event, step, registration.options.platform).matched)) return true;
291
291
  }
292
292
  return false;
@@ -353,6 +353,10 @@ var HotkeyManager = class HotkeyManager {
353
353
  parsedChord
354
354
  };
355
355
  }
356
+ if (process.env.NODE_ENV !== "production") {
357
+ const steps = registration.kind === "sequence" ? registration.parsedSequence : [registration.parsedChord];
358
+ for (const { key } of steps) if (key !== void 0 && !isKeyNameShape(key)) console.warn(`Unknown key '${key}' in '${formatForMessage(registration.hotkey)}'. Key names are case-sensitive, such as 'Escape' and 'F1', so this hotkey may never fire.`);
359
+ }
356
360
  const conflicting = this.#findConflictingRegistration(registration);
357
361
  if (conflicting) handleConflict(conflicting.id, formatForMessage(registration.hotkey), options.conflictBehavior ?? "warn", (conflictId) => this.#unregister(conflictId));
358
362
  this.registrations.setState((prev) => new Map(prev).set(id, registration));
@@ -477,7 +481,7 @@ var HotkeyManager = class HotkeyManager {
477
481
  let reserved = false;
478
482
  for (const registration of registrations) {
479
483
  const live = this.#isLive(registration);
480
- if (!live && registration.options.preventDefault !== "always" || registration.options.eventType !== eventType || !isEventForTarget(event, target) || ignoresInputs(registration) && shouldIgnoreInputEvent(event, target, registration.target)) continue;
484
+ if (!live && registration.options.preventDefault !== "always" || registration.options.eventType !== eventType || ignoresInputs(registration) && shouldIgnoreInputEvent(event, target)) continue;
481
485
  const result = matchKeyboardEvent(event, registration.parsedChord, registration.options.platform);
482
486
  if (!result.matched) continue;
483
487
  if (!live) {
@@ -533,7 +537,7 @@ var HotkeyManager = class HotkeyManager {
533
537
  for (const registration of this.registrations.state.values()) {
534
538
  if (registration.kind !== "sequence" || registration.currentIndex === 0 || !this.#isLive(registration) || registration.options.eventType !== eventType || now - registration.lastKeyTime > (registration.options.timeout ?? 1e3)) continue;
535
539
  const target = registration.target;
536
- if (path.includes(target.nodeType === 1 ? target : getTargetDocument(target)) && !(ignoresInputs(registration) && shouldIgnoreInputEvent(event, target, target)) && matchKeyboardEvent(event, registration.parsedSequence[registration.currentIndex], registration.options.platform).matched) return true;
540
+ if (path.includes(isDocumentLevel(target) ? getTargetDocument(target) : target) && !(ignoresInputs(registration) && shouldIgnoreInputEvent(event, target)) && matchKeyboardEvent(event, registration.parsedSequence[registration.currentIndex], registration.options.platform).matched) return true;
537
541
  }
538
542
  return false;
539
543
  }
@@ -556,7 +560,7 @@ var HotkeyManager = class HotkeyManager {
556
560
  const candidates = /* @__PURE__ */ new Map();
557
561
  let bestScore = 0;
558
562
  for (const registration of registrations) {
559
- if (!isEventForTarget(event, target) || !this.#isLive(registration) || ignoresInputs(registration) && shouldIgnoreInputEvent(event, target, registration.target) || registration.options.eventType !== eventType) continue;
563
+ if (!this.#isLive(registration) || ignoresInputs(registration) && shouldIgnoreInputEvent(event, target) || registration.options.eventType !== eventType) continue;
560
564
  const timeout = registration.options.timeout ?? 1e3;
561
565
  if (registration.currentIndex > 0 && now - registration.lastKeyTime > timeout) {
562
566
  registration.currentIndex = 0;
@@ -662,31 +666,6 @@ var HotkeyManager = class HotkeyManager {
662
666
  this.#notify();
663
667
  }
664
668
  /**
665
- * Triggers a registration's callback programmatically from devtools.
666
- * Creates a synthetic KeyboardEvent from the chord (or a sequence's last step)
667
- * and invokes the callback.
668
- *
669
- * @param id - The registration ID to trigger
670
- * @returns True if the registration was found and triggered
671
- */
672
- triggerRegistration(id) {
673
- const registration = this.registrations.state.get(id);
674
- if (!registration) return false;
675
- const context = this.#callbackContext(registration);
676
- const parsed = registration.kind === "chord" ? registration.parsedChord : registration.parsedSequence[registration.parsedSequence.length - 1];
677
- const syntheticEvent = new KeyboardEvent(registration.options.eventType ?? "keydown", {
678
- ...parsed.code !== void 0 ? { code: parsed.code } : { key: parsed.key },
679
- ctrlKey: parsed.ctrl,
680
- shiftKey: parsed.shift,
681
- altKey: parsed.alt,
682
- metaKey: parsed.meta,
683
- bubbles: true,
684
- cancelable: true
685
- });
686
- this.#fire(registration, syntheticEvent, context);
687
- return true;
688
- }
689
- /**
690
669
  * Gets the number of registered hotkeys, chords and sequences.
691
670
  */
692
671
  getRegistrationCount() {
@@ -1,6 +1,6 @@
1
+ import { Store } from "./_store.js";
1
2
  import { Hotkey, HotkeyChord, HotkeySequence, ParsedHotkeyChord } from "./hotkey.types.js";
2
3
  import { HotkeyConflict, HotkeyConflictOptions } from "./conflicts.js";
3
- import { Store } from "@tanstack/store";
4
4
  //#region src/hotkey-recorder.d.ts
5
5
  /** Event property used to record a binding. Physical codes are the default. */
6
6
  export type RecorderKeyMode = 'code' | 'key';
@@ -1,12 +1,11 @@
1
1
  import { detectPlatform } from "./platform.js";
2
- import { normalizeKeyboardEvent } from "./_keyboard-event.js";
2
+ import { normalizeKeyboardEvent, splitChordParts } from "./_keyboard-event.js";
3
3
  import { isModifierKey, parseHotkey, simplifyHotkey } from "./parse.js";
4
- import { validateHotkey } from "./validate.js";
5
4
  import { isTypingEvent } from "./_event-target.js";
5
+ import { createStore } from "./_store.js";
6
6
  import { beginRecording, captureRecordingEvent, endRecording } from "./_recording-guard.js";
7
7
  import { findHotkeyConflicts } from "./conflicts.js";
8
8
  import { chordRejection, hotkeyChordFromKeydown } from "./_recorder-chord.js";
9
- import { Store } from "@tanstack/store";
10
9
 
11
10
  //#region src/hotkey-recorder.ts
12
11
  const initialState = {
@@ -49,7 +48,7 @@ const initialState = {
49
48
  * ```
50
49
  */
51
50
  var HotkeyRecorder = class {
52
- store = new Store(initialState);
51
+ store = createStore(initialState);
53
52
  #keydownHandler = null;
54
53
  #optionsSource;
55
54
  #optionOverrides = {};
@@ -101,7 +100,7 @@ var HotkeyRecorder = class {
101
100
  * and records chords according to `maxSteps`.
102
101
  */
103
102
  start() {
104
- if (this.#keydownHandler) return;
103
+ if (this.#keydownHandler || typeof document === "undefined") return;
105
104
  this.#clearIdleTimer();
106
105
  this.#events = [];
107
106
  beginRecording(this);
@@ -156,11 +155,10 @@ var HotkeyRecorder = class {
156
155
  }
157
156
  const chord = hotkeyChordFromKeydown(event, platform, options.recordBy);
158
157
  if (chord === null) return;
159
- const validation = validateHotkey(chord);
160
- if (!validation.valid) {
158
+ if (splitChordParts(chord).some((part) => !part.trim())) {
161
159
  options.onReject?.({
162
160
  reason: "invalid",
163
- message: validation.errors.join("; "),
161
+ message: "Whitespace keys cannot be recorded",
164
162
  hotkey: chord
165
163
  });
166
164
  return;
@@ -1,4 +1,4 @@
1
- import { Store } from "@tanstack/store";
1
+ import { Store } from "./_store.js";
2
2
  //#region src/key-state-tracker.d.ts
3
3
  /**
4
4
  * State interface for the KeyStateTracker.