@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,272 +0,0 @@
1
- import { Store } from '@tanstack/store'
2
- import { detectPlatform } from './constants'
3
- import { isInputElement } from './manager.utils'
4
- import { hotkeyChordFromKeydown } from './recorder-chord'
5
- import type { Hotkey } from './hotkey'
6
-
7
- /**
8
- * State interface for the HotkeyRecorder.
9
- */
10
- export interface HotkeyRecorderState {
11
- /** Whether recording is currently active */
12
- isRecording: boolean
13
- /** The currently recorded hotkey (for live preview) */
14
- recordedHotkey: Hotkey | null
15
- }
16
-
17
- /**
18
- * Options for configuring a HotkeyRecorder instance.
19
- */
20
- export interface HotkeyRecorderOptions {
21
- /** Callback when a hotkey is successfully recorded */
22
- onRecord: (hotkey: Hotkey) => void
23
- /** Optional callback when recording is cancelled (Escape pressed) */
24
- onCancel?: () => void
25
- /** Optional callback when shortcut is cleared (Backspace/Delete pressed) */
26
- onClear?: () => void
27
- /**
28
- * Whether to ignore keyboard events from input-like elements (text inputs,
29
- * textarea, select, contenteditable). When true, typing in inputs passes
30
- * through normally instead of being captured as a hotkey recording.
31
- * Escape always works regardless of this setting.
32
- * @default true
33
- */
34
- ignoreInputs?: boolean
35
- }
36
-
37
- /**
38
- * Framework-agnostic class for recording keyboard shortcuts.
39
- *
40
- * This class handles all the complexity of capturing keyboard events,
41
- * converting them to hotkey strings, and handling edge cases like
42
- * Escape to cancel or Backspace/Delete to clear.
43
- *
44
- * State Management:
45
- * - Uses TanStack Store for reactive state management
46
- * - State can be accessed via `recorder.store.state` when using the class directly
47
- * - When using framework adapters (React), use `useSelector` hooks for reactive state
48
- *
49
- * @example
50
- * ```ts
51
- * const recorder = new HotkeyRecorder({
52
- * onRecord: (hotkey) => {
53
- * console.log('Recorded:', hotkey)
54
- * },
55
- * onCancel: () => {
56
- * console.log('Recording cancelled')
57
- * },
58
- * })
59
- *
60
- * // Start recording
61
- * recorder.start()
62
- *
63
- * // Access state directly
64
- * console.log(recorder.store.state.isRecording) // true
65
- *
66
- * // Subscribe to changes with TanStack Store
67
- * const unsubscribe = recorder.store.subscribe(() => {
68
- * console.log('Recording:', recorder.store.state.isRecording)
69
- * })
70
- *
71
- * // Cleanup
72
- * recorder.destroy()
73
- * unsubscribe()
74
- * ```
75
- */
76
- export class HotkeyRecorder {
77
- /**
78
- * The TanStack Store instance containing the recorder state.
79
- * Use this to subscribe to state changes or access current state.
80
- */
81
- readonly store: Store<HotkeyRecorderState> = new Store<HotkeyRecorderState>({
82
- isRecording: false,
83
- recordedHotkey: null,
84
- })
85
-
86
- #keydownHandler: ((event: KeyboardEvent) => void) | null = null
87
- #options: HotkeyRecorderOptions
88
- #platform: 'mac' | 'windows' | 'linux'
89
-
90
- constructor(options: HotkeyRecorderOptions) {
91
- this.#options = options
92
- this.#platform = detectPlatform()
93
- }
94
-
95
- /**
96
- * Updates the recorder options, including callbacks.
97
- * This allows framework adapters to sync callback changes without recreating the recorder.
98
- */
99
- setOptions(options: Partial<HotkeyRecorderOptions>): void {
100
- this.#options = {
101
- ...this.#options,
102
- ...options,
103
- }
104
- }
105
-
106
- /**
107
- * Start recording a new hotkey.
108
- *
109
- * Sets up a keydown event listener that captures keyboard events
110
- * and converts them to hotkey strings. Recording continues until
111
- * a valid hotkey is recorded, Escape is pressed, or stop/cancel is called.
112
- */
113
- start(): void {
114
- // Prevent starting recording if already recording
115
- if (this.#keydownHandler) {
116
- return
117
- }
118
-
119
- // Update store state
120
- this.store.setState(() => ({
121
- isRecording: true,
122
- recordedHotkey: null,
123
- }))
124
-
125
- // Create keydown handler
126
- const handler = (event: KeyboardEvent) => {
127
- // Check if we're still recording (handler might be called after stop/cancel)
128
- if (!this.#keydownHandler) {
129
- return
130
- }
131
-
132
- // If ignoreInputs is enabled (default) and focus is in an input element,
133
- // let the event pass through so the user can type normally.
134
- // Escape is the exception — it should always cancel recording.
135
- if (this.#options.ignoreInputs !== false) {
136
- const activeEl =
137
- typeof document !== 'undefined' ? document.activeElement : null
138
- if (isInputElement(activeEl) && event.key !== 'Escape') {
139
- return
140
- }
141
- }
142
-
143
- event.preventDefault()
144
- event.stopPropagation()
145
-
146
- // Handle Escape to cancel
147
- if (event.key === 'Escape') {
148
- this.cancel()
149
- return
150
- }
151
-
152
- // Handle Backspace/Delete to clear shortcut
153
- if (event.key === 'Backspace' || event.key === 'Delete') {
154
- if (
155
- !event.ctrlKey &&
156
- !event.shiftKey &&
157
- !event.altKey &&
158
- !event.metaKey
159
- ) {
160
- this.#options.onClear?.()
161
- this.#options.onRecord('' as Hotkey)
162
- this.stop()
163
- return
164
- }
165
- }
166
-
167
- const finalHotkey = hotkeyChordFromKeydown(event, this.#platform)
168
- if (finalHotkey === null) {
169
- return
170
- }
171
-
172
- // Remove listener FIRST to prevent any additional events
173
- const handlerToRemove = this.#keydownHandler as
174
- | ((event: KeyboardEvent) => void)
175
- | null
176
- if (handlerToRemove) {
177
- this.#removeListener(handlerToRemove)
178
- this.#keydownHandler = null
179
- }
180
-
181
- // Update store state immediately
182
- this.store.setState(() => ({
183
- isRecording: false,
184
- recordedHotkey: finalHotkey,
185
- }))
186
-
187
- // Call callback AFTER listener is removed and state is set
188
- this.#options.onRecord(finalHotkey)
189
- }
190
-
191
- this.#keydownHandler = handler
192
- this.#addListener(handler)
193
- }
194
-
195
- /**
196
- * Stop recording (same as cancel, but doesn't call onCancel).
197
- *
198
- * Removes the event listener and resets the recording state.
199
- */
200
- stop(): void {
201
- // Remove event listener immediately
202
- if (this.#keydownHandler) {
203
- this.#removeListener(this.#keydownHandler)
204
- this.#keydownHandler = null
205
- }
206
-
207
- // Update store state
208
- this.store.setState(() => ({
209
- isRecording: false,
210
- recordedHotkey: null,
211
- }))
212
- }
213
-
214
- /**
215
- * Cancel recording without saving.
216
- *
217
- * Removes the event listener, resets the recording state, and calls
218
- * the onCancel callback if provided.
219
- */
220
- cancel(): void {
221
- // Remove event listener immediately
222
- if (this.#keydownHandler) {
223
- this.#removeListener(this.#keydownHandler)
224
- this.#keydownHandler = null
225
- }
226
-
227
- // Update store state
228
- this.store.setState(() => ({
229
- isRecording: false,
230
- recordedHotkey: null,
231
- }))
232
-
233
- // Call cancel callback
234
- this.#options.onCancel?.()
235
- }
236
-
237
- /**
238
- * Adds the keydown event listener to the document.
239
- */
240
- #addListener(handler: (event: KeyboardEvent) => void): void {
241
- if (typeof document === 'undefined') {
242
- return // SSR safety
243
- }
244
-
245
- document.addEventListener('keydown', handler, true)
246
- }
247
-
248
- /**
249
- * Removes the keydown event listener from the document.
250
- */
251
- #removeListener(handler: (event: KeyboardEvent) => void): void {
252
- if (typeof document === 'undefined') {
253
- return
254
- }
255
-
256
- document.removeEventListener('keydown', handler, true)
257
- }
258
-
259
- /**
260
- * Clean up event listeners and reset state.
261
- *
262
- * Call this when you're done with the recorder to ensure
263
- * all event listeners are properly removed.
264
- */
265
- destroy(): void {
266
- this.stop()
267
- this.store.setState(() => ({
268
- isRecording: false,
269
- recordedHotkey: null,
270
- }))
271
- }
272
- }
@@ -1,328 +0,0 @@
1
- import { Store } from '@tanstack/store'
2
- import { detectPlatform } from './constants'
3
- import { isInputElement } from './manager.utils'
4
- import { hotkeyChordFromKeydown } from './recorder-chord'
5
- import type { HotkeySequence } from './sequence-manager'
6
-
7
- /**
8
- * How the user can commit a recorded sequence from the keyboard.
9
- * - `'enter'`: plain Enter (no modifiers) commits when at least one step exists.
10
- * - `'none'`: only {@link HotkeySequenceRecorder.commit} finishes recording (or idle timeout if set).
11
- */
12
- export type HotkeySequenceRecorderCommitKeys = 'enter' | 'none'
13
-
14
- /**
15
- * State interface for the HotkeySequenceRecorder.
16
- */
17
- export interface HotkeySequenceRecorderState {
18
- /** Whether recording is currently active */
19
- isRecording: boolean
20
- /** Chords captured so far in the current recording session */
21
- steps: HotkeySequence
22
- /** The last successfully committed sequence, or null if none / after starting a new session */
23
- recordedSequence: HotkeySequence | null
24
- }
25
-
26
- /**
27
- * Options for configuring a HotkeySequenceRecorder instance.
28
- */
29
- export interface HotkeySequenceRecorderOptions {
30
- /** Callback when a sequence is successfully recorded (including empty array when cleared) */
31
- onRecord: (sequence: HotkeySequence) => void
32
- /** Optional callback when recording is cancelled (Escape pressed) */
33
- onCancel?: () => void
34
- /** Optional callback when the sequence is cleared (Backspace/Delete with no steps) */
35
- onClear?: () => void
36
- /**
37
- * Whether plain Enter commits the current steps. Ignored when {@link commitKeys} is `'none'`.
38
- * @default true
39
- */
40
- commitOnEnter?: boolean
41
- /**
42
- * Keyboard commit mode. When `'none'`, use {@link HotkeySequenceRecorder.commit} (and optional idle timeout).
43
- * @default 'enter'
44
- */
45
- commitKeys?: HotkeySequenceRecorderCommitKeys
46
- /**
47
- * Milliseconds of inactivity after the **last completed chord** before auto-committing.
48
- * The timer does not run while waiting for the first chord (`steps.length === 0`).
49
- */
50
- idleTimeoutMs?: number
51
- /**
52
- * Whether to ignore keyboard events from input-like elements (text inputs,
53
- * textarea, select, contenteditable). When true, typing in inputs passes
54
- * through normally instead of being captured as a sequence recording.
55
- * Escape always works regardless of this setting.
56
- * @default true
57
- */
58
- ignoreInputs?: boolean
59
- }
60
-
61
- const defaultHotkeySequenceRecorderOptions: Pick<
62
- HotkeySequenceRecorderOptions,
63
- 'commitOnEnter' | 'commitKeys'
64
- > = {
65
- commitOnEnter: true,
66
- commitKeys: 'enter',
67
- }
68
-
69
- function resolvedCommitOnEnter(
70
- options: HotkeySequenceRecorderOptions,
71
- ): boolean {
72
- if (options.commitKeys === 'none') {
73
- return false
74
- }
75
- return options.commitOnEnter !== false
76
- }
77
-
78
- /**
79
- * Framework-agnostic class for recording multi-chord sequences (Vim-style shortcuts).
80
- *
81
- * Each step is captured like a single hotkey chord. Press **Enter** (no modifiers) to commit
82
- * when {@link HotkeySequenceRecorderOptions.commitKeys} is `'enter'` (default), **Escape** to cancel,
83
- * **Backspace/Delete** to remove the last step or clear when empty.
84
- */
85
- export class HotkeySequenceRecorder {
86
- readonly store: Store<HotkeySequenceRecorderState> =
87
- new Store<HotkeySequenceRecorderState>({
88
- isRecording: false,
89
- steps: [],
90
- recordedSequence: null,
91
- })
92
-
93
- #keydownHandler: ((event: KeyboardEvent) => void) | null = null
94
- #options: HotkeySequenceRecorderOptions
95
- #platform: 'mac' | 'windows' | 'linux'
96
- #idleTimer: ReturnType<typeof setTimeout> | null = null
97
-
98
- constructor(options: HotkeySequenceRecorderOptions) {
99
- this.#options = {
100
- ...defaultHotkeySequenceRecorderOptions,
101
- ...options,
102
- }
103
- this.#platform = detectPlatform()
104
- }
105
-
106
- setOptions(options: Partial<HotkeySequenceRecorderOptions>): void {
107
- this.#options = {
108
- ...defaultHotkeySequenceRecorderOptions,
109
- ...this.#options,
110
- ...options,
111
- }
112
- }
113
-
114
- #clearIdleTimer(): void {
115
- if (this.#idleTimer !== null) {
116
- clearTimeout(this.#idleTimer)
117
- this.#idleTimer = null
118
- }
119
- }
120
-
121
- #scheduleIdleTimer(): void {
122
- this.#clearIdleTimer()
123
- const ms = this.#options.idleTimeoutMs
124
- if (ms === undefined || ms <= 0) {
125
- return
126
- }
127
- const steps = this.store.state.steps
128
- if (steps.length === 0) {
129
- return
130
- }
131
- this.#idleTimer = setTimeout(() => {
132
- this.#idleTimer = null
133
- if (!this.#keydownHandler) {
134
- return
135
- }
136
- if (this.store.state.steps.length >= 1) {
137
- this.commit()
138
- }
139
- }, ms)
140
- }
141
-
142
- #patchSteps(updater: (prev: HotkeySequence) => HotkeySequence): void {
143
- this.store.setState((s) => ({
144
- ...s,
145
- steps: updater(s.steps),
146
- }))
147
- }
148
-
149
- start(): void {
150
- if (this.#keydownHandler) {
151
- return
152
- }
153
-
154
- this.#clearIdleTimer()
155
- this.store.setState(() => ({
156
- isRecording: true,
157
- steps: [],
158
- recordedSequence: null,
159
- }))
160
-
161
- const handler = (event: KeyboardEvent) => {
162
- if (!this.#keydownHandler) {
163
- return
164
- }
165
-
166
- // If ignoreInputs is enabled (default) and focus is in an input element,
167
- // let the event pass through so the user can type normally.
168
- // Escape is the exception — it should always cancel recording.
169
- if (this.#options.ignoreInputs !== false) {
170
- const activeEl =
171
- typeof document !== 'undefined' ? document.activeElement : null
172
- if (isInputElement(activeEl) && event.key !== 'Escape') {
173
- return
174
- }
175
- }
176
-
177
- event.preventDefault()
178
- event.stopPropagation()
179
-
180
- if (event.key === 'Escape') {
181
- this.cancel()
182
- return
183
- }
184
-
185
- if (event.key === 'Backspace' || event.key === 'Delete') {
186
- if (
187
- !event.ctrlKey &&
188
- !event.shiftKey &&
189
- !event.altKey &&
190
- !event.metaKey
191
- ) {
192
- const steps = this.store.state.steps
193
- if (steps.length === 0) {
194
- this.#options.onClear?.()
195
- this.#options.onRecord([])
196
- this.#finishWithoutCommitCallback()
197
- return
198
- }
199
- this.#patchSteps((prev) => prev.slice(0, -1))
200
- const next = this.store.state.steps
201
- if (next.length === 0) {
202
- this.#clearIdleTimer()
203
- } else {
204
- this.#scheduleIdleTimer()
205
- }
206
- return
207
- }
208
- }
209
-
210
- const enterCommits =
211
- resolvedCommitOnEnter(this.#options) &&
212
- event.key === 'Enter' &&
213
- !event.ctrlKey &&
214
- !event.shiftKey &&
215
- !event.altKey &&
216
- !event.metaKey
217
-
218
- if (enterCommits && this.store.state.steps.length >= 1) {
219
- this.commit()
220
- return
221
- }
222
-
223
- if (enterCommits && this.store.state.steps.length === 0) {
224
- return
225
- }
226
-
227
- const finalHotkey = hotkeyChordFromKeydown(event, this.#platform)
228
- if (finalHotkey === null) {
229
- return
230
- }
231
-
232
- this.#patchSteps((prev) => [...prev, finalHotkey])
233
- this.#scheduleIdleTimer()
234
- }
235
-
236
- this.#keydownHandler = handler
237
- this.#addListener(handler)
238
- }
239
-
240
- /**
241
- * Commit the current steps as a sequence. No-op if fewer than one step.
242
- */
243
- commit(): void {
244
- const steps = this.store.state.steps
245
- if (steps.length < 1) {
246
- return
247
- }
248
-
249
- const sequence = [...steps]
250
-
251
- if (this.#keydownHandler) {
252
- this.#removeListener(this.#keydownHandler)
253
- this.#keydownHandler = null
254
- }
255
- this.#clearIdleTimer()
256
-
257
- this.store.setState(() => ({
258
- isRecording: false,
259
- steps: [],
260
- recordedSequence: sequence,
261
- }))
262
-
263
- this.#options.onRecord(sequence)
264
- }
265
-
266
- #finishWithoutCommitCallback(): void {
267
- if (this.#keydownHandler) {
268
- this.#removeListener(this.#keydownHandler)
269
- this.#keydownHandler = null
270
- }
271
- this.#clearIdleTimer()
272
- this.store.setState(() => ({
273
- isRecording: false,
274
- steps: [],
275
- recordedSequence: null,
276
- }))
277
- }
278
-
279
- stop(): void {
280
- if (this.#keydownHandler) {
281
- this.#removeListener(this.#keydownHandler)
282
- this.#keydownHandler = null
283
- }
284
- this.#clearIdleTimer()
285
- this.store.setState(() => ({
286
- isRecording: false,
287
- steps: [],
288
- recordedSequence: null,
289
- }))
290
- }
291
-
292
- cancel(): void {
293
- if (this.#keydownHandler) {
294
- this.#removeListener(this.#keydownHandler)
295
- this.#keydownHandler = null
296
- }
297
- this.#clearIdleTimer()
298
- this.store.setState(() => ({
299
- isRecording: false,
300
- steps: [],
301
- recordedSequence: null,
302
- }))
303
- this.#options.onCancel?.()
304
- }
305
-
306
- #addListener(handler: (event: KeyboardEvent) => void): void {
307
- if (typeof document === 'undefined') {
308
- return
309
- }
310
- document.addEventListener('keydown', handler, true)
311
- }
312
-
313
- #removeListener(handler: (event: KeyboardEvent) => void): void {
314
- if (typeof document === 'undefined') {
315
- return
316
- }
317
- document.removeEventListener('keydown', handler, true)
318
- }
319
-
320
- destroy(): void {
321
- this.stop()
322
- this.store.setState(() => ({
323
- isRecording: false,
324
- steps: [],
325
- recordedSequence: null,
326
- }))
327
- }
328
- }