@tooee/commands 0.1.16 → 0.1.18
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/context.d.ts +6 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +96 -14
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/sequence.d.ts +18 -1
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +48 -7
- package/dist/sequence.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/use-actions.d.ts +4 -0
- package/dist/use-actions.d.ts.map +1 -1
- package/dist/use-actions.js +7 -1
- package/dist/use-actions.js.map +1 -1
- package/package.json +1 -1
- package/src/context.tsx +128 -13
- package/src/index.ts +18 -3
- package/src/sequence.ts +69 -8
- package/src/types.ts +27 -0
- package/src/use-actions.ts +15 -1
package/dist/context.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { Command, CommandContext, CommandRegistry } from "./types.js";
|
|
2
|
+
import type { Command, CommandContext, CommandGroup, CommandRegistry, CommandSequenceState, RegisteredCommandGroup } from "./types.js";
|
|
3
3
|
import type { Mode } from "./mode.js";
|
|
4
4
|
type ContextGetter = () => Partial<CommandContext>;
|
|
5
5
|
interface CommandContextValue {
|
|
6
6
|
registry: CommandRegistry;
|
|
7
7
|
leaderKey?: string;
|
|
8
8
|
contextSources: Map<string, ContextGetter>;
|
|
9
|
+
groups: Map<string, RegisteredCommandGroup>;
|
|
9
10
|
}
|
|
10
11
|
declare const CommandContext: import("react").Context<CommandContextValue | null>;
|
|
11
12
|
export interface CommandProviderProps {
|
|
@@ -13,13 +14,16 @@ export interface CommandProviderProps {
|
|
|
13
14
|
leader?: string;
|
|
14
15
|
keymap?: Record<string, string>;
|
|
15
16
|
initialMode?: Mode;
|
|
17
|
+
sequenceTimeoutMs?: number;
|
|
16
18
|
}
|
|
17
|
-
export declare function CommandProvider({ children, leader, keymap, initialMode }: CommandProviderProps): ReactNode;
|
|
19
|
+
export declare function CommandProvider({ children, leader, keymap, initialMode, sequenceTimeoutMs, }: CommandProviderProps): ReactNode;
|
|
18
20
|
export declare function useCommandContext(): {
|
|
19
21
|
commands: Command[];
|
|
20
22
|
invoke: (id: string) => void;
|
|
21
23
|
};
|
|
22
24
|
export declare function useCommandRegistry(): CommandContextValue;
|
|
25
|
+
export declare function useCommandSequenceState(): CommandSequenceState | null;
|
|
26
|
+
export declare function useCommandGroup(group: CommandGroup): void;
|
|
23
27
|
export declare function useProvideCommandContext(getter: () => Partial<CommandContext>): void;
|
|
24
28
|
export {};
|
|
25
29
|
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAEd,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,YAAY,EACZ,eAAe,EACf,oBAAoB,EAGpB,sBAAsB,EACvB,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAQrC,KAAK,aAAa,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAAA;AAElD,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IAC1C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAA;CAC5C;AAED,QAAA,MAAM,cAAc,qDAAkD,CAAA;AAGtE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,SAAS,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,WAAW,EACX,iBAAiB,GAClB,EAAE,oBAAoB,aAQtB;AAqLD,wBAAgB,iBAAiB,IAAI;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAazF;AAED,wBAAgB,kBAAkB,IAAI,mBAAmB,CAMxD;AAED,wBAAgB,uBAAuB,IAAI,oBAAoB,GAAG,IAAI,CAErE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CA8BzD;AAkBD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAuBpF"}
|
package/dist/context.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useRef, useCallback, useEffect, useMemo, } from "react";
|
|
2
|
+
import { createContext, useContext, useRef, useCallback, useEffect, useMemo, useState, } from "react";
|
|
3
3
|
import { useKeyboard } from "@opentui/react";
|
|
4
4
|
import { ModeProvider, useMode, useSetMode } from "./mode.js";
|
|
5
5
|
import { parseHotkey } from "./parse.js";
|
|
@@ -7,16 +7,21 @@ import { matchStep } from "./match.js";
|
|
|
7
7
|
import { SequenceTracker } from "./sequence.js";
|
|
8
8
|
const DEFAULT_MODES = ["cursor"];
|
|
9
9
|
const CommandContext = createContext(null);
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const CommandSequenceContext = createContext(null);
|
|
11
|
+
export function CommandProvider({ children, leader, keymap, initialMode, sequenceTimeoutMs, }) {
|
|
12
|
+
return (_jsx(ModeProvider, { initialMode: initialMode, children: _jsx(CommandDispatcher, { leader: leader, keymap: keymap, sequenceTimeoutMs: sequenceTimeoutMs, children: children }) }));
|
|
12
13
|
}
|
|
13
|
-
function CommandDispatcher({ children, leader, keymap, }) {
|
|
14
|
+
function CommandDispatcher({ children, leader, keymap, sequenceTimeoutMs, }) {
|
|
14
15
|
const registryRef = useRef(null);
|
|
15
16
|
const contextSourcesRef = useRef(new Map());
|
|
17
|
+
const groupsRef = useRef(new Map());
|
|
16
18
|
const mode = useMode();
|
|
17
19
|
const modeRef = useRef(mode);
|
|
18
20
|
modeRef.current = mode;
|
|
19
21
|
const setMode = useSetMode();
|
|
22
|
+
const [sequenceState, setSequenceState] = useState(null);
|
|
23
|
+
const clearSequenceStateRef = useRef(() => setSequenceState(null));
|
|
24
|
+
clearSequenceStateRef.current = () => setSequenceState(null);
|
|
20
25
|
const buildCtx = useCallback(() => {
|
|
21
26
|
const ctx = {
|
|
22
27
|
mode: modeRef.current,
|
|
@@ -51,7 +56,10 @@ function CommandDispatcher({ children, leader, keymap, }) {
|
|
|
51
56
|
},
|
|
52
57
|
};
|
|
53
58
|
}
|
|
54
|
-
const trackerRef = useRef(new SequenceTracker(
|
|
59
|
+
const trackerRef = useRef(new SequenceTracker({
|
|
60
|
+
timeout: sequenceTimeoutMs,
|
|
61
|
+
onReset: () => clearSequenceStateRef.current(),
|
|
62
|
+
}));
|
|
55
63
|
const parseCacheRef = useRef(new Map());
|
|
56
64
|
const getParsedHotkey = useCallback((hotkey) => {
|
|
57
65
|
const cache = parseCacheRef.current;
|
|
@@ -73,8 +81,7 @@ function CommandDispatcher({ children, leader, keymap, }) {
|
|
|
73
81
|
const ctx = buildCtx();
|
|
74
82
|
// Collect eligible commands with their parsed hotkeys
|
|
75
83
|
const singleStepCandidates = [];
|
|
76
|
-
const
|
|
77
|
-
const multiStepCommands = [];
|
|
84
|
+
const multiStepCandidates = [];
|
|
78
85
|
for (const command of registry.commands.values()) {
|
|
79
86
|
const commandModes = command.modes ?? DEFAULT_MODES;
|
|
80
87
|
if (!commandModes.includes(currentMode))
|
|
@@ -89,34 +96,61 @@ function CommandDispatcher({ children, leader, keymap, }) {
|
|
|
89
96
|
singleStepCandidates.push({ command, parsed });
|
|
90
97
|
}
|
|
91
98
|
else {
|
|
92
|
-
|
|
93
|
-
multiStepCommands.push(command);
|
|
99
|
+
multiStepCandidates.push({ command, hotkey, parsed });
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
// Check multi-step sequences first (they consume buffer state)
|
|
97
|
-
if (
|
|
98
|
-
const
|
|
99
|
-
|
|
103
|
+
if (multiStepCandidates.length > 0) {
|
|
104
|
+
const multiStepHotkeys = multiStepCandidates.map((candidate) => candidate.parsed);
|
|
105
|
+
const result = trackerRef.current.feedWithState(event, multiStepHotkeys);
|
|
106
|
+
if (result.matchedIndex >= 0) {
|
|
100
107
|
event.preventDefault();
|
|
101
|
-
|
|
108
|
+
setSequenceState(null);
|
|
109
|
+
multiStepCandidates[result.matchedIndex].command.handler(ctx);
|
|
102
110
|
return;
|
|
103
111
|
}
|
|
112
|
+
if (result.pending) {
|
|
113
|
+
const firstCandidate = multiStepCandidates[result.pending.indexes[0]];
|
|
114
|
+
setSequenceState({
|
|
115
|
+
prefix: firstCandidate.parsed.steps.slice(0, result.pending.prefixLength),
|
|
116
|
+
candidates: result.pending.indexes
|
|
117
|
+
.map((idx) => multiStepCandidates[idx])
|
|
118
|
+
.filter(({ command }) => !command.hidden)
|
|
119
|
+
.map(({ command, hotkey, parsed }) => ({
|
|
120
|
+
command,
|
|
121
|
+
hotkey,
|
|
122
|
+
steps: parsed.steps,
|
|
123
|
+
remainingSteps: parsed.steps.slice(result.pending.prefixLength),
|
|
124
|
+
nextStep: parsed.steps[result.pending.prefixLength],
|
|
125
|
+
group: groupsRef.current.get(stepsKey(parsed.steps.slice(0, result.pending.prefixLength + 1))),
|
|
126
|
+
})),
|
|
127
|
+
});
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
setSequenceState(null);
|
|
104
132
|
}
|
|
105
133
|
// Check single-step matches
|
|
106
134
|
for (const { command, parsed } of singleStepCandidates) {
|
|
107
135
|
if (matchStep(event, parsed.steps[0])) {
|
|
108
136
|
event.preventDefault();
|
|
137
|
+
setSequenceState(null);
|
|
109
138
|
command.handler(ctx);
|
|
110
139
|
return;
|
|
111
140
|
}
|
|
112
141
|
}
|
|
113
142
|
});
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
trackerRef.current.reset();
|
|
145
|
+
setSequenceState(null);
|
|
146
|
+
}, [mode]);
|
|
114
147
|
const ctxValue = useMemo(() => ({
|
|
115
148
|
registry: registryRef.current,
|
|
116
149
|
leaderKey: leader,
|
|
117
150
|
contextSources: contextSourcesRef.current,
|
|
151
|
+
groups: groupsRef.current,
|
|
118
152
|
}), [leader]);
|
|
119
|
-
return _jsx(CommandContext.Provider, { value: ctxValue, children: children });
|
|
153
|
+
return (_jsx(CommandContext.Provider, { value: ctxValue, children: _jsx(CommandSequenceContext, { value: sequenceState, children: children }) }));
|
|
120
154
|
}
|
|
121
155
|
export function useCommandContext() {
|
|
122
156
|
const ctx = useContext(CommandContext);
|
|
@@ -138,6 +172,54 @@ export function useCommandRegistry() {
|
|
|
138
172
|
}
|
|
139
173
|
return ctx;
|
|
140
174
|
}
|
|
175
|
+
export function useCommandSequenceState() {
|
|
176
|
+
return useContext(CommandSequenceContext);
|
|
177
|
+
}
|
|
178
|
+
export function useCommandGroup(group) {
|
|
179
|
+
const ctx = useContext(CommandContext);
|
|
180
|
+
if (!ctx) {
|
|
181
|
+
throw new Error("useCommandGroup must be used within a CommandProvider");
|
|
182
|
+
}
|
|
183
|
+
const groupRef = useRef(group);
|
|
184
|
+
groupRef.current = group;
|
|
185
|
+
const { groups, leaderKey } = ctx;
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
const parsed = parseHotkey(groupRef.current.prefix, leaderKey);
|
|
188
|
+
const registered = {
|
|
189
|
+
...groupRef.current,
|
|
190
|
+
prefixKey: stepsKey(parsed.steps),
|
|
191
|
+
};
|
|
192
|
+
groups.set(registered.prefixKey, registered);
|
|
193
|
+
return () => {
|
|
194
|
+
groups.delete(registered.prefixKey);
|
|
195
|
+
};
|
|
196
|
+
}, [
|
|
197
|
+
group.id,
|
|
198
|
+
group.prefix,
|
|
199
|
+
group.title,
|
|
200
|
+
group.description,
|
|
201
|
+
group.icon,
|
|
202
|
+
group.order,
|
|
203
|
+
groups,
|
|
204
|
+
leaderKey,
|
|
205
|
+
]);
|
|
206
|
+
}
|
|
207
|
+
function stepsKey(steps) {
|
|
208
|
+
return steps.map(formatStepKey).join(" ");
|
|
209
|
+
}
|
|
210
|
+
function formatStepKey(step) {
|
|
211
|
+
const modifiers = [];
|
|
212
|
+
if (step.ctrl)
|
|
213
|
+
modifiers.push("ctrl");
|
|
214
|
+
if (step.meta)
|
|
215
|
+
modifiers.push("meta");
|
|
216
|
+
if (step.option)
|
|
217
|
+
modifiers.push("option");
|
|
218
|
+
if (step.shift)
|
|
219
|
+
modifiers.push("shift");
|
|
220
|
+
modifiers.push(step.key);
|
|
221
|
+
return modifiers.join("+");
|
|
222
|
+
}
|
|
141
223
|
let nextContextSourceId = 0;
|
|
142
224
|
export function useProvideCommandContext(getter) {
|
|
143
225
|
const ctx = useContext(CommandContext);
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,aAAa,EACb,UAAU,EACV,MAAM,EACN,WAAW,EACX,SAAS,EACT,OAAO,
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export type { Command, CommandContext, CommandContextBase, CommandHandler, CommandWhen, ParsedHotkey, ParsedStep, } from "./types.js";
|
|
1
|
+
export type { Command, CommandContext, CommandContextBase, CommandGroup, CommandHandler, CommandSequenceCandidate, CommandSequenceState, CommandWhen, ParsedHotkey, ParsedStep, RegisteredCommandGroup, } from "./types.js";
|
|
2
2
|
export type { Mode } from "./mode.js";
|
|
3
3
|
export { ModeProvider, useMode, useSetMode } from "./mode.js";
|
|
4
4
|
export type { ModeProviderProps } from "./mode.js";
|
|
5
5
|
export { parseHotkey } from "./parse.js";
|
|
6
6
|
export { matchStep } from "./match.js";
|
|
7
|
-
export { SequenceTracker } from "./sequence.js";
|
|
8
|
-
export type { SequenceTrackerOptions } from "./sequence.js";
|
|
9
|
-
export { CommandProvider, useCommandContext, useProvideCommandContext } from "./context.js";
|
|
7
|
+
export { DEFAULT_SEQUENCE_TIMEOUT_MS, SequenceTracker } from "./sequence.js";
|
|
8
|
+
export type { SequenceFeedResult, SequencePendingMatch, SequenceTrackerOptions, } from "./sequence.js";
|
|
9
|
+
export { CommandProvider, useCommandContext, useCommandGroup, useCommandRegistry, useCommandSequenceState, useProvideCommandContext, } from "./context.js";
|
|
10
10
|
export type { CommandProviderProps } from "./context.js";
|
|
11
11
|
export { useCommand } from "./use-command.js";
|
|
12
12
|
export type { UseCommandOptions } from "./use-command.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,YAAY,EACZ,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,wBAAwB,EACxB,oBAAoB,EACpB,WAAW,EACX,YAAY,EACZ,UAAU,EACV,sBAAsB,GACvB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,2BAA2B,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC5E,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { ModeProvider, useMode, useSetMode } from "./mode.js";
|
|
2
2
|
export { parseHotkey } from "./parse.js";
|
|
3
3
|
export { matchStep } from "./match.js";
|
|
4
|
-
export { SequenceTracker } from "./sequence.js";
|
|
5
|
-
export { CommandProvider, useCommandContext, useProvideCommandContext } from "./context.js";
|
|
4
|
+
export { DEFAULT_SEQUENCE_TIMEOUT_MS, SequenceTracker } from "./sequence.js";
|
|
5
|
+
export { CommandProvider, useCommandContext, useCommandGroup, useCommandRegistry, useCommandSequenceState, useProvideCommandContext, } from "./context.js";
|
|
6
6
|
export { useCommand } from "./use-command.js";
|
|
7
7
|
export { useActions } from "./use-actions.js";
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE7D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,2BAA2B,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAM5E,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/sequence.d.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
import type { KeyEvent } from "@opentui/core";
|
|
2
2
|
import type { ParsedHotkey } from "./types.js";
|
|
3
|
+
export declare const DEFAULT_SEQUENCE_TIMEOUT_MS = 1500;
|
|
3
4
|
export interface SequenceTrackerOptions {
|
|
4
5
|
timeout?: number;
|
|
6
|
+
onReset?: () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface SequencePendingMatch {
|
|
9
|
+
prefixLength: number;
|
|
10
|
+
indexes: number[];
|
|
11
|
+
}
|
|
12
|
+
export interface SequenceFeedResult {
|
|
13
|
+
matchedIndex: number;
|
|
14
|
+
pending: SequencePendingMatch | null;
|
|
5
15
|
}
|
|
6
16
|
export declare class SequenceTracker {
|
|
7
17
|
private buffer;
|
|
8
18
|
private timer;
|
|
9
19
|
private timeout;
|
|
20
|
+
private onReset?;
|
|
10
21
|
constructor(options?: SequenceTrackerOptions);
|
|
11
22
|
/**
|
|
12
23
|
* Feed a key event and check against registered hotkeys.
|
|
13
|
-
* Returns the index of the matched hotkey, or -1 if no match.
|
|
24
|
+
* Returns the index of the matched hotkey, or -1 if no full match.
|
|
14
25
|
*/
|
|
15
26
|
feed(event: KeyEvent, hotkeys: ParsedHotkey[]): number;
|
|
27
|
+
/**
|
|
28
|
+
* Feed a key event and return both full-match and pending-prefix state.
|
|
29
|
+
*/
|
|
30
|
+
feedWithState(event: KeyEvent, hotkeys: ParsedHotkey[]): SequenceFeedResult;
|
|
31
|
+
getPendingMatch(hotkeys: ParsedHotkey[]): SequencePendingMatch | null;
|
|
16
32
|
private matchesBuffer;
|
|
33
|
+
private findPendingMatch;
|
|
17
34
|
reset(): void;
|
|
18
35
|
private resetTimer;
|
|
19
36
|
private clearTimer;
|
package/dist/sequence.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequence.d.ts","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG9C,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"sequence.d.ts","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG9C,eAAO,MAAM,2BAA2B,OAAO,CAAA;AAE/C,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAA;CACrC;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAC,CAAY;gBAEhB,OAAO,CAAC,EAAE,sBAAsB;IAK5C;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM;IAItD;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,kBAAkB;IAuB3E,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,oBAAoB,GAAG,IAAI;IAIrE,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,gBAAgB;IA+BxB,KAAK,IAAI,IAAI;IAOb,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,UAAU;CAMnB"}
|
package/dist/sequence.js
CHANGED
|
@@ -1,31 +1,45 @@
|
|
|
1
1
|
import { matchStep } from "./match.js";
|
|
2
|
+
export const DEFAULT_SEQUENCE_TIMEOUT_MS = 1500;
|
|
2
3
|
export class SequenceTracker {
|
|
3
4
|
buffer = [];
|
|
4
5
|
timer = null;
|
|
5
6
|
timeout;
|
|
7
|
+
onReset;
|
|
6
8
|
constructor(options) {
|
|
7
|
-
this.timeout = options?.timeout ??
|
|
9
|
+
this.timeout = options?.timeout ?? DEFAULT_SEQUENCE_TIMEOUT_MS;
|
|
10
|
+
this.onReset = options?.onReset;
|
|
8
11
|
}
|
|
9
12
|
/**
|
|
10
13
|
* Feed a key event and check against registered hotkeys.
|
|
11
|
-
* Returns the index of the matched hotkey, or -1 if no match.
|
|
14
|
+
* Returns the index of the matched hotkey, or -1 if no full match.
|
|
12
15
|
*/
|
|
13
16
|
feed(event, hotkeys) {
|
|
17
|
+
return this.feedWithState(event, hotkeys).matchedIndex;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Feed a key event and return both full-match and pending-prefix state.
|
|
21
|
+
*/
|
|
22
|
+
feedWithState(event, hotkeys) {
|
|
14
23
|
this.buffer.push(event);
|
|
15
24
|
this.resetTimer();
|
|
16
25
|
for (let i = 0; i < hotkeys.length; i++) {
|
|
17
26
|
const hotkey = hotkeys[i];
|
|
18
27
|
if (this.matchesBuffer(hotkey)) {
|
|
19
28
|
this.reset();
|
|
20
|
-
return i;
|
|
29
|
+
return { matchedIndex: i, pending: null };
|
|
21
30
|
}
|
|
22
31
|
}
|
|
23
32
|
// Prune buffer if no hotkey could possibly match
|
|
24
|
-
const maxLen = Math.max(...hotkeys.map((h) => h.steps.length));
|
|
25
|
-
if (
|
|
26
|
-
this.buffer.
|
|
33
|
+
const maxLen = Math.max(0, ...hotkeys.map((h) => h.steps.length));
|
|
34
|
+
if (maxLen > 0) {
|
|
35
|
+
while (this.buffer.length > maxLen) {
|
|
36
|
+
this.buffer.shift();
|
|
37
|
+
}
|
|
27
38
|
}
|
|
28
|
-
return -1;
|
|
39
|
+
return { matchedIndex: -1, pending: this.findPendingMatch(hotkeys) };
|
|
40
|
+
}
|
|
41
|
+
getPendingMatch(hotkeys) {
|
|
42
|
+
return this.findPendingMatch(hotkeys);
|
|
29
43
|
}
|
|
30
44
|
matchesBuffer(hotkey) {
|
|
31
45
|
const { steps } = hotkey;
|
|
@@ -38,9 +52,36 @@ export class SequenceTracker {
|
|
|
38
52
|
}
|
|
39
53
|
return true;
|
|
40
54
|
}
|
|
55
|
+
findPendingMatch(hotkeys) {
|
|
56
|
+
const maxPrefixLength = Math.min(this.buffer.length, Math.max(0, ...hotkeys.map((h) => h.steps.length - 1)));
|
|
57
|
+
for (let prefixLength = maxPrefixLength; prefixLength > 0; prefixLength--) {
|
|
58
|
+
const start = this.buffer.length - prefixLength;
|
|
59
|
+
const indexes = [];
|
|
60
|
+
for (let hotkeyIndex = 0; hotkeyIndex < hotkeys.length; hotkeyIndex++) {
|
|
61
|
+
const hotkey = hotkeys[hotkeyIndex];
|
|
62
|
+
if (hotkey.steps.length <= prefixLength)
|
|
63
|
+
continue;
|
|
64
|
+
let matches = true;
|
|
65
|
+
for (let i = 0; i < prefixLength; i++) {
|
|
66
|
+
if (!matchStep(this.buffer[start + i], hotkey.steps[i])) {
|
|
67
|
+
matches = false;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (matches)
|
|
72
|
+
indexes.push(hotkeyIndex);
|
|
73
|
+
}
|
|
74
|
+
if (indexes.length > 0)
|
|
75
|
+
return { prefixLength, indexes };
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
41
79
|
reset() {
|
|
80
|
+
const hadBuffer = this.buffer.length > 0;
|
|
42
81
|
this.buffer = [];
|
|
43
82
|
this.clearTimer();
|
|
83
|
+
if (hadBuffer)
|
|
84
|
+
this.onReset?.();
|
|
44
85
|
}
|
|
45
86
|
resetTimer() {
|
|
46
87
|
this.clearTimer();
|
package/dist/sequence.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequence.js","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"sequence.js","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEtC,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAA;AAiB/C,MAAM,OAAO,eAAe;IAClB,MAAM,GAAe,EAAE,CAAA;IACvB,KAAK,GAAyC,IAAI,CAAA;IAClD,OAAO,CAAQ;IACf,OAAO,CAAa;IAE5B,YAAY,OAAgC;QAC1C,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,2BAA2B,CAAA;QAC9D,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,CAAA;IACjC,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,KAAe,EAAE,OAAuB;QAC3C,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,YAAY,CAAA;IACxD,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,KAAe,EAAE,OAAuB;QACpD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACvB,IAAI,CAAC,UAAU,EAAE,CAAA;QAEjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAE,CAAA;YAC1B,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC/B,IAAI,CAAC,KAAK,EAAE,CAAA;gBACZ,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;YAC3C,CAAC;QACH,CAAC;QAED,iDAAiD;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;QACjE,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;gBACnC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;YACrB,CAAC;QACH,CAAC;QAED,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAA;IACtE,CAAC;IAED,eAAe,CAAC,OAAuB;QACrC,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACvC,CAAC;IAEO,aAAa,CAAC,MAAoB;QACxC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAA;QACxB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;YAAE,OAAO,KAAK,CAAA;QAEnD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;QAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC;gBAAE,OAAO,KAAK,CAAA;QAClE,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,gBAAgB,CAAC,OAAuB;QAC9C,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAC9B,IAAI,CAAC,MAAM,CAAC,MAAM,EAClB,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CACvD,CAAA;QAED,KAAK,IAAI,YAAY,GAAG,eAAe,EAAE,YAAY,GAAG,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC;YAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,YAAY,CAAA;YAC/C,MAAM,OAAO,GAAa,EAAE,CAAA;YAE5B,KAAK,IAAI,WAAW,GAAG,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC;gBACtE,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAE,CAAA;gBACpC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,YAAY;oBAAE,SAAQ;gBAEjD,IAAI,OAAO,GAAG,IAAI,CAAA;gBAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;wBAC1D,OAAO,GAAG,KAAK,CAAA;wBACf,MAAK;oBACP,CAAC;gBACH,CAAC;gBAED,IAAI,OAAO;oBAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACxC,CAAC;YAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,CAAA;QAC1D,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK;QACH,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAA;QACxC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;QAChB,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,IAAI,SAAS;YAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAA;IACjC,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;IAC3D,CAAC;IAEO,UAAU;QAChB,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACxB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACnB,CAAC;IACH,CAAC;CACF"}
|
package/dist/types.d.ts
CHANGED
|
@@ -40,4 +40,27 @@ export interface CommandRegistry {
|
|
|
40
40
|
register: (command: Command) => () => void;
|
|
41
41
|
invoke: (id: string) => void;
|
|
42
42
|
}
|
|
43
|
+
export interface CommandGroup {
|
|
44
|
+
id: string;
|
|
45
|
+
title: string;
|
|
46
|
+
prefix: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
icon?: string;
|
|
49
|
+
order?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface RegisteredCommandGroup extends CommandGroup {
|
|
52
|
+
prefixKey: string;
|
|
53
|
+
}
|
|
54
|
+
export interface CommandSequenceCandidate {
|
|
55
|
+
command: Command;
|
|
56
|
+
hotkey: string;
|
|
57
|
+
steps: ParsedStep[];
|
|
58
|
+
remainingSteps: ParsedStep[];
|
|
59
|
+
nextStep: ParsedStep;
|
|
60
|
+
group?: CommandGroup;
|
|
61
|
+
}
|
|
62
|
+
export interface CommandSequenceState {
|
|
63
|
+
prefix: ParsedStep[];
|
|
64
|
+
candidates: CommandSequenceCandidate[];
|
|
65
|
+
}
|
|
43
66
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAC7B,QAAQ,EAAE;QAAE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,OAAO,EAAE,CAAA;KAAE,CAAA;IACjE,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB;AAED,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACxD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAC1E,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAA;AAE1D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,cAAc,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAA;IAC1C,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAC7B,QAAQ,EAAE;QAAE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,OAAO,EAAE,CAAA;KAAE,CAAA;IACjE,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB;AAED,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACxD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAC1E,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAA;AAE1D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,cAAc,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAA;IAC1C,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,sBAAuB,SAAQ,YAAY;IAC1D,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,cAAc,EAAE,UAAU,EAAE,CAAA;IAC5B,QAAQ,EAAE,UAAU,CAAA;IACpB,KAAK,CAAC,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,UAAU,EAAE,wBAAwB,EAAE,CAAA;CACvC"}
|
package/dist/use-actions.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface ActionDefinition {
|
|
|
7
7
|
modes?: Mode[];
|
|
8
8
|
handler: CommandHandler;
|
|
9
9
|
when?: CommandWhen;
|
|
10
|
+
category?: string;
|
|
11
|
+
group?: string;
|
|
12
|
+
icon?: string;
|
|
13
|
+
hidden?: boolean;
|
|
10
14
|
}
|
|
11
15
|
export declare function useActions(actions: ActionDefinition[] | undefined): void;
|
|
12
16
|
//# sourceMappingURL=use-actions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-actions.d.ts","sourceRoot":"","sources":["../src/use-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAW,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,OAAO,EAAE,cAAc,CAAA;IACvB,IAAI,CAAC,EAAE,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"use-actions.d.ts","sourceRoot":"","sources":["../src/use-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAW,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,OAAO,EAAE,cAAc,CAAA;IACvB,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,SAAS,GAAG,IAAI,CA0CxE"}
|
package/dist/use-actions.js
CHANGED
|
@@ -4,7 +4,9 @@ export function useActions(actions) {
|
|
|
4
4
|
const { registry } = useCommandRegistry();
|
|
5
5
|
const actionsRef = useRef(actions);
|
|
6
6
|
actionsRef.current = actions;
|
|
7
|
-
const key = useMemo(() => actions
|
|
7
|
+
const key = useMemo(() => actions
|
|
8
|
+
?.map((a) => `${a.id}:${a.title}:${a.hotkey ?? ""}:${a.category ?? ""}:${a.group ?? ""}:${a.icon ?? ""}:${a.hidden ?? false}`)
|
|
9
|
+
.join(",") ?? "", [actions]);
|
|
8
10
|
useEffect(() => {
|
|
9
11
|
const current = actionsRef.current;
|
|
10
12
|
if (!current || current.length === 0)
|
|
@@ -17,6 +19,10 @@ export function useActions(actions) {
|
|
|
17
19
|
modes: action.modes,
|
|
18
20
|
handler: (ctx) => actionsRef.current?.[i]?.handler(ctx),
|
|
19
21
|
when: action.when ? (ctx) => actionsRef.current?.[i]?.when?.(ctx) ?? false : undefined,
|
|
22
|
+
category: action.category,
|
|
23
|
+
group: action.group,
|
|
24
|
+
icon: action.icon,
|
|
25
|
+
hidden: action.hidden,
|
|
20
26
|
};
|
|
21
27
|
return registry.register(command);
|
|
22
28
|
});
|
package/dist/use-actions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-actions.js","sourceRoot":"","sources":["../src/use-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"use-actions.js","sourceRoot":"","sources":["../src/use-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAiBjD,MAAM,UAAU,UAAU,CAAC,OAAuC;IAChE,MAAM,EAAE,QAAQ,EAAE,GAAG,kBAAkB,EAAE,CAAA;IACzC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;IAClC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAA;IAE5B,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CACH,OAAO;QACL,EAAE,GAAG,CACH,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,EAAE,CACnH;SACA,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EACpB,CAAC,OAAO,CAAC,CACV,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAA;QAClC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QAE5C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YAC5C,MAAM,OAAO,GAAY;gBACvB,EAAE,EAAE,MAAM,CAAC,EAAE;gBACb,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,aAAa,EAAE,MAAM,CAAC,MAAM;gBAC5B,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC;gBACvD,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,SAAS;gBACtF,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAA;YACD,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QACnC,CAAC,CAAC,CAAA;QAEF,OAAO,GAAG,EAAE;YACV,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;gBACrC,UAAU,EAAE,CAAA;YACd,CAAC;QACH,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;AACrB,CAAC"}
|
package/package.json
CHANGED
package/src/context.tsx
CHANGED
|
@@ -5,10 +5,20 @@ import {
|
|
|
5
5
|
useCallback,
|
|
6
6
|
useEffect,
|
|
7
7
|
useMemo,
|
|
8
|
+
useState,
|
|
8
9
|
type ReactNode,
|
|
9
10
|
} from "react"
|
|
10
11
|
import { useKeyboard } from "@opentui/react"
|
|
11
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
Command,
|
|
14
|
+
CommandContext,
|
|
15
|
+
CommandGroup,
|
|
16
|
+
CommandRegistry,
|
|
17
|
+
CommandSequenceState,
|
|
18
|
+
ParsedHotkey,
|
|
19
|
+
ParsedStep,
|
|
20
|
+
RegisteredCommandGroup,
|
|
21
|
+
} from "./types.js"
|
|
12
22
|
import type { Mode } from "./mode.js"
|
|
13
23
|
import { ModeProvider, useMode, useSetMode } from "./mode.js"
|
|
14
24
|
import { parseHotkey } from "./parse.js"
|
|
@@ -23,21 +33,30 @@ interface CommandContextValue {
|
|
|
23
33
|
registry: CommandRegistry
|
|
24
34
|
leaderKey?: string
|
|
25
35
|
contextSources: Map<string, ContextGetter>
|
|
36
|
+
groups: Map<string, RegisteredCommandGroup>
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
const CommandContext = createContext<CommandContextValue | null>(null)
|
|
40
|
+
const CommandSequenceContext = createContext<CommandSequenceState | null>(null)
|
|
29
41
|
|
|
30
42
|
export interface CommandProviderProps {
|
|
31
43
|
children: ReactNode
|
|
32
44
|
leader?: string
|
|
33
45
|
keymap?: Record<string, string>
|
|
34
46
|
initialMode?: Mode
|
|
47
|
+
sequenceTimeoutMs?: number
|
|
35
48
|
}
|
|
36
49
|
|
|
37
|
-
export function CommandProvider({
|
|
50
|
+
export function CommandProvider({
|
|
51
|
+
children,
|
|
52
|
+
leader,
|
|
53
|
+
keymap,
|
|
54
|
+
initialMode,
|
|
55
|
+
sequenceTimeoutMs,
|
|
56
|
+
}: CommandProviderProps) {
|
|
38
57
|
return (
|
|
39
58
|
<ModeProvider initialMode={initialMode}>
|
|
40
|
-
<CommandDispatcher leader={leader} keymap={keymap}>
|
|
59
|
+
<CommandDispatcher leader={leader} keymap={keymap} sequenceTimeoutMs={sequenceTimeoutMs}>
|
|
41
60
|
{children}
|
|
42
61
|
</CommandDispatcher>
|
|
43
62
|
</ModeProvider>
|
|
@@ -48,17 +67,23 @@ function CommandDispatcher({
|
|
|
48
67
|
children,
|
|
49
68
|
leader,
|
|
50
69
|
keymap,
|
|
70
|
+
sequenceTimeoutMs,
|
|
51
71
|
}: {
|
|
52
72
|
children: ReactNode
|
|
53
73
|
leader?: string
|
|
54
74
|
keymap?: Record<string, string>
|
|
75
|
+
sequenceTimeoutMs?: number
|
|
55
76
|
}) {
|
|
56
77
|
const registryRef = useRef<CommandRegistry | null>(null)
|
|
57
78
|
const contextSourcesRef = useRef(new Map<string, ContextGetter>())
|
|
79
|
+
const groupsRef = useRef(new Map<string, RegisteredCommandGroup>())
|
|
58
80
|
const mode = useMode()
|
|
59
81
|
const modeRef = useRef(mode)
|
|
60
82
|
modeRef.current = mode
|
|
61
83
|
const setMode = useSetMode()
|
|
84
|
+
const [sequenceState, setSequenceState] = useState<CommandSequenceState | null>(null)
|
|
85
|
+
const clearSequenceStateRef = useRef(() => setSequenceState(null))
|
|
86
|
+
clearSequenceStateRef.current = () => setSequenceState(null)
|
|
62
87
|
|
|
63
88
|
const buildCtx = useCallback((): CommandContext => {
|
|
64
89
|
const ctx: Record<string, any> = {
|
|
@@ -96,7 +121,12 @@ function CommandDispatcher({
|
|
|
96
121
|
}
|
|
97
122
|
}
|
|
98
123
|
|
|
99
|
-
const trackerRef = useRef(
|
|
124
|
+
const trackerRef = useRef(
|
|
125
|
+
new SequenceTracker({
|
|
126
|
+
timeout: sequenceTimeoutMs,
|
|
127
|
+
onReset: () => clearSequenceStateRef.current(),
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
100
130
|
const parseCacheRef = useRef(new Map<string, ParsedHotkey>())
|
|
101
131
|
|
|
102
132
|
const getParsedHotkey = useCallback(
|
|
@@ -124,8 +154,7 @@ function CommandDispatcher({
|
|
|
124
154
|
|
|
125
155
|
// Collect eligible commands with their parsed hotkeys
|
|
126
156
|
const singleStepCandidates: { command: Command; parsed: ParsedHotkey }[] = []
|
|
127
|
-
const
|
|
128
|
-
const multiStepCommands: Command[] = []
|
|
157
|
+
const multiStepCandidates: { command: Command; hotkey: string; parsed: ParsedHotkey }[] = []
|
|
129
158
|
|
|
130
159
|
for (const command of registry.commands.values()) {
|
|
131
160
|
const commandModes = command.modes ?? DEFAULT_MODES
|
|
@@ -140,41 +169,77 @@ function CommandDispatcher({
|
|
|
140
169
|
if (parsed.steps.length === 1) {
|
|
141
170
|
singleStepCandidates.push({ command, parsed })
|
|
142
171
|
} else {
|
|
143
|
-
|
|
144
|
-
multiStepCommands.push(command)
|
|
172
|
+
multiStepCandidates.push({ command, hotkey, parsed })
|
|
145
173
|
}
|
|
146
174
|
}
|
|
147
175
|
|
|
148
176
|
// Check multi-step sequences first (they consume buffer state)
|
|
149
|
-
if (
|
|
150
|
-
const
|
|
151
|
-
|
|
177
|
+
if (multiStepCandidates.length > 0) {
|
|
178
|
+
const multiStepHotkeys = multiStepCandidates.map((candidate) => candidate.parsed)
|
|
179
|
+
const result = trackerRef.current.feedWithState(event, multiStepHotkeys)
|
|
180
|
+
if (result.matchedIndex >= 0) {
|
|
181
|
+
event.preventDefault()
|
|
182
|
+
setSequenceState(null)
|
|
183
|
+
multiStepCandidates[result.matchedIndex]!.command.handler(ctx)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (result.pending) {
|
|
188
|
+
const firstCandidate = multiStepCandidates[result.pending.indexes[0]!]!
|
|
189
|
+
setSequenceState({
|
|
190
|
+
prefix: firstCandidate.parsed.steps.slice(0, result.pending.prefixLength),
|
|
191
|
+
candidates: result.pending.indexes
|
|
192
|
+
.map((idx) => multiStepCandidates[idx]!)
|
|
193
|
+
.filter(({ command }) => !command.hidden)
|
|
194
|
+
.map(({ command, hotkey, parsed }) => ({
|
|
195
|
+
command,
|
|
196
|
+
hotkey,
|
|
197
|
+
steps: parsed.steps,
|
|
198
|
+
remainingSteps: parsed.steps.slice(result.pending!.prefixLength),
|
|
199
|
+
nextStep: parsed.steps[result.pending!.prefixLength]!,
|
|
200
|
+
group: groupsRef.current.get(
|
|
201
|
+
stepsKey(parsed.steps.slice(0, result.pending!.prefixLength + 1)),
|
|
202
|
+
),
|
|
203
|
+
})),
|
|
204
|
+
})
|
|
152
205
|
event.preventDefault()
|
|
153
|
-
multiStepCommands[idx]!.handler(ctx)
|
|
154
206
|
return
|
|
155
207
|
}
|
|
208
|
+
|
|
209
|
+
setSequenceState(null)
|
|
156
210
|
}
|
|
157
211
|
|
|
158
212
|
// Check single-step matches
|
|
159
213
|
for (const { command, parsed } of singleStepCandidates) {
|
|
160
214
|
if (matchStep(event, parsed.steps[0]!)) {
|
|
161
215
|
event.preventDefault()
|
|
216
|
+
setSequenceState(null)
|
|
162
217
|
command.handler(ctx)
|
|
163
218
|
return
|
|
164
219
|
}
|
|
165
220
|
}
|
|
166
221
|
})
|
|
167
222
|
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
trackerRef.current.reset()
|
|
225
|
+
setSequenceState(null)
|
|
226
|
+
}, [mode])
|
|
227
|
+
|
|
168
228
|
const ctxValue = useMemo(
|
|
169
229
|
() => ({
|
|
170
230
|
registry: registryRef.current!,
|
|
171
231
|
leaderKey: leader,
|
|
172
232
|
contextSources: contextSourcesRef.current,
|
|
233
|
+
groups: groupsRef.current,
|
|
173
234
|
}),
|
|
174
235
|
[leader],
|
|
175
236
|
)
|
|
176
237
|
|
|
177
|
-
return
|
|
238
|
+
return (
|
|
239
|
+
<CommandContext.Provider value={ctxValue}>
|
|
240
|
+
<CommandSequenceContext value={sequenceState}>{children}</CommandSequenceContext>
|
|
241
|
+
</CommandContext.Provider>
|
|
242
|
+
)
|
|
178
243
|
}
|
|
179
244
|
|
|
180
245
|
export function useCommandContext(): { commands: Command[]; invoke: (id: string) => void } {
|
|
@@ -200,6 +265,56 @@ export function useCommandRegistry(): CommandContextValue {
|
|
|
200
265
|
return ctx
|
|
201
266
|
}
|
|
202
267
|
|
|
268
|
+
export function useCommandSequenceState(): CommandSequenceState | null {
|
|
269
|
+
return useContext(CommandSequenceContext)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function useCommandGroup(group: CommandGroup): void {
|
|
273
|
+
const ctx = useContext(CommandContext)
|
|
274
|
+
if (!ctx) {
|
|
275
|
+
throw new Error("useCommandGroup must be used within a CommandProvider")
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const groupRef = useRef(group)
|
|
279
|
+
groupRef.current = group
|
|
280
|
+
const { groups, leaderKey } = ctx
|
|
281
|
+
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
const parsed = parseHotkey(groupRef.current.prefix, leaderKey)
|
|
284
|
+
const registered: RegisteredCommandGroup = {
|
|
285
|
+
...groupRef.current,
|
|
286
|
+
prefixKey: stepsKey(parsed.steps),
|
|
287
|
+
}
|
|
288
|
+
groups.set(registered.prefixKey, registered)
|
|
289
|
+
return () => {
|
|
290
|
+
groups.delete(registered.prefixKey)
|
|
291
|
+
}
|
|
292
|
+
}, [
|
|
293
|
+
group.id,
|
|
294
|
+
group.prefix,
|
|
295
|
+
group.title,
|
|
296
|
+
group.description,
|
|
297
|
+
group.icon,
|
|
298
|
+
group.order,
|
|
299
|
+
groups,
|
|
300
|
+
leaderKey,
|
|
301
|
+
])
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function stepsKey(steps: ParsedStep[]): string {
|
|
305
|
+
return steps.map(formatStepKey).join(" ")
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function formatStepKey(step: ParsedStep): string {
|
|
309
|
+
const modifiers = []
|
|
310
|
+
if (step.ctrl) modifiers.push("ctrl")
|
|
311
|
+
if (step.meta) modifiers.push("meta")
|
|
312
|
+
if (step.option) modifiers.push("option")
|
|
313
|
+
if (step.shift) modifiers.push("shift")
|
|
314
|
+
modifiers.push(step.key)
|
|
315
|
+
return modifiers.join("+")
|
|
316
|
+
}
|
|
317
|
+
|
|
203
318
|
let nextContextSourceId = 0
|
|
204
319
|
|
|
205
320
|
export function useProvideCommandContext(getter: () => Partial<CommandContext>): void {
|
package/src/index.ts
CHANGED
|
@@ -2,19 +2,34 @@ export type {
|
|
|
2
2
|
Command,
|
|
3
3
|
CommandContext,
|
|
4
4
|
CommandContextBase,
|
|
5
|
+
CommandGroup,
|
|
5
6
|
CommandHandler,
|
|
7
|
+
CommandSequenceCandidate,
|
|
8
|
+
CommandSequenceState,
|
|
6
9
|
CommandWhen,
|
|
7
10
|
ParsedHotkey,
|
|
8
11
|
ParsedStep,
|
|
12
|
+
RegisteredCommandGroup,
|
|
9
13
|
} from "./types.js"
|
|
10
14
|
export type { Mode } from "./mode.js"
|
|
11
15
|
export { ModeProvider, useMode, useSetMode } from "./mode.js"
|
|
12
16
|
export type { ModeProviderProps } from "./mode.js"
|
|
13
17
|
export { parseHotkey } from "./parse.js"
|
|
14
18
|
export { matchStep } from "./match.js"
|
|
15
|
-
export { SequenceTracker } from "./sequence.js"
|
|
16
|
-
export type {
|
|
17
|
-
|
|
19
|
+
export { DEFAULT_SEQUENCE_TIMEOUT_MS, SequenceTracker } from "./sequence.js"
|
|
20
|
+
export type {
|
|
21
|
+
SequenceFeedResult,
|
|
22
|
+
SequencePendingMatch,
|
|
23
|
+
SequenceTrackerOptions,
|
|
24
|
+
} from "./sequence.js"
|
|
25
|
+
export {
|
|
26
|
+
CommandProvider,
|
|
27
|
+
useCommandContext,
|
|
28
|
+
useCommandGroup,
|
|
29
|
+
useCommandRegistry,
|
|
30
|
+
useCommandSequenceState,
|
|
31
|
+
useProvideCommandContext,
|
|
32
|
+
} from "./context.js"
|
|
18
33
|
export type { CommandProviderProps } from "./context.js"
|
|
19
34
|
export { useCommand } from "./use-command.js"
|
|
20
35
|
export type { UseCommandOptions } from "./use-command.js"
|
package/src/sequence.ts
CHANGED
|
@@ -2,24 +2,46 @@ import type { KeyEvent } from "@opentui/core"
|
|
|
2
2
|
import type { ParsedHotkey } from "./types.js"
|
|
3
3
|
import { matchStep } from "./match.js"
|
|
4
4
|
|
|
5
|
+
export const DEFAULT_SEQUENCE_TIMEOUT_MS = 1500
|
|
6
|
+
|
|
5
7
|
export interface SequenceTrackerOptions {
|
|
6
|
-
timeout?: number // ms, default
|
|
8
|
+
timeout?: number // ms, default DEFAULT_SEQUENCE_TIMEOUT_MS
|
|
9
|
+
onReset?: () => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SequencePendingMatch {
|
|
13
|
+
prefixLength: number
|
|
14
|
+
indexes: number[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SequenceFeedResult {
|
|
18
|
+
matchedIndex: number
|
|
19
|
+
pending: SequencePendingMatch | null
|
|
7
20
|
}
|
|
8
21
|
|
|
9
22
|
export class SequenceTracker {
|
|
10
23
|
private buffer: KeyEvent[] = []
|
|
11
24
|
private timer: ReturnType<typeof setTimeout> | null = null
|
|
12
25
|
private timeout: number
|
|
26
|
+
private onReset?: () => void
|
|
13
27
|
|
|
14
28
|
constructor(options?: SequenceTrackerOptions) {
|
|
15
|
-
this.timeout = options?.timeout ??
|
|
29
|
+
this.timeout = options?.timeout ?? DEFAULT_SEQUENCE_TIMEOUT_MS
|
|
30
|
+
this.onReset = options?.onReset
|
|
16
31
|
}
|
|
17
32
|
|
|
18
33
|
/**
|
|
19
34
|
* Feed a key event and check against registered hotkeys.
|
|
20
|
-
* Returns the index of the matched hotkey, or -1 if no match.
|
|
35
|
+
* Returns the index of the matched hotkey, or -1 if no full match.
|
|
21
36
|
*/
|
|
22
37
|
feed(event: KeyEvent, hotkeys: ParsedHotkey[]): number {
|
|
38
|
+
return this.feedWithState(event, hotkeys).matchedIndex
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Feed a key event and return both full-match and pending-prefix state.
|
|
43
|
+
*/
|
|
44
|
+
feedWithState(event: KeyEvent, hotkeys: ParsedHotkey[]): SequenceFeedResult {
|
|
23
45
|
this.buffer.push(event)
|
|
24
46
|
this.resetTimer()
|
|
25
47
|
|
|
@@ -27,17 +49,23 @@ export class SequenceTracker {
|
|
|
27
49
|
const hotkey = hotkeys[i]!
|
|
28
50
|
if (this.matchesBuffer(hotkey)) {
|
|
29
51
|
this.reset()
|
|
30
|
-
return i
|
|
52
|
+
return { matchedIndex: i, pending: null }
|
|
31
53
|
}
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
// Prune buffer if no hotkey could possibly match
|
|
35
|
-
const maxLen = Math.max(...hotkeys.map((h) => h.steps.length))
|
|
36
|
-
if (
|
|
37
|
-
this.buffer.
|
|
57
|
+
const maxLen = Math.max(0, ...hotkeys.map((h) => h.steps.length))
|
|
58
|
+
if (maxLen > 0) {
|
|
59
|
+
while (this.buffer.length > maxLen) {
|
|
60
|
+
this.buffer.shift()
|
|
61
|
+
}
|
|
38
62
|
}
|
|
39
63
|
|
|
40
|
-
return -1
|
|
64
|
+
return { matchedIndex: -1, pending: this.findPendingMatch(hotkeys) }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getPendingMatch(hotkeys: ParsedHotkey[]): SequencePendingMatch | null {
|
|
68
|
+
return this.findPendingMatch(hotkeys)
|
|
41
69
|
}
|
|
42
70
|
|
|
43
71
|
private matchesBuffer(hotkey: ParsedHotkey): boolean {
|
|
@@ -51,9 +79,42 @@ export class SequenceTracker {
|
|
|
51
79
|
return true
|
|
52
80
|
}
|
|
53
81
|
|
|
82
|
+
private findPendingMatch(hotkeys: ParsedHotkey[]): SequencePendingMatch | null {
|
|
83
|
+
const maxPrefixLength = Math.min(
|
|
84
|
+
this.buffer.length,
|
|
85
|
+
Math.max(0, ...hotkeys.map((h) => h.steps.length - 1)),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
for (let prefixLength = maxPrefixLength; prefixLength > 0; prefixLength--) {
|
|
89
|
+
const start = this.buffer.length - prefixLength
|
|
90
|
+
const indexes: number[] = []
|
|
91
|
+
|
|
92
|
+
for (let hotkeyIndex = 0; hotkeyIndex < hotkeys.length; hotkeyIndex++) {
|
|
93
|
+
const hotkey = hotkeys[hotkeyIndex]!
|
|
94
|
+
if (hotkey.steps.length <= prefixLength) continue
|
|
95
|
+
|
|
96
|
+
let matches = true
|
|
97
|
+
for (let i = 0; i < prefixLength; i++) {
|
|
98
|
+
if (!matchStep(this.buffer[start + i]!, hotkey.steps[i]!)) {
|
|
99
|
+
matches = false
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (matches) indexes.push(hotkeyIndex)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (indexes.length > 0) return { prefixLength, indexes }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
|
|
54
113
|
reset(): void {
|
|
114
|
+
const hadBuffer = this.buffer.length > 0
|
|
55
115
|
this.buffer = []
|
|
56
116
|
this.clearTimer()
|
|
117
|
+
if (hadBuffer) this.onReset?.()
|
|
57
118
|
}
|
|
58
119
|
|
|
59
120
|
private resetTimer(): void {
|
package/src/types.ts
CHANGED
|
@@ -44,3 +44,30 @@ export interface CommandRegistry {
|
|
|
44
44
|
register: (command: Command) => () => void
|
|
45
45
|
invoke: (id: string) => void
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
export interface CommandGroup {
|
|
49
|
+
id: string
|
|
50
|
+
title: string
|
|
51
|
+
prefix: string
|
|
52
|
+
description?: string
|
|
53
|
+
icon?: string
|
|
54
|
+
order?: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RegisteredCommandGroup extends CommandGroup {
|
|
58
|
+
prefixKey: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CommandSequenceCandidate {
|
|
62
|
+
command: Command
|
|
63
|
+
hotkey: string
|
|
64
|
+
steps: ParsedStep[]
|
|
65
|
+
remainingSteps: ParsedStep[]
|
|
66
|
+
nextStep: ParsedStep
|
|
67
|
+
group?: CommandGroup
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface CommandSequenceState {
|
|
71
|
+
prefix: ParsedStep[]
|
|
72
|
+
candidates: CommandSequenceCandidate[]
|
|
73
|
+
}
|
package/src/use-actions.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface ActionDefinition {
|
|
|
10
10
|
modes?: Mode[]
|
|
11
11
|
handler: CommandHandler
|
|
12
12
|
when?: CommandWhen
|
|
13
|
+
category?: string
|
|
14
|
+
group?: string
|
|
15
|
+
icon?: string
|
|
16
|
+
hidden?: boolean
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export function useActions(actions: ActionDefinition[] | undefined): void {
|
|
@@ -18,7 +22,13 @@ export function useActions(actions: ActionDefinition[] | undefined): void {
|
|
|
18
22
|
actionsRef.current = actions
|
|
19
23
|
|
|
20
24
|
const key = useMemo(
|
|
21
|
-
() =>
|
|
25
|
+
() =>
|
|
26
|
+
actions
|
|
27
|
+
?.map(
|
|
28
|
+
(a) =>
|
|
29
|
+
`${a.id}:${a.title}:${a.hotkey ?? ""}:${a.category ?? ""}:${a.group ?? ""}:${a.icon ?? ""}:${a.hidden ?? false}`,
|
|
30
|
+
)
|
|
31
|
+
.join(",") ?? "",
|
|
22
32
|
[actions],
|
|
23
33
|
)
|
|
24
34
|
|
|
@@ -34,6 +44,10 @@ export function useActions(actions: ActionDefinition[] | undefined): void {
|
|
|
34
44
|
modes: action.modes,
|
|
35
45
|
handler: (ctx) => actionsRef.current?.[i]?.handler(ctx),
|
|
36
46
|
when: action.when ? (ctx) => actionsRef.current?.[i]?.when?.(ctx) ?? false : undefined,
|
|
47
|
+
category: action.category,
|
|
48
|
+
group: action.group,
|
|
49
|
+
icon: action.icon,
|
|
50
|
+
hidden: action.hidden,
|
|
37
51
|
}
|
|
38
52
|
return registry.register(command)
|
|
39
53
|
})
|