@tanstack/hotkeys 0.19.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.
@@ -0,0 +1,5 @@
1
+ import { AppHotkeys, AppHotkeysStore, AppHotkeysStoreOptions } from "./app-hotkeys.js";
2
+ //#region src/_app-hotkeys-factory.d.ts
3
+ /** Creates the store for app hotkeys from `defineAppHotkeys`. */
4
+ export declare function createAppHotkeysStore<T extends AppHotkeys>(appHotkeys: T, options: AppHotkeysStoreOptions<T>): AppHotkeysStore<T>;
5
+ //#endregion
@@ -0,0 +1,13 @@
1
+ //#region src/_app-hotkeys-factory.ts
2
+ let factory;
3
+ function setAppHotkeysStoreFactory(create) {
4
+ factory = create;
5
+ }
6
+ /** Creates the store for app hotkeys from `defineAppHotkeys`. */
7
+ function createAppHotkeysStore(appHotkeys, options) {
8
+ if (!factory) throw new Error("[hotkeys] App hotkeys must come from defineAppHotkeys.");
9
+ return factory(appHotkeys, options);
10
+ }
11
+
12
+ //#endregion
13
+ export { createAppHotkeysStore, setAppHotkeysStoreFactory };
@@ -34,9 +34,8 @@ function codeToLogicalKey(code) {
34
34
  if (/^Key[A-Z]$/i.test(code)) return code.slice(3).toUpperCase();
35
35
  if (/^Digit[0-9]$/i.test(code)) return code.slice(5);
36
36
  if (/^Numpad[0-9]$/i.test(code)) return code.slice(6);
37
- const punctuation = PUNCTUATION_CODE_MAP[code];
38
- if (punctuation) return punctuation;
39
- return {
37
+ if (Object.hasOwn(PUNCTUATION_CODE_MAP, code)) return PUNCTUATION_CODE_MAP[code];
38
+ const named = {
40
39
  NumpadAdd: "+",
41
40
  NumpadSubtract: "-",
42
41
  NumpadMultiply: "*",
@@ -44,7 +43,8 @@ function codeToLogicalKey(code) {
44
43
  NumpadDecimal: ".",
45
44
  NumpadEnter: "Enter",
46
45
  Space: "Space"
47
- }[code];
46
+ };
47
+ return Object.hasOwn(named, code) ? named[code] : void 0;
48
48
  }
49
49
  /**
50
50
  * Whether AltGr changed what a key typed, as in AltGr+Q typing `@`. False when
@@ -102,6 +102,14 @@ function keysEqual(left, right) {
102
102
  function assertLogicalKey(key) {
103
103
  if (/^\[.*\]$/s.test(key.trim())) throw new Error("Logical key must not contain a bracketed physical code. Use the code field instead.");
104
104
  }
105
+ /**
106
+ * Whether a key is written like a key the library knows: one character,
107
+ * F1-F24, or a capitalized name such as `Escape`. Key names are
108
+ * case-sensitive, so `escape` and `f1` don't qualify.
109
+ */
110
+ function isKeyNameShape(key) {
111
+ return /^(?:.|F(?:[1-9]|1\d|2[0-4])|[A-Z][a-z][A-Za-z\d]*)$/su.test(key);
112
+ }
105
113
 
106
114
  //#endregion
107
- export { altGraphTypedCharacter, assertLogicalKey, codeToLogicalKey, isAsciiLetter, isCodeName, isLatinLetter, isPrintableKey, keysEqual, modifierAlias, normalizeKeyboardEvent, physicalCodeOf, splitChordParts };
115
+ export { altGraphTypedCharacter, assertLogicalKey, codeToLogicalKey, isAsciiLetter, isCodeName, isKeyNameShape, isLatinLetter, isPrintableKey, keysEqual, modifierAlias, normalizeKeyboardEvent, physicalCodeOf, splitChordParts };
@@ -60,6 +60,26 @@ const LOGICAL_ONLY_NAMED_KEYS = [
60
60
  "Hankaku",
61
61
  "ZenkakuHankaku"
62
62
  ];
63
+ /** Arrow, Home/End, and PageUp/PageDown keys. */
64
+ const NAVIGATION_KEY_NAMES = [
65
+ "ArrowUp",
66
+ "ArrowDown",
67
+ "ArrowLeft",
68
+ "ArrowRight",
69
+ "Home",
70
+ "End",
71
+ "PageUp",
72
+ "PageDown"
73
+ ];
74
+ /** Editing and special keys. */
75
+ const EDITING_KEY_NAMES = [
76
+ "Enter",
77
+ "Escape",
78
+ "Space",
79
+ "Tab",
80
+ "Backspace",
81
+ "Delete"
82
+ ];
63
83
 
64
84
  //#endregion
65
- export { LOGICAL_ONLY_NAMED_KEYS, SHARED_NAMED_KEYS };
85
+ export { EDITING_KEY_NAMES, LOGICAL_ONLY_NAMED_KEYS, NAVIGATION_KEY_NAMES, SHARED_NAMED_KEYS };
@@ -35,7 +35,7 @@ function getDefaultIgnoreInputs(parsedChord) {
35
35
  function handleConflict(conflictingId, keyDisplay, conflictBehavior, unregister) {
36
36
  if (conflictBehavior === "allow") return;
37
37
  if (conflictBehavior === "warn") {
38
- console.warn(`'${keyDisplay}' is already registered. Multiple handlers may be triggered. Use conflictBehavior: 'replace' to replace the existing handler, or conflictBehavior: 'allow' to suppress this warning.`);
38
+ if (process.env.NODE_ENV !== "production") console.warn(`'${keyDisplay}' is already registered. Multiple handlers may be triggered. Use conflictBehavior: 'replace' to replace the existing handler, or conflictBehavior: 'allow' to suppress this warning.`);
39
39
  return;
40
40
  }
41
41
  if (conflictBehavior === "error") throw new Error(`'${keyDisplay}' is already registered. Use conflictBehavior: 'replace' to replace the existing handler, or conflictBehavior: 'allow' to allow multiple registrations.`);
@@ -0,0 +1,14 @@
1
+ import { Store as Store$1 } from "./vendor/@tanstack/store/dist/store.js";
2
+ //#region src/_store.d.ts
3
+ /**
4
+ * The part of TanStack Store's `Store` that TanStack Hotkeys uses. Its stores
5
+ * work with TanStack Store's framework adapters, such as `useSelector` from
6
+ * `@tanstack/react-store`.
7
+ */
8
+ export type Store<T> = Pick<Store$1<T>, 'state' | 'get' | 'setState' | 'subscribe'>;
9
+ /**
10
+ * Creates a store. Listeners run synchronously after each change, and an
11
+ * update to an equal value (by `Object.is`) notifies no one.
12
+ */
13
+ export declare function createStore<T>(initialState: T): Store<T>;
14
+ //#endregion
package/dist/_store.js ADDED
@@ -0,0 +1,43 @@
1
+ //#region src/_store.ts
2
+ const queue = /* @__PURE__ */ new Set();
3
+ function notify(listeners) {
4
+ for (const listener of listeners) queue.add(listener);
5
+ for (const listener of queue) {
6
+ queue.delete(listener);
7
+ listener();
8
+ }
9
+ }
10
+ /**
11
+ * Creates a store. Listeners run synchronously after each change, and an
12
+ * update to an equal value (by `Object.is`) notifies no one.
13
+ */
14
+ function createStore(initialState) {
15
+ let state = initialState;
16
+ const listeners = /* @__PURE__ */ new Set();
17
+ return {
18
+ get state() {
19
+ return state;
20
+ },
21
+ get: () => state,
22
+ setState(updater) {
23
+ const next = updater(state);
24
+ if (Object.is(state, next)) return;
25
+ state = next;
26
+ notify(listeners);
27
+ },
28
+ subscribe(observerOrFn) {
29
+ const listener = () => {
30
+ if (typeof observerOrFn === "function") observerOrFn(state);
31
+ else observerOrFn.next?.(state);
32
+ };
33
+ listeners.add(listener);
34
+ return { unsubscribe() {
35
+ listeners.delete(listener);
36
+ queue.delete(listener);
37
+ } };
38
+ }
39
+ };
40
+ }
41
+
42
+ //#endregion
43
+ export { createStore };
package/dist/adapter.d.ts CHANGED
@@ -1,6 +1,8 @@
1
+ import { Store, createStore } from "./_store.js";
1
2
  import { HotkeyCallback, HotkeyChord, HotkeySequence, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
2
3
  import { ActivateHotkeyScopeOptions, HotkeyChordOptions, HotkeyOptions, HotkeySequenceOptions, Target } from "./hotkey-manager.js";
3
4
  import { AppHotkey, AppHotkeysStore } from "./app-hotkeys.js";
5
+ import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
4
6
  //#region src/adapter.d.ts
5
7
  /**
6
8
  * A hotkey and its callback, with optional per-binding options. Chords accept
@@ -160,4 +162,5 @@ export declare function getHotkeysProviderScopeKey(options: {
160
162
  * Returns a function that releases the activation.
161
163
  */
162
164
  export declare function activateHotkeysProviderScope(key: string): () => void;
163
- //#endregion
165
+ //#endregion
166
+ export { type Store, createAppHotkeysStore, createStore };
package/dist/adapter.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { detectPlatform } from "./platform.js";
2
2
  import { isHotkeySequence, normalizeHotkey } from "./parse.js";
3
+ import { createStore } from "./_store.js";
3
4
  import { defaultHotkeyOptions } from "./_registration.js";
4
5
  import { getHotkeyManager } from "./hotkey-manager.js";
6
+ import { createAppHotkeysStore } from "./_app-hotkeys-factory.js";
5
7
  import { getAppHotkeyRegistrationOptions, isAppHotkey, resolveAppHotkey } from "./app-hotkeys.js";
6
8
 
7
9
  //#region src/adapter.ts
@@ -171,4 +173,4 @@ function activateHotkeysProviderScope(key) {
171
173
  }
172
174
 
173
175
  //#endregion
174
- export { activateHotkeysProviderScope, createHotkeyBindings, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults, prepareHotkeyDefinitions, subscribePublishedAppHotkeys };
176
+ export { activateHotkeysProviderScope, createAppHotkeysStore, createHotkeyBindings, createStore, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults, prepareHotkeyDefinitions, subscribePublishedAppHotkeys };
@@ -1,6 +1,6 @@
1
+ import { Store } from "./_store.js";
1
2
  import { Hotkey, HotkeyChord, HotkeyMeta, HotkeySequence } from "./hotkey.types.js";
2
3
  import { HotkeyChordOptions, HotkeyRegistrationView, HotkeySequenceOptions } from "./hotkey-manager.js";
3
- import { Store } from "@tanstack/store";
4
4
  //#region src/app-hotkeys.d.ts
5
5
  type Platform = 'mac' | 'windows' | 'linux';
6
6
  type DeclaredOptionKeys = 'capture' | 'conflictBehavior' | 'eventType' | 'ignoreInputs' | 'preventDefault' | 'stopPropagation';
@@ -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
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",
@@ -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));
@@ -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 = {};
@@ -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.
@@ -1,5 +1,5 @@
1
1
  import { MODIFIER_KEYS, normalizeKeyName } from "./constants.js";
2
- import { Store } from "@tanstack/store";
2
+ import { createStore } from "./_store.js";
3
3
 
4
4
  //#region src/key-state-tracker.ts
5
5
  /**
@@ -51,7 +51,7 @@ var KeyStateTracker = class KeyStateTracker {
51
51
  * The TanStack Store instance containing the tracker state.
52
52
  * Use this to subscribe to state changes or access current state.
53
53
  */
54
- store = new Store(getDefaultKeyStateTrackerState());
54
+ store = createStore(getDefaultKeyStateTrackerState());
55
55
  #heldEntries = /* @__PURE__ */ new Map();
56
56
  /** Window listeners as `[type, listener, capture]`, removed together on destroy. */
57
57
  #listeners = [];
@@ -42,8 +42,8 @@ export declare function validateHotkey(hotkey: ValidatableHotkey): ValidationRes
42
42
  */
43
43
  export declare function assertValidHotkey(hotkey: ValidatableHotkey): void;
44
44
  /**
45
- * Validates a hotkey and logs warnings to the console.
46
- * Useful for development-time feedback.
45
+ * Validates a hotkey and logs errors and warnings to the console.
46
+ * Useful for development-time feedback. Logs nothing in production builds.
47
47
  *
48
48
  * @param hotkey - The hotkey string to validate
49
49
  * @returns True if the hotkey is valid (may still have warnings)
package/dist/validate.js CHANGED
@@ -1,5 +1,4 @@
1
- import { ALL_KEYS, normalizeKeyName } from "./constants.js";
2
- import { modifierAlias, physicalCodeOf, splitChordParts } from "./_keyboard-event.js";
1
+ import { isKeyNameShape, modifierAlias, physicalCodeOf, splitChordParts } from "./_keyboard-event.js";
3
2
 
4
3
  //#region src/validate.ts
5
4
  /**
@@ -62,17 +61,13 @@ function validateChord(hotkey) {
62
61
  const keyPart = parts[parts.length - 1];
63
62
  for (const modifier of modifierParts) if (!modifierAlias(modifier)) errors.push(`Unknown modifier: '${modifier}'`);
64
63
  if (keyPart.length > 1 && (keyPart.startsWith("[") || keyPart.endsWith("]")) && physicalCodeOf(keyPart) === void 0) errors.push("Invalid physical key: use a bracketed KeyboardEvent.code, such as [KeyS]");
65
- if (!isKnownKey(normalizeKeyName(keyPart))) warnings.push(`Unknown key: '${keyPart}'. This may still work but won't have type-safe autocomplete.`);
64
+ if (physicalCodeOf(keyPart) === void 0 && !isKeyNameShape(keyPart)) warnings.push(`Unknown key: '${keyPart}'. Key names are case-sensitive, such as 'Escape' and 'F1'.`);
66
65
  return {
67
66
  valid: errors.length === 0,
68
67
  warnings,
69
68
  errors
70
69
  };
71
70
  }
72
- /** Physical codes, single characters, and named keys are known. */
73
- function isKnownKey(key) {
74
- return physicalCodeOf(key) !== void 0 || Array.from(key).length === 1 || ALL_KEYS.has(key);
75
- }
76
71
  /**
77
72
  * Validates a hotkey and throws an error if invalid.
78
73
  * Useful for development-time validation.
@@ -91,8 +86,8 @@ function assertValidHotkey(hotkey) {
91
86
  if (!result.valid) throw new Error(`Invalid hotkey '${String(hotkey)}': ${result.errors.join(", ")}`);
92
87
  }
93
88
  /**
94
- * Validates a hotkey and logs warnings to the console.
95
- * Useful for development-time feedback.
89
+ * Validates a hotkey and logs errors and warnings to the console.
90
+ * Useful for development-time feedback. Logs nothing in production builds.
96
91
  *
97
92
  * @param hotkey - The hotkey string to validate
98
93
  * @returns True if the hotkey is valid (may still have warnings)
@@ -106,8 +101,10 @@ function assertValidHotkey(hotkey) {
106
101
  */
107
102
  function checkHotkey(hotkey) {
108
103
  const result = validateHotkey(hotkey);
109
- if (result.errors.length > 0) console.error(`Hotkey '${String(hotkey)}' errors:`, result.errors.join("; "));
110
- if (result.warnings.length > 0) console.warn(`Hotkey '${String(hotkey)}' warnings:`, result.warnings.join("; "));
104
+ if (process.env.NODE_ENV !== "production") {
105
+ if (result.errors.length > 0) console.error(`Hotkey '${String(hotkey)}' errors:`, result.errors.join("; "));
106
+ if (result.warnings.length > 0) console.warn(`Hotkey '${String(hotkey)}' warnings:`, result.warnings.join("; "));
107
+ }
111
108
  return result.valid;
112
109
  }
113
110
 
@@ -0,0 +1,23 @@
1
+ import { Observer, Subscription } from "./types.js";
2
+ //#region ../../node_modules/.pnpm/@tanstack+store@0.11.2/node_modules/@tanstack/store/dist/store.d.ts
3
+ //#region src/store.d.ts
4
+ type StoreAction = (...args: Array<any>) => any;
5
+ type StoreActionMap = Record<string, StoreAction>;
6
+ type StoreActionsFactory<T, TActions extends StoreActionMap> = (store: {
7
+ setState: Store<T>['setState'];
8
+ get: Store<T>['get'];
9
+ }) => TActions;
10
+ type NonFunction<T> = T extends ((...args: Array<any>) => any) ? never : T;
11
+ declare class Store<T, TActions extends StoreActionMap = never> {
12
+ private atom;
13
+ readonly actions: TActions;
14
+ constructor(getValue: (prev?: NoInfer<T>) => T);
15
+ constructor(initialValue: T);
16
+ constructor(initialValue: NonFunction<T>, actionsFactory: StoreActionsFactory<T, TActions>);
17
+ setState(updater: (prev: T) => T): void;
18
+ get state(): T;
19
+ get(): T;
20
+ subscribe(observerOrFn: Observer<T> | ((value: T) => void)): Subscription;
21
+ }
22
+ //#endregion
23
+ export { Store, StoreAction, StoreActionMap, StoreActionsFactory };
@@ -0,0 +1,11 @@
1
+ //#region ../../node_modules/.pnpm/@tanstack+store@0.11.2/node_modules/@tanstack/store/dist/types.d.ts
2
+ type Observer<T> = {
3
+ next?: (value: T) => void;
4
+ error?: (err: unknown) => void;
5
+ complete?: () => void;
6
+ };
7
+ interface Subscription {
8
+ unsubscribe: () => void;
9
+ }
10
+ //#endregion
11
+ export { Observer, Subscription };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/hotkeys",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Type-safe, framework-agnostic keyboard hotkey management for the browser",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -39,9 +39,12 @@
39
39
  "dist",
40
40
  "skills"
41
41
  ],
42
- "dependencies": {
42
+ "devDependencies": {
43
43
  "@tanstack/store": "^0.11.2"
44
44
  },
45
+ "inlinedDependencies": {
46
+ "@tanstack/store": "0.11.2"
47
+ },
45
48
  "scripts": {
46
49
  "clean": "premove ./build ./dist",
47
50
  "lint": "eslint ./src",
@@ -33,7 +33,7 @@ The library owns the keyboard. Register every shortcut with your framework adapt
33
33
  | Instead of | Use |
34
34
  | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
35
35
  | A `keydown` listener that checks `event.key`, `metaKey`, or `ctrlKey` | A registered hotkey such as `'Mod+S'`. Inside a widget's own key handler, `matchesKeyboardEvent(event, 'Mod+Enter')`. |
36
- | `isMac` checks and `⌘`/`Ctrl` label maps | `Mod` in the hotkey, and `formatForDisplay(hotkey)` for labels |
36
+ | `isMac` checks and `⌘`/`Ctrl` label maps | `Mod` in the hotkey, `formatForDisplay(hotkey)` for labels, or `formatForDisplay({ key })` for held keys |
37
37
  | Skipping events from text fields, calling `preventDefault` | The defaults: single keys and Shift/Alt chords are skipped while typing, `Mod` chords and `Escape` still fire, and matched keys are prevented |
38
38
  | A shortcut registry, service, context, or wrapper hook | The adapter API, called where each action lives. For one list of every shortcut, load `@tanstack/hotkeys#app-hotkeys`. |
39
39
  | A "shortcuts enabled" flag or topmost-dialog tracking | Scopes, with `exclusive` for dialogs |
@@ -59,6 +59,7 @@ When nesting scopes, stacking dialogs, or keeping some hotkeys working during an
59
59
  - **A disabled hotkey doesn't block the browser.** With `enabled: false`, `Mod+S` opens the Save Page dialog. Add `preventDefault: 'always'` to keep the action blocked while the hotkey can't fire, whether it's disabled or its scope is off. Skip it for hotkeys turned off to give the key back to the browser, such as `Mod+C`, `Mod+Z`, `Tab`, or single keys behind a user's off switch. Don't keep the hotkey enabled and return early from the callback; the hotkey would also stay `active` in app hotkey lists and show its hint.
60
60
  - **Modifiers match exactly.** `ArrowDown` never fires while Shift is held, so register `Shift+ArrowDown` instead of checking `event.shiftKey`. Shift that a symbol needs is the exception: `?` matches Shift+/.
61
61
  - **Chords repeat while held.** Set `requireReset: true` on toggles, or holding the keys flickers them.
62
+ - **Key names are case-sensitive.** Write `Escape`, `ArrowUp`, `F1`, and `+` as the types spell them; `escape`, `Esc`, `f1`, and `Plus` never match. Only letters match in any case. Hotkeys from plain JavaScript or saved data skip the types, so check them with `validateHotkey`.
62
63
  - **Letters follow the typed character.** For position-based controls such as WASD, use physical bindings such as `'[KeyW]'`. Keep letters for mnemonic commands such as `Mod+S`.
63
64
  - **Each open exclusive scope needs its own name.** A reusable dialog with a fixed scope name breaks when one dialog opens another: both share one scope and both handle `Escape`. Build the name per instance.
64
65
  - **A chord on a sequence's first key runs every time the sequence starts.** With `G` and `['G', 'G']`, the first `G` runs the chord. Later keys belong to the sequence. To show "waiting for the next key", read `matchedStepCount` from the adapter's registrations list.