@tanstack/hotkeys 0.11.0 → 0.12.1
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.
- package/dist/_match.js +2 -0
- package/dist/adapter.d.ts +108 -19
- package/dist/adapter.js +90 -65
- package/dist/app-hotkeys.d.ts +239 -0
- package/dist/app-hotkeys.js +295 -0
- package/dist/conflicts.d.ts +5 -9
- package/dist/conflicts.js +6 -11
- package/dist/format.d.ts +19 -24
- package/dist/format.js +19 -30
- package/dist/hint.d.ts +1 -0
- package/dist/hint.js +5 -2
- package/dist/hotkey-manager.d.ts +110 -59
- package/dist/hotkey-manager.js +254 -151
- package/dist/hotkey-recorder.d.ts +66 -42
- package/dist/hotkey-recorder.js +148 -107
- package/dist/hotkey.types.d.ts +26 -12
- package/dist/index.d.ts +10 -11
- package/dist/index.js +5 -6
- package/dist/match.d.ts +38 -5
- package/dist/match.js +72 -4
- package/dist/parse.d.ts +20 -8
- package/dist/parse.js +15 -5
- package/dist/recorder-options.d.ts +6 -3
- package/dist/validate.d.ts +10 -4
- package/dist/validate.js +26 -4
- package/package.json +1 -1
- package/dist/hotkey-sequence-recorder.d.ts +0 -84
- package/dist/hotkey-sequence-recorder.js +0 -259
- package/dist/sequence-manager.d.ts +0 -169
- package/dist/sequence-manager.js +0 -443
package/dist/sequence-manager.js
DELETED
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
import { normalizeKeyName } from "./constants.js";
|
|
2
|
-
import { detectPlatform } from "./platform.js";
|
|
3
|
-
import { normalizeKeyboardEvent } from "./_keyboard-event.js";
|
|
4
|
-
import { isModifierKey, parseHotkey } from "./parse.js";
|
|
5
|
-
import { formatHotkeySequence } from "./format.js";
|
|
6
|
-
import { matchKeyboardEvent } from "./_match.js";
|
|
7
|
-
import { areHotkeysEqual, matchesKeyboardEvent } from "./match.js";
|
|
8
|
-
import { isRecordingEvent } from "./_recording-guard.js";
|
|
9
|
-
import { defaultHotkeyOptions, getDefaultIgnoreInputs, handleConflict, optionsEqual } from "./_registration.js";
|
|
10
|
-
import { isEventForTarget, shouldIgnoreInputEvent } from "./_event-target.js";
|
|
11
|
-
import { Store } from "@tanstack/store";
|
|
12
|
-
|
|
13
|
-
//#region src/sequence-manager.ts
|
|
14
|
-
/**
|
|
15
|
-
* Default timeout between keys in a sequence (in milliseconds).
|
|
16
|
-
* Exported for consumers that display sequence state (e.g. devtools).
|
|
17
|
-
*/
|
|
18
|
-
const DEFAULT_SEQUENCE_TIMEOUT = 1e3;
|
|
19
|
-
let sequenceIdCounter = 0;
|
|
20
|
-
/**
|
|
21
|
-
* Generates a unique ID for sequence registrations.
|
|
22
|
-
*/
|
|
23
|
-
function generateSequenceId() {
|
|
24
|
-
return `sequence_${++sequenceIdCounter}`;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Manages keyboard sequence matching for Vim-style shortcuts.
|
|
28
|
-
*
|
|
29
|
-
* This class allows registering multi-key sequences like 'g g' or 'd d'
|
|
30
|
-
* that trigger callbacks when the full sequence is pressed within
|
|
31
|
-
* a configurable timeout.
|
|
32
|
-
*
|
|
33
|
-
* Modifier-only `keydown` / `keyup` events (Shift, Control, Alt, or Meta
|
|
34
|
-
* alone) are not processed: they neither match a step nor reset progress.
|
|
35
|
-
* That supports chained modifier chords (e.g. `Shift+R` then `Shift+T`) when
|
|
36
|
-
* the user presses a modifier alone between steps.
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```ts
|
|
40
|
-
* const matcher = SequenceManager.getInstance()
|
|
41
|
-
*
|
|
42
|
-
* // Register 'g g' to go to top
|
|
43
|
-
* const unregister = matcher.register(['G', 'G'], (event, context) => {
|
|
44
|
-
* scrollToTop()
|
|
45
|
-
* }, { timeout: 500 })
|
|
46
|
-
*
|
|
47
|
-
* // Later, to unregister:
|
|
48
|
-
* unregister()
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
/**
|
|
52
|
-
* Builds a devtools view from an internal registration.
|
|
53
|
-
*/
|
|
54
|
-
function toRegistrationView(reg) {
|
|
55
|
-
const len = reg.parsedSequence.length;
|
|
56
|
-
const inProgress = reg.currentIndex > 0 && reg.currentIndex < len;
|
|
57
|
-
return {
|
|
58
|
-
id: reg.id,
|
|
59
|
-
sequence: reg.sequence,
|
|
60
|
-
options: reg.options,
|
|
61
|
-
target: reg.target,
|
|
62
|
-
triggerCount: reg.triggerCount,
|
|
63
|
-
hasFired: reg.hasFired,
|
|
64
|
-
matchedStepCount: inProgress ? reg.currentIndex : 0,
|
|
65
|
-
partialMatchLastKeyTime: inProgress ? reg.lastKeyTime : 0
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
var SequenceManager = class SequenceManager {
|
|
69
|
-
static #instance = null;
|
|
70
|
-
/**
|
|
71
|
-
* The TanStack Store containing sequence registration views for devtools.
|
|
72
|
-
* Subscribe to this to observe registration changes.
|
|
73
|
-
*/
|
|
74
|
-
registrations = new Store(/* @__PURE__ */ new Map());
|
|
75
|
-
#registrations = /* @__PURE__ */ new Map();
|
|
76
|
-
#targetListeners = /* @__PURE__ */ new Map();
|
|
77
|
-
#targetRegistrations = /* @__PURE__ */ new Map();
|
|
78
|
-
#platform;
|
|
79
|
-
constructor() {
|
|
80
|
-
this.#platform = detectPlatform();
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Gets the singleton instance of SequenceManager.
|
|
84
|
-
*/
|
|
85
|
-
static getInstance() {
|
|
86
|
-
if (!SequenceManager.#instance) SequenceManager.#instance = new SequenceManager();
|
|
87
|
-
return SequenceManager.#instance;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Resets the singleton instance. Useful for testing.
|
|
91
|
-
*/
|
|
92
|
-
static resetInstance() {
|
|
93
|
-
if (SequenceManager.#instance) {
|
|
94
|
-
SequenceManager.#instance.destroy();
|
|
95
|
-
SequenceManager.#instance = null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
#syncRegistrationView(registration) {
|
|
99
|
-
this.registrations.setState((prev) => new Map(prev).set(registration.id, toRegistrationView(registration)));
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Registers a hotkey sequence handler.
|
|
103
|
-
*
|
|
104
|
-
* @param sequence - Array of hotkey strings that form the sequence
|
|
105
|
-
* @param callback - Function to call when the sequence is completed
|
|
106
|
-
* @param options - Options for the sequence behavior
|
|
107
|
-
* @returns A handle to update or unregister the sequence
|
|
108
|
-
*/
|
|
109
|
-
register(sequence, callback, options = {}) {
|
|
110
|
-
if (sequence.length === 0) throw new Error("Sequence must contain at least one hotkey");
|
|
111
|
-
const id = generateSequenceId();
|
|
112
|
-
const platform = options.platform ?? this.#platform;
|
|
113
|
-
const parsedSequence = sequence.map((hotkey) => parseHotkey(hotkey, platform));
|
|
114
|
-
const target = options.target ?? (typeof document !== "undefined" ? document : {});
|
|
115
|
-
const conflictBehavior = options.conflictBehavior ?? "warn";
|
|
116
|
-
const conflictingRegistration = this.#findConflictingSequence(parsedSequence, target);
|
|
117
|
-
if (conflictingRegistration) handleConflict(conflictingRegistration.id, formatHotkeySequence(sequence), conflictBehavior, (regId) => this.#unregister(regId));
|
|
118
|
-
const firstStep = parsedSequence[0];
|
|
119
|
-
const resolvedIgnoreInputs = options.ignoreInputs ?? getDefaultIgnoreInputs(firstStep);
|
|
120
|
-
const registration = {
|
|
121
|
-
id,
|
|
122
|
-
sequence,
|
|
123
|
-
parsedSequence,
|
|
124
|
-
callback,
|
|
125
|
-
options: {
|
|
126
|
-
...defaultHotkeyOptions,
|
|
127
|
-
timeout: DEFAULT_SEQUENCE_TIMEOUT,
|
|
128
|
-
...options,
|
|
129
|
-
platform,
|
|
130
|
-
ignoreInputs: resolvedIgnoreInputs
|
|
131
|
-
},
|
|
132
|
-
target,
|
|
133
|
-
currentIndex: 0,
|
|
134
|
-
lastKeyTime: 0,
|
|
135
|
-
triggerCount: 0,
|
|
136
|
-
hasFired: false
|
|
137
|
-
};
|
|
138
|
-
this.#registrations.set(id, registration);
|
|
139
|
-
this.registrations.setState((prev) => new Map(prev).set(id, toRegistrationView(registration)));
|
|
140
|
-
if (!this.#targetRegistrations.has(target)) this.#targetRegistrations.set(target, /* @__PURE__ */ new Set());
|
|
141
|
-
this.#targetRegistrations.get(target).add(id);
|
|
142
|
-
this.#ensureListenersForTarget(target);
|
|
143
|
-
const manager = this;
|
|
144
|
-
return {
|
|
145
|
-
get id() {
|
|
146
|
-
return id;
|
|
147
|
-
},
|
|
148
|
-
get isActive() {
|
|
149
|
-
return manager.#registrations.has(id);
|
|
150
|
-
},
|
|
151
|
-
get callback() {
|
|
152
|
-
return manager.#registrations.get(id)?.callback ?? callback;
|
|
153
|
-
},
|
|
154
|
-
set callback(newCallback) {
|
|
155
|
-
const reg = manager.#registrations.get(id);
|
|
156
|
-
if (reg) reg.callback = newCallback;
|
|
157
|
-
},
|
|
158
|
-
setOptions: (newOptions) => {
|
|
159
|
-
const reg = manager.#registrations.get(id);
|
|
160
|
-
if (reg && !optionsEqual(reg.options, {
|
|
161
|
-
...reg.options,
|
|
162
|
-
...newOptions
|
|
163
|
-
})) {
|
|
164
|
-
reg.options = {
|
|
165
|
-
...reg.options,
|
|
166
|
-
...newOptions
|
|
167
|
-
};
|
|
168
|
-
manager.registrations.setState((prev) => new Map(prev).set(id, toRegistrationView(reg)));
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
unregister: () => {
|
|
172
|
-
manager.#unregister(id);
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Unregisters a sequence by its registration ID.
|
|
178
|
-
*/
|
|
179
|
-
#unregister(id) {
|
|
180
|
-
const registration = this.#registrations.get(id);
|
|
181
|
-
if (!registration) return;
|
|
182
|
-
const target = registration.target;
|
|
183
|
-
this.#registrations.delete(id);
|
|
184
|
-
this.registrations.setState((prev) => {
|
|
185
|
-
const next = new Map(prev);
|
|
186
|
-
next.delete(id);
|
|
187
|
-
return next;
|
|
188
|
-
});
|
|
189
|
-
const targetRegs = this.#targetRegistrations.get(target);
|
|
190
|
-
if (targetRegs) {
|
|
191
|
-
targetRegs.delete(id);
|
|
192
|
-
if (targetRegs.size === 0) this.#removeListenersForTarget(target);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Ensures event listeners are attached for a specific target.
|
|
197
|
-
*/
|
|
198
|
-
#ensureListenersForTarget(target) {
|
|
199
|
-
if (typeof document === "undefined") return;
|
|
200
|
-
if (this.#targetListeners.has(target)) return;
|
|
201
|
-
const keydownHandler = this.#createTargetKeyDownHandler(target);
|
|
202
|
-
const keyupHandler = this.#createTargetKeyUpHandler(target);
|
|
203
|
-
target.addEventListener("keydown", keydownHandler);
|
|
204
|
-
target.addEventListener("keyup", keyupHandler);
|
|
205
|
-
this.#targetListeners.set(target, {
|
|
206
|
-
keydown: keydownHandler,
|
|
207
|
-
keyup: keyupHandler
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Removes event listeners for a specific target.
|
|
212
|
-
*/
|
|
213
|
-
#removeListenersForTarget(target) {
|
|
214
|
-
if (typeof document === "undefined") return;
|
|
215
|
-
const listeners = this.#targetListeners.get(target);
|
|
216
|
-
if (!listeners) return;
|
|
217
|
-
target.removeEventListener("keydown", listeners.keydown);
|
|
218
|
-
target.removeEventListener("keyup", listeners.keyup);
|
|
219
|
-
this.#targetListeners.delete(target);
|
|
220
|
-
this.#targetRegistrations.delete(target);
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Creates a keydown handler for a specific target.
|
|
224
|
-
*/
|
|
225
|
-
#createTargetKeyDownHandler(target) {
|
|
226
|
-
return (event) => {
|
|
227
|
-
this.#processTargetEvent(event, target, "keydown");
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Creates a keyup handler for a specific target.
|
|
232
|
-
*/
|
|
233
|
-
#createTargetKeyUpHandler(target) {
|
|
234
|
-
return (event) => {
|
|
235
|
-
this.#processTargetEvent(event, target, "keyup");
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Processes keyboard events for a specific target and event type.
|
|
240
|
-
*/
|
|
241
|
-
#processTargetEvent(event, target, eventType) {
|
|
242
|
-
if (isRecordingEvent(event)) {
|
|
243
|
-
for (const id of this.#targetRegistrations.get(target) ?? []) {
|
|
244
|
-
const registration = this.#registrations.get(id);
|
|
245
|
-
if (registration && registration.currentIndex > 0) {
|
|
246
|
-
registration.currentIndex = 0;
|
|
247
|
-
this.#syncRegistrationView(registration);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (event.type === "keydown" && event.repeat || normalizeKeyboardEvent(event).isComposing) return;
|
|
253
|
-
if (isModifierKey(normalizeKeyName(event.key))) return;
|
|
254
|
-
const targetRegs = this.#targetRegistrations.get(target);
|
|
255
|
-
if (!targetRegs) return;
|
|
256
|
-
const now = Date.now();
|
|
257
|
-
const candidates = /* @__PURE__ */ new Map();
|
|
258
|
-
let bestScore = 0;
|
|
259
|
-
registrationIds: for (const id of targetRegs) {
|
|
260
|
-
const registration = this.#registrations.get(id);
|
|
261
|
-
if (!registration) continue;
|
|
262
|
-
if (!isEventForTarget(event, target)) continue;
|
|
263
|
-
if (!registration.options.enabled) continue;
|
|
264
|
-
if (registration.options.ignoreInputs !== false) {
|
|
265
|
-
if (shouldIgnoreInputEvent(event, target, registration.target)) continue registrationIds;
|
|
266
|
-
}
|
|
267
|
-
if (registration.options.eventType !== eventType) continue;
|
|
268
|
-
const timeout = registration.options.timeout ?? 1e3;
|
|
269
|
-
if (registration.currentIndex > 0 && now - registration.lastKeyTime > timeout) {
|
|
270
|
-
registration.currentIndex = 0;
|
|
271
|
-
this.#syncRegistrationView(registration);
|
|
272
|
-
}
|
|
273
|
-
const expectedHotkey = registration.parsedSequence[registration.currentIndex];
|
|
274
|
-
if (!expectedHotkey) continue;
|
|
275
|
-
let match = matchKeyboardEvent(event, expectedHotkey, registration.options.platform);
|
|
276
|
-
let nextIndex = registration.currentIndex + 1;
|
|
277
|
-
if (!match.matched && registration.currentIndex > 0) {
|
|
278
|
-
match = matchKeyboardEvent(event, registration.parsedSequence[0], registration.options.platform);
|
|
279
|
-
nextIndex = 1;
|
|
280
|
-
}
|
|
281
|
-
candidates.set(id, {
|
|
282
|
-
score: match.score,
|
|
283
|
-
nextIndex
|
|
284
|
-
});
|
|
285
|
-
bestScore = Math.max(bestScore, match.score);
|
|
286
|
-
}
|
|
287
|
-
for (const [id, candidate] of candidates) {
|
|
288
|
-
const registration = this.#registrations.get(id);
|
|
289
|
-
if (!registration || !registration.options.enabled) continue;
|
|
290
|
-
if (candidate.score > 0 && candidate.score === bestScore) {
|
|
291
|
-
registration.lastKeyTime = now;
|
|
292
|
-
registration.currentIndex = candidate.nextIndex;
|
|
293
|
-
if (registration.currentIndex >= registration.parsedSequence.length) {
|
|
294
|
-
if (registration.options.preventDefault) event.preventDefault();
|
|
295
|
-
if (registration.options.stopPropagation) event.stopPropagation();
|
|
296
|
-
const context = {
|
|
297
|
-
hotkey: registration.sequence.join(" "),
|
|
298
|
-
parsedHotkey: registration.parsedSequence[registration.parsedSequence.length - 1]
|
|
299
|
-
};
|
|
300
|
-
registration.triggerCount++;
|
|
301
|
-
registration.hasFired = true;
|
|
302
|
-
registration.currentIndex = 0;
|
|
303
|
-
this.#syncRegistrationView(registration);
|
|
304
|
-
registration.callback(event, context);
|
|
305
|
-
} else this.#syncRegistrationView(registration);
|
|
306
|
-
} else if (registration.currentIndex > 0) {
|
|
307
|
-
registration.currentIndex = 0;
|
|
308
|
-
this.#syncRegistrationView(registration);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Compares resolved step identities without changing the supplied sequence strings.
|
|
314
|
-
*/
|
|
315
|
-
#findConflictingSequence(parsedSequence, target) {
|
|
316
|
-
for (const registration of this.#registrations.values()) if (registration.target === target && registration.parsedSequence.length === parsedSequence.length && parsedSequence.every((step, index) => areHotkeysEqual(step, registration.parsedSequence[index]))) return registration;
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Resets all sequence progress.
|
|
321
|
-
*/
|
|
322
|
-
resetAll() {
|
|
323
|
-
for (const registration of this.#registrations.values()) {
|
|
324
|
-
registration.currentIndex = 0;
|
|
325
|
-
registration.lastKeyTime = 0;
|
|
326
|
-
this.#syncRegistrationView(registration);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Triggers a sequence's callback programmatically from devtools.
|
|
331
|
-
* Creates a synthetic KeyboardEvent from the last key in the sequence.
|
|
332
|
-
*
|
|
333
|
-
* @param id - The registration ID to trigger
|
|
334
|
-
* @returns True if the registration was found and triggered
|
|
335
|
-
*/
|
|
336
|
-
triggerSequence(id) {
|
|
337
|
-
const registration = this.#registrations.get(id);
|
|
338
|
-
if (!registration) return false;
|
|
339
|
-
const lastParsed = registration.parsedSequence[registration.parsedSequence.length - 1];
|
|
340
|
-
if (!lastParsed) return false;
|
|
341
|
-
const syntheticEvent = new KeyboardEvent(registration.options.eventType ?? "keydown", {
|
|
342
|
-
...lastParsed.code !== void 0 ? { code: lastParsed.code } : { key: lastParsed.key },
|
|
343
|
-
ctrlKey: lastParsed.ctrl,
|
|
344
|
-
shiftKey: lastParsed.shift,
|
|
345
|
-
altKey: lastParsed.alt,
|
|
346
|
-
metaKey: lastParsed.meta,
|
|
347
|
-
bubbles: true,
|
|
348
|
-
cancelable: true
|
|
349
|
-
});
|
|
350
|
-
registration.triggerCount++;
|
|
351
|
-
registration.hasFired = true;
|
|
352
|
-
this.#syncRegistrationView(registration);
|
|
353
|
-
const context = {
|
|
354
|
-
hotkey: registration.sequence.join(" "),
|
|
355
|
-
parsedHotkey: lastParsed
|
|
356
|
-
};
|
|
357
|
-
registration.callback(syntheticEvent, context);
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Gets the number of registered sequences.
|
|
362
|
-
*/
|
|
363
|
-
getRegistrationCount() {
|
|
364
|
-
return this.#registrations.size;
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Destroys the manager and removes all listeners.
|
|
368
|
-
*/
|
|
369
|
-
destroy() {
|
|
370
|
-
for (const target of this.#targetListeners.keys()) this.#removeListenersForTarget(target);
|
|
371
|
-
this.#registrations.clear();
|
|
372
|
-
this.registrations.setState(() => /* @__PURE__ */ new Map());
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
/**
|
|
376
|
-
* Gets the singleton SequenceManager instance.
|
|
377
|
-
* Convenience function for accessing the manager.
|
|
378
|
-
*/
|
|
379
|
-
function getSequenceManager() {
|
|
380
|
-
return SequenceManager.getInstance();
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Creates a simple sequence matcher for one-off use.
|
|
384
|
-
*
|
|
385
|
-
* This is a low-level helper that does not support ignoreInputs, target,
|
|
386
|
-
* or other HotkeyOptions. Callers must handle input filtering themselves
|
|
387
|
-
* if attaching to document.
|
|
388
|
-
*
|
|
389
|
-
* @param sequence - The sequence of hotkeys to match
|
|
390
|
-
* @param options - Options including timeout
|
|
391
|
-
* @returns An object with match() and reset() methods
|
|
392
|
-
*
|
|
393
|
-
* @example
|
|
394
|
-
* ```ts
|
|
395
|
-
* const matcher = createSequenceMatcher(['G', 'G'], { timeout: 500 })
|
|
396
|
-
*
|
|
397
|
-
* document.addEventListener('keydown', (event) => {
|
|
398
|
-
* if (matcher.match(event)) {
|
|
399
|
-
* console.log('Sequence matched!')
|
|
400
|
-
* }
|
|
401
|
-
* })
|
|
402
|
-
* ```
|
|
403
|
-
*/
|
|
404
|
-
function createSequenceMatcher(sequence, options = {}) {
|
|
405
|
-
const platform = options.platform ?? detectPlatform();
|
|
406
|
-
const timeout = options.timeout ?? 1e3;
|
|
407
|
-
const parsedSequence = sequence.map((hotkey) => parseHotkey(hotkey, platform));
|
|
408
|
-
let currentIndex = 0;
|
|
409
|
-
let lastKeyTime = 0;
|
|
410
|
-
return {
|
|
411
|
-
match(event) {
|
|
412
|
-
if (event.type === "keydown" && event.repeat || normalizeKeyboardEvent(event, platform).isComposing || isModifierKey(normalizeKeyName(event.key))) return false;
|
|
413
|
-
const now = Date.now();
|
|
414
|
-
if (currentIndex > 0 && now - lastKeyTime > timeout) currentIndex = 0;
|
|
415
|
-
const expected = parsedSequence[currentIndex];
|
|
416
|
-
if (!expected) return false;
|
|
417
|
-
if (matchesKeyboardEvent(event, expected, platform)) {
|
|
418
|
-
lastKeyTime = now;
|
|
419
|
-
currentIndex++;
|
|
420
|
-
if (currentIndex >= parsedSequence.length) {
|
|
421
|
-
currentIndex = 0;
|
|
422
|
-
return true;
|
|
423
|
-
}
|
|
424
|
-
} else if (currentIndex > 0) {
|
|
425
|
-
if (matchesKeyboardEvent(event, parsedSequence[0], platform)) {
|
|
426
|
-
currentIndex = 1;
|
|
427
|
-
lastKeyTime = now;
|
|
428
|
-
} else currentIndex = 0;
|
|
429
|
-
}
|
|
430
|
-
return false;
|
|
431
|
-
},
|
|
432
|
-
reset() {
|
|
433
|
-
currentIndex = 0;
|
|
434
|
-
lastKeyTime = 0;
|
|
435
|
-
},
|
|
436
|
-
getProgress() {
|
|
437
|
-
return currentIndex;
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
//#endregion
|
|
443
|
-
export { DEFAULT_SEQUENCE_TIMEOUT, SequenceManager, createSequenceMatcher, getSequenceManager };
|