@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
@@ -1,6 +1,11 @@
1
- import { detectPlatform } from "./constants.js";
2
- import { isInputElement } from "./manager.utils.js";
3
- import { hotkeyChordFromKeydown } from "./recorder-chord.js";
1
+ import { detectPlatform } from "./platform.js";
2
+ import { normalizeKeyboardEvent } from "./_keyboard-event.js";
3
+ import { isModifierKey, parseHotkey } from "./parse.js";
4
+ import { validateHotkey } from "./validate.js";
5
+ import { beginRecording, captureRecordingEvent, endRecording } from "./_recording-guard.js";
6
+ import { shouldIgnoreInputEvent } from "./_event-target.js";
7
+ import { findHotkeyConflicts } from "./conflicts.js";
8
+ import { chordRejection, hotkeyChordFromKeydown } from "./_recorder-chord.js";
4
9
  import { Store } from "@tanstack/store";
5
10
 
6
11
  //#region src/hotkey-recorder.ts
@@ -44,14 +49,18 @@ import { Store } from "@tanstack/store";
44
49
  * ```
45
50
  */
46
51
  var HotkeyRecorder = class {
52
+ /**
53
+ * The TanStack Store instance containing the recorder state.
54
+ * Use this to subscribe to state changes or access current state.
55
+ */
56
+ store = new Store({
57
+ isRecording: false,
58
+ recordedHotkey: null
59
+ });
47
60
  #keydownHandler = null;
48
61
  #options;
49
62
  #platform;
50
63
  constructor(options) {
51
- this.store = new Store({
52
- isRecording: false,
53
- recordedHotkey: null
54
- });
55
64
  this.#options = options;
56
65
  this.#platform = detectPlatform();
57
66
  }
@@ -74,31 +83,80 @@ var HotkeyRecorder = class {
74
83
  */
75
84
  start() {
76
85
  if (this.#keydownHandler) return;
86
+ beginRecording(this);
77
87
  this.store.setState(() => ({
78
88
  isRecording: true,
79
89
  recordedHotkey: null
80
90
  }));
81
91
  const handler = (event) => {
82
92
  if (!this.#keydownHandler) return;
83
- if (this.#options.ignoreInputs !== false) {
84
- if (isInputElement(typeof document !== "undefined" ? document.activeElement : null) && event.key !== "Escape") return;
85
- }
93
+ if (this.#options.ignoreInputs !== false && event.key !== "Escape" && shouldIgnoreInputEvent(event, document, document)) return;
94
+ const platform = this.#options.platform ?? this.#platform;
95
+ if (normalizeKeyboardEvent(event, platform).isComposing) return;
96
+ captureRecordingEvent(event);
86
97
  event.preventDefault();
87
98
  event.stopPropagation();
99
+ if (event.repeat) return;
88
100
  if (event.key === "Escape") {
89
101
  this.cancel();
90
102
  return;
91
103
  }
92
104
  if (event.key === "Backspace" || event.key === "Delete") {
93
105
  if (!event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
94
- this.#options.onClear?.();
95
- this.#options.onRecord("");
96
106
  this.stop();
107
+ this.#options.onClear?.();
97
108
  return;
98
109
  }
99
110
  }
100
- const finalHotkey = hotkeyChordFromKeydown(event, this.#platform);
111
+ if (isModifierKey(event.key) || event.key === "AltGraph") return;
112
+ const rejection = chordRejection(event, {
113
+ ...this.#options,
114
+ platform
115
+ });
116
+ if (rejection) {
117
+ this.#options.onReject?.(rejection);
118
+ return;
119
+ }
120
+ const finalHotkey = hotkeyChordFromKeydown(event, platform, this.#options.recordBy);
101
121
  if (finalHotkey === null) return;
122
+ const validation = validateHotkey(finalHotkey);
123
+ if (!validation.valid) {
124
+ this.#options.onReject?.({
125
+ reason: "invalid",
126
+ message: validation.errors.join("; "),
127
+ hotkey: finalHotkey
128
+ });
129
+ return;
130
+ }
131
+ const accepted = this.#options.validate?.(finalHotkey, {
132
+ event,
133
+ parsedHotkey: parseHotkey(finalHotkey, platform)
134
+ });
135
+ if (accepted !== void 0 && accepted !== true) {
136
+ this.#options.onReject?.({
137
+ reason: "validation",
138
+ message: typeof accepted === "string" ? accepted : "This shortcut is not allowed.",
139
+ hotkey: finalHotkey
140
+ });
141
+ return;
142
+ }
143
+ if (this.#options.detectConflicts) {
144
+ const conflicts = findHotkeyConflicts(finalHotkey, {
145
+ ...typeof this.#options.detectConflicts === "object" ? this.#options.detectConflicts : {},
146
+ platform,
147
+ events: [event]
148
+ });
149
+ if (conflicts.length) {
150
+ this.#options.onReject?.({
151
+ reason: "conflict",
152
+ message: "This shortcut conflicts with a registered binding.",
153
+ hotkey: finalHotkey,
154
+ conflicts
155
+ });
156
+ return;
157
+ }
158
+ }
159
+ endRecording(this);
102
160
  const handlerToRemove = this.#keydownHandler;
103
161
  if (handlerToRemove) {
104
162
  this.#removeListener(handlerToRemove);
@@ -119,6 +177,7 @@ var HotkeyRecorder = class {
119
177
  * Removes the event listener and resets the recording state.
120
178
  */
121
179
  stop() {
180
+ endRecording(this);
122
181
  if (this.#keydownHandler) {
123
182
  this.#removeListener(this.#keydownHandler);
124
183
  this.#keydownHandler = null;
@@ -135,6 +194,7 @@ var HotkeyRecorder = class {
135
194
  * the onCancel callback if provided.
136
195
  */
137
196
  cancel() {
197
+ endRecording(this);
138
198
  if (this.#keydownHandler) {
139
199
  this.#removeListener(this.#keydownHandler);
140
200
  this.#keydownHandler = null;
@@ -175,5 +235,4 @@ var HotkeyRecorder = class {
175
235
  };
176
236
 
177
237
  //#endregion
178
- export { HotkeyRecorder };
179
- //# sourceMappingURL=hotkey-recorder.js.map
238
+ export { HotkeyRecorder };
@@ -1,17 +1,17 @@
1
1
  import { HotkeySequence } from "./sequence-manager.js";
2
+ import { HotkeySequenceRecorderValidationContext, RecorderOptions } from "./recorder-options.js";
2
3
  import { Store } from "@tanstack/store";
3
-
4
4
  //#region src/hotkey-sequence-recorder.d.ts
5
5
  /**
6
6
  * How the user can commit a recorded sequence from the keyboard.
7
7
  * - `'enter'`: plain Enter (no modifiers) commits when at least one step exists.
8
8
  * - `'none'`: only {@link HotkeySequenceRecorder.commit} finishes recording (or idle timeout if set).
9
9
  */
10
- type HotkeySequenceRecorderCommitKeys = 'enter' | 'none';
10
+ export type HotkeySequenceRecorderCommitKeys = 'enter' | 'none';
11
11
  /**
12
12
  * State interface for the HotkeySequenceRecorder.
13
13
  */
14
- interface HotkeySequenceRecorderState {
14
+ export interface HotkeySequenceRecorderState {
15
15
  /** Whether recording is currently active */
16
16
  isRecording: boolean;
17
17
  /** Chords captured so far in the current recording session */
@@ -22,8 +22,10 @@ interface HotkeySequenceRecorderState {
22
22
  /**
23
23
  * Options for configuring a HotkeySequenceRecorder instance.
24
24
  */
25
- interface HotkeySequenceRecorderOptions {
26
- /** Callback when a sequence is successfully recorded (including empty array when cleared) */
25
+ export interface HotkeySequenceRecorderOptions extends RecorderOptions {
26
+ /** Validate the completed sequence. Rejection preserves the steps for editing. */
27
+ validate?: (sequence: HotkeySequence, context: HotkeySequenceRecorderValidationContext) => boolean | string;
28
+ /** Callback when a sequence is successfully recorded */
27
29
  onRecord: (sequence: HotkeySequence) => void;
28
30
  /** Optional callback when recording is cancelled (Escape pressed) */
29
31
  onCancel?: () => void;
@@ -60,20 +62,22 @@ interface HotkeySequenceRecorderOptions {
60
62
  * when {@link HotkeySequenceRecorderOptions.commitKeys} is `'enter'` (default), **Escape** to cancel,
61
63
  * **Backspace/Delete** to remove the last step or clear when empty.
62
64
  */
63
- declare class HotkeySequenceRecorder {
65
+ export declare class HotkeySequenceRecorder {
64
66
  #private;
65
67
  readonly store: Store<HotkeySequenceRecorderState>;
66
68
  constructor(options: HotkeySequenceRecorderOptions);
69
+ /** Merges current callbacks and options without discarding recorded steps. */
67
70
  setOptions(options: Partial<HotkeySequenceRecorderOptions>): void;
71
+ /** Starts a fresh recording; repeated starts during an active session are ignored. */
68
72
  start(): void;
69
73
  /**
70
74
  * Commit the current steps as a sequence. No-op if fewer than one step.
71
75
  */
72
76
  commit(): void;
77
+ /** Stops and discards in-progress steps without invoking onCancel. */
73
78
  stop(): void;
79
+ /** Stops, discards in-progress steps, and notifies onCancel. */
74
80
  cancel(): void;
75
81
  destroy(): void;
76
82
  }
77
- //#endregion
78
- export { HotkeySequenceRecorder, HotkeySequenceRecorderCommitKeys, HotkeySequenceRecorderOptions, HotkeySequenceRecorderState };
79
- //# sourceMappingURL=hotkey-sequence-recorder.d.ts.map
83
+ //#endregion
@@ -1,6 +1,11 @@
1
- import { detectPlatform } from "./constants.js";
2
- import { isInputElement } from "./manager.utils.js";
3
- import { hotkeyChordFromKeydown } from "./recorder-chord.js";
1
+ import { detectPlatform } from "./platform.js";
2
+ import { normalizeKeyboardEvent } from "./_keyboard-event.js";
3
+ import { isModifierKey, parseHotkey } from "./parse.js";
4
+ import { validateHotkey } from "./validate.js";
5
+ import { beginRecording, captureRecordingEvent, endRecording } from "./_recording-guard.js";
6
+ import { shouldIgnoreInputEvent } from "./_event-target.js";
7
+ import { findHotkeyConflicts } from "./conflicts.js";
8
+ import { chordRejection, hotkeyChordFromKeydown } from "./_recorder-chord.js";
4
9
  import { Store } from "@tanstack/store";
5
10
 
6
11
  //#region src/hotkey-sequence-recorder.ts
@@ -8,6 +13,7 @@ const defaultHotkeySequenceRecorderOptions = {
8
13
  commitOnEnter: true,
9
14
  commitKeys: "enter"
10
15
  };
16
+ /** Resolves the Enter policy, with commitKeys: none overriding the legacy boolean. */
11
17
  function resolvedCommitOnEnter(options) {
12
18
  if (options.commitKeys === "none") return false;
13
19
  return options.commitOnEnter !== false;
@@ -20,22 +26,24 @@ function resolvedCommitOnEnter(options) {
20
26
  * **Backspace/Delete** to remove the last step or clear when empty.
21
27
  */
22
28
  var HotkeySequenceRecorder = class {
29
+ store = new Store({
30
+ isRecording: false,
31
+ steps: [],
32
+ recordedSequence: null
33
+ });
23
34
  #keydownHandler = null;
24
35
  #options;
25
36
  #platform;
37
+ #events = [];
26
38
  #idleTimer = null;
27
39
  constructor(options) {
28
- this.store = new Store({
29
- isRecording: false,
30
- steps: [],
31
- recordedSequence: null
32
- });
33
40
  this.#options = {
34
41
  ...defaultHotkeySequenceRecorderOptions,
35
42
  ...options
36
43
  };
37
44
  this.#platform = detectPlatform();
38
45
  }
46
+ /** Merges current callbacks and options without discarding recorded steps. */
39
47
  setOptions(options) {
40
48
  this.#options = {
41
49
  ...defaultHotkeySequenceRecorderOptions,
@@ -43,12 +51,14 @@ var HotkeySequenceRecorder = class {
43
51
  ...options
44
52
  };
45
53
  }
54
+ /** Cancels a pending idle commit so it cannot fire after editing or stopping. */
46
55
  #clearIdleTimer() {
47
56
  if (this.#idleTimer !== null) {
48
57
  clearTimeout(this.#idleTimer);
49
58
  this.#idleTimer = null;
50
59
  }
51
60
  }
61
+ /** Restarts optional idle commit after a step is recorded or edited. */
52
62
  #scheduleIdleTimer() {
53
63
  this.#clearIdleTimer();
54
64
  const ms = this.#options.idleTimeoutMs;
@@ -60,15 +70,19 @@ var HotkeySequenceRecorder = class {
60
70
  if (this.store.state.steps.length >= 1) this.commit();
61
71
  }, ms);
62
72
  }
73
+ /** Publishes edited steps while preserving the remaining recorder state. */
63
74
  #patchSteps(updater) {
64
75
  this.store.setState((s) => ({
65
76
  ...s,
66
77
  steps: updater(s.steps)
67
78
  }));
68
79
  }
80
+ /** Starts a fresh recording; repeated starts during an active session are ignored. */
69
81
  start() {
70
82
  if (this.#keydownHandler) return;
71
83
  this.#clearIdleTimer();
84
+ this.#events = [];
85
+ beginRecording(this);
72
86
  this.store.setState(() => ({
73
87
  isRecording: true,
74
88
  steps: [],
@@ -76,11 +90,13 @@ var HotkeySequenceRecorder = class {
76
90
  }));
77
91
  const handler = (event) => {
78
92
  if (!this.#keydownHandler) return;
79
- if (this.#options.ignoreInputs !== false) {
80
- if (isInputElement(typeof document !== "undefined" ? document.activeElement : null) && event.key !== "Escape") return;
81
- }
93
+ if (this.#options.ignoreInputs !== false && event.key !== "Escape" && shouldIgnoreInputEvent(event, document, document)) return;
94
+ const platform = this.#options.platform ?? this.#platform;
95
+ if (normalizeKeyboardEvent(event, platform).isComposing) return;
96
+ captureRecordingEvent(event);
82
97
  event.preventDefault();
83
98
  event.stopPropagation();
99
+ if (event.repeat) return;
84
100
  if (event.key === "Escape") {
85
101
  this.cancel();
86
102
  return;
@@ -88,11 +104,11 @@ var HotkeySequenceRecorder = class {
88
104
  if (event.key === "Backspace" || event.key === "Delete") {
89
105
  if (!event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
90
106
  if (this.store.state.steps.length === 0) {
107
+ this.stop();
91
108
  this.#options.onClear?.();
92
- this.#options.onRecord([]);
93
- this.#finishWithoutCommitCallback();
94
109
  return;
95
110
  }
111
+ this.#events.pop();
96
112
  this.#patchSteps((prev) => prev.slice(0, -1));
97
113
  if (this.store.state.steps.length === 0) this.#clearIdleTimer();
98
114
  else this.#scheduleIdleTimer();
@@ -105,8 +121,27 @@ var HotkeySequenceRecorder = class {
105
121
  return;
106
122
  }
107
123
  if (enterCommits && this.store.state.steps.length === 0) return;
108
- const finalHotkey = hotkeyChordFromKeydown(event, this.#platform);
124
+ if (isModifierKey(event.key) || event.key === "AltGraph") return;
125
+ const rejection = chordRejection(event, {
126
+ ...this.#options,
127
+ platform
128
+ });
129
+ if (rejection) {
130
+ this.#options.onReject?.(rejection);
131
+ return;
132
+ }
133
+ const finalHotkey = hotkeyChordFromKeydown(event, platform, this.#options.recordBy);
109
134
  if (finalHotkey === null) return;
135
+ const validation = validateHotkey(finalHotkey);
136
+ if (!validation.valid) {
137
+ this.#options.onReject?.({
138
+ reason: "invalid",
139
+ message: validation.errors.join("; "),
140
+ hotkey: finalHotkey
141
+ });
142
+ return;
143
+ }
144
+ this.#events.push(event);
110
145
  this.#patchSteps((prev) => [...prev, finalHotkey]);
111
146
  this.#scheduleIdleTimer();
112
147
  };
@@ -120,6 +155,38 @@ var HotkeySequenceRecorder = class {
120
155
  const steps = this.store.state.steps;
121
156
  if (steps.length < 1) return;
122
157
  const sequence = [...steps];
158
+ const platform = this.#options.platform ?? this.#platform;
159
+ this.#clearIdleTimer();
160
+ const accepted = this.#options.validate?.(sequence, {
161
+ events: [...this.#events],
162
+ parsedSequence: sequence.map((step) => parseHotkey(step, platform))
163
+ });
164
+ if (accepted !== void 0 && accepted !== true) {
165
+ this.#options.onReject?.({
166
+ reason: "validation",
167
+ message: typeof accepted === "string" ? accepted : "This sequence is not allowed.",
168
+ sequence
169
+ });
170
+ return;
171
+ }
172
+ if (this.#options.detectConflicts) {
173
+ const conflicts = findHotkeyConflicts(sequence, {
174
+ ...typeof this.#options.detectConflicts === "object" ? this.#options.detectConflicts : {},
175
+ platform,
176
+ events: this.#events
177
+ });
178
+ if (conflicts.length) {
179
+ this.#options.onReject?.({
180
+ reason: "conflict",
181
+ message: "This sequence conflicts with a registered binding.",
182
+ sequence,
183
+ conflicts
184
+ });
185
+ return;
186
+ }
187
+ }
188
+ endRecording(this);
189
+ this.#events = [];
123
190
  if (this.#keydownHandler) {
124
191
  this.#removeListener(this.#keydownHandler);
125
192
  this.#keydownHandler = null;
@@ -132,19 +199,10 @@ var HotkeySequenceRecorder = class {
132
199
  }));
133
200
  this.#options.onRecord(sequence);
134
201
  }
135
- #finishWithoutCommitCallback() {
136
- if (this.#keydownHandler) {
137
- this.#removeListener(this.#keydownHandler);
138
- this.#keydownHandler = null;
139
- }
140
- this.#clearIdleTimer();
141
- this.store.setState(() => ({
142
- isRecording: false,
143
- steps: [],
144
- recordedSequence: null
145
- }));
146
- }
202
+ /** Stops and discards in-progress steps without invoking onCancel. */
147
203
  stop() {
204
+ endRecording(this);
205
+ this.#events = [];
148
206
  if (this.#keydownHandler) {
149
207
  this.#removeListener(this.#keydownHandler);
150
208
  this.#keydownHandler = null;
@@ -156,7 +214,10 @@ var HotkeySequenceRecorder = class {
156
214
  recordedSequence: null
157
215
  }));
158
216
  }
217
+ /** Stops, discards in-progress steps, and notifies onCancel. */
159
218
  cancel() {
219
+ endRecording(this);
220
+ this.#events = [];
160
221
  if (this.#keydownHandler) {
161
222
  this.#removeListener(this.#keydownHandler);
162
223
  this.#keydownHandler = null;
@@ -169,10 +230,12 @@ var HotkeySequenceRecorder = class {
169
230
  }));
170
231
  this.#options.onCancel?.();
171
232
  }
233
+ /** Captures keydowns before application handlers, with an SSR-safe document check. */
172
234
  #addListener(handler) {
173
235
  if (typeof document === "undefined") return;
174
236
  document.addEventListener("keydown", handler, true);
175
237
  }
238
+ /** Detaches the capture listener when recording ends. */
176
239
  #removeListener(handler) {
177
240
  if (typeof document === "undefined") return;
178
241
  document.removeEventListener("keydown", handler, true);
@@ -188,5 +251,4 @@ var HotkeySequenceRecorder = class {
188
251
  };
189
252
 
190
253
  //#endregion
191
- export { HotkeySequenceRecorder };
192
- //# sourceMappingURL=hotkey-sequence-recorder.js.map
254
+ export { HotkeySequenceRecorder };