@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.
- package/README.md +15 -1
- package/dist/{manager.utils.js → _event-target.js} +2 -41
- package/dist/_keyboard-event.js +75 -0
- package/dist/_match.js +48 -0
- package/dist/_named-keys.d.ts +6 -0
- package/dist/_named-keys.js +65 -0
- package/dist/_recorder-chord.js +42 -0
- package/dist/_recording-guard.js +55 -0
- package/dist/_registration.js +49 -0
- package/dist/conflicts.d.ts +36 -0
- package/dist/conflicts.js +54 -0
- package/dist/constants.d.ts +16 -144
- package/dist/constants.js +55 -172
- package/dist/display-labels.d.ts +90 -0
- package/dist/display-labels.js +112 -0
- package/dist/format.d.ts +48 -12
- package/dist/format.js +35 -39
- package/dist/hint.d.ts +20 -0
- package/dist/hint.js +23 -0
- package/dist/hotkey-manager.d.ts +14 -12
- package/dist/hotkey-manager.js +70 -21
- package/dist/hotkey-recorder.d.ts +8 -8
- package/dist/hotkey-recorder.js +74 -15
- package/dist/hotkey-sequence-recorder.d.ts +13 -9
- package/dist/hotkey-sequence-recorder.js +90 -28
- package/dist/{hotkey.d.ts → hotkey.types.d.ts} +58 -126
- package/dist/index.d.ts +14 -9
- package/dist/index.js +10 -6
- package/dist/key-state-tracker.d.ts +4 -7
- package/dist/key-state-tracker.js +30 -27
- package/dist/key.types.d.ts +79 -0
- package/dist/match.d.ts +24 -11
- package/dist/match.js +19 -30
- package/dist/parse.d.ts +18 -15
- package/dist/parse.js +39 -22
- package/dist/platform.d.ts +41 -0
- package/dist/platform.js +52 -0
- package/dist/recorder-options.d.ts +31 -0
- package/dist/sequence-manager.d.ts +27 -15
- package/dist/sequence-manager.js +61 -30
- package/dist/validate.d.ts +16 -8
- package/dist/validate.js +8 -13
- package/package.json +8 -13
- package/dist/constants.cjs +0 -535
- package/dist/constants.cjs.map +0 -1
- package/dist/constants.d.cts +0 -290
- package/dist/constants.js.map +0 -1
- package/dist/format.cjs +0 -91
- package/dist/format.cjs.map +0 -1
- package/dist/format.d.cts +0 -60
- package/dist/format.js.map +0 -1
- package/dist/hotkey-manager.cjs +0 -383
- package/dist/hotkey-manager.cjs.map +0 -1
- package/dist/hotkey-manager.d.cts +0 -230
- package/dist/hotkey-manager.js.map +0 -1
- package/dist/hotkey-recorder.cjs +0 -179
- package/dist/hotkey-recorder.cjs.map +0 -1
- package/dist/hotkey-recorder.d.cts +0 -116
- package/dist/hotkey-recorder.js.map +0 -1
- package/dist/hotkey-sequence-recorder.cjs +0 -192
- package/dist/hotkey-sequence-recorder.cjs.map +0 -1
- package/dist/hotkey-sequence-recorder.d.cts +0 -79
- package/dist/hotkey-sequence-recorder.js.map +0 -1
- package/dist/hotkey.d.cts +0 -301
- package/dist/index.cjs +0 -63
- package/dist/index.d.cts +0 -13
- package/dist/key-state-tracker.cjs +0 -197
- package/dist/key-state-tracker.cjs.map +0 -1
- package/dist/key-state-tracker.d.cts +0 -107
- package/dist/key-state-tracker.js.map +0 -1
- package/dist/manager.utils.cjs +0 -115
- package/dist/manager.utils.cjs.map +0 -1
- package/dist/manager.utils.d.cts +0 -13
- package/dist/manager.utils.d.ts +0 -13
- package/dist/manager.utils.js.map +0 -1
- package/dist/match.cjs +0 -153
- package/dist/match.cjs.map +0 -1
- package/dist/match.d.cts +0 -85
- package/dist/match.js.map +0 -1
- package/dist/parse.cjs +0 -228
- package/dist/parse.cjs.map +0 -1
- package/dist/parse.d.cts +0 -128
- package/dist/parse.js.map +0 -1
- package/dist/recorder-chord.cjs +0 -18
- package/dist/recorder-chord.cjs.map +0 -1
- package/dist/recorder-chord.js +0 -18
- package/dist/recorder-chord.js.map +0 -1
- package/dist/sequence-manager.cjs +0 -415
- package/dist/sequence-manager.cjs.map +0 -1
- package/dist/sequence-manager.d.cts +0 -157
- package/dist/sequence-manager.js.map +0 -1
- package/dist/validate.cjs +0 -116
- package/dist/validate.cjs.map +0 -1
- package/dist/validate.d.cts +0 -56
- package/dist/validate.js.map +0 -1
- package/src/constants.ts +0 -603
- package/src/format.ts +0 -142
- package/src/hotkey-manager.ts +0 -759
- package/src/hotkey-recorder.ts +0 -272
- package/src/hotkey-sequence-recorder.ts +0 -328
- package/src/hotkey.ts +0 -436
- package/src/index.ts +0 -11
- package/src/key-state-tracker.ts +0 -249
- package/src/manager.utils.ts +0 -231
- package/src/match.ts +0 -259
- package/src/parse.ts +0 -328
- package/src/recorder-chord.ts +0 -25
- package/src/sequence-manager.ts +0 -734
- package/src/validate.ts +0 -171
package/dist/hotkey-recorder.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { detectPlatform } from "./
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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 "./
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|