@tanstack/hotkeys 0.8.0 → 0.10.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.
Files changed (109) hide show
  1. package/README.md +15 -1
  2. package/dist/{manager.utils.js → _event-target.js} +2 -41
  3. package/dist/_keyboard-event.js +75 -0
  4. package/dist/_match.js +48 -0
  5. package/dist/_named-keys.d.ts +6 -0
  6. package/dist/_named-keys.js +65 -0
  7. package/dist/_recorder-chord.js +42 -0
  8. package/dist/_recording-guard.js +55 -0
  9. package/dist/_registration.js +49 -0
  10. package/dist/conflicts.d.ts +36 -0
  11. package/dist/conflicts.js +54 -0
  12. package/dist/constants.d.ts +16 -144
  13. package/dist/constants.js +55 -172
  14. package/dist/display-labels.d.ts +90 -0
  15. package/dist/display-labels.js +112 -0
  16. package/dist/format.d.ts +48 -12
  17. package/dist/format.js +35 -39
  18. package/dist/hint.d.ts +20 -0
  19. package/dist/hint.js +23 -0
  20. package/dist/hotkey-manager.d.ts +14 -12
  21. package/dist/hotkey-manager.js +70 -21
  22. package/dist/hotkey-recorder.d.ts +8 -8
  23. package/dist/hotkey-recorder.js +74 -15
  24. package/dist/hotkey-sequence-recorder.d.ts +13 -9
  25. package/dist/hotkey-sequence-recorder.js +90 -28
  26. package/dist/{hotkey.d.ts → hotkey.types.d.ts} +58 -126
  27. package/dist/index.d.ts +14 -9
  28. package/dist/index.js +10 -6
  29. package/dist/key-state-tracker.d.ts +4 -7
  30. package/dist/key-state-tracker.js +30 -27
  31. package/dist/key.types.d.ts +79 -0
  32. package/dist/match.d.ts +24 -11
  33. package/dist/match.js +19 -30
  34. package/dist/parse.d.ts +18 -15
  35. package/dist/parse.js +39 -22
  36. package/dist/platform.d.ts +41 -0
  37. package/dist/platform.js +52 -0
  38. package/dist/recorder-options.d.ts +31 -0
  39. package/dist/sequence-manager.d.ts +27 -15
  40. package/dist/sequence-manager.js +61 -30
  41. package/dist/validate.d.ts +16 -8
  42. package/dist/validate.js +8 -13
  43. package/package.json +8 -13
  44. package/dist/constants.cjs +0 -535
  45. package/dist/constants.cjs.map +0 -1
  46. package/dist/constants.d.cts +0 -290
  47. package/dist/constants.js.map +0 -1
  48. package/dist/format.cjs +0 -91
  49. package/dist/format.cjs.map +0 -1
  50. package/dist/format.d.cts +0 -60
  51. package/dist/format.js.map +0 -1
  52. package/dist/hotkey-manager.cjs +0 -383
  53. package/dist/hotkey-manager.cjs.map +0 -1
  54. package/dist/hotkey-manager.d.cts +0 -230
  55. package/dist/hotkey-manager.js.map +0 -1
  56. package/dist/hotkey-recorder.cjs +0 -179
  57. package/dist/hotkey-recorder.cjs.map +0 -1
  58. package/dist/hotkey-recorder.d.cts +0 -116
  59. package/dist/hotkey-recorder.js.map +0 -1
  60. package/dist/hotkey-sequence-recorder.cjs +0 -192
  61. package/dist/hotkey-sequence-recorder.cjs.map +0 -1
  62. package/dist/hotkey-sequence-recorder.d.cts +0 -79
  63. package/dist/hotkey-sequence-recorder.js.map +0 -1
  64. package/dist/hotkey.d.cts +0 -301
  65. package/dist/index.cjs +0 -63
  66. package/dist/index.d.cts +0 -13
  67. package/dist/key-state-tracker.cjs +0 -197
  68. package/dist/key-state-tracker.cjs.map +0 -1
  69. package/dist/key-state-tracker.d.cts +0 -107
  70. package/dist/key-state-tracker.js.map +0 -1
  71. package/dist/manager.utils.cjs +0 -115
  72. package/dist/manager.utils.cjs.map +0 -1
  73. package/dist/manager.utils.d.cts +0 -13
  74. package/dist/manager.utils.d.ts +0 -13
  75. package/dist/manager.utils.js.map +0 -1
  76. package/dist/match.cjs +0 -153
  77. package/dist/match.cjs.map +0 -1
  78. package/dist/match.d.cts +0 -85
  79. package/dist/match.js.map +0 -1
  80. package/dist/parse.cjs +0 -228
  81. package/dist/parse.cjs.map +0 -1
  82. package/dist/parse.d.cts +0 -128
  83. package/dist/parse.js.map +0 -1
  84. package/dist/recorder-chord.cjs +0 -18
  85. package/dist/recorder-chord.cjs.map +0 -1
  86. package/dist/recorder-chord.js +0 -18
  87. package/dist/recorder-chord.js.map +0 -1
  88. package/dist/sequence-manager.cjs +0 -415
  89. package/dist/sequence-manager.cjs.map +0 -1
  90. package/dist/sequence-manager.d.cts +0 -157
  91. package/dist/sequence-manager.js.map +0 -1
  92. package/dist/validate.cjs +0 -116
  93. package/dist/validate.cjs.map +0 -1
  94. package/dist/validate.d.cts +0 -56
  95. package/dist/validate.js.map +0 -1
  96. package/src/constants.ts +0 -603
  97. package/src/format.ts +0 -142
  98. package/src/hotkey-manager.ts +0 -759
  99. package/src/hotkey-recorder.ts +0 -272
  100. package/src/hotkey-sequence-recorder.ts +0 -328
  101. package/src/hotkey.ts +0 -436
  102. package/src/index.ts +0 -11
  103. package/src/key-state-tracker.ts +0 -249
  104. package/src/manager.utils.ts +0 -231
  105. package/src/match.ts +0 -259
  106. package/src/parse.ts +0 -328
  107. package/src/recorder-chord.ts +0 -25
  108. package/src/sequence-manager.ts +0 -734
  109. package/src/validate.ts +0 -171
package/dist/match.js CHANGED
@@ -1,11 +1,14 @@
1
- import { PUNCTUATION_CODE_MAP, detectPlatform, isSingleLetterKey, normalizeKeyName } from "./constants.js";
2
- import { parseHotkey } from "./parse.js";
1
+ import { detectPlatform } from "./platform.js";
2
+ import { keysEqual } from "./_keyboard-event.js";
3
+ import { parseHotkey, parseRegisterableHotkey } from "./parse.js";
4
+ import { matchKeyboardEvent } from "./_match.js";
3
5
 
4
6
  //#region src/match.ts
5
7
  /**
6
8
  * Checks if a KeyboardEvent matches a hotkey.
7
9
  *
8
- * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code`
10
+ * Physical bindings such as `Mod+[KeyS]` match `event.code` exactly.
11
+ * Logical bindings use `event.key`, with a fallback to `code`
9
12
  * for letter keys, digit keys (0-9), and punctuation keys when `key` produces special
10
13
  * characters (e.g., macOS Option+letter, Shift+number, or Option+punctuation).
11
14
  * Letter keys are matched case-insensitively.
@@ -31,30 +34,7 @@ import { parseHotkey } from "./parse.js";
31
34
  * ```
32
35
  */
33
36
  function matchesKeyboardEvent(event, hotkey, platform = detectPlatform()) {
34
- const parsed = typeof hotkey === "string" ? parseHotkey(hotkey, platform) : hotkey;
35
- if (event.ctrlKey !== parsed.ctrl) return false;
36
- if (event.shiftKey !== parsed.shift) return false;
37
- if (event.altKey !== parsed.alt) return false;
38
- if (event.metaKey !== parsed.meta) return false;
39
- const eventKey = normalizeKeyName(event.key);
40
- const hotkeyKey = parsed.key;
41
- if (eventKey !== "Dead" && eventKey.length === 1 && hotkeyKey.length === 1) {
42
- if (eventKey.toUpperCase() === hotkeyKey.toUpperCase()) return true;
43
- if (isSingleLetterKey(eventKey) && (/^[A-Za-z]$/.test(eventKey) || !event.altKey)) return false;
44
- }
45
- if (event.code && (eventKey === "Dead" || eventKey.length === 1 && hotkeyKey.length === 1)) {
46
- if (event.code.startsWith("Key")) {
47
- const codeLetter = event.code.slice(3);
48
- if (codeLetter.length === 1 && /^[A-Za-z]$/.test(codeLetter)) return codeLetter.toUpperCase() === hotkeyKey.toUpperCase();
49
- }
50
- if (event.code.startsWith("Digit")) {
51
- const codeDigit = event.code.slice(5);
52
- if (codeDigit.length === 1 && /^[0-9]$/.test(codeDigit)) return codeDigit === hotkeyKey;
53
- }
54
- if (event.code in PUNCTUATION_CODE_MAP) return PUNCTUATION_CODE_MAP[event.code] === hotkeyKey;
55
- return false;
56
- }
57
- return eventKey === hotkeyKey;
37
+ return matchKeyboardEvent(event, hotkey, platform).matched;
58
38
  }
59
39
  /**
60
40
  * Creates a keyboard event handler that calls the callback when the hotkey matches.
@@ -142,10 +122,19 @@ function formatParsedHotkey(parsed) {
142
122
  if (parsed.alt) parts.push("Alt");
143
123
  if (parsed.shift) parts.push("Shift");
144
124
  if (parsed.meta) parts.push("Meta");
145
- parts.push(parsed.key);
125
+ parts.push(parsed.code !== void 0 ? `[${parsed.code}]` : parsed.key);
146
126
  return parts.join("+");
147
127
  }
128
+ /**
129
+ * Compares normalized binding identity, including all four modifiers.
130
+ * Aliases resolve on the supplied platform. A physical code and a logical key
131
+ * remain distinct even when one keyboard event could match both.
132
+ */
133
+ function areHotkeysEqual(left, right, platform = detectPlatform()) {
134
+ const a = parseRegisterableHotkey(left, platform);
135
+ const b = parseRegisterableHotkey(right, platform);
136
+ return a.ctrl === b.ctrl && a.alt === b.alt && a.shift === b.shift && a.meta === b.meta && (a.code !== void 0 || b.code !== void 0 ? a.code === b.code : keysEqual(a.key, b.key));
137
+ }
148
138
 
149
139
  //#endregion
150
- export { createHotkeyHandler, createMultiHotkeyHandler, matchesKeyboardEvent };
151
- //# sourceMappingURL=match.js.map
140
+ export { areHotkeysEqual, createHotkeyHandler, createMultiHotkeyHandler, matchesKeyboardEvent };
package/dist/parse.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- import { Hotkey, Key, ParsedHotkey, RawHotkey, RegisterableHotkey } from "./hotkey.js";
1
+ import { Key } from "./key.types.js";
2
2
  import { MODIFIER_ALIASES } from "./constants.js";
3
-
3
+ import { Hotkey, ParsedHotkey, RawHotkey, RegisterableHotkey } from "./hotkey.types.js";
4
4
  //#region src/parse.d.ts
5
5
  /**
6
6
  * Parses a hotkey string into its component parts.
7
7
  *
8
8
  * @param hotkey - The hotkey string to parse (e.g., 'Mod+Shift+S')
9
9
  * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
10
- * @returns A ParsedHotkey object with the key and modifier flags
10
+ * @returns A ParsedHotkey with either logical key or physical code and modifier flags
11
11
  *
12
12
  * @example
13
13
  * ```ts
@@ -16,7 +16,7 @@ import { MODIFIER_ALIASES } from "./constants.js";
16
16
  * parseHotkey('Control+Shift+A') // { key: 'A', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] }
17
17
  * ```
18
18
  */
19
- declare function parseHotkey(hotkey: Hotkey | (string & {}), platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
19
+ export declare function parseHotkey(hotkey: Hotkey | (string & {}), platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
20
20
  /**
21
21
  * Converts a RawHotkey object to a ParsedHotkey.
22
22
  * Optional modifier booleans default to false; modifiers array is derived from them.
@@ -38,7 +38,7 @@ declare function parseHotkey(hotkey: Hotkey | (string & {}), platform?: 'mac' |
38
38
  * // { key: 'S', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] }
39
39
  * ```
40
40
  */
41
- declare function rawHotkeyToParsedHotkey(raw: RawHotkey, platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
41
+ export declare function rawHotkeyToParsedHotkey(raw: RawHotkey, platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
42
42
  /**
43
43
  * Normalizes a hotkey string to its canonical form.
44
44
  *
@@ -59,16 +59,16 @@ declare function rawHotkeyToParsedHotkey(raw: RawHotkey, platform?: 'mac' | 'win
59
59
  * normalizeHotkey('esc') // 'Escape'
60
60
  * ```
61
61
  */
62
- declare function normalizeHotkey(hotkey: Key | (string & {}), platform?: 'mac' | 'windows' | 'linux'): Hotkey;
62
+ export declare function normalizeHotkey(hotkey: Key | (string & {}), platform?: 'mac' | 'windows' | 'linux'): Hotkey;
63
63
  /**
64
64
  * Same canonical string as {@link normalizeHotkey}, but from an already-parsed hotkey.
65
65
  */
66
- declare function normalizeHotkeyFromParsed(parsed: ParsedHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
66
+ export declare function normalizeHotkeyFromParsed(parsed: ParsedHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
67
67
  /**
68
68
  * Normalizes a string or {@link RawHotkey} object to the same canonical hotkey string.
69
69
  * Use this in framework adapters instead of branching on `formatHotkey(rawHotkeyToParsedHotkey(...))`.
70
70
  */
71
- declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
71
+ export declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkey, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
72
72
  /**
73
73
  * Checks if a string is a recognized modifier token (including aliases).
74
74
  *
@@ -78,7 +78,7 @@ declare function normalizeRegisterableHotkey(hotkey: RegisterableHotkey, platfor
78
78
  * @param key - Key name or alias (e.g. from a hotkey string or `event.key`)
79
79
  * @returns True if the string is a recognized modifier
80
80
  */
81
- declare function isModifierKey(key: Key | (string & {})): key is keyof typeof MODIFIER_ALIASES;
81
+ export declare function isModifierKey(key: Key | (string & {})): key is keyof typeof MODIFIER_ALIASES;
82
82
  /**
83
83
  * Parses a KeyboardEvent into a ParsedHotkey object.
84
84
  *
@@ -97,14 +97,14 @@ declare function isModifierKey(key: Key | (string & {})): key is keyof typeof MO
97
97
  * })
98
98
  * ```
99
99
  */
100
- declare function parseKeyboardEvent(event: KeyboardEvent): ParsedHotkey;
100
+ export declare function parseKeyboardEvent(event: KeyboardEvent, platform?: 'mac' | 'windows' | 'linux'): ParsedHotkey;
101
101
  /**
102
102
  * Normalizes a keyboard event to the same canonical hotkey string as {@link normalizeHotkey}.
103
103
  *
104
104
  * @param event - The keyboard event (typically `keydown`)
105
105
  * @param platform - Target platform for `Mod` eligibility
106
106
  */
107
- declare function normalizeHotkeyFromEvent(event: KeyboardEvent, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
107
+ export declare function normalizeHotkeyFromEvent(event: KeyboardEvent, platform?: 'mac' | 'windows' | 'linux'): Hotkey;
108
108
  /**
109
109
  * Checks if a hotkey or ParsedHotkey contains at least one non-modifier key.
110
110
  *
@@ -122,7 +122,10 @@ declare function normalizeHotkeyFromEvent(event: KeyboardEvent, platform?: 'mac'
122
122
  * hasNonModifierKey(parseHotkey('Mod+A')) // true
123
123
  * ```
124
124
  */
125
- declare function hasNonModifierKey(hotkey: Hotkey | ParsedHotkey | (string & {}), platform?: 'mac' | 'windows' | 'linux'): boolean;
126
- //#endregion
127
- export { hasNonModifierKey, isModifierKey, normalizeHotkey, normalizeHotkeyFromEvent, normalizeHotkeyFromParsed, normalizeRegisterableHotkey, parseHotkey, parseKeyboardEvent, rawHotkeyToParsedHotkey };
128
- //# sourceMappingURL=parse.d.ts.map
125
+ export declare function hasNonModifierKey(hotkey: Hotkey | ParsedHotkey | (string & {}), platform?: 'mac' | 'windows' | 'linux'): boolean;
126
+ /**
127
+ * Parses a string or raw binding, resolving Mod for the supplied platform.
128
+ * Physical tokens such as `[KeyQ]` retain `code`; they never become logical Q.
129
+ */
130
+ export declare function parseRegisterableHotkey(hotkey: RegisterableHotkey, platform?: "mac" | "windows" | "linux"): ParsedHotkey;
131
+ //#endregion
package/dist/parse.js CHANGED
@@ -1,4 +1,6 @@
1
- import { MODIFIER_ALIASES, MODIFIER_ORDER, detectPlatform, normalizeKeyName, resolveModifier } from "./constants.js";
1
+ import { MODIFIER_ALIASES, MODIFIER_ORDER, normalizeKeyName } from "./constants.js";
2
+ import { detectPlatform, resolveModifier } from "./platform.js";
3
+ import { assertLogicalKey, normalizeKeyboardEvent, splitHotkeyParts } from "./_keyboard-event.js";
2
4
 
3
5
  //#region src/parse.ts
4
6
  /**
@@ -6,7 +8,7 @@ import { MODIFIER_ALIASES, MODIFIER_ORDER, detectPlatform, normalizeKeyName, res
6
8
  *
7
9
  * @param hotkey - The hotkey string to parse (e.g., 'Mod+Shift+S')
8
10
  * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)
9
- * @returns A ParsedHotkey object with the key and modifier flags
11
+ * @returns A ParsedHotkey with either logical key or physical code and modifier flags
10
12
  *
11
13
  * @example
12
14
  * ```ts
@@ -16,7 +18,7 @@ import { MODIFIER_ALIASES, MODIFIER_ORDER, detectPlatform, normalizeKeyName, res
16
18
  * ```
17
19
  */
18
20
  function parseHotkey(hotkey, platform = detectPlatform()) {
19
- const parts = hotkey.split("+");
21
+ const parts = splitHotkeyParts(hotkey);
20
22
  const modifiers = /* @__PURE__ */ new Set();
21
23
  let key = "";
22
24
  for (let i = 0; i < parts.length; i++) {
@@ -31,8 +33,9 @@ function parseHotkey(hotkey, platform = detectPlatform()) {
31
33
  }
32
34
  }
33
35
  if (!key && parts.length > 0) key = normalizeKeyName(parts[parts.length - 1].trim());
36
+ const code = /^\[([A-Za-z][A-Za-z0-9]*)\]$/.exec(key)?.[1];
34
37
  return {
35
- key,
38
+ ...code !== void 0 ? { code } : { key },
36
39
  ctrl: modifiers.has("Control"),
37
40
  shift: modifiers.has("Shift"),
38
41
  alt: modifiers.has("Alt"),
@@ -62,12 +65,15 @@ function parseHotkey(hotkey, platform = detectPlatform()) {
62
65
  * ```
63
66
  */
64
67
  function rawHotkeyToParsedHotkey(raw, platform = detectPlatform()) {
68
+ if (raw.code === void 0) assertLogicalKey(raw.key);
65
69
  let ctrl = raw.ctrl ?? false;
66
70
  const shift = raw.shift ?? false;
67
71
  const alt = raw.alt ?? false;
68
72
  let meta = raw.meta ?? false;
69
- if (raw.mod) if (resolveModifier("Mod", platform) === "Control") ctrl = true;
70
- else meta = true;
73
+ if (raw.mod) {
74
+ if (resolveModifier("Mod", platform) === "Control") ctrl = true;
75
+ else meta = true;
76
+ }
71
77
  const modifiers = MODIFIER_ORDER.filter((m) => {
72
78
  switch (m) {
73
79
  case "Control": return ctrl;
@@ -78,7 +84,7 @@ function rawHotkeyToParsedHotkey(raw, platform = detectPlatform()) {
78
84
  }
79
85
  });
80
86
  return {
81
- key: raw.key,
87
+ ...raw.code !== void 0 ? { code: raw.code } : { key: normalizeKeyName(raw.key) },
82
88
  ctrl,
83
89
  shift,
84
90
  alt,
@@ -92,6 +98,7 @@ function rawHotkeyToParsedHotkey(raw, platform = detectPlatform()) {
92
98
  * otherwise full `Control+Alt+Shift+Meta` order.
93
99
  */
94
100
  function normalizedHotkeyStringFromParsed(parsed, platform) {
101
+ if (parsed.code === void 0) assertLogicalKey(parsed.key);
95
102
  const canUseMod = platform === "mac" ? parsed.meta && !parsed.ctrl : parsed.ctrl && !parsed.meta;
96
103
  const parts = [];
97
104
  if (canUseMod) {
@@ -99,7 +106,7 @@ function normalizedHotkeyStringFromParsed(parsed, platform) {
99
106
  if (parsed.alt) parts.push("Alt");
100
107
  if (parsed.shift) parts.push("Shift");
101
108
  } else for (const modifier of MODIFIER_ORDER) if (parsed.modifiers.includes(modifier)) parts.push(modifier);
102
- parts.push(normalizeKeyName(parsed.key));
109
+ parts.push(parsed.code !== void 0 ? `[${parsed.code}]` : normalizeKeyName(parsed.key));
103
110
  return parts.join("+");
104
111
  }
105
112
  /**
@@ -168,19 +175,22 @@ function isModifierKey(key) {
168
175
  * })
169
176
  * ```
170
177
  */
171
- function parseKeyboardEvent(event) {
172
- const normalizedKey = normalizeKeyName(event.key);
178
+ function parseKeyboardEvent(event, platform) {
179
+ const normalized = normalizeKeyboardEvent(event, platform);
180
+ const ctrl = normalized.altGraph ? false : normalized.ctrl;
181
+ const alt = normalized.altGraph ? false : normalized.alt;
182
+ const shift = normalized.shift;
173
183
  const modifiers = [];
174
- if (event.ctrlKey) modifiers.push("Control");
175
- if (event.altKey) modifiers.push("Alt");
176
- if (event.shiftKey) modifiers.push("Shift");
177
- if (event.metaKey) modifiers.push("Meta");
184
+ if (ctrl) modifiers.push("Control");
185
+ if (alt) modifiers.push("Alt");
186
+ if (shift) modifiers.push("Shift");
187
+ if (normalized.meta) modifiers.push("Meta");
178
188
  return {
179
- key: normalizedKey,
180
- ctrl: event.ctrlKey,
181
- shift: event.shiftKey,
182
- alt: event.altKey,
183
- meta: event.metaKey,
189
+ key: normalized.key,
190
+ ctrl,
191
+ shift,
192
+ alt,
193
+ meta: normalized.meta,
184
194
  modifiers
185
195
  };
186
196
  }
@@ -191,7 +201,7 @@ function parseKeyboardEvent(event) {
191
201
  * @param platform - Target platform for `Mod` eligibility
192
202
  */
193
203
  function normalizeHotkeyFromEvent(event, platform = detectPlatform()) {
194
- return normalizedHotkeyStringFromParsed(parseKeyboardEvent(event), platform);
204
+ return normalizedHotkeyStringFromParsed(parseKeyboardEvent(event, platform), platform);
195
205
  }
196
206
  /**
197
207
  * Checks if a hotkey or ParsedHotkey contains at least one non-modifier key.
@@ -212,9 +222,16 @@ function normalizeHotkeyFromEvent(event, platform = detectPlatform()) {
212
222
  */
213
223
  function hasNonModifierKey(hotkey, platform = detectPlatform()) {
214
224
  const parsed = typeof hotkey === "string" ? parseHotkey(hotkey, platform) : hotkey;
225
+ if (parsed.code !== void 0) return parsed.code.length > 0 && !/^(Control|Shift|Alt|Meta)(Left|Right)$/.test(parsed.code);
215
226
  return !isModifierKey(parsed.key) && parsed.key.length > 0;
216
227
  }
228
+ /**
229
+ * Parses a string or raw binding, resolving Mod for the supplied platform.
230
+ * Physical tokens such as `[KeyQ]` retain `code`; they never become logical Q.
231
+ */
232
+ function parseRegisterableHotkey(hotkey, platform = detectPlatform()) {
233
+ return typeof hotkey === "string" ? parseHotkey(hotkey, platform) : rawHotkeyToParsedHotkey(hotkey, platform);
234
+ }
217
235
 
218
236
  //#endregion
219
- export { hasNonModifierKey, isModifierKey, normalizeHotkey, normalizeHotkeyFromEvent, normalizeHotkeyFromParsed, normalizeRegisterableHotkey, parseHotkey, parseKeyboardEvent, rawHotkeyToParsedHotkey };
220
- //# sourceMappingURL=parse.js.map
237
+ export { hasNonModifierKey, isModifierKey, normalizeHotkey, normalizeHotkeyFromEvent, normalizeHotkeyFromParsed, normalizeRegisterableHotkey, parseHotkey, parseKeyboardEvent, parseRegisterableHotkey, rawHotkeyToParsedHotkey };
@@ -0,0 +1,41 @@
1
+ import { CanonicalModifier } from "./key.types.js";
2
+ //#region src/platform.d.ts
3
+ /**
4
+ * Detects the current platform based on browser navigator properties.
5
+ *
6
+ * Used internally to resolve platform-adaptive modifiers like 'Mod' (Command on Mac,
7
+ * Control elsewhere) and for platform-specific hotkey formatting.
8
+ *
9
+ * @returns The detected platform: 'mac', 'windows', or 'linux'
10
+ * @remarks Defaults to 'linux' in SSR environments where navigator is undefined
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const platform = detectPlatform() // 'mac' | 'windows' | 'linux'
15
+ * const modifier = resolveModifier('Mod', platform) // 'Meta' on Mac, 'Control' elsewhere
16
+ * ```
17
+ */
18
+ export declare function detectPlatform(): 'mac' | 'windows' | 'linux';
19
+ /**
20
+ * Resolves the platform-adaptive 'Mod' modifier to the appropriate canonical modifier.
21
+ *
22
+ * The 'Mod' token represents the "primary modifier" on each platform:
23
+ * - macOS: 'Meta' (Command key ⌘)
24
+ * - Windows/Linux: 'Control' (Ctrl key)
25
+ *
26
+ * This enables cross-platform hotkey definitions like 'Mod+S' that automatically
27
+ * map to Command+S on Mac and Ctrl+S on Windows/Linux.
28
+ *
29
+ * @param modifier - The modifier to resolve. If 'Mod', resolves based on platform.
30
+ * @param platform - The target platform. Defaults to auto-detection.
31
+ * @returns The canonical modifier name ('Control', 'Shift', 'Alt', or 'Meta')
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * resolveModifier('Mod', 'mac') // 'Meta'
36
+ * resolveModifier('Mod', 'windows') // 'Control'
37
+ * resolveModifier('Control', 'mac') // 'Control' (unchanged)
38
+ * ```
39
+ */
40
+ export declare function resolveModifier(modifier: CanonicalModifier | 'Mod', platform?: 'mac' | 'windows' | 'linux'): CanonicalModifier;
41
+ //#endregion
@@ -0,0 +1,52 @@
1
+ //#region src/platform.ts
2
+ /**
3
+ * Detects the current platform based on browser navigator properties.
4
+ *
5
+ * Used internally to resolve platform-adaptive modifiers like 'Mod' (Command on Mac,
6
+ * Control elsewhere) and for platform-specific hotkey formatting.
7
+ *
8
+ * @returns The detected platform: 'mac', 'windows', or 'linux'
9
+ * @remarks Defaults to 'linux' in SSR environments where navigator is undefined
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const platform = detectPlatform() // 'mac' | 'windows' | 'linux'
14
+ * const modifier = resolveModifier('Mod', platform) // 'Meta' on Mac, 'Control' elsewhere
15
+ * ```
16
+ */
17
+ function detectPlatform() {
18
+ if (typeof navigator === "undefined") return "linux";
19
+ const platform = navigator.platform?.toLowerCase() ?? "";
20
+ const userAgent = navigator.userAgent?.toLowerCase() ?? "";
21
+ if (platform.includes("mac") || userAgent.includes("mac")) return "mac";
22
+ if (platform.includes("win") || userAgent.includes("win")) return "windows";
23
+ return "linux";
24
+ }
25
+ /**
26
+ * Resolves the platform-adaptive 'Mod' modifier to the appropriate canonical modifier.
27
+ *
28
+ * The 'Mod' token represents the "primary modifier" on each platform:
29
+ * - macOS: 'Meta' (Command key ⌘)
30
+ * - Windows/Linux: 'Control' (Ctrl key)
31
+ *
32
+ * This enables cross-platform hotkey definitions like 'Mod+S' that automatically
33
+ * map to Command+S on Mac and Ctrl+S on Windows/Linux.
34
+ *
35
+ * @param modifier - The modifier to resolve. If 'Mod', resolves based on platform.
36
+ * @param platform - The target platform. Defaults to auto-detection.
37
+ * @returns The canonical modifier name ('Control', 'Shift', 'Alt', or 'Meta')
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * resolveModifier('Mod', 'mac') // 'Meta'
42
+ * resolveModifier('Mod', 'windows') // 'Control'
43
+ * resolveModifier('Control', 'mac') // 'Control' (unchanged)
44
+ * ```
45
+ */
46
+ function resolveModifier(modifier, platform = detectPlatform()) {
47
+ if (modifier === "Mod") return platform === "mac" ? "Meta" : "Control";
48
+ return modifier;
49
+ }
50
+
51
+ //#endregion
52
+ export { detectPlatform, resolveModifier };
@@ -0,0 +1,31 @@
1
+ import { Hotkey, ParsedHotkey } from "./hotkey.types.js";
2
+ import { HotkeyConflict, HotkeyConflictOptions } from "./conflicts.js";
3
+ //#region src/recorder-options.d.ts
4
+ /** Event property used to record a binding. Physical codes are the default. */
5
+ export type RecorderKeyMode = 'code' | 'key';
6
+ export interface RecorderRejection {
7
+ reason: 'missing-code' | 'alt-graph' | 'invalid' | 'conflict' | 'validation';
8
+ message: string;
9
+ hotkey?: Hotkey;
10
+ sequence?: Array<Hotkey>;
11
+ conflicts?: Array<HotkeyConflict>;
12
+ }
13
+ export interface RecorderOptions {
14
+ /** Physical code recording is the default. Choose key to record logical characters. */
15
+ recordBy?: RecorderKeyMode;
16
+ /** Platform used for Mod and AltGraph handling. Defaults to platform detection. */
17
+ platform?: 'mac' | 'windows' | 'linux';
18
+ /** Opt in to live-registry conflicts, including sequence prefixes. Default: false. */
19
+ detectConflicts?: boolean | HotkeyConflictOptions;
20
+ /** Rejected candidates leave recording active. */
21
+ onReject?: (rejection: RecorderRejection) => void;
22
+ }
23
+ export interface HotkeyRecorderValidationContext {
24
+ event: KeyboardEvent;
25
+ parsedHotkey: ParsedHotkey;
26
+ }
27
+ export interface HotkeySequenceRecorderValidationContext {
28
+ events: ReadonlyArray<KeyboardEvent>;
29
+ parsedSequence: ReadonlyArray<ParsedHotkey>;
30
+ }
31
+ //#endregion
@@ -1,14 +1,13 @@
1
- import { Hotkey, HotkeyCallback } from "./hotkey.js";
1
+ import { Hotkey, HotkeyCallback, ParsedHotkey } from "./hotkey.types.js";
2
2
  import { HotkeyOptions } from "./hotkey-manager.js";
3
3
  import { Store } from "@tanstack/store";
4
-
5
4
  //#region src/sequence-manager.d.ts
6
- type Target = HTMLElement | Document | Window;
5
+ export type Target = HTMLElement | Document | Window;
7
6
  /**
8
7
  * Options for hotkey sequence matching.
9
8
  * Extends HotkeyOptions but excludes requireReset (not applicable to sequences).
10
9
  */
11
- interface SequenceOptions extends Omit<HotkeyOptions, 'requireReset'> {
10
+ export interface SequenceOptions extends Omit<HotkeyOptions, 'requireReset'> {
12
11
  /** Timeout between keys in milliseconds. Default: 1000 */
13
12
  timeout?: number;
14
13
  }
@@ -18,7 +17,7 @@ interface SequenceOptions extends Omit<HotkeyOptions, 'requireReset'> {
18
17
  * Each element is one step (a `Hotkey` string). Steps may include modifiers;
19
18
  * the same modifier can appear on consecutive steps (e.g. `Shift+R` then
20
19
  * `Shift+T`). Modifier-only key events do not advance or reset matching—see
21
- * `SequenceManager`.
20
+ * `SequenceManager`. Automatic keydown repeats and IME composition are also ignored.
22
21
  *
23
22
  * @example
24
23
  * ```ts
@@ -28,17 +27,17 @@ interface SequenceOptions extends Omit<HotkeyOptions, 'requireReset'> {
28
27
  * const chainedShift: HotkeySequence = ['Shift+R', 'Shift+T']
29
28
  * ```
30
29
  */
31
- type HotkeySequence = Array<Hotkey>;
30
+ export type HotkeySequence = Array<Hotkey>;
32
31
  /**
33
32
  * Default timeout between keys in a sequence (in milliseconds).
34
33
  * Exported for consumers that display sequence state (e.g. devtools).
35
34
  */
36
- declare const DEFAULT_SEQUENCE_TIMEOUT = 1000;
35
+ export declare const DEFAULT_SEQUENCE_TIMEOUT = 1000;
37
36
  /**
38
37
  * View of a sequence registration for devtools display.
39
38
  * Progress fields reflect an in-progress match (between first key and completion or timeout).
40
39
  */
41
- interface SequenceRegistrationView {
40
+ export interface SequenceRegistrationView {
42
41
  id: string;
43
42
  sequence: HotkeySequence;
44
43
  options: SequenceOptions;
@@ -51,6 +50,21 @@ interface SequenceRegistrationView {
51
50
  /** `Date.now()` when the last step in the current attempt matched; 0 if none. */
52
51
  partialMatchLastKeyTime: number;
53
52
  }
53
+ /**
54
+ * Internal representation of a sequence registration.
55
+ */
56
+ export interface SequenceRegistration {
57
+ id: string;
58
+ sequence: HotkeySequence;
59
+ parsedSequence: Array<ParsedHotkey>;
60
+ callback: HotkeyCallback;
61
+ options: SequenceOptions;
62
+ target: Target;
63
+ currentIndex: number;
64
+ lastKeyTime: number;
65
+ triggerCount: number;
66
+ hasFired: boolean;
67
+ }
54
68
  /**
55
69
  * A handle returned from SequenceManager.register() that allows updating
56
70
  * the callback and options without re-registering the sequence.
@@ -64,14 +78,14 @@ interface SequenceRegistrationView {
64
78
  * handle.unregister()
65
79
  * ```
66
80
  */
67
- interface SequenceRegistrationHandle {
81
+ export interface SequenceRegistrationHandle {
68
82
  readonly id: string;
69
83
  readonly isActive: boolean;
70
84
  callback: HotkeyCallback;
71
85
  setOptions: (options: Partial<SequenceOptions>) => void;
72
86
  unregister: () => void;
73
87
  }
74
- declare class SequenceManager {
88
+ export declare class SequenceManager {
75
89
  #private;
76
90
  /**
77
91
  * The TanStack Store containing sequence registration views for devtools.
@@ -121,7 +135,7 @@ declare class SequenceManager {
121
135
  * Gets the singleton SequenceManager instance.
122
136
  * Convenience function for accessing the manager.
123
137
  */
124
- declare function getSequenceManager(): SequenceManager;
138
+ export declare function getSequenceManager(): SequenceManager;
125
139
  /**
126
140
  * Creates a simple sequence matcher for one-off use.
127
141
  *
@@ -144,7 +158,7 @@ declare function getSequenceManager(): SequenceManager;
144
158
  * })
145
159
  * ```
146
160
  */
147
- declare function createSequenceMatcher(sequence: HotkeySequence, options?: {
161
+ export declare function createSequenceMatcher(sequence: HotkeySequence, options?: {
148
162
  timeout?: number;
149
163
  platform?: 'mac' | 'windows' | 'linux';
150
164
  }): {
@@ -152,6 +166,4 @@ declare function createSequenceMatcher(sequence: HotkeySequence, options?: {
152
166
  reset: () => void;
153
167
  getProgress: () => number;
154
168
  };
155
- //#endregion
156
- export { DEFAULT_SEQUENCE_TIMEOUT, HotkeySequence, SequenceManager, SequenceOptions, SequenceRegistrationHandle, SequenceRegistrationView, createSequenceMatcher, getSequenceManager };
157
- //# sourceMappingURL=sequence-manager.d.ts.map
169
+ //#endregion