use-kbd 0.3.0 → 0.5.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 +35 -36
- package/dist/index.cjs +2145 -409
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +646 -76
- package/dist/index.d.ts +646 -76
- package/dist/index.js +2108 -405
- package/dist/index.js.map +1 -1
- package/package.json +6 -8
- package/src/styles.css +302 -67
package/dist/index.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createContext, useRef, useState, useCallback, useMemo, useEffect, useContext, Fragment as Fragment$1 } from 'react';
|
|
3
|
-
import { max, min } from '@rdub/base';
|
|
1
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { createContext, forwardRef, useRef, useState, useCallback, useMemo, useEffect, useContext, Fragment as Fragment$1 } from 'react';
|
|
4
3
|
|
|
5
|
-
// src/
|
|
4
|
+
// src/types.ts
|
|
5
|
+
function extractCaptures(state) {
|
|
6
|
+
return state.filter(
|
|
7
|
+
(e) => (e.type === "digit" || e.type === "digits") && e.value !== void 0
|
|
8
|
+
).map((e) => e.value);
|
|
9
|
+
}
|
|
10
|
+
function isDigitPlaceholder(elem) {
|
|
11
|
+
return elem.type === "digit" || elem.type === "digits";
|
|
12
|
+
}
|
|
13
|
+
function countPlaceholders(seq) {
|
|
14
|
+
return seq.filter(isDigitPlaceholder).length;
|
|
15
|
+
}
|
|
6
16
|
function createTwoColumnRenderer(config) {
|
|
7
17
|
const { headers, getRows } = config;
|
|
8
18
|
const [labelHeader, leftHeader, rightHeader] = headers;
|
|
@@ -60,9 +70,7 @@ function useActionsRegistry(options = {}) {
|
|
|
60
70
|
const filterRedundantOverrides = useCallback((overrides2) => {
|
|
61
71
|
const filtered = {};
|
|
62
72
|
for (const [key, actionOrActions] of Object.entries(overrides2)) {
|
|
63
|
-
if (actionOrActions === "") {
|
|
64
|
-
continue;
|
|
65
|
-
} else if (Array.isArray(actionOrActions)) {
|
|
73
|
+
if (actionOrActions === "") ; else if (Array.isArray(actionOrActions)) {
|
|
66
74
|
const nonDefaultActions = actionOrActions.filter((a) => !isDefaultBinding(key, a));
|
|
67
75
|
if (nonDefaultActions.length > 0) {
|
|
68
76
|
filtered[key] = nonDefaultActions.length === 1 ? nonDefaultActions[0] : nonDefaultActions;
|
|
@@ -126,10 +134,10 @@ function useActionsRegistry(options = {}) {
|
|
|
126
134
|
actionsRef.current.delete(id);
|
|
127
135
|
setActionsVersion((v) => v + 1);
|
|
128
136
|
}, []);
|
|
129
|
-
const execute = useCallback((id) => {
|
|
137
|
+
const execute = useCallback((id, captures) => {
|
|
130
138
|
const action = actionsRef.current.get(id);
|
|
131
139
|
if (action && (action.config.enabled ?? true)) {
|
|
132
|
-
action.config.handler();
|
|
140
|
+
action.config.handler(void 0, captures);
|
|
133
141
|
}
|
|
134
142
|
}, []);
|
|
135
143
|
const keymap = useMemo(() => {
|
|
@@ -153,9 +161,7 @@ function useActionsRegistry(options = {}) {
|
|
|
153
161
|
}
|
|
154
162
|
}
|
|
155
163
|
for (const [key, actionOrActions] of Object.entries(overrides)) {
|
|
156
|
-
if (actionOrActions === "") {
|
|
157
|
-
continue;
|
|
158
|
-
} else {
|
|
164
|
+
if (actionOrActions === "") ; else {
|
|
159
165
|
const actions2 = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
160
166
|
for (const actionId of actions2) {
|
|
161
167
|
addToKey(key, actionId);
|
|
@@ -185,34 +191,55 @@ function useActionsRegistry(options = {}) {
|
|
|
185
191
|
}
|
|
186
192
|
return bindings;
|
|
187
193
|
}, [keymap]);
|
|
194
|
+
const getFirstBindingForAction = useCallback((actionId) => {
|
|
195
|
+
return getBindingsForAction(actionId)[0];
|
|
196
|
+
}, [getBindingsForAction]);
|
|
188
197
|
const setBinding = useCallback((actionId, key) => {
|
|
189
|
-
|
|
190
|
-
...prev,
|
|
191
|
-
[key]: actionId
|
|
192
|
-
}));
|
|
193
|
-
}, [updateOverrides]);
|
|
194
|
-
const removeBinding = useCallback((key) => {
|
|
195
|
-
const actionsWithDefault = [];
|
|
196
|
-
for (const [id, { config }] of actionsRef.current) {
|
|
197
|
-
if (config.defaultBindings?.includes(key)) {
|
|
198
|
-
actionsWithDefault.push(id);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (actionsWithDefault.length > 0) {
|
|
198
|
+
if (isDefaultBinding(key, actionId)) {
|
|
202
199
|
updateRemovedDefaults((prev) => {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
207
|
-
|
|
200
|
+
const existing = prev[actionId] ?? [];
|
|
201
|
+
if (existing.includes(key)) {
|
|
202
|
+
const filtered = existing.filter((k) => k !== key);
|
|
203
|
+
if (filtered.length === 0) {
|
|
204
|
+
const { [actionId]: _, ...rest } = prev;
|
|
205
|
+
return rest;
|
|
208
206
|
}
|
|
207
|
+
return { ...prev, [actionId]: filtered };
|
|
209
208
|
}
|
|
210
|
-
return
|
|
209
|
+
return prev;
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
updateOverrides((prev) => ({
|
|
213
|
+
...prev,
|
|
214
|
+
[key]: actionId
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
}, [updateOverrides, updateRemovedDefaults, isDefaultBinding]);
|
|
218
|
+
const removeBinding = useCallback((actionId, key) => {
|
|
219
|
+
const action = actionsRef.current.get(actionId);
|
|
220
|
+
const isDefault = action?.config.defaultBindings?.includes(key);
|
|
221
|
+
if (isDefault) {
|
|
222
|
+
updateRemovedDefaults((prev) => {
|
|
223
|
+
const existing = prev[actionId] ?? [];
|
|
224
|
+
if (existing.includes(key)) return prev;
|
|
225
|
+
return { ...prev, [actionId]: [...existing, key] };
|
|
211
226
|
});
|
|
212
227
|
}
|
|
213
228
|
updateOverrides((prev) => {
|
|
214
|
-
const
|
|
215
|
-
|
|
229
|
+
const boundAction = prev[key];
|
|
230
|
+
if (boundAction === actionId) {
|
|
231
|
+
const { [key]: _, ...rest } = prev;
|
|
232
|
+
return rest;
|
|
233
|
+
}
|
|
234
|
+
if (Array.isArray(boundAction) && boundAction.includes(actionId)) {
|
|
235
|
+
const newActions = boundAction.filter((a) => a !== actionId);
|
|
236
|
+
if (newActions.length === 0) {
|
|
237
|
+
const { [key]: _, ...rest } = prev;
|
|
238
|
+
return rest;
|
|
239
|
+
}
|
|
240
|
+
return { ...prev, [key]: newActions.length === 1 ? newActions[0] : newActions };
|
|
241
|
+
}
|
|
242
|
+
return prev;
|
|
216
243
|
});
|
|
217
244
|
}, [updateOverrides, updateRemovedDefaults]);
|
|
218
245
|
const resetOverrides = useCallback(() => {
|
|
@@ -230,6 +257,7 @@ function useActionsRegistry(options = {}) {
|
|
|
230
257
|
keymap,
|
|
231
258
|
actionRegistry,
|
|
232
259
|
getBindingsForAction,
|
|
260
|
+
getFirstBindingForAction,
|
|
233
261
|
overrides,
|
|
234
262
|
setBinding,
|
|
235
263
|
removeBinding,
|
|
@@ -242,15 +270,119 @@ function useActionsRegistry(options = {}) {
|
|
|
242
270
|
keymap,
|
|
243
271
|
actionRegistry,
|
|
244
272
|
getBindingsForAction,
|
|
273
|
+
getFirstBindingForAction,
|
|
245
274
|
overrides,
|
|
246
275
|
setBinding,
|
|
247
276
|
removeBinding,
|
|
248
277
|
resetOverrides
|
|
249
278
|
]);
|
|
250
279
|
}
|
|
280
|
+
var OmnibarEndpointsRegistryContext = createContext(null);
|
|
281
|
+
function useOmnibarEndpointsRegistry() {
|
|
282
|
+
const endpointsRef = useRef(/* @__PURE__ */ new Map());
|
|
283
|
+
const [endpointsVersion, setEndpointsVersion] = useState(0);
|
|
284
|
+
const register = useCallback((id, config) => {
|
|
285
|
+
endpointsRef.current.set(id, {
|
|
286
|
+
id,
|
|
287
|
+
config,
|
|
288
|
+
registeredAt: Date.now()
|
|
289
|
+
});
|
|
290
|
+
setEndpointsVersion((v) => v + 1);
|
|
291
|
+
}, []);
|
|
292
|
+
const unregister = useCallback((id) => {
|
|
293
|
+
endpointsRef.current.delete(id);
|
|
294
|
+
setEndpointsVersion((v) => v + 1);
|
|
295
|
+
}, []);
|
|
296
|
+
const queryEndpoint = useCallback(async (endpointId, query, pagination, signal) => {
|
|
297
|
+
const ep = endpointsRef.current.get(endpointId);
|
|
298
|
+
if (!ep) return null;
|
|
299
|
+
if (ep.config.enabled === false) return null;
|
|
300
|
+
if (query.length < (ep.config.minQueryLength ?? 2)) return null;
|
|
301
|
+
try {
|
|
302
|
+
const response = await ep.config.fetch(query, signal, pagination);
|
|
303
|
+
const entriesWithGroup = response.entries.map((entry) => ({
|
|
304
|
+
...entry,
|
|
305
|
+
group: entry.group ?? ep.config.group
|
|
306
|
+
}));
|
|
307
|
+
return {
|
|
308
|
+
endpointId: ep.id,
|
|
309
|
+
entries: entriesWithGroup,
|
|
310
|
+
total: response.total,
|
|
311
|
+
hasMore: response.hasMore
|
|
312
|
+
};
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
315
|
+
return { endpointId: ep.id, entries: [] };
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
endpointId: ep.id,
|
|
319
|
+
entries: [],
|
|
320
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}, []);
|
|
324
|
+
const queryAll = useCallback(async (query, signal) => {
|
|
325
|
+
const endpoints2 = Array.from(endpointsRef.current.values());
|
|
326
|
+
const promises = endpoints2.filter((ep) => ep.config.enabled !== false).filter((ep) => query.length >= (ep.config.minQueryLength ?? 2)).map(async (ep) => {
|
|
327
|
+
const pageSize = ep.config.pageSize ?? 10;
|
|
328
|
+
const result = await queryEndpoint(ep.id, query, { offset: 0, limit: pageSize }, signal);
|
|
329
|
+
return result ?? { endpointId: ep.id, entries: [] };
|
|
330
|
+
});
|
|
331
|
+
return Promise.all(promises);
|
|
332
|
+
}, [queryEndpoint]);
|
|
333
|
+
const endpoints = useMemo(() => {
|
|
334
|
+
return new Map(endpointsRef.current);
|
|
335
|
+
}, [endpointsVersion]);
|
|
336
|
+
return useMemo(() => ({
|
|
337
|
+
register,
|
|
338
|
+
unregister,
|
|
339
|
+
endpoints,
|
|
340
|
+
queryAll,
|
|
341
|
+
queryEndpoint
|
|
342
|
+
}), [register, unregister, endpoints, queryAll, queryEndpoint]);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/constants.ts
|
|
346
|
+
var DEFAULT_SEQUENCE_TIMEOUT = Infinity;
|
|
347
|
+
var ACTION_MODAL = "__hotkeys:modal";
|
|
348
|
+
var ACTION_OMNIBAR = "__hotkeys:omnibar";
|
|
349
|
+
var ACTION_LOOKUP = "__hotkeys:lookup";
|
|
350
|
+
|
|
351
|
+
// src/utils.ts
|
|
352
|
+
var { max } = Math;
|
|
353
|
+
var SHIFTED_SYMBOLS = /* @__PURE__ */ new Set([
|
|
354
|
+
"!",
|
|
355
|
+
"@",
|
|
356
|
+
"#",
|
|
357
|
+
"$",
|
|
358
|
+
"%",
|
|
359
|
+
"^",
|
|
360
|
+
"&",
|
|
361
|
+
"*",
|
|
362
|
+
"(",
|
|
363
|
+
")",
|
|
364
|
+
"_",
|
|
365
|
+
"+",
|
|
366
|
+
"{",
|
|
367
|
+
"}",
|
|
368
|
+
"|",
|
|
369
|
+
":",
|
|
370
|
+
'"',
|
|
371
|
+
"<",
|
|
372
|
+
">",
|
|
373
|
+
"?",
|
|
374
|
+
"~"
|
|
375
|
+
]);
|
|
376
|
+
function isShiftedSymbol(key) {
|
|
377
|
+
return SHIFTED_SYMBOLS.has(key);
|
|
378
|
+
}
|
|
251
379
|
function isMac() {
|
|
252
380
|
if (typeof navigator === "undefined") return false;
|
|
253
|
-
|
|
381
|
+
const platform = navigator.userAgentData?.platform;
|
|
382
|
+
if (platform) {
|
|
383
|
+
return platform === "macOS" || platform === "iOS";
|
|
384
|
+
}
|
|
385
|
+
return /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
254
386
|
}
|
|
255
387
|
function normalizeKey(key) {
|
|
256
388
|
const keyMap = {
|
|
@@ -285,7 +417,7 @@ function formatKeyForDisplay(key) {
|
|
|
285
417
|
"space": "Space",
|
|
286
418
|
"escape": "Esc",
|
|
287
419
|
"enter": "\u21B5",
|
|
288
|
-
"tab": "
|
|
420
|
+
"tab": "\u21E5",
|
|
289
421
|
"backspace": "\u232B",
|
|
290
422
|
"delete": "Del",
|
|
291
423
|
"arrowup": "\u2191",
|
|
@@ -308,7 +440,18 @@ function formatKeyForDisplay(key) {
|
|
|
308
440
|
}
|
|
309
441
|
return key;
|
|
310
442
|
}
|
|
443
|
+
var DIGIT_PLACEHOLDER = "__DIGIT__";
|
|
444
|
+
var DIGITS_PLACEHOLDER = "__DIGITS__";
|
|
445
|
+
function isPlaceholderSentinel(key) {
|
|
446
|
+
return key === DIGIT_PLACEHOLDER || key === DIGITS_PLACEHOLDER;
|
|
447
|
+
}
|
|
311
448
|
function formatSingleCombination(combo) {
|
|
449
|
+
if (combo.key === DIGIT_PLACEHOLDER) {
|
|
450
|
+
return { display: "#", id: "\\d" };
|
|
451
|
+
}
|
|
452
|
+
if (combo.key === DIGITS_PLACEHOLDER) {
|
|
453
|
+
return { display: "##", id: "\\d+" };
|
|
454
|
+
}
|
|
312
455
|
const mac = isMac();
|
|
313
456
|
const parts = [];
|
|
314
457
|
const idParts = [];
|
|
@@ -354,6 +497,10 @@ function formatCombination(input) {
|
|
|
354
497
|
const single = formatSingleCombination(input);
|
|
355
498
|
return { ...single, isSequence: false };
|
|
356
499
|
}
|
|
500
|
+
function formatBinding(binding) {
|
|
501
|
+
const parsed = parseHotkeyString(binding);
|
|
502
|
+
return formatCombination(parsed).display;
|
|
503
|
+
}
|
|
357
504
|
function isModifierKey(key) {
|
|
358
505
|
return ["Control", "Alt", "Shift", "Meta"].includes(key);
|
|
359
506
|
}
|
|
@@ -410,12 +557,110 @@ function parseHotkeyString(hotkeyStr) {
|
|
|
410
557
|
const parts = hotkeyStr.trim().split(/\s+/);
|
|
411
558
|
return parts.map(parseSingleCombination);
|
|
412
559
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
return {
|
|
560
|
+
var NO_MODIFIERS = { ctrl: false, alt: false, shift: false, meta: false };
|
|
561
|
+
function parseSeqElem(str) {
|
|
562
|
+
if (str === "\\d") {
|
|
563
|
+
return { type: "digit" };
|
|
564
|
+
}
|
|
565
|
+
if (str === "\\d+") {
|
|
566
|
+
return { type: "digits" };
|
|
567
|
+
}
|
|
568
|
+
if (str.length === 1 && /^[A-Z]$/.test(str)) {
|
|
569
|
+
return {
|
|
570
|
+
type: "key",
|
|
571
|
+
key: str.toLowerCase(),
|
|
572
|
+
modifiers: { ctrl: false, alt: false, shift: true, meta: false }
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const parts = str.toLowerCase().split("+");
|
|
576
|
+
const key = parts[parts.length - 1];
|
|
577
|
+
return {
|
|
578
|
+
type: "key",
|
|
579
|
+
key,
|
|
580
|
+
modifiers: {
|
|
581
|
+
ctrl: parts.includes("ctrl") || parts.includes("control"),
|
|
582
|
+
alt: parts.includes("alt") || parts.includes("option"),
|
|
583
|
+
shift: parts.includes("shift"),
|
|
584
|
+
meta: parts.includes("meta") || parts.includes("cmd") || parts.includes("command")
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function parseKeySeq(hotkeyStr) {
|
|
589
|
+
if (!hotkeyStr.trim()) return [];
|
|
590
|
+
const parts = hotkeyStr.trim().split(/\s+/);
|
|
591
|
+
return parts.map(parseSeqElem);
|
|
592
|
+
}
|
|
593
|
+
function formatSeqElem(elem) {
|
|
594
|
+
if (elem.type === "digit") {
|
|
595
|
+
return { display: "\u27E8#\u27E9", id: "\\d" };
|
|
596
|
+
}
|
|
597
|
+
if (elem.type === "digits") {
|
|
598
|
+
return { display: "\u27E8##\u27E9", id: "\\d+" };
|
|
599
|
+
}
|
|
600
|
+
const mac = isMac();
|
|
601
|
+
const parts = [];
|
|
602
|
+
const idParts = [];
|
|
603
|
+
if (elem.modifiers.ctrl) {
|
|
604
|
+
parts.push(mac ? "\u2303" : "Ctrl");
|
|
605
|
+
idParts.push("ctrl");
|
|
606
|
+
}
|
|
607
|
+
if (elem.modifiers.meta) {
|
|
608
|
+
parts.push(mac ? "\u2318" : "Win");
|
|
609
|
+
idParts.push("meta");
|
|
610
|
+
}
|
|
611
|
+
if (elem.modifiers.alt) {
|
|
612
|
+
parts.push(mac ? "\u2325" : "Alt");
|
|
613
|
+
idParts.push("alt");
|
|
614
|
+
}
|
|
615
|
+
if (elem.modifiers.shift) {
|
|
616
|
+
parts.push(mac ? "\u21E7" : "Shift");
|
|
617
|
+
idParts.push("shift");
|
|
618
|
+
}
|
|
619
|
+
parts.push(formatKeyForDisplay(elem.key));
|
|
620
|
+
idParts.push(elem.key);
|
|
621
|
+
return {
|
|
622
|
+
display: mac ? parts.join("") : parts.join("+"),
|
|
623
|
+
id: idParts.join("+")
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function formatKeySeq(seq) {
|
|
627
|
+
if (seq.length === 0) {
|
|
628
|
+
return { display: "", id: "", isSequence: false };
|
|
629
|
+
}
|
|
630
|
+
const formatted = seq.map(formatSeqElem);
|
|
631
|
+
if (seq.length === 1) {
|
|
632
|
+
return { ...formatted[0], isSequence: false };
|
|
417
633
|
}
|
|
418
|
-
return
|
|
634
|
+
return {
|
|
635
|
+
display: formatted.map((f) => f.display).join(" "),
|
|
636
|
+
id: formatted.map((f) => f.id).join(" "),
|
|
637
|
+
isSequence: true
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function hasDigitPlaceholders(seq) {
|
|
641
|
+
return seq.some((elem) => elem.type === "digit" || elem.type === "digits");
|
|
642
|
+
}
|
|
643
|
+
function keySeqToHotkeySequence(seq) {
|
|
644
|
+
return seq.map((elem) => {
|
|
645
|
+
if (elem.type === "digit") {
|
|
646
|
+
return { key: "\\d", modifiers: NO_MODIFIERS };
|
|
647
|
+
}
|
|
648
|
+
if (elem.type === "digits") {
|
|
649
|
+
return { key: "\\d+", modifiers: NO_MODIFIERS };
|
|
650
|
+
}
|
|
651
|
+
return { key: elem.key, modifiers: elem.modifiers };
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
function hotkeySequenceToKeySeq(seq) {
|
|
655
|
+
return seq.map((combo) => {
|
|
656
|
+
if (combo.key === "\\d" && !combo.modifiers.ctrl && !combo.modifiers.alt && !combo.modifiers.shift && !combo.modifiers.meta) {
|
|
657
|
+
return { type: "digit" };
|
|
658
|
+
}
|
|
659
|
+
if (combo.key === "\\d+" && !combo.modifiers.ctrl && !combo.modifiers.alt && !combo.modifiers.shift && !combo.modifiers.meta) {
|
|
660
|
+
return { type: "digits" };
|
|
661
|
+
}
|
|
662
|
+
return { type: "key", key: combo.key, modifiers: combo.modifiers };
|
|
663
|
+
});
|
|
419
664
|
}
|
|
420
665
|
function isPrefix(a, b) {
|
|
421
666
|
if (a.length >= b.length) return false;
|
|
@@ -427,11 +672,50 @@ function isPrefix(a, b) {
|
|
|
427
672
|
function combinationsEqual(a, b) {
|
|
428
673
|
return a.key === b.key && a.modifiers.ctrl === b.modifiers.ctrl && a.modifiers.alt === b.modifiers.alt && a.modifiers.shift === b.modifiers.shift && a.modifiers.meta === b.modifiers.meta;
|
|
429
674
|
}
|
|
675
|
+
function keyMatchesPattern(pending, pattern) {
|
|
676
|
+
if (pending.modifiers.ctrl !== pattern.modifiers.ctrl || pending.modifiers.alt !== pattern.modifiers.alt || pending.modifiers.shift !== pattern.modifiers.shift || pending.modifiers.meta !== pattern.modifiers.meta) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
if (pending.key === pattern.key) return true;
|
|
680
|
+
return /^[0-9]$/.test(pending.key) && (pattern.key === DIGIT_PLACEHOLDER || pattern.key === DIGITS_PLACEHOLDER);
|
|
681
|
+
}
|
|
682
|
+
function isDigitKey(key) {
|
|
683
|
+
return /^[0-9]$/.test(key);
|
|
684
|
+
}
|
|
685
|
+
function seqElemsCouldConflict(a, b) {
|
|
686
|
+
if (a.type === "digit" && b.type === "digit") return true;
|
|
687
|
+
if (a.type === "digit" && b.type === "key" && isDigitKey(b.key)) return true;
|
|
688
|
+
if (a.type === "key" && isDigitKey(a.key) && b.type === "digit") return true;
|
|
689
|
+
if (a.type === "digits" && b.type === "digits") return true;
|
|
690
|
+
if (a.type === "digits" && b.type === "digit") return true;
|
|
691
|
+
if (a.type === "digit" && b.type === "digits") return true;
|
|
692
|
+
if (a.type === "digits" && b.type === "key" && isDigitKey(b.key)) return true;
|
|
693
|
+
if (a.type === "key" && isDigitKey(a.key) && b.type === "digits") return true;
|
|
694
|
+
if (a.type === "key" && b.type === "key") {
|
|
695
|
+
return a.key === b.key && a.modifiers.ctrl === b.modifiers.ctrl && a.modifiers.alt === b.modifiers.alt && a.modifiers.shift === b.modifiers.shift && a.modifiers.meta === b.modifiers.meta;
|
|
696
|
+
}
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
function keySeqIsPrefix(a, b) {
|
|
700
|
+
if (a.length >= b.length) return false;
|
|
701
|
+
for (let i = 0; i < a.length; i++) {
|
|
702
|
+
if (!seqElemsCouldConflict(a[i], b[i])) return false;
|
|
703
|
+
}
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
function keySeqsCouldConflict(a, b) {
|
|
707
|
+
if (a.length !== b.length) return false;
|
|
708
|
+
for (let i = 0; i < a.length; i++) {
|
|
709
|
+
if (!seqElemsCouldConflict(a[i], b[i])) return false;
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
430
713
|
function findConflicts(keymap) {
|
|
431
714
|
const conflicts = /* @__PURE__ */ new Map();
|
|
432
715
|
const entries = Object.entries(keymap).map(([key, actionOrActions]) => ({
|
|
433
716
|
key,
|
|
434
717
|
sequence: parseHotkeyString(key),
|
|
718
|
+
keySeq: parseKeySeq(key),
|
|
435
719
|
actions: Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
|
|
436
720
|
}));
|
|
437
721
|
const keyToActions = /* @__PURE__ */ new Map();
|
|
@@ -448,7 +732,36 @@ function findConflicts(keymap) {
|
|
|
448
732
|
for (let j = i + 1; j < entries.length; j++) {
|
|
449
733
|
const a = entries[i];
|
|
450
734
|
const b = entries[j];
|
|
451
|
-
if (
|
|
735
|
+
if (keySeqsCouldConflict(a.keySeq, b.keySeq) && a.key !== b.key) {
|
|
736
|
+
const existingA = conflicts.get(a.key) ?? [];
|
|
737
|
+
if (!existingA.includes(`conflicts with: ${b.key}`)) {
|
|
738
|
+
conflicts.set(a.key, [...existingA, ...a.actions, `conflicts with: ${b.key}`]);
|
|
739
|
+
}
|
|
740
|
+
const existingB = conflicts.get(b.key) ?? [];
|
|
741
|
+
if (!existingB.includes(`conflicts with: ${a.key}`)) {
|
|
742
|
+
conflicts.set(b.key, [...existingB, ...b.actions, `conflicts with: ${a.key}`]);
|
|
743
|
+
}
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
if (keySeqIsPrefix(a.keySeq, b.keySeq)) {
|
|
747
|
+
const existingA = conflicts.get(a.key) ?? [];
|
|
748
|
+
if (!existingA.includes(`prefix of: ${b.key}`)) {
|
|
749
|
+
conflicts.set(a.key, [...existingA, ...a.actions, `prefix of: ${b.key}`]);
|
|
750
|
+
}
|
|
751
|
+
const existingB = conflicts.get(b.key) ?? [];
|
|
752
|
+
if (!existingB.includes(`has prefix: ${a.key}`)) {
|
|
753
|
+
conflicts.set(b.key, [...existingB, ...b.actions, `has prefix: ${a.key}`]);
|
|
754
|
+
}
|
|
755
|
+
} else if (keySeqIsPrefix(b.keySeq, a.keySeq)) {
|
|
756
|
+
const existingB = conflicts.get(b.key) ?? [];
|
|
757
|
+
if (!existingB.includes(`prefix of: ${a.key}`)) {
|
|
758
|
+
conflicts.set(b.key, [...existingB, ...b.actions, `prefix of: ${a.key}`]);
|
|
759
|
+
}
|
|
760
|
+
const existingA = conflicts.get(a.key) ?? [];
|
|
761
|
+
if (!existingA.includes(`has prefix: ${b.key}`)) {
|
|
762
|
+
conflicts.set(a.key, [...existingA, ...a.actions, `has prefix: ${b.key}`]);
|
|
763
|
+
}
|
|
764
|
+
} else if (isPrefix(a.sequence, b.sequence)) {
|
|
452
765
|
const existingA = conflicts.get(a.key) ?? [];
|
|
453
766
|
if (!existingA.includes(`prefix of: ${b.key}`)) {
|
|
454
767
|
conflicts.set(a.key, [...existingA, ...a.actions, `prefix of: ${b.key}`]);
|
|
@@ -486,27 +799,77 @@ function getSequenceCompletions(pendingKeys, keymap) {
|
|
|
486
799
|
if (pendingKeys.length === 0) return [];
|
|
487
800
|
const completions = [];
|
|
488
801
|
for (const [hotkeyStr, actionOrActions] of Object.entries(keymap)) {
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
802
|
+
const keySeq = parseKeySeq(hotkeyStr);
|
|
803
|
+
const hasDigitsPlaceholder = keySeq.some((e) => e.type === "digits");
|
|
804
|
+
if (!hasDigitsPlaceholder && keySeq.length < pendingKeys.length) continue;
|
|
805
|
+
let keySeqIdx = 0;
|
|
806
|
+
let pendingIdx = 0;
|
|
807
|
+
let isMatch = true;
|
|
808
|
+
const captures = [];
|
|
809
|
+
let currentDigits = "";
|
|
810
|
+
for (; pendingIdx < pendingKeys.length && keySeqIdx < keySeq.length; pendingIdx++) {
|
|
811
|
+
const elem = keySeq[keySeqIdx];
|
|
812
|
+
if (elem.type === "digits") {
|
|
813
|
+
if (!/^[0-9]$/.test(pendingKeys[pendingIdx].key)) {
|
|
814
|
+
isMatch = false;
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
currentDigits += pendingKeys[pendingIdx].key;
|
|
818
|
+
if (pendingIdx + 1 < pendingKeys.length && /^[0-9]$/.test(pendingKeys[pendingIdx + 1].key)) {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
captures.push(parseInt(currentDigits, 10));
|
|
822
|
+
currentDigits = "";
|
|
823
|
+
keySeqIdx++;
|
|
824
|
+
} else if (elem.type === "digit") {
|
|
825
|
+
if (!/^[0-9]$/.test(pendingKeys[pendingIdx].key)) {
|
|
826
|
+
isMatch = false;
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
captures.push(parseInt(pendingKeys[pendingIdx].key, 10));
|
|
830
|
+
keySeqIdx++;
|
|
831
|
+
} else {
|
|
832
|
+
const keyElem = elem;
|
|
833
|
+
const targetCombo = { key: keyElem.key, modifiers: keyElem.modifiers };
|
|
834
|
+
if (!keyMatchesPattern(pendingKeys[pendingIdx], targetCombo)) {
|
|
835
|
+
isMatch = false;
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
keySeqIdx++;
|
|
496
839
|
}
|
|
497
840
|
}
|
|
498
|
-
if (
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
841
|
+
if (pendingIdx < pendingKeys.length) {
|
|
842
|
+
isMatch = false;
|
|
843
|
+
}
|
|
844
|
+
if (!isMatch) continue;
|
|
845
|
+
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
846
|
+
if (keySeqIdx === keySeq.length) {
|
|
847
|
+
completions.push({
|
|
848
|
+
nextKeys: "",
|
|
849
|
+
fullSequence: hotkeyStr,
|
|
850
|
+
display: formatKeySeq(keySeq),
|
|
851
|
+
actions,
|
|
852
|
+
isComplete: true,
|
|
853
|
+
captures: captures.length > 0 ? captures : void 0
|
|
854
|
+
});
|
|
855
|
+
} else {
|
|
856
|
+
const remainingKeySeq = keySeq.slice(keySeqIdx);
|
|
857
|
+
const nextKeys = formatKeySeq(remainingKeySeq).display;
|
|
502
858
|
completions.push({
|
|
503
859
|
nextKeys,
|
|
860
|
+
nextKeySeq: remainingKeySeq,
|
|
504
861
|
fullSequence: hotkeyStr,
|
|
505
|
-
display:
|
|
506
|
-
actions
|
|
862
|
+
display: formatKeySeq(keySeq),
|
|
863
|
+
actions,
|
|
864
|
+
isComplete: false,
|
|
865
|
+
captures: captures.length > 0 ? captures : void 0
|
|
507
866
|
});
|
|
508
867
|
}
|
|
509
868
|
}
|
|
869
|
+
completions.sort((a, b) => {
|
|
870
|
+
if (a.isComplete !== b.isComplete) return a.isComplete ? -1 : 1;
|
|
871
|
+
return a.fullSequence.localeCompare(b.fullSequence);
|
|
872
|
+
});
|
|
510
873
|
return completions;
|
|
511
874
|
}
|
|
512
875
|
function getActionBindings(keymap) {
|
|
@@ -597,7 +960,7 @@ function searchActions(query, actions, keymap) {
|
|
|
597
960
|
}
|
|
598
961
|
const matched = labelMatch.matched || descMatch.matched || groupMatch.matched || idMatch.matched || keywordScore > 0;
|
|
599
962
|
if (!matched && query) continue;
|
|
600
|
-
const score = (labelMatch.matched ? labelMatch.score * 3 : 0) + (descMatch.matched ? descMatch.score * 1.5 : 0) + (groupMatch.matched ? groupMatch.score
|
|
963
|
+
const score = (labelMatch.matched ? labelMatch.score * 3 : 0) + (descMatch.matched ? descMatch.score * 1.5 : 0) + (groupMatch.matched ? groupMatch.score : 0) + (idMatch.matched ? idMatch.score * 0.5 : 0) + keywordScore * 2;
|
|
601
964
|
results.push({
|
|
602
965
|
id,
|
|
603
966
|
action,
|
|
@@ -644,6 +1007,95 @@ function sequencesMatch(a, b) {
|
|
|
644
1007
|
}
|
|
645
1008
|
return true;
|
|
646
1009
|
}
|
|
1010
|
+
function isDigit(key) {
|
|
1011
|
+
return /^[0-9]$/.test(key);
|
|
1012
|
+
}
|
|
1013
|
+
function initMatchState(seq) {
|
|
1014
|
+
return seq.map((elem) => {
|
|
1015
|
+
if (elem.type === "digit") return { type: "digit" };
|
|
1016
|
+
if (elem.type === "digits") return { type: "digits" };
|
|
1017
|
+
return { type: "key", key: elem.key, modifiers: elem.modifiers };
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
function matchesKeyElem(combo, elem) {
|
|
1021
|
+
const shiftMatches = isShiftedChar(combo.key) ? elem.modifiers.shift ? combo.modifiers.shift : true : combo.modifiers.shift === elem.modifiers.shift;
|
|
1022
|
+
return combo.modifiers.ctrl === elem.modifiers.ctrl && combo.modifiers.alt === elem.modifiers.alt && shiftMatches && combo.modifiers.meta === elem.modifiers.meta && combo.key === elem.key;
|
|
1023
|
+
}
|
|
1024
|
+
function advanceMatchState(state, pattern, combo) {
|
|
1025
|
+
const newState = [...state];
|
|
1026
|
+
let pos = 0;
|
|
1027
|
+
for (let i = 0; i < state.length; i++) {
|
|
1028
|
+
const elem = state[i];
|
|
1029
|
+
if (elem.type === "key" && !elem.matched) break;
|
|
1030
|
+
if (elem.type === "digit" && elem.value === void 0) break;
|
|
1031
|
+
if (elem.type === "digits" && elem.value === void 0) {
|
|
1032
|
+
if (!elem.partial) break;
|
|
1033
|
+
if (isDigit(combo.key)) {
|
|
1034
|
+
const newPartial = (elem.partial || "") + combo.key;
|
|
1035
|
+
newState[i] = { type: "digits", partial: newPartial };
|
|
1036
|
+
return { status: "partial", state: newState };
|
|
1037
|
+
} else {
|
|
1038
|
+
const digitValue = parseInt(elem.partial, 10);
|
|
1039
|
+
newState[i] = { type: "digits", value: digitValue };
|
|
1040
|
+
pos = i + 1;
|
|
1041
|
+
if (pos >= pattern.length) {
|
|
1042
|
+
return { status: "failed" };
|
|
1043
|
+
}
|
|
1044
|
+
break;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
pos++;
|
|
1048
|
+
}
|
|
1049
|
+
if (pos >= pattern.length) {
|
|
1050
|
+
return { status: "failed" };
|
|
1051
|
+
}
|
|
1052
|
+
const currentPattern = pattern[pos];
|
|
1053
|
+
if (currentPattern.type === "digit") {
|
|
1054
|
+
if (!isDigit(combo.key) || combo.modifiers.ctrl || combo.modifiers.alt || combo.modifiers.meta) {
|
|
1055
|
+
return { status: "failed" };
|
|
1056
|
+
}
|
|
1057
|
+
newState[pos] = { type: "digit", value: parseInt(combo.key, 10) };
|
|
1058
|
+
} else if (currentPattern.type === "digits") {
|
|
1059
|
+
if (!isDigit(combo.key) || combo.modifiers.ctrl || combo.modifiers.alt || combo.modifiers.meta) {
|
|
1060
|
+
return { status: "failed" };
|
|
1061
|
+
}
|
|
1062
|
+
newState[pos] = { type: "digits", partial: combo.key };
|
|
1063
|
+
} else {
|
|
1064
|
+
if (!matchesKeyElem(combo, currentPattern)) {
|
|
1065
|
+
return { status: "failed" };
|
|
1066
|
+
}
|
|
1067
|
+
newState[pos] = { type: "key", key: currentPattern.key, modifiers: currentPattern.modifiers, matched: true };
|
|
1068
|
+
}
|
|
1069
|
+
const isComplete = newState.every((elem) => {
|
|
1070
|
+
if (elem.type === "key") return elem.matched === true;
|
|
1071
|
+
if (elem.type === "digit") return elem.value !== void 0;
|
|
1072
|
+
if (elem.type === "digits") return elem.value !== void 0;
|
|
1073
|
+
return false;
|
|
1074
|
+
});
|
|
1075
|
+
if (isComplete) {
|
|
1076
|
+
const captures = newState.filter(
|
|
1077
|
+
(e) => (e.type === "digit" || e.type === "digits") && e.value !== void 0
|
|
1078
|
+
).map((e) => e.value);
|
|
1079
|
+
return { status: "matched", state: newState, captures };
|
|
1080
|
+
}
|
|
1081
|
+
return { status: "partial", state: newState };
|
|
1082
|
+
}
|
|
1083
|
+
function isCollectingDigits(state) {
|
|
1084
|
+
return state.some((elem) => elem.type === "digits" && elem.partial !== void 0 && elem.value === void 0);
|
|
1085
|
+
}
|
|
1086
|
+
function finalizeDigits(state) {
|
|
1087
|
+
return state.map((elem) => {
|
|
1088
|
+
if (elem.type === "digits" && elem.partial !== void 0 && elem.value === void 0) {
|
|
1089
|
+
return { type: "digits", value: parseInt(elem.partial, 10) };
|
|
1090
|
+
}
|
|
1091
|
+
return elem;
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
function extractMatchCaptures(state) {
|
|
1095
|
+
return state.filter(
|
|
1096
|
+
(e) => (e.type === "digit" || e.type === "digits") && e.value !== void 0
|
|
1097
|
+
).map((e) => e.value);
|
|
1098
|
+
}
|
|
647
1099
|
function useHotkeys(keymap, handlers, options = {}) {
|
|
648
1100
|
const {
|
|
649
1101
|
enabled = true,
|
|
@@ -651,7 +1103,7 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
651
1103
|
preventDefault = true,
|
|
652
1104
|
stopPropagation = true,
|
|
653
1105
|
enableOnFormTags = false,
|
|
654
|
-
sequenceTimeout =
|
|
1106
|
+
sequenceTimeout = DEFAULT_SEQUENCE_TIMEOUT,
|
|
655
1107
|
onTimeout = "submit",
|
|
656
1108
|
onSequenceStart,
|
|
657
1109
|
onSequenceProgress,
|
|
@@ -667,11 +1119,13 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
667
1119
|
const timeoutRef = useRef(null);
|
|
668
1120
|
const pendingKeysRef = useRef([]);
|
|
669
1121
|
pendingKeysRef.current = pendingKeys;
|
|
1122
|
+
const matchStatesRef = useRef(/* @__PURE__ */ new Map());
|
|
670
1123
|
const parsedKeymapRef = useRef([]);
|
|
671
1124
|
useEffect(() => {
|
|
672
1125
|
parsedKeymapRef.current = Object.entries(keymap).map(([key, actionOrActions]) => ({
|
|
673
1126
|
key,
|
|
674
1127
|
sequence: parseHotkeyString(key),
|
|
1128
|
+
keySeq: parseKeySeq(key),
|
|
675
1129
|
actions: Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
|
|
676
1130
|
}));
|
|
677
1131
|
}, [keymap]);
|
|
@@ -679,6 +1133,7 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
679
1133
|
setPendingKeys([]);
|
|
680
1134
|
setIsAwaitingSequence(false);
|
|
681
1135
|
setTimeoutStartedAt(null);
|
|
1136
|
+
matchStatesRef.current.clear();
|
|
682
1137
|
if (timeoutRef.current) {
|
|
683
1138
|
clearTimeout(timeoutRef.current);
|
|
684
1139
|
timeoutRef.current = null;
|
|
@@ -688,7 +1143,7 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
688
1143
|
clearPending();
|
|
689
1144
|
onSequenceCancel?.();
|
|
690
1145
|
}, [clearPending, onSequenceCancel]);
|
|
691
|
-
const tryExecute = useCallback((sequence, e) => {
|
|
1146
|
+
const tryExecute = useCallback((sequence, e, captures) => {
|
|
692
1147
|
for (const entry of parsedKeymapRef.current) {
|
|
693
1148
|
if (sequencesMatch(sequence, entry.sequence)) {
|
|
694
1149
|
for (const action of entry.actions) {
|
|
@@ -700,7 +1155,27 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
700
1155
|
if (stopPropagation) {
|
|
701
1156
|
e.stopPropagation();
|
|
702
1157
|
}
|
|
703
|
-
handler(e);
|
|
1158
|
+
handler(e, captures);
|
|
1159
|
+
return true;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return false;
|
|
1165
|
+
}, [preventDefault, stopPropagation]);
|
|
1166
|
+
const tryExecuteKeySeq = useCallback((matchKey, captures, e) => {
|
|
1167
|
+
for (const entry of parsedKeymapRef.current) {
|
|
1168
|
+
if (entry.key === matchKey) {
|
|
1169
|
+
for (const action of entry.actions) {
|
|
1170
|
+
const handler = handlersRef.current[action];
|
|
1171
|
+
if (handler) {
|
|
1172
|
+
if (preventDefault) {
|
|
1173
|
+
e.preventDefault();
|
|
1174
|
+
}
|
|
1175
|
+
if (stopPropagation) {
|
|
1176
|
+
e.stopPropagation();
|
|
1177
|
+
}
|
|
1178
|
+
handler(e, captures.length > 0 ? captures : void 0);
|
|
704
1179
|
return true;
|
|
705
1180
|
}
|
|
706
1181
|
}
|
|
@@ -730,7 +1205,8 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
730
1205
|
const handleKeyDown = (e) => {
|
|
731
1206
|
if (!enableOnFormTags) {
|
|
732
1207
|
const eventTarget = e.target;
|
|
733
|
-
|
|
1208
|
+
const isTextInput = eventTarget instanceof HTMLInputElement && ["text", "email", "password", "search", "tel", "url", "number", "date", "datetime-local", "month", "time", "week"].includes(eventTarget.type);
|
|
1209
|
+
if (isTextInput || eventTarget instanceof HTMLTextAreaElement || eventTarget instanceof HTMLSelectElement || eventTarget.isContentEditable) {
|
|
734
1210
|
return;
|
|
735
1211
|
}
|
|
736
1212
|
}
|
|
@@ -743,7 +1219,24 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
743
1219
|
}
|
|
744
1220
|
if (e.key === "Enter" && pendingKeysRef.current.length > 0) {
|
|
745
1221
|
e.preventDefault();
|
|
746
|
-
|
|
1222
|
+
let executed = false;
|
|
1223
|
+
for (const [key, state] of matchStatesRef.current.entries()) {
|
|
1224
|
+
const finalizedState = isCollectingDigits(state) ? finalizeDigits(state) : state;
|
|
1225
|
+
const isComplete = finalizedState.every((elem) => {
|
|
1226
|
+
if (elem.type === "key") return elem.matched === true;
|
|
1227
|
+
if (elem.type === "digit") return elem.value !== void 0;
|
|
1228
|
+
if (elem.type === "digits") return elem.value !== void 0;
|
|
1229
|
+
return false;
|
|
1230
|
+
});
|
|
1231
|
+
if (isComplete) {
|
|
1232
|
+
const captures = extractMatchCaptures(finalizedState);
|
|
1233
|
+
executed = tryExecuteKeySeq(key, captures, e);
|
|
1234
|
+
if (executed) break;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
if (!executed) {
|
|
1238
|
+
executed = tryExecute(pendingKeysRef.current, e);
|
|
1239
|
+
}
|
|
747
1240
|
clearPending();
|
|
748
1241
|
if (!executed) {
|
|
749
1242
|
onSequenceCancel?.();
|
|
@@ -756,49 +1249,173 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
756
1249
|
return;
|
|
757
1250
|
}
|
|
758
1251
|
const currentCombo = eventToCombination(e);
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
if (hasPotentialMatch(newSequence)) {
|
|
766
|
-
if (hasSequenceExtension(newSequence)) {
|
|
767
|
-
setPendingKeys(newSequence);
|
|
768
|
-
setIsAwaitingSequence(true);
|
|
769
|
-
if (pendingKeysRef.current.length === 0) {
|
|
770
|
-
onSequenceStart?.(newSequence);
|
|
771
|
-
} else {
|
|
772
|
-
onSequenceProgress?.(newSequence);
|
|
1252
|
+
if (e.key === "Backspace" && pendingKeysRef.current.length > 0) {
|
|
1253
|
+
let backspaceMatches = false;
|
|
1254
|
+
for (const entry of parsedKeymapRef.current) {
|
|
1255
|
+
let state = matchStatesRef.current.get(entry.key);
|
|
1256
|
+
if (!state) {
|
|
1257
|
+
state = initMatchState(entry.keySeq);
|
|
773
1258
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1259
|
+
if (isCollectingDigits(state)) {
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
const result = advanceMatchState(state, entry.keySeq, currentCombo);
|
|
1263
|
+
if (result.status === "matched" || result.status === "partial") {
|
|
1264
|
+
backspaceMatches = true;
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
if (!backspaceMatches) {
|
|
1269
|
+
e.preventDefault();
|
|
1270
|
+
const newPending = pendingKeysRef.current.slice(0, -1);
|
|
1271
|
+
if (newPending.length === 0) {
|
|
1272
|
+
clearPending();
|
|
1273
|
+
onSequenceCancel?.();
|
|
1274
|
+
} else {
|
|
1275
|
+
setPendingKeys(newPending);
|
|
1276
|
+
matchStatesRef.current.clear();
|
|
1277
|
+
for (const combo of newPending) {
|
|
1278
|
+
for (const entry of parsedKeymapRef.current) {
|
|
1279
|
+
let state = matchStatesRef.current.get(entry.key);
|
|
1280
|
+
if (!state) {
|
|
1281
|
+
state = initMatchState(entry.keySeq);
|
|
780
1282
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
setTimeoutStartedAt(null);
|
|
789
|
-
onSequenceCancel?.();
|
|
1283
|
+
const result = advanceMatchState(state, entry.keySeq, combo);
|
|
1284
|
+
if (result.status === "partial") {
|
|
1285
|
+
matchStatesRef.current.set(entry.key, result.state);
|
|
1286
|
+
} else {
|
|
1287
|
+
matchStatesRef.current.delete(entry.key);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
790
1290
|
}
|
|
791
|
-
timeoutRef.current = null;
|
|
792
|
-
}, sequenceTimeout);
|
|
793
|
-
if (preventDefault) {
|
|
794
|
-
e.preventDefault();
|
|
795
1291
|
}
|
|
796
1292
|
return;
|
|
797
1293
|
}
|
|
798
1294
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1295
|
+
const newSequence = [...pendingKeysRef.current, currentCombo];
|
|
1296
|
+
const completeMatches = [];
|
|
1297
|
+
let hasPartials = false;
|
|
1298
|
+
const matchStates = matchStatesRef.current;
|
|
1299
|
+
const hadPartialMatches = matchStates.size > 0;
|
|
1300
|
+
for (const entry of parsedKeymapRef.current) {
|
|
1301
|
+
let state = matchStates.get(entry.key);
|
|
1302
|
+
if (hadPartialMatches && !state) {
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
if (!state) {
|
|
1306
|
+
state = initMatchState(entry.keySeq);
|
|
1307
|
+
matchStates.set(entry.key, state);
|
|
1308
|
+
}
|
|
1309
|
+
const result = advanceMatchState(state, entry.keySeq, currentCombo);
|
|
1310
|
+
if (result.status === "matched") {
|
|
1311
|
+
completeMatches.push({
|
|
1312
|
+
key: entry.key,
|
|
1313
|
+
state: result.state,
|
|
1314
|
+
captures: result.captures
|
|
1315
|
+
});
|
|
1316
|
+
matchStates.delete(entry.key);
|
|
1317
|
+
} else if (result.status === "partial") {
|
|
1318
|
+
matchStates.set(entry.key, result.state);
|
|
1319
|
+
hasPartials = true;
|
|
1320
|
+
} else {
|
|
1321
|
+
matchStates.delete(entry.key);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
if (completeMatches.length === 1 && !hasPartials) {
|
|
1325
|
+
const match = completeMatches[0];
|
|
1326
|
+
if (tryExecuteKeySeq(match.key, match.captures, e)) {
|
|
1327
|
+
clearPending();
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (completeMatches.length > 0 || hasPartials) {
|
|
1332
|
+
setPendingKeys(newSequence);
|
|
1333
|
+
setIsAwaitingSequence(true);
|
|
1334
|
+
if (pendingKeysRef.current.length === 0) {
|
|
1335
|
+
onSequenceStart?.(newSequence);
|
|
1336
|
+
} else {
|
|
1337
|
+
onSequenceProgress?.(newSequence);
|
|
1338
|
+
}
|
|
1339
|
+
if (preventDefault) {
|
|
1340
|
+
e.preventDefault();
|
|
1341
|
+
}
|
|
1342
|
+
if (Number.isFinite(sequenceTimeout)) {
|
|
1343
|
+
setTimeoutStartedAt(Date.now());
|
|
1344
|
+
timeoutRef.current = setTimeout(() => {
|
|
1345
|
+
for (const [key, state] of matchStates.entries()) {
|
|
1346
|
+
if (isCollectingDigits(state)) {
|
|
1347
|
+
const finalizedState = finalizeDigits(state);
|
|
1348
|
+
const entry = parsedKeymapRef.current.find((e2) => e2.key === key);
|
|
1349
|
+
if (entry) {
|
|
1350
|
+
const isComplete = finalizedState.every((elem) => {
|
|
1351
|
+
if (elem.type === "key") return elem.matched === true;
|
|
1352
|
+
if (elem.type === "digit") return elem.value !== void 0;
|
|
1353
|
+
if (elem.type === "digits") return elem.value !== void 0;
|
|
1354
|
+
return false;
|
|
1355
|
+
});
|
|
1356
|
+
if (isComplete) {
|
|
1357
|
+
void extractMatchCaptures(finalizedState);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
setPendingKeys([]);
|
|
1363
|
+
setIsAwaitingSequence(false);
|
|
1364
|
+
setTimeoutStartedAt(null);
|
|
1365
|
+
matchStatesRef.current.clear();
|
|
1366
|
+
onSequenceCancel?.();
|
|
1367
|
+
timeoutRef.current = null;
|
|
1368
|
+
}, sequenceTimeout);
|
|
1369
|
+
}
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const exactMatch = tryExecute(newSequence, e);
|
|
1373
|
+
if (exactMatch) {
|
|
1374
|
+
clearPending();
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
if (hasPotentialMatch(newSequence)) {
|
|
1378
|
+
if (hasSequenceExtension(newSequence)) {
|
|
1379
|
+
setPendingKeys(newSequence);
|
|
1380
|
+
setIsAwaitingSequence(true);
|
|
1381
|
+
if (pendingKeysRef.current.length === 0) {
|
|
1382
|
+
onSequenceStart?.(newSequence);
|
|
1383
|
+
} else {
|
|
1384
|
+
onSequenceProgress?.(newSequence);
|
|
1385
|
+
}
|
|
1386
|
+
if (Number.isFinite(sequenceTimeout)) {
|
|
1387
|
+
setTimeoutStartedAt(Date.now());
|
|
1388
|
+
timeoutRef.current = setTimeout(() => {
|
|
1389
|
+
if (onTimeout === "submit") {
|
|
1390
|
+
setPendingKeys((current) => {
|
|
1391
|
+
if (current.length > 0) {
|
|
1392
|
+
onSequenceCancel?.();
|
|
1393
|
+
}
|
|
1394
|
+
return [];
|
|
1395
|
+
});
|
|
1396
|
+
setIsAwaitingSequence(false);
|
|
1397
|
+
setTimeoutStartedAt(null);
|
|
1398
|
+
} else {
|
|
1399
|
+
setPendingKeys([]);
|
|
1400
|
+
setIsAwaitingSequence(false);
|
|
1401
|
+
setTimeoutStartedAt(null);
|
|
1402
|
+
onSequenceCancel?.();
|
|
1403
|
+
}
|
|
1404
|
+
timeoutRef.current = null;
|
|
1405
|
+
}, sequenceTimeout);
|
|
1406
|
+
}
|
|
1407
|
+
if (preventDefault) {
|
|
1408
|
+
e.preventDefault();
|
|
1409
|
+
}
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
if (pendingKeysRef.current.length > 0) {
|
|
1414
|
+
setPendingKeys(newSequence);
|
|
1415
|
+
if (preventDefault) {
|
|
1416
|
+
e.preventDefault();
|
|
1417
|
+
}
|
|
1418
|
+
return;
|
|
802
1419
|
}
|
|
803
1420
|
const singleMatch = tryExecute([currentCombo], e);
|
|
804
1421
|
if (!singleMatch) {
|
|
@@ -809,21 +1426,23 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
809
1426
|
if (preventDefault) {
|
|
810
1427
|
e.preventDefault();
|
|
811
1428
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1429
|
+
if (Number.isFinite(sequenceTimeout)) {
|
|
1430
|
+
setTimeoutStartedAt(Date.now());
|
|
1431
|
+
timeoutRef.current = setTimeout(() => {
|
|
1432
|
+
if (onTimeout === "submit") {
|
|
1433
|
+
setPendingKeys([]);
|
|
1434
|
+
setIsAwaitingSequence(false);
|
|
1435
|
+
setTimeoutStartedAt(null);
|
|
1436
|
+
onSequenceCancel?.();
|
|
1437
|
+
} else {
|
|
1438
|
+
setPendingKeys([]);
|
|
1439
|
+
setIsAwaitingSequence(false);
|
|
1440
|
+
setTimeoutStartedAt(null);
|
|
1441
|
+
onSequenceCancel?.();
|
|
1442
|
+
}
|
|
1443
|
+
timeoutRef.current = null;
|
|
1444
|
+
}, sequenceTimeout);
|
|
1445
|
+
}
|
|
827
1446
|
}
|
|
828
1447
|
}
|
|
829
1448
|
};
|
|
@@ -845,6 +1464,7 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
845
1464
|
clearPending,
|
|
846
1465
|
cancelSequence,
|
|
847
1466
|
tryExecute,
|
|
1467
|
+
tryExecuteKeySeq,
|
|
848
1468
|
hasPotentialMatch,
|
|
849
1469
|
hasSequenceExtension,
|
|
850
1470
|
onSequenceStart,
|
|
@@ -856,12 +1476,11 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
856
1476
|
var HotkeysContext = createContext(null);
|
|
857
1477
|
var DEFAULT_CONFIG = {
|
|
858
1478
|
storageKey: "use-kbd",
|
|
859
|
-
sequenceTimeout:
|
|
860
|
-
disableConflicts:
|
|
1479
|
+
sequenceTimeout: DEFAULT_SEQUENCE_TIMEOUT,
|
|
1480
|
+
disableConflicts: false,
|
|
1481
|
+
// Keep conflicting bindings active; SeqM handles disambiguation
|
|
861
1482
|
minViewportWidth: 768,
|
|
862
|
-
enableOnTouch: false
|
|
863
|
-
modalTrigger: "?",
|
|
864
|
-
omnibarTrigger: "meta+k"
|
|
1483
|
+
enableOnTouch: false
|
|
865
1484
|
};
|
|
866
1485
|
function HotkeysProvider({
|
|
867
1486
|
config: configProp = {},
|
|
@@ -872,6 +1491,7 @@ function HotkeysProvider({
|
|
|
872
1491
|
...configProp
|
|
873
1492
|
}), [configProp]);
|
|
874
1493
|
const registry = useActionsRegistry({ storageKey: config.storageKey });
|
|
1494
|
+
const endpointsRegistry = useOmnibarEndpointsRegistry();
|
|
875
1495
|
const [isEnabled, setIsEnabled] = useState(true);
|
|
876
1496
|
useEffect(() => {
|
|
877
1497
|
if (typeof window === "undefined") return;
|
|
@@ -910,16 +1530,12 @@ function HotkeysProvider({
|
|
|
910
1530
|
const openOmnibar = useCallback(() => setIsOmnibarOpen(true), []);
|
|
911
1531
|
const closeOmnibar = useCallback(() => setIsOmnibarOpen(false), []);
|
|
912
1532
|
const toggleOmnibar = useCallback(() => setIsOmnibarOpen((prev) => !prev), []);
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
map[config.omnibarTrigger] = "__hotkeys:omnibar";
|
|
920
|
-
}
|
|
921
|
-
return map;
|
|
922
|
-
}, [registry.keymap, config.modalTrigger, config.omnibarTrigger]);
|
|
1533
|
+
const [isLookupOpen, setIsLookupOpen] = useState(false);
|
|
1534
|
+
const openLookup = useCallback(() => setIsLookupOpen(true), []);
|
|
1535
|
+
const closeLookup = useCallback(() => setIsLookupOpen(false), []);
|
|
1536
|
+
const toggleLookup = useCallback(() => setIsLookupOpen((prev) => !prev), []);
|
|
1537
|
+
const [isEditingBinding, setIsEditingBinding] = useState(false);
|
|
1538
|
+
const keymap = registry.keymap;
|
|
923
1539
|
const conflicts = useMemo(() => findConflicts(keymap), [keymap]);
|
|
924
1540
|
const hasConflicts2 = conflicts.size > 0;
|
|
925
1541
|
const effectiveKeymap = useMemo(() => {
|
|
@@ -939,20 +1555,24 @@ function HotkeysProvider({
|
|
|
939
1555
|
for (const [id, action] of registry.actions) {
|
|
940
1556
|
map[id] = action.config.handler;
|
|
941
1557
|
}
|
|
942
|
-
map["__hotkeys:modal"] = toggleModal;
|
|
943
|
-
map["__hotkeys:omnibar"] = toggleOmnibar;
|
|
944
1558
|
return map;
|
|
945
|
-
}, [registry.actions
|
|
946
|
-
const hotkeysEnabled = isEnabled && !
|
|
1559
|
+
}, [registry.actions]);
|
|
1560
|
+
const hotkeysEnabled = isEnabled && !isEditingBinding && !isOmnibarOpen && !isLookupOpen;
|
|
947
1561
|
const {
|
|
948
1562
|
pendingKeys,
|
|
949
1563
|
isAwaitingSequence,
|
|
1564
|
+
cancelSequence,
|
|
950
1565
|
timeoutStartedAt: sequenceTimeoutStartedAt,
|
|
951
1566
|
sequenceTimeout
|
|
952
1567
|
} = useHotkeys(effectiveKeymap, handlers, {
|
|
953
1568
|
enabled: hotkeysEnabled,
|
|
954
1569
|
sequenceTimeout: config.sequenceTimeout
|
|
955
1570
|
});
|
|
1571
|
+
useEffect(() => {
|
|
1572
|
+
if (isAwaitingSequence && isModalOpen) {
|
|
1573
|
+
closeModal();
|
|
1574
|
+
}
|
|
1575
|
+
}, [isAwaitingSequence, isModalOpen, closeModal]);
|
|
956
1576
|
const searchActionsHelper = useCallback(
|
|
957
1577
|
(query) => searchActions(query, registry.actionRegistry, keymap),
|
|
958
1578
|
[registry.actionRegistry, keymap]
|
|
@@ -963,6 +1583,7 @@ function HotkeysProvider({
|
|
|
963
1583
|
);
|
|
964
1584
|
const value = useMemo(() => ({
|
|
965
1585
|
registry,
|
|
1586
|
+
endpointsRegistry,
|
|
966
1587
|
isEnabled,
|
|
967
1588
|
isModalOpen,
|
|
968
1589
|
openModal,
|
|
@@ -972,9 +1593,16 @@ function HotkeysProvider({
|
|
|
972
1593
|
openOmnibar,
|
|
973
1594
|
closeOmnibar,
|
|
974
1595
|
toggleOmnibar,
|
|
1596
|
+
isLookupOpen,
|
|
1597
|
+
openLookup,
|
|
1598
|
+
closeLookup,
|
|
1599
|
+
toggleLookup,
|
|
1600
|
+
isEditingBinding,
|
|
1601
|
+
setIsEditingBinding,
|
|
975
1602
|
executeAction: registry.execute,
|
|
976
1603
|
pendingKeys,
|
|
977
1604
|
isAwaitingSequence,
|
|
1605
|
+
cancelSequence,
|
|
978
1606
|
sequenceTimeoutStartedAt,
|
|
979
1607
|
sequenceTimeout,
|
|
980
1608
|
conflicts,
|
|
@@ -983,6 +1611,7 @@ function HotkeysProvider({
|
|
|
983
1611
|
getCompletions
|
|
984
1612
|
}), [
|
|
985
1613
|
registry,
|
|
1614
|
+
endpointsRegistry,
|
|
986
1615
|
isEnabled,
|
|
987
1616
|
isModalOpen,
|
|
988
1617
|
openModal,
|
|
@@ -992,8 +1621,14 @@ function HotkeysProvider({
|
|
|
992
1621
|
openOmnibar,
|
|
993
1622
|
closeOmnibar,
|
|
994
1623
|
toggleOmnibar,
|
|
1624
|
+
isLookupOpen,
|
|
1625
|
+
openLookup,
|
|
1626
|
+
closeLookup,
|
|
1627
|
+
toggleLookup,
|
|
1628
|
+
isEditingBinding,
|
|
995
1629
|
pendingKeys,
|
|
996
1630
|
isAwaitingSequence,
|
|
1631
|
+
cancelSequence,
|
|
997
1632
|
sequenceTimeoutStartedAt,
|
|
998
1633
|
sequenceTimeout,
|
|
999
1634
|
conflicts,
|
|
@@ -1001,7 +1636,7 @@ function HotkeysProvider({
|
|
|
1001
1636
|
searchActionsHelper,
|
|
1002
1637
|
getCompletions
|
|
1003
1638
|
]);
|
|
1004
|
-
return /* @__PURE__ */ jsx(ActionsRegistryContext.Provider, { value: registry, children: /* @__PURE__ */ jsx(HotkeysContext.Provider, { value, children }) });
|
|
1639
|
+
return /* @__PURE__ */ jsx(ActionsRegistryContext.Provider, { value: registry, children: /* @__PURE__ */ jsx(OmnibarEndpointsRegistryContext.Provider, { value: endpointsRegistry, children: /* @__PURE__ */ jsx(HotkeysContext.Provider, { value, children }) }) });
|
|
1005
1640
|
}
|
|
1006
1641
|
function useHotkeysContext() {
|
|
1007
1642
|
const context = useContext(HotkeysContext);
|
|
@@ -1027,9 +1662,9 @@ function useAction(id, config) {
|
|
|
1027
1662
|
useEffect(() => {
|
|
1028
1663
|
registryRef.current.register(id, {
|
|
1029
1664
|
...config,
|
|
1030
|
-
handler: () => {
|
|
1665
|
+
handler: (e, captures) => {
|
|
1031
1666
|
if (enabledRef.current) {
|
|
1032
|
-
handlerRef.current();
|
|
1667
|
+
handlerRef.current(e, captures);
|
|
1033
1668
|
}
|
|
1034
1669
|
}
|
|
1035
1670
|
});
|
|
@@ -1063,9 +1698,9 @@ function useActions(actions) {
|
|
|
1063
1698
|
for (const [id, config] of Object.entries(actions)) {
|
|
1064
1699
|
registryRef.current.register(id, {
|
|
1065
1700
|
...config,
|
|
1066
|
-
handler: () => {
|
|
1701
|
+
handler: (e, captures) => {
|
|
1067
1702
|
if (enabledRef.current[id]) {
|
|
1068
|
-
handlersRef.current[id]?.();
|
|
1703
|
+
handlersRef.current[id]?.(e, captures);
|
|
1069
1704
|
}
|
|
1070
1705
|
}
|
|
1071
1706
|
});
|
|
@@ -1089,6 +1724,38 @@ function useActions(actions) {
|
|
|
1089
1724
|
)
|
|
1090
1725
|
]);
|
|
1091
1726
|
}
|
|
1727
|
+
function useOmnibarEndpoint(id, config) {
|
|
1728
|
+
const registry = useContext(OmnibarEndpointsRegistryContext);
|
|
1729
|
+
if (!registry) {
|
|
1730
|
+
throw new Error("useOmnibarEndpoint must be used within a HotkeysProvider");
|
|
1731
|
+
}
|
|
1732
|
+
const registryRef = useRef(registry);
|
|
1733
|
+
registryRef.current = registry;
|
|
1734
|
+
const fetchRef = useRef(config.fetch);
|
|
1735
|
+
fetchRef.current = config.fetch;
|
|
1736
|
+
const enabledRef = useRef(config.enabled ?? true);
|
|
1737
|
+
enabledRef.current = config.enabled ?? true;
|
|
1738
|
+
useEffect(() => {
|
|
1739
|
+
registryRef.current.register(id, {
|
|
1740
|
+
...config,
|
|
1741
|
+
fetch: async (query, signal, pagination) => {
|
|
1742
|
+
if (!enabledRef.current) return { entries: [] };
|
|
1743
|
+
return fetchRef.current(query, signal, pagination);
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
return () => {
|
|
1747
|
+
registryRef.current.unregister(id);
|
|
1748
|
+
};
|
|
1749
|
+
}, [
|
|
1750
|
+
id,
|
|
1751
|
+
config.group,
|
|
1752
|
+
config.priority,
|
|
1753
|
+
config.minQueryLength,
|
|
1754
|
+
config.pageSize,
|
|
1755
|
+
config.pagination
|
|
1756
|
+
// Note: we use refs for fetch and enabled, so they don't cause re-registration
|
|
1757
|
+
]);
|
|
1758
|
+
}
|
|
1092
1759
|
function useEventCallback(fn) {
|
|
1093
1760
|
const ref = useRef(fn);
|
|
1094
1761
|
ref.current = fn;
|
|
@@ -1101,7 +1768,7 @@ function useRecordHotkey(options = {}) {
|
|
|
1101
1768
|
onTab: onTabProp,
|
|
1102
1769
|
onShiftTab: onShiftTabProp,
|
|
1103
1770
|
preventDefault = true,
|
|
1104
|
-
sequenceTimeout =
|
|
1771
|
+
sequenceTimeout = DEFAULT_SEQUENCE_TIMEOUT,
|
|
1105
1772
|
pauseTimeout = false
|
|
1106
1773
|
} = options;
|
|
1107
1774
|
const onCapture = useEventCallback(onCaptureProp);
|
|
@@ -1119,6 +1786,7 @@ function useRecordHotkey(options = {}) {
|
|
|
1119
1786
|
const pauseTimeoutRef = useRef(pauseTimeout);
|
|
1120
1787
|
pauseTimeoutRef.current = pauseTimeout;
|
|
1121
1788
|
const pendingKeysRef = useRef([]);
|
|
1789
|
+
const hashCycleRef = useRef(0);
|
|
1122
1790
|
const clearTimeout_ = useCallback(() => {
|
|
1123
1791
|
if (timeoutRef.current) {
|
|
1124
1792
|
clearTimeout(timeoutRef.current);
|
|
@@ -1148,6 +1816,7 @@ function useRecordHotkey(options = {}) {
|
|
|
1148
1816
|
pressedKeysRef.current.clear();
|
|
1149
1817
|
hasNonModifierRef.current = false;
|
|
1150
1818
|
currentComboRef.current = null;
|
|
1819
|
+
hashCycleRef.current = 0;
|
|
1151
1820
|
onCancel?.();
|
|
1152
1821
|
}, [clearTimeout_, onCancel]);
|
|
1153
1822
|
const commit = useCallback(() => {
|
|
@@ -1168,6 +1837,7 @@ function useRecordHotkey(options = {}) {
|
|
|
1168
1837
|
pressedKeysRef.current.clear();
|
|
1169
1838
|
hasNonModifierRef.current = false;
|
|
1170
1839
|
currentComboRef.current = null;
|
|
1840
|
+
hashCycleRef.current = 0;
|
|
1171
1841
|
return cancel;
|
|
1172
1842
|
}, [cancel, clearTimeout_]);
|
|
1173
1843
|
useEffect(() => {
|
|
@@ -1178,9 +1848,13 @@ function useRecordHotkey(options = {}) {
|
|
|
1178
1848
|
}
|
|
1179
1849
|
} else if (isRecording && pendingKeysRef.current.length > 0 && !timeoutRef.current) {
|
|
1180
1850
|
const currentSequence = pendingKeysRef.current;
|
|
1181
|
-
|
|
1851
|
+
if (sequenceTimeout === 0) {
|
|
1182
1852
|
submit(currentSequence);
|
|
1183
|
-
}
|
|
1853
|
+
} else if (Number.isFinite(sequenceTimeout)) {
|
|
1854
|
+
timeoutRef.current = setTimeout(() => {
|
|
1855
|
+
submit(currentSequence);
|
|
1856
|
+
}, sequenceTimeout);
|
|
1857
|
+
}
|
|
1184
1858
|
}
|
|
1185
1859
|
}, [pauseTimeout, isRecording, sequenceTimeout, submit]);
|
|
1186
1860
|
useEffect(() => {
|
|
@@ -1239,22 +1913,23 @@ function useRecordHotkey(options = {}) {
|
|
|
1239
1913
|
key = e.code.slice(5);
|
|
1240
1914
|
}
|
|
1241
1915
|
pressedKeysRef.current.add(key);
|
|
1916
|
+
let nonModifierKey = "";
|
|
1917
|
+
for (const k of pressedKeysRef.current) {
|
|
1918
|
+
if (!isModifierKey(k)) {
|
|
1919
|
+
nonModifierKey = normalizeKey(k);
|
|
1920
|
+
hasNonModifierRef.current = true;
|
|
1921
|
+
break;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1242
1924
|
const combo = {
|
|
1243
|
-
key:
|
|
1925
|
+
key: nonModifierKey,
|
|
1244
1926
|
modifiers: {
|
|
1245
1927
|
ctrl: e.ctrlKey,
|
|
1246
1928
|
alt: e.altKey,
|
|
1247
|
-
shift: e.shiftKey,
|
|
1929
|
+
shift: e.shiftKey && !isShiftedSymbol(nonModifierKey),
|
|
1248
1930
|
meta: e.metaKey
|
|
1249
1931
|
}
|
|
1250
1932
|
};
|
|
1251
|
-
for (const k of pressedKeysRef.current) {
|
|
1252
|
-
if (!isModifierKey(k)) {
|
|
1253
|
-
combo.key = normalizeKey(k);
|
|
1254
|
-
hasNonModifierRef.current = true;
|
|
1255
|
-
break;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
1933
|
if (combo.key) {
|
|
1259
1934
|
currentComboRef.current = combo;
|
|
1260
1935
|
setActiveKeys(combo);
|
|
@@ -1279,16 +1954,41 @@ function useRecordHotkey(options = {}) {
|
|
|
1279
1954
|
pressedKeysRef.current.delete(key);
|
|
1280
1955
|
const shouldComplete = pressedKeysRef.current.size === 0 || e.key === "Meta" && hasNonModifierRef.current;
|
|
1281
1956
|
if (shouldComplete && hasNonModifierRef.current && currentComboRef.current) {
|
|
1282
|
-
|
|
1957
|
+
let combo = currentComboRef.current;
|
|
1283
1958
|
pressedKeysRef.current.clear();
|
|
1284
1959
|
hasNonModifierRef.current = false;
|
|
1285
1960
|
currentComboRef.current = null;
|
|
1286
1961
|
setActiveKeys(null);
|
|
1287
|
-
|
|
1962
|
+
let newSequence;
|
|
1963
|
+
const noModifiers = !combo.modifiers.ctrl && !combo.modifiers.alt && !combo.modifiers.meta && !combo.modifiers.shift;
|
|
1964
|
+
if (combo.key === "#" && noModifiers) {
|
|
1965
|
+
const pending = pendingKeysRef.current;
|
|
1966
|
+
const lastCombo = pending[pending.length - 1];
|
|
1967
|
+
if (hashCycleRef.current === 0) {
|
|
1968
|
+
combo = { key: DIGIT_PLACEHOLDER, modifiers: { ctrl: false, alt: false, shift: false, meta: false } };
|
|
1969
|
+
newSequence = [...pending, combo];
|
|
1970
|
+
hashCycleRef.current = 1;
|
|
1971
|
+
} else if (hashCycleRef.current === 1 && lastCombo?.key === DIGIT_PLACEHOLDER) {
|
|
1972
|
+
newSequence = [...pending.slice(0, -1), { key: DIGITS_PLACEHOLDER, modifiers: { ctrl: false, alt: false, shift: false, meta: false } }];
|
|
1973
|
+
hashCycleRef.current = 2;
|
|
1974
|
+
} else if (hashCycleRef.current === 2 && lastCombo?.key === DIGITS_PLACEHOLDER) {
|
|
1975
|
+
newSequence = [...pending.slice(0, -1), { key: "#", modifiers: { ctrl: false, alt: false, shift: false, meta: false } }];
|
|
1976
|
+
hashCycleRef.current = 3;
|
|
1977
|
+
} else {
|
|
1978
|
+
combo = { key: DIGIT_PLACEHOLDER, modifiers: { ctrl: false, alt: false, shift: false, meta: false } };
|
|
1979
|
+
newSequence = [...pending, combo];
|
|
1980
|
+
hashCycleRef.current = 1;
|
|
1981
|
+
}
|
|
1982
|
+
} else {
|
|
1983
|
+
hashCycleRef.current = 0;
|
|
1984
|
+
newSequence = [...pendingKeysRef.current, combo];
|
|
1985
|
+
}
|
|
1288
1986
|
pendingKeysRef.current = newSequence;
|
|
1289
1987
|
setPendingKeys(newSequence);
|
|
1290
1988
|
clearTimeout_();
|
|
1291
|
-
if (
|
|
1989
|
+
if (sequenceTimeout === 0) {
|
|
1990
|
+
submit(newSequence);
|
|
1991
|
+
} else if (!pauseTimeoutRef.current && Number.isFinite(sequenceTimeout)) {
|
|
1292
1992
|
timeoutRef.current = setTimeout(() => {
|
|
1293
1993
|
submit(newSequence);
|
|
1294
1994
|
}, sequenceTimeout);
|
|
@@ -1304,7 +2004,6 @@ function useRecordHotkey(options = {}) {
|
|
|
1304
2004
|
};
|
|
1305
2005
|
}, [isRecording, preventDefault, sequenceTimeout, clearTimeout_, submit, cancel, onCapture, onTab, onShiftTab]);
|
|
1306
2006
|
const display = sequence ? formatCombination(sequence) : null;
|
|
1307
|
-
const combination = sequence && sequence.length > 0 ? sequence[0] : null;
|
|
1308
2007
|
return {
|
|
1309
2008
|
isRecording,
|
|
1310
2009
|
startRecording,
|
|
@@ -1314,12 +2013,11 @@ function useRecordHotkey(options = {}) {
|
|
|
1314
2013
|
display,
|
|
1315
2014
|
pendingKeys,
|
|
1316
2015
|
activeKeys,
|
|
1317
|
-
|
|
1318
|
-
// deprecated
|
|
2016
|
+
sequenceTimeout
|
|
1319
2017
|
};
|
|
1320
2018
|
}
|
|
1321
2019
|
function useEditableHotkeys(defaults, handlers, options = {}) {
|
|
1322
|
-
const { storageKey, disableConflicts =
|
|
2020
|
+
const { storageKey, disableConflicts = false, ...hotkeyOptions } = options;
|
|
1323
2021
|
const [overrides, setOverrides] = useState(() => {
|
|
1324
2022
|
if (!storageKey || typeof window === "undefined") return {};
|
|
1325
2023
|
try {
|
|
@@ -1414,6 +2112,8 @@ function useEditableHotkeys(defaults, handlers, options = {}) {
|
|
|
1414
2112
|
sequenceTimeout
|
|
1415
2113
|
};
|
|
1416
2114
|
}
|
|
2115
|
+
var { max: max2, min } = Math;
|
|
2116
|
+
var DEFAULT_DEBOUNCE_MS = 150;
|
|
1417
2117
|
function useOmnibar(options) {
|
|
1418
2118
|
const {
|
|
1419
2119
|
actions,
|
|
@@ -1422,17 +2122,27 @@ function useOmnibar(options) {
|
|
|
1422
2122
|
openKey = "meta+k",
|
|
1423
2123
|
enabled = true,
|
|
1424
2124
|
onExecute,
|
|
2125
|
+
onExecuteRemote,
|
|
1425
2126
|
onOpen,
|
|
1426
2127
|
onClose,
|
|
1427
|
-
maxResults = 10
|
|
2128
|
+
maxResults = 10,
|
|
2129
|
+
endpointsRegistry,
|
|
2130
|
+
debounceMs = DEFAULT_DEBOUNCE_MS
|
|
1428
2131
|
} = options;
|
|
1429
2132
|
const [isOpen, setIsOpen] = useState(false);
|
|
1430
2133
|
const [query, setQuery] = useState("");
|
|
1431
2134
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
2135
|
+
const [endpointStates, setEndpointStates] = useState(/* @__PURE__ */ new Map());
|
|
1432
2136
|
const handlersRef = useRef(handlers);
|
|
1433
2137
|
handlersRef.current = handlers;
|
|
1434
2138
|
const onExecuteRef = useRef(onExecute);
|
|
1435
2139
|
onExecuteRef.current = onExecute;
|
|
2140
|
+
const onExecuteRemoteRef = useRef(onExecuteRemote);
|
|
2141
|
+
onExecuteRemoteRef.current = onExecuteRemote;
|
|
2142
|
+
const abortControllerRef = useRef(null);
|
|
2143
|
+
const debounceTimerRef = useRef(null);
|
|
2144
|
+
const currentQueryRef = useRef(query);
|
|
2145
|
+
currentQueryRef.current = query;
|
|
1436
2146
|
const omnibarKeymap = useMemo(() => {
|
|
1437
2147
|
if (!enabled) return {};
|
|
1438
2148
|
return { [openKey]: "omnibar:toggle" };
|
|
@@ -1458,12 +2168,189 @@ function useOmnibar(options) {
|
|
|
1458
2168
|
const allResults = searchActions(query, actions, keymap);
|
|
1459
2169
|
return allResults.slice(0, maxResults);
|
|
1460
2170
|
}, [query, actions, keymap, maxResults]);
|
|
2171
|
+
useEffect(() => {
|
|
2172
|
+
if (debounceTimerRef.current) {
|
|
2173
|
+
clearTimeout(debounceTimerRef.current);
|
|
2174
|
+
debounceTimerRef.current = null;
|
|
2175
|
+
}
|
|
2176
|
+
if (abortControllerRef.current) {
|
|
2177
|
+
abortControllerRef.current.abort();
|
|
2178
|
+
abortControllerRef.current = null;
|
|
2179
|
+
}
|
|
2180
|
+
if (!endpointsRegistry || !query.trim()) {
|
|
2181
|
+
setEndpointStates(/* @__PURE__ */ new Map());
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
setEndpointStates((prev) => {
|
|
2185
|
+
const next = new Map(prev);
|
|
2186
|
+
for (const [id] of endpointsRegistry.endpoints) {
|
|
2187
|
+
next.set(id, { entries: [], offset: 0, isLoading: true });
|
|
2188
|
+
}
|
|
2189
|
+
return next;
|
|
2190
|
+
});
|
|
2191
|
+
debounceTimerRef.current = setTimeout(async () => {
|
|
2192
|
+
const controller = new AbortController();
|
|
2193
|
+
abortControllerRef.current = controller;
|
|
2194
|
+
try {
|
|
2195
|
+
const endpointResults = await endpointsRegistry.queryAll(query, controller.signal);
|
|
2196
|
+
if (controller.signal.aborted) return;
|
|
2197
|
+
setEndpointStates(() => {
|
|
2198
|
+
const next = /* @__PURE__ */ new Map();
|
|
2199
|
+
for (const epResult of endpointResults) {
|
|
2200
|
+
const ep = endpointsRegistry.endpoints.get(epResult.endpointId);
|
|
2201
|
+
const pageSize = ep?.config.pageSize ?? 10;
|
|
2202
|
+
next.set(epResult.endpointId, {
|
|
2203
|
+
entries: epResult.entries,
|
|
2204
|
+
offset: pageSize,
|
|
2205
|
+
total: epResult.total,
|
|
2206
|
+
hasMore: epResult.hasMore ?? (epResult.total !== void 0 ? epResult.entries.length < epResult.total : void 0),
|
|
2207
|
+
isLoading: false
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
return next;
|
|
2211
|
+
});
|
|
2212
|
+
} catch (error) {
|
|
2213
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
2214
|
+
console.error("Omnibar endpoint query failed:", error);
|
|
2215
|
+
setEndpointStates((prev) => {
|
|
2216
|
+
const next = new Map(prev);
|
|
2217
|
+
for (const [id, state] of next) {
|
|
2218
|
+
next.set(id, { ...state, isLoading: false });
|
|
2219
|
+
}
|
|
2220
|
+
return next;
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
}, debounceMs);
|
|
2224
|
+
return () => {
|
|
2225
|
+
if (debounceTimerRef.current) {
|
|
2226
|
+
clearTimeout(debounceTimerRef.current);
|
|
2227
|
+
}
|
|
2228
|
+
if (abortControllerRef.current) {
|
|
2229
|
+
abortControllerRef.current.abort();
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
}, [query, endpointsRegistry, debounceMs]);
|
|
2233
|
+
const loadMore = useCallback(async (endpointId) => {
|
|
2234
|
+
if (!endpointsRegistry) return;
|
|
2235
|
+
const currentState = endpointStates.get(endpointId);
|
|
2236
|
+
if (!currentState || currentState.isLoading) return;
|
|
2237
|
+
if (currentState.hasMore === false) return;
|
|
2238
|
+
const ep = endpointsRegistry.endpoints.get(endpointId);
|
|
2239
|
+
if (!ep) return;
|
|
2240
|
+
const pageSize = ep.config.pageSize ?? 10;
|
|
2241
|
+
setEndpointStates((prev) => {
|
|
2242
|
+
const next = new Map(prev);
|
|
2243
|
+
const state = next.get(endpointId);
|
|
2244
|
+
if (state) {
|
|
2245
|
+
next.set(endpointId, { ...state, isLoading: true });
|
|
2246
|
+
}
|
|
2247
|
+
return next;
|
|
2248
|
+
});
|
|
2249
|
+
try {
|
|
2250
|
+
const controller = new AbortController();
|
|
2251
|
+
const result = await endpointsRegistry.queryEndpoint(
|
|
2252
|
+
endpointId,
|
|
2253
|
+
currentQueryRef.current,
|
|
2254
|
+
{ offset: currentState.offset, limit: pageSize },
|
|
2255
|
+
controller.signal
|
|
2256
|
+
);
|
|
2257
|
+
if (!result) return;
|
|
2258
|
+
setEndpointStates((prev) => {
|
|
2259
|
+
const next = new Map(prev);
|
|
2260
|
+
const state = next.get(endpointId);
|
|
2261
|
+
if (state) {
|
|
2262
|
+
next.set(endpointId, {
|
|
2263
|
+
entries: [...state.entries, ...result.entries],
|
|
2264
|
+
offset: state.offset + pageSize,
|
|
2265
|
+
total: result.total ?? state.total,
|
|
2266
|
+
hasMore: result.hasMore ?? (result.total !== void 0 ? state.entries.length + result.entries.length < result.total : void 0),
|
|
2267
|
+
isLoading: false
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
return next;
|
|
2271
|
+
});
|
|
2272
|
+
} catch (error) {
|
|
2273
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
2274
|
+
console.error(`Omnibar loadMore failed for ${endpointId}:`, error);
|
|
2275
|
+
setEndpointStates((prev) => {
|
|
2276
|
+
const next = new Map(prev);
|
|
2277
|
+
const state = next.get(endpointId);
|
|
2278
|
+
if (state) {
|
|
2279
|
+
next.set(endpointId, { ...state, isLoading: false });
|
|
2280
|
+
}
|
|
2281
|
+
return next;
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
}, [endpointsRegistry, endpointStates]);
|
|
2285
|
+
const remoteResults = useMemo(() => {
|
|
2286
|
+
if (!endpointsRegistry) return [];
|
|
2287
|
+
const processed = [];
|
|
2288
|
+
for (const [endpointId, state] of endpointStates) {
|
|
2289
|
+
const endpoint = endpointsRegistry.endpoints.get(endpointId);
|
|
2290
|
+
const priority = endpoint?.config.priority ?? 0;
|
|
2291
|
+
for (const entry of state.entries) {
|
|
2292
|
+
const labelMatch = fuzzyMatch(query, entry.label);
|
|
2293
|
+
const descMatch = entry.description ? fuzzyMatch(query, entry.description) : null;
|
|
2294
|
+
const keywordsMatch = entry.keywords?.map((k) => fuzzyMatch(query, k)) ?? [];
|
|
2295
|
+
let score = 0;
|
|
2296
|
+
let labelMatches = [];
|
|
2297
|
+
if (labelMatch.matched) {
|
|
2298
|
+
score = Math.max(score, labelMatch.score * 3);
|
|
2299
|
+
labelMatches = labelMatch.ranges;
|
|
2300
|
+
}
|
|
2301
|
+
if (descMatch?.matched) {
|
|
2302
|
+
score = Math.max(score, descMatch.score * 1.5);
|
|
2303
|
+
}
|
|
2304
|
+
for (const km of keywordsMatch) {
|
|
2305
|
+
if (km.matched) {
|
|
2306
|
+
score = Math.max(score, km.score * 2);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
processed.push({
|
|
2310
|
+
id: `${endpointId}:${entry.id}`,
|
|
2311
|
+
entry,
|
|
2312
|
+
endpointId,
|
|
2313
|
+
priority,
|
|
2314
|
+
score: score || 1,
|
|
2315
|
+
labelMatches
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
processed.sort((a, b) => {
|
|
2320
|
+
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
2321
|
+
return b.score - a.score;
|
|
2322
|
+
});
|
|
2323
|
+
return processed;
|
|
2324
|
+
}, [endpointStates, endpointsRegistry, query]);
|
|
2325
|
+
const isLoadingRemote = useMemo(() => {
|
|
2326
|
+
for (const [, state] of endpointStates) {
|
|
2327
|
+
if (state.isLoading) return true;
|
|
2328
|
+
}
|
|
2329
|
+
return false;
|
|
2330
|
+
}, [endpointStates]);
|
|
2331
|
+
const endpointPagination = useMemo(() => {
|
|
2332
|
+
const info = /* @__PURE__ */ new Map();
|
|
2333
|
+
if (!endpointsRegistry) return info;
|
|
2334
|
+
for (const [endpointId, state] of endpointStates) {
|
|
2335
|
+
const ep = endpointsRegistry.endpoints.get(endpointId);
|
|
2336
|
+
info.set(endpointId, {
|
|
2337
|
+
endpointId,
|
|
2338
|
+
loaded: state.entries.length,
|
|
2339
|
+
total: state.total,
|
|
2340
|
+
hasMore: state.hasMore ?? false,
|
|
2341
|
+
isLoading: state.isLoading,
|
|
2342
|
+
mode: ep?.config.pagination ?? "none"
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
return info;
|
|
2346
|
+
}, [endpointStates, endpointsRegistry]);
|
|
2347
|
+
const totalResults = results.length + remoteResults.length;
|
|
1461
2348
|
const completions = useMemo(() => {
|
|
1462
2349
|
return getSequenceCompletions(pendingKeys, keymap);
|
|
1463
2350
|
}, [pendingKeys, keymap]);
|
|
1464
2351
|
useEffect(() => {
|
|
1465
2352
|
setSelectedIndex(0);
|
|
1466
|
-
}, [results]);
|
|
2353
|
+
}, [results, remoteResults]);
|
|
1467
2354
|
const open = useCallback(() => {
|
|
1468
2355
|
setIsOpen(true);
|
|
1469
2356
|
setQuery("");
|
|
@@ -1490,24 +2377,56 @@ function useOmnibar(options) {
|
|
|
1490
2377
|
});
|
|
1491
2378
|
}, [onOpen, onClose]);
|
|
1492
2379
|
const selectNext = useCallback(() => {
|
|
1493
|
-
setSelectedIndex((prev) => min(prev + 1,
|
|
1494
|
-
}, [
|
|
2380
|
+
setSelectedIndex((prev) => min(prev + 1, totalResults - 1));
|
|
2381
|
+
}, [totalResults]);
|
|
1495
2382
|
const selectPrev = useCallback(() => {
|
|
1496
|
-
setSelectedIndex((prev) =>
|
|
2383
|
+
setSelectedIndex((prev) => max2(prev - 1, 0));
|
|
1497
2384
|
}, []);
|
|
1498
2385
|
const resetSelection = useCallback(() => {
|
|
1499
2386
|
setSelectedIndex(0);
|
|
1500
2387
|
}, []);
|
|
1501
2388
|
const execute = useCallback((actionId) => {
|
|
1502
|
-
const
|
|
1503
|
-
if (
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
2389
|
+
const localCount = results.length;
|
|
2390
|
+
if (actionId) {
|
|
2391
|
+
const remoteResult = remoteResults.find((r) => r.id === actionId);
|
|
2392
|
+
if (remoteResult) {
|
|
2393
|
+
close();
|
|
2394
|
+
const entry = remoteResult.entry;
|
|
2395
|
+
if ("handler" in entry && entry.handler) {
|
|
2396
|
+
entry.handler();
|
|
2397
|
+
}
|
|
2398
|
+
onExecuteRemoteRef.current?.(entry);
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
close();
|
|
2402
|
+
if (handlersRef.current?.[actionId]) {
|
|
2403
|
+
const event = new KeyboardEvent("keydown", { key: "Enter" });
|
|
2404
|
+
handlersRef.current[actionId](event);
|
|
2405
|
+
}
|
|
2406
|
+
onExecuteRef.current?.(actionId);
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
if (selectedIndex < localCount) {
|
|
2410
|
+
const id = results[selectedIndex]?.id;
|
|
2411
|
+
if (!id) return;
|
|
2412
|
+
close();
|
|
2413
|
+
if (handlersRef.current?.[id]) {
|
|
2414
|
+
const event = new KeyboardEvent("keydown", { key: "Enter" });
|
|
2415
|
+
handlersRef.current[id](event);
|
|
2416
|
+
}
|
|
2417
|
+
onExecuteRef.current?.(id);
|
|
2418
|
+
} else {
|
|
2419
|
+
const remoteIndex = selectedIndex - localCount;
|
|
2420
|
+
const remoteResult = remoteResults[remoteIndex];
|
|
2421
|
+
if (!remoteResult) return;
|
|
2422
|
+
close();
|
|
2423
|
+
const entry = remoteResult.entry;
|
|
2424
|
+
if ("handler" in entry && entry.handler) {
|
|
2425
|
+
entry.handler();
|
|
2426
|
+
}
|
|
2427
|
+
onExecuteRemoteRef.current?.(entry);
|
|
2428
|
+
}
|
|
2429
|
+
}, [results, remoteResults, selectedIndex, close]);
|
|
1511
2430
|
useEffect(() => {
|
|
1512
2431
|
if (!isOpen) return;
|
|
1513
2432
|
const handleKeyDown = (e) => {
|
|
@@ -1549,7 +2468,12 @@ function useOmnibar(options) {
|
|
|
1549
2468
|
query,
|
|
1550
2469
|
setQuery,
|
|
1551
2470
|
results,
|
|
2471
|
+
remoteResults,
|
|
2472
|
+
isLoadingRemote,
|
|
2473
|
+
endpointPagination,
|
|
2474
|
+
loadMore,
|
|
1552
2475
|
selectedIndex,
|
|
2476
|
+
totalResults,
|
|
1553
2477
|
selectNext,
|
|
1554
2478
|
selectPrev,
|
|
1555
2479
|
execute,
|
|
@@ -1559,6 +2483,400 @@ function useOmnibar(options) {
|
|
|
1559
2483
|
isAwaitingSequence
|
|
1560
2484
|
};
|
|
1561
2485
|
}
|
|
2486
|
+
var baseStyle = {
|
|
2487
|
+
width: "1em",
|
|
2488
|
+
height: "1em",
|
|
2489
|
+
verticalAlign: "middle"
|
|
2490
|
+
};
|
|
2491
|
+
function Up({ className, style }) {
|
|
2492
|
+
return /* @__PURE__ */ jsx(
|
|
2493
|
+
"svg",
|
|
2494
|
+
{
|
|
2495
|
+
className,
|
|
2496
|
+
style: { ...baseStyle, ...style },
|
|
2497
|
+
viewBox: "0 0 24 24",
|
|
2498
|
+
fill: "none",
|
|
2499
|
+
stroke: "currentColor",
|
|
2500
|
+
strokeWidth: "3",
|
|
2501
|
+
strokeLinecap: "round",
|
|
2502
|
+
strokeLinejoin: "round",
|
|
2503
|
+
children: /* @__PURE__ */ jsx("path", { d: "M12 19V5M5 12l7-7 7 7" })
|
|
2504
|
+
}
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
function Down({ className, style }) {
|
|
2508
|
+
return /* @__PURE__ */ jsx(
|
|
2509
|
+
"svg",
|
|
2510
|
+
{
|
|
2511
|
+
className,
|
|
2512
|
+
style: { ...baseStyle, ...style },
|
|
2513
|
+
viewBox: "0 0 24 24",
|
|
2514
|
+
fill: "none",
|
|
2515
|
+
stroke: "currentColor",
|
|
2516
|
+
strokeWidth: "3",
|
|
2517
|
+
strokeLinecap: "round",
|
|
2518
|
+
strokeLinejoin: "round",
|
|
2519
|
+
children: /* @__PURE__ */ jsx("path", { d: "M12 5v14M5 12l7 7 7-7" })
|
|
2520
|
+
}
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
function Left({ className, style }) {
|
|
2524
|
+
return /* @__PURE__ */ jsx(
|
|
2525
|
+
"svg",
|
|
2526
|
+
{
|
|
2527
|
+
className,
|
|
2528
|
+
style: { ...baseStyle, ...style },
|
|
2529
|
+
viewBox: "0 0 24 24",
|
|
2530
|
+
fill: "none",
|
|
2531
|
+
stroke: "currentColor",
|
|
2532
|
+
strokeWidth: "3",
|
|
2533
|
+
strokeLinecap: "round",
|
|
2534
|
+
strokeLinejoin: "round",
|
|
2535
|
+
children: /* @__PURE__ */ jsx("path", { d: "M19 12H5M12 5l-7 7 7 7" })
|
|
2536
|
+
}
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
function Right({ className, style }) {
|
|
2540
|
+
return /* @__PURE__ */ jsx(
|
|
2541
|
+
"svg",
|
|
2542
|
+
{
|
|
2543
|
+
className,
|
|
2544
|
+
style: { ...baseStyle, ...style },
|
|
2545
|
+
viewBox: "0 0 24 24",
|
|
2546
|
+
fill: "none",
|
|
2547
|
+
stroke: "currentColor",
|
|
2548
|
+
strokeWidth: "3",
|
|
2549
|
+
strokeLinecap: "round",
|
|
2550
|
+
strokeLinejoin: "round",
|
|
2551
|
+
children: /* @__PURE__ */ jsx("path", { d: "M5 12h14M12 5l7 7-7 7" })
|
|
2552
|
+
}
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
function Enter({ className, style }) {
|
|
2556
|
+
return /* @__PURE__ */ jsxs(
|
|
2557
|
+
"svg",
|
|
2558
|
+
{
|
|
2559
|
+
className,
|
|
2560
|
+
style: { ...baseStyle, ...style },
|
|
2561
|
+
viewBox: "0 0 24 24",
|
|
2562
|
+
fill: "none",
|
|
2563
|
+
stroke: "currentColor",
|
|
2564
|
+
strokeWidth: "3",
|
|
2565
|
+
strokeLinecap: "round",
|
|
2566
|
+
strokeLinejoin: "round",
|
|
2567
|
+
children: [
|
|
2568
|
+
/* @__PURE__ */ jsx("path", { d: "M9 10l-4 4 4 4" }),
|
|
2569
|
+
/* @__PURE__ */ jsx("path", { d: "M19 6v8a2 2 0 01-2 2H5" })
|
|
2570
|
+
]
|
|
2571
|
+
}
|
|
2572
|
+
);
|
|
2573
|
+
}
|
|
2574
|
+
function Backspace({ className, style }) {
|
|
2575
|
+
return /* @__PURE__ */ jsxs(
|
|
2576
|
+
"svg",
|
|
2577
|
+
{
|
|
2578
|
+
className,
|
|
2579
|
+
style: { ...baseStyle, ...style },
|
|
2580
|
+
viewBox: "0 0 24 24",
|
|
2581
|
+
fill: "none",
|
|
2582
|
+
stroke: "currentColor",
|
|
2583
|
+
strokeWidth: "2",
|
|
2584
|
+
strokeLinecap: "round",
|
|
2585
|
+
strokeLinejoin: "round",
|
|
2586
|
+
children: [
|
|
2587
|
+
/* @__PURE__ */ jsx("path", { d: "M21 4H8l-7 8 7 8h13a2 2 0 002-2V6a2 2 0 00-2-2z" }),
|
|
2588
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "9", x2: "12", y2: "15" }),
|
|
2589
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "9", x2: "18", y2: "15" })
|
|
2590
|
+
]
|
|
2591
|
+
}
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
function Tab({ className, style }) {
|
|
2595
|
+
return /* @__PURE__ */ jsxs(
|
|
2596
|
+
"svg",
|
|
2597
|
+
{
|
|
2598
|
+
className,
|
|
2599
|
+
style: { ...baseStyle, ...style },
|
|
2600
|
+
viewBox: "0 0 24 24",
|
|
2601
|
+
fill: "none",
|
|
2602
|
+
stroke: "currentColor",
|
|
2603
|
+
strokeWidth: "2",
|
|
2604
|
+
strokeLinecap: "round",
|
|
2605
|
+
strokeLinejoin: "round",
|
|
2606
|
+
children: [
|
|
2607
|
+
/* @__PURE__ */ jsx("line", { x1: "4", y1: "12", x2: "16", y2: "12" }),
|
|
2608
|
+
/* @__PURE__ */ jsx("polyline", { points: "12 8 16 12 12 16" }),
|
|
2609
|
+
/* @__PURE__ */ jsx("line", { x1: "20", y1: "6", x2: "20", y2: "18" })
|
|
2610
|
+
]
|
|
2611
|
+
}
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
function getKeyIcon(key) {
|
|
2615
|
+
switch (key.toLowerCase()) {
|
|
2616
|
+
case "arrowup":
|
|
2617
|
+
return Up;
|
|
2618
|
+
case "arrowdown":
|
|
2619
|
+
return Down;
|
|
2620
|
+
case "arrowleft":
|
|
2621
|
+
return Left;
|
|
2622
|
+
case "arrowright":
|
|
2623
|
+
return Right;
|
|
2624
|
+
case "enter":
|
|
2625
|
+
return Enter;
|
|
2626
|
+
case "backspace":
|
|
2627
|
+
return Backspace;
|
|
2628
|
+
case "tab":
|
|
2629
|
+
return Tab;
|
|
2630
|
+
default:
|
|
2631
|
+
return null;
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
var baseStyle2 = {
|
|
2635
|
+
width: "1.2em",
|
|
2636
|
+
height: "1.2em",
|
|
2637
|
+
marginRight: "2px",
|
|
2638
|
+
verticalAlign: "middle"
|
|
2639
|
+
};
|
|
2640
|
+
var wideStyle = {
|
|
2641
|
+
...baseStyle2,
|
|
2642
|
+
width: "1.4em"
|
|
2643
|
+
};
|
|
2644
|
+
var Command = forwardRef(
|
|
2645
|
+
({ className, style, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
2646
|
+
"svg",
|
|
2647
|
+
{
|
|
2648
|
+
ref,
|
|
2649
|
+
className,
|
|
2650
|
+
style: { ...baseStyle2, ...style },
|
|
2651
|
+
viewBox: "0 0 24 24",
|
|
2652
|
+
fill: "currentColor",
|
|
2653
|
+
...props,
|
|
2654
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6 4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2v4H6a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2h4v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-2v-4h2a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v2h-4V6a2 2 0 0 0-2-2H6zm4 6h4v4h-4v-4z" })
|
|
2655
|
+
}
|
|
2656
|
+
)
|
|
2657
|
+
);
|
|
2658
|
+
Command.displayName = "Command";
|
|
2659
|
+
var Ctrl = forwardRef(
|
|
2660
|
+
({ className, style, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
2661
|
+
"svg",
|
|
2662
|
+
{
|
|
2663
|
+
ref,
|
|
2664
|
+
className,
|
|
2665
|
+
style: { ...baseStyle2, ...style },
|
|
2666
|
+
viewBox: "0 0 24 24",
|
|
2667
|
+
fill: "none",
|
|
2668
|
+
stroke: "currentColor",
|
|
2669
|
+
strokeWidth: "3",
|
|
2670
|
+
strokeLinecap: "round",
|
|
2671
|
+
strokeLinejoin: "round",
|
|
2672
|
+
...props,
|
|
2673
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6 15l6-6 6 6" })
|
|
2674
|
+
}
|
|
2675
|
+
)
|
|
2676
|
+
);
|
|
2677
|
+
Ctrl.displayName = "Ctrl";
|
|
2678
|
+
var Shift = forwardRef(
|
|
2679
|
+
({ className, style, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
2680
|
+
"svg",
|
|
2681
|
+
{
|
|
2682
|
+
ref,
|
|
2683
|
+
className,
|
|
2684
|
+
style: { ...wideStyle, ...style },
|
|
2685
|
+
viewBox: "0 0 28 24",
|
|
2686
|
+
fill: "none",
|
|
2687
|
+
stroke: "currentColor",
|
|
2688
|
+
strokeWidth: "2",
|
|
2689
|
+
strokeLinejoin: "round",
|
|
2690
|
+
...props,
|
|
2691
|
+
children: /* @__PURE__ */ jsx("path", { d: "M14 3L3 14h6v7h10v-7h6L14 3z" })
|
|
2692
|
+
}
|
|
2693
|
+
)
|
|
2694
|
+
);
|
|
2695
|
+
Shift.displayName = "Shift";
|
|
2696
|
+
var Option = forwardRef(
|
|
2697
|
+
({ className, style, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
2698
|
+
"svg",
|
|
2699
|
+
{
|
|
2700
|
+
ref,
|
|
2701
|
+
className,
|
|
2702
|
+
style: { ...baseStyle2, ...style },
|
|
2703
|
+
viewBox: "0 0 24 24",
|
|
2704
|
+
fill: "none",
|
|
2705
|
+
stroke: "currentColor",
|
|
2706
|
+
strokeWidth: "2.5",
|
|
2707
|
+
strokeLinecap: "round",
|
|
2708
|
+
strokeLinejoin: "round",
|
|
2709
|
+
...props,
|
|
2710
|
+
children: /* @__PURE__ */ jsx("path", { d: "M4 6h6l8 12h6M14 6h6" })
|
|
2711
|
+
}
|
|
2712
|
+
)
|
|
2713
|
+
);
|
|
2714
|
+
Option.displayName = "Option";
|
|
2715
|
+
var Alt = forwardRef(
|
|
2716
|
+
({ className, style, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
2717
|
+
"svg",
|
|
2718
|
+
{
|
|
2719
|
+
ref,
|
|
2720
|
+
className,
|
|
2721
|
+
style: { ...baseStyle2, ...style },
|
|
2722
|
+
viewBox: "0 0 24 24",
|
|
2723
|
+
fill: "none",
|
|
2724
|
+
stroke: "currentColor",
|
|
2725
|
+
strokeWidth: "2.5",
|
|
2726
|
+
strokeLinecap: "round",
|
|
2727
|
+
strokeLinejoin: "round",
|
|
2728
|
+
...props,
|
|
2729
|
+
children: /* @__PURE__ */ jsx("path", { d: "M4 18h8M12 18l4-6M12 18l4 0M16 12l4-6h-8" })
|
|
2730
|
+
}
|
|
2731
|
+
)
|
|
2732
|
+
);
|
|
2733
|
+
Alt.displayName = "Alt";
|
|
2734
|
+
function getModifierIcon(modifier) {
|
|
2735
|
+
switch (modifier) {
|
|
2736
|
+
case "meta":
|
|
2737
|
+
return Command;
|
|
2738
|
+
case "ctrl":
|
|
2739
|
+
return Ctrl;
|
|
2740
|
+
case "shift":
|
|
2741
|
+
return Shift;
|
|
2742
|
+
case "opt":
|
|
2743
|
+
return Option;
|
|
2744
|
+
case "alt":
|
|
2745
|
+
return isMac() ? Option : Alt;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
var ModifierIcon = forwardRef(
|
|
2749
|
+
({ modifier, ...props }, ref) => {
|
|
2750
|
+
const Icon = getModifierIcon(modifier);
|
|
2751
|
+
return /* @__PURE__ */ jsx(Icon, { ref, ...props });
|
|
2752
|
+
}
|
|
2753
|
+
);
|
|
2754
|
+
ModifierIcon.displayName = "ModifierIcon";
|
|
2755
|
+
function renderModifierIcons(modifiers, className = "kbd-modifier-icon") {
|
|
2756
|
+
const icons = [];
|
|
2757
|
+
if (modifiers.meta) {
|
|
2758
|
+
icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "meta", className }, "meta"));
|
|
2759
|
+
}
|
|
2760
|
+
if (modifiers.ctrl) {
|
|
2761
|
+
icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "ctrl", className }, "ctrl"));
|
|
2762
|
+
}
|
|
2763
|
+
if (modifiers.alt) {
|
|
2764
|
+
icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "alt", className }, "alt"));
|
|
2765
|
+
}
|
|
2766
|
+
if (modifiers.shift) {
|
|
2767
|
+
icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "shift", className }, "shift"));
|
|
2768
|
+
}
|
|
2769
|
+
return icons;
|
|
2770
|
+
}
|
|
2771
|
+
function renderKeyContent(key, iconClassName = "kbd-key-icon") {
|
|
2772
|
+
const Icon = getKeyIcon(key);
|
|
2773
|
+
const displayKey = formatKeyForDisplay(key);
|
|
2774
|
+
return Icon ? /* @__PURE__ */ jsx(Icon, { className: iconClassName }) : /* @__PURE__ */ jsx(Fragment, { children: displayKey });
|
|
2775
|
+
}
|
|
2776
|
+
function renderSeqElem(elem, index, kbdClassName = "kbd-kbd") {
|
|
2777
|
+
if (elem.type === "digit") {
|
|
2778
|
+
return /* @__PURE__ */ jsx("kbd", { className: kbdClassName, children: "\u27E8#\u27E9" }, index);
|
|
2779
|
+
}
|
|
2780
|
+
if (elem.type === "digits") {
|
|
2781
|
+
return /* @__PURE__ */ jsx("kbd", { className: kbdClassName, children: "\u27E8##\u27E9" }, index);
|
|
2782
|
+
}
|
|
2783
|
+
return /* @__PURE__ */ jsxs("kbd", { className: kbdClassName, children: [
|
|
2784
|
+
renderModifierIcons(elem.modifiers),
|
|
2785
|
+
renderKeyContent(elem.key)
|
|
2786
|
+
] }, index);
|
|
2787
|
+
}
|
|
2788
|
+
function renderKeySeq(keySeq, kbdClassName = "kbd-kbd") {
|
|
2789
|
+
return keySeq.map((elem, i) => renderSeqElem(elem, i, kbdClassName));
|
|
2790
|
+
}
|
|
2791
|
+
function KeyCombo({ combo }) {
|
|
2792
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2793
|
+
renderModifierIcons(combo.modifiers),
|
|
2794
|
+
renderKeyContent(combo.key)
|
|
2795
|
+
] });
|
|
2796
|
+
}
|
|
2797
|
+
function SeqElemDisplay({ elem }) {
|
|
2798
|
+
if (elem.type === "digit") {
|
|
2799
|
+
return /* @__PURE__ */ jsx("span", { className: "kbd-placeholder", title: "Any single digit (0-9)", children: "#" });
|
|
2800
|
+
}
|
|
2801
|
+
if (elem.type === "digits") {
|
|
2802
|
+
return /* @__PURE__ */ jsx("span", { className: "kbd-placeholder", title: "One or more digits (0-9)", children: "##" });
|
|
2803
|
+
}
|
|
2804
|
+
return /* @__PURE__ */ jsx(KeyCombo, { combo: { key: elem.key, modifiers: elem.modifiers } });
|
|
2805
|
+
}
|
|
2806
|
+
function BindingDisplay({ binding }) {
|
|
2807
|
+
const sequence = parseKeySeq(binding);
|
|
2808
|
+
return /* @__PURE__ */ jsx(Fragment, { children: sequence.map((elem, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2809
|
+
i > 0 && /* @__PURE__ */ jsx("span", { className: "kbd-sequence-sep", children: " " }),
|
|
2810
|
+
/* @__PURE__ */ jsx(SeqElemDisplay, { elem })
|
|
2811
|
+
] }, i)) });
|
|
2812
|
+
}
|
|
2813
|
+
function Kbd({
|
|
2814
|
+
action,
|
|
2815
|
+
separator = " / ",
|
|
2816
|
+
all = false,
|
|
2817
|
+
fallback = null,
|
|
2818
|
+
className,
|
|
2819
|
+
clickable = true
|
|
2820
|
+
}) {
|
|
2821
|
+
const ctx = useMaybeHotkeysContext();
|
|
2822
|
+
const warnedRef = useRef(false);
|
|
2823
|
+
const bindings = ctx ? all ? ctx.registry.getBindingsForAction(action) : [ctx.registry.getFirstBindingForAction(action)].filter(Boolean) : [];
|
|
2824
|
+
useEffect(() => {
|
|
2825
|
+
if (!ctx) return;
|
|
2826
|
+
if (warnedRef.current) return;
|
|
2827
|
+
const timer = setTimeout(() => {
|
|
2828
|
+
if (!ctx.registry.actions.has(action)) {
|
|
2829
|
+
console.warn(`Kbd: Action "${action}" not found in registry`);
|
|
2830
|
+
warnedRef.current = true;
|
|
2831
|
+
}
|
|
2832
|
+
}, 100);
|
|
2833
|
+
return () => clearTimeout(timer);
|
|
2834
|
+
}, [ctx, action]);
|
|
2835
|
+
if (!ctx) {
|
|
2836
|
+
return null;
|
|
2837
|
+
}
|
|
2838
|
+
if (bindings.length === 0) {
|
|
2839
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
2840
|
+
}
|
|
2841
|
+
const content = bindings.map((binding, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2842
|
+
i > 0 && separator,
|
|
2843
|
+
/* @__PURE__ */ jsx(BindingDisplay, { binding })
|
|
2844
|
+
] }, binding));
|
|
2845
|
+
if (clickable) {
|
|
2846
|
+
return /* @__PURE__ */ jsx(
|
|
2847
|
+
"kbd",
|
|
2848
|
+
{
|
|
2849
|
+
className: `${className || ""} kbd-clickable`.trim(),
|
|
2850
|
+
onClick: () => ctx.executeAction(action),
|
|
2851
|
+
role: "button",
|
|
2852
|
+
tabIndex: 0,
|
|
2853
|
+
onKeyDown: (e) => {
|
|
2854
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2855
|
+
e.preventDefault();
|
|
2856
|
+
ctx.executeAction(action);
|
|
2857
|
+
}
|
|
2858
|
+
},
|
|
2859
|
+
children: content
|
|
2860
|
+
}
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2863
|
+
return /* @__PURE__ */ jsx("kbd", { className, children: content });
|
|
2864
|
+
}
|
|
2865
|
+
function Key(props) {
|
|
2866
|
+
return /* @__PURE__ */ jsx(Kbd, { ...props, clickable: false });
|
|
2867
|
+
}
|
|
2868
|
+
function Kbds(props) {
|
|
2869
|
+
return /* @__PURE__ */ jsx(Kbd, { ...props, all: true });
|
|
2870
|
+
}
|
|
2871
|
+
function KbdModal(props) {
|
|
2872
|
+
return /* @__PURE__ */ jsx(Kbd, { ...props, action: ACTION_MODAL });
|
|
2873
|
+
}
|
|
2874
|
+
function KbdOmnibar(props) {
|
|
2875
|
+
return /* @__PURE__ */ jsx(Kbd, { ...props, action: ACTION_OMNIBAR });
|
|
2876
|
+
}
|
|
2877
|
+
function KbdLookup(props) {
|
|
2878
|
+
return /* @__PURE__ */ jsx(Kbd, { ...props, action: ACTION_LOOKUP });
|
|
2879
|
+
}
|
|
1562
2880
|
function buildActionMap(keymap) {
|
|
1563
2881
|
const map = /* @__PURE__ */ new Map();
|
|
1564
2882
|
for (const [key, actionOrActions] of Object.entries(keymap)) {
|
|
@@ -1626,7 +2944,7 @@ function KeybindingEditor({
|
|
|
1626
2944
|
return Array.from(allActions).map((action) => {
|
|
1627
2945
|
const key = actionMap.get(action) ?? defaultActionMap.get(action) ?? "";
|
|
1628
2946
|
const defaultKey = defaultActionMap.get(action) ?? "";
|
|
1629
|
-
const combo =
|
|
2947
|
+
const combo = parseHotkeyString(key);
|
|
1630
2948
|
const display = formatCombination(combo);
|
|
1631
2949
|
const conflictActions = conflicts.get(key);
|
|
1632
2950
|
return {
|
|
@@ -1728,149 +3046,241 @@ function KeybindingEditor({
|
|
|
1728
3046
|
"button",
|
|
1729
3047
|
{
|
|
1730
3048
|
onClick: () => startEditing(action),
|
|
1731
|
-
disabled: isRecording,
|
|
1732
|
-
style: {
|
|
1733
|
-
padding: "4px 8px",
|
|
1734
|
-
backgroundColor: "#f5f5f5",
|
|
1735
|
-
border: "1px solid #ddd",
|
|
1736
|
-
borderRadius: "4px",
|
|
1737
|
-
cursor: isRecording ? "not-allowed" : "pointer",
|
|
1738
|
-
fontSize: "0.875rem",
|
|
1739
|
-
opacity: isRecording ? 0.5 : 1
|
|
1740
|
-
},
|
|
1741
|
-
children: "Edit"
|
|
1742
|
-
}
|
|
1743
|
-
) })
|
|
1744
|
-
] }, action);
|
|
1745
|
-
}) })
|
|
1746
|
-
] })
|
|
1747
|
-
] });
|
|
1748
|
-
}
|
|
1749
|
-
var baseStyle = {
|
|
1750
|
-
width: "1.2em",
|
|
1751
|
-
height: "1.2em",
|
|
1752
|
-
marginRight: "2px",
|
|
1753
|
-
verticalAlign: "middle"
|
|
1754
|
-
};
|
|
1755
|
-
var wideStyle = {
|
|
1756
|
-
...baseStyle,
|
|
1757
|
-
width: "1.4em"
|
|
1758
|
-
};
|
|
1759
|
-
function CommandIcon({ className, style }) {
|
|
1760
|
-
return /* @__PURE__ */ jsx(
|
|
1761
|
-
"svg",
|
|
1762
|
-
{
|
|
1763
|
-
className,
|
|
1764
|
-
style: { ...baseStyle, ...style },
|
|
1765
|
-
viewBox: "0 0 24 24",
|
|
1766
|
-
fill: "currentColor",
|
|
1767
|
-
children: /* @__PURE__ */ jsx("path", { d: "M6 4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2v4H6a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2h4v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-2v-4h2a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v2h-4V6a2 2 0 0 0-2-2H6zm4 6h4v4h-4v-4z" })
|
|
1768
|
-
}
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1771
|
-
function CtrlIcon({ className, style }) {
|
|
1772
|
-
return /* @__PURE__ */ jsx(
|
|
1773
|
-
"svg",
|
|
1774
|
-
{
|
|
1775
|
-
className,
|
|
1776
|
-
style: { ...baseStyle, ...style },
|
|
1777
|
-
viewBox: "0 0 24 24",
|
|
1778
|
-
fill: "none",
|
|
1779
|
-
stroke: "currentColor",
|
|
1780
|
-
strokeWidth: "3",
|
|
1781
|
-
strokeLinecap: "round",
|
|
1782
|
-
strokeLinejoin: "round",
|
|
1783
|
-
children: /* @__PURE__ */ jsx("path", { d: "M6 15l6-6 6 6" })
|
|
1784
|
-
}
|
|
1785
|
-
);
|
|
3049
|
+
disabled: isRecording,
|
|
3050
|
+
style: {
|
|
3051
|
+
padding: "4px 8px",
|
|
3052
|
+
backgroundColor: "#f5f5f5",
|
|
3053
|
+
border: "1px solid #ddd",
|
|
3054
|
+
borderRadius: "4px",
|
|
3055
|
+
cursor: isRecording ? "not-allowed" : "pointer",
|
|
3056
|
+
fontSize: "0.875rem",
|
|
3057
|
+
opacity: isRecording ? 0.5 : 1
|
|
3058
|
+
},
|
|
3059
|
+
children: "Edit"
|
|
3060
|
+
}
|
|
3061
|
+
) })
|
|
3062
|
+
] }, action);
|
|
3063
|
+
}) })
|
|
3064
|
+
] })
|
|
3065
|
+
] });
|
|
1786
3066
|
}
|
|
1787
|
-
function
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
3067
|
+
function LookupModal({ defaultBinding = "meta+shift+k" } = {}) {
|
|
3068
|
+
const {
|
|
3069
|
+
isLookupOpen,
|
|
3070
|
+
closeLookup,
|
|
3071
|
+
toggleLookup,
|
|
3072
|
+
registry,
|
|
3073
|
+
executeAction
|
|
3074
|
+
} = useHotkeysContext();
|
|
3075
|
+
useAction(ACTION_LOOKUP, {
|
|
3076
|
+
label: "Key lookup",
|
|
3077
|
+
group: "Global",
|
|
3078
|
+
defaultBindings: defaultBinding ? [defaultBinding] : [],
|
|
3079
|
+
handler: useCallback(() => toggleLookup(), [toggleLookup])
|
|
3080
|
+
});
|
|
3081
|
+
const [pendingKeys, setPendingKeys] = useState([]);
|
|
3082
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
3083
|
+
const allBindings = useMemo(() => {
|
|
3084
|
+
const results = [];
|
|
3085
|
+
const keymap = registry.keymap;
|
|
3086
|
+
for (const [binding, actionOrActions] of Object.entries(keymap)) {
|
|
3087
|
+
if (binding.startsWith("__")) continue;
|
|
3088
|
+
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
3089
|
+
const sequence = parseHotkeyString(binding);
|
|
3090
|
+
const keySeq = parseKeySeq(binding);
|
|
3091
|
+
const display = formatKeySeq(keySeq).display;
|
|
3092
|
+
const labels = actions.map((actionId) => {
|
|
3093
|
+
const action = registry.actions.get(actionId);
|
|
3094
|
+
return action?.config.label || actionId;
|
|
3095
|
+
});
|
|
3096
|
+
results.push({ binding, sequence, keySeq, display, actions, labels });
|
|
1799
3097
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
{
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
3098
|
+
results.sort((a, b) => a.binding.localeCompare(b.binding));
|
|
3099
|
+
return results;
|
|
3100
|
+
}, [registry.keymap, registry.actions]);
|
|
3101
|
+
const filteredBindings = useMemo(() => {
|
|
3102
|
+
if (pendingKeys.length === 0) return allBindings;
|
|
3103
|
+
return allBindings.filter((result) => {
|
|
3104
|
+
const keySeq = result.keySeq;
|
|
3105
|
+
if (keySeq.length < pendingKeys.length) return false;
|
|
3106
|
+
let keySeqIdx = 0;
|
|
3107
|
+
for (let i = 0; i < pendingKeys.length && keySeqIdx < keySeq.length; i++) {
|
|
3108
|
+
const pending = pendingKeys[i];
|
|
3109
|
+
const elem = keySeq[keySeqIdx];
|
|
3110
|
+
const isDigit2 = /^[0-9]$/.test(pending.key);
|
|
3111
|
+
if (elem.type === "digits") {
|
|
3112
|
+
if (!isDigit2) return false;
|
|
3113
|
+
if (i + 1 < pendingKeys.length && /^[0-9]$/.test(pendingKeys[i + 1].key)) {
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
keySeqIdx++;
|
|
3117
|
+
} else if (elem.type === "digit") {
|
|
3118
|
+
if (!isDigit2) return false;
|
|
3119
|
+
keySeqIdx++;
|
|
3120
|
+
} else {
|
|
3121
|
+
if (pending.key !== elem.key) return false;
|
|
3122
|
+
if (pending.modifiers.ctrl !== elem.modifiers.ctrl) return false;
|
|
3123
|
+
if (pending.modifiers.alt !== elem.modifiers.alt) return false;
|
|
3124
|
+
if (pending.modifiers.shift !== elem.modifiers.shift) return false;
|
|
3125
|
+
if (pending.modifiers.meta !== elem.modifiers.meta) return false;
|
|
3126
|
+
keySeqIdx++;
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
return true;
|
|
3130
|
+
});
|
|
3131
|
+
}, [allBindings, pendingKeys]);
|
|
3132
|
+
const groupedByNextKey = useMemo(() => {
|
|
3133
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3134
|
+
for (const result of filteredBindings) {
|
|
3135
|
+
if (result.sequence.length > pendingKeys.length) {
|
|
3136
|
+
const nextCombo = result.sequence[pendingKeys.length];
|
|
3137
|
+
const nextKey = formatCombination([nextCombo]).display;
|
|
3138
|
+
const existing = groups.get(nextKey) || [];
|
|
3139
|
+
existing.push(result);
|
|
3140
|
+
groups.set(nextKey, existing);
|
|
3141
|
+
} else {
|
|
3142
|
+
const existing = groups.get("") || [];
|
|
3143
|
+
existing.push(result);
|
|
3144
|
+
groups.set("", existing);
|
|
3145
|
+
}
|
|
1815
3146
|
}
|
|
1816
|
-
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
stroke: "currentColor",
|
|
1827
|
-
strokeWidth: "2.5",
|
|
1828
|
-
strokeLinecap: "round",
|
|
1829
|
-
strokeLinejoin: "round",
|
|
1830
|
-
children: /* @__PURE__ */ jsx("path", { d: "M4 18h8M12 18l4-6M12 18l4 0M16 12l4-6h-8" })
|
|
3147
|
+
return groups;
|
|
3148
|
+
}, [filteredBindings, pendingKeys]);
|
|
3149
|
+
const formattedPendingKeys = useMemo(() => {
|
|
3150
|
+
if (pendingKeys.length === 0) return "";
|
|
3151
|
+
return formatCombination(pendingKeys).display;
|
|
3152
|
+
}, [pendingKeys]);
|
|
3153
|
+
useEffect(() => {
|
|
3154
|
+
if (isLookupOpen) {
|
|
3155
|
+
setPendingKeys([]);
|
|
3156
|
+
setSelectedIndex(0);
|
|
1831
3157
|
}
|
|
1832
|
-
);
|
|
3158
|
+
}, [isLookupOpen]);
|
|
3159
|
+
useEffect(() => {
|
|
3160
|
+
setSelectedIndex(0);
|
|
3161
|
+
}, [filteredBindings.length]);
|
|
3162
|
+
useEffect(() => {
|
|
3163
|
+
if (!isLookupOpen) return;
|
|
3164
|
+
const handleKeyDown = (e) => {
|
|
3165
|
+
if (e.key === "Escape") {
|
|
3166
|
+
e.preventDefault();
|
|
3167
|
+
if (pendingKeys.length > 0) {
|
|
3168
|
+
setPendingKeys([]);
|
|
3169
|
+
} else {
|
|
3170
|
+
closeLookup();
|
|
3171
|
+
}
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
if (e.key === "Backspace") {
|
|
3175
|
+
e.preventDefault();
|
|
3176
|
+
setPendingKeys((prev) => prev.slice(0, -1));
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
if (e.key === "ArrowDown") {
|
|
3180
|
+
e.preventDefault();
|
|
3181
|
+
setSelectedIndex((prev) => Math.min(prev + 1, filteredBindings.length - 1));
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
if (e.key === "ArrowUp") {
|
|
3185
|
+
e.preventDefault();
|
|
3186
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
if (e.key === "Enter") {
|
|
3190
|
+
e.preventDefault();
|
|
3191
|
+
const selected = filteredBindings[selectedIndex];
|
|
3192
|
+
if (selected && selected.actions.length > 0) {
|
|
3193
|
+
closeLookup();
|
|
3194
|
+
executeAction(selected.actions[0]);
|
|
3195
|
+
}
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
if (isModifierKey(e.key)) return;
|
|
3199
|
+
e.preventDefault();
|
|
3200
|
+
const newCombo = {
|
|
3201
|
+
key: normalizeKey(e.key),
|
|
3202
|
+
modifiers: {
|
|
3203
|
+
ctrl: e.ctrlKey,
|
|
3204
|
+
alt: e.altKey,
|
|
3205
|
+
shift: e.shiftKey,
|
|
3206
|
+
meta: e.metaKey
|
|
3207
|
+
}
|
|
3208
|
+
};
|
|
3209
|
+
setPendingKeys((prev) => [...prev, newCombo]);
|
|
3210
|
+
};
|
|
3211
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
3212
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
3213
|
+
}, [isLookupOpen, pendingKeys, filteredBindings, selectedIndex, closeLookup, executeAction]);
|
|
3214
|
+
const handleBackdropClick = useCallback(() => {
|
|
3215
|
+
closeLookup();
|
|
3216
|
+
}, [closeLookup]);
|
|
3217
|
+
if (!isLookupOpen) return null;
|
|
3218
|
+
return /* @__PURE__ */ jsx("div", { className: "kbd-lookup-backdrop", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs("div", { className: "kbd-lookup", onClick: (e) => e.stopPropagation(), children: [
|
|
3219
|
+
/* @__PURE__ */ jsxs("div", { className: "kbd-lookup-header", children: [
|
|
3220
|
+
/* @__PURE__ */ jsx("div", { className: "kbd-lookup-search", children: formattedPendingKeys ? /* @__PURE__ */ jsx("kbd", { className: "kbd-sequence-keys", children: formattedPendingKeys }) : /* @__PURE__ */ jsx("span", { className: "kbd-lookup-placeholder", children: "Type keys to filter..." }) }),
|
|
3221
|
+
/* @__PURE__ */ jsxs("span", { className: "kbd-lookup-hint", children: [
|
|
3222
|
+
"\u2191\u2193 navigate \xB7 Enter select \xB7 Esc ",
|
|
3223
|
+
pendingKeys.length > 0 ? "clear" : "close",
|
|
3224
|
+
" \xB7 \u232B back"
|
|
3225
|
+
] })
|
|
3226
|
+
] }),
|
|
3227
|
+
/* @__PURE__ */ jsx("div", { className: "kbd-lookup-results", children: filteredBindings.length === 0 ? /* @__PURE__ */ jsx("div", { className: "kbd-lookup-empty", children: "No matching shortcuts" }) : filteredBindings.map((result, index) => /* @__PURE__ */ jsxs(
|
|
3228
|
+
"div",
|
|
3229
|
+
{
|
|
3230
|
+
className: `kbd-lookup-result ${index === selectedIndex ? "selected" : ""}`,
|
|
3231
|
+
onClick: () => {
|
|
3232
|
+
closeLookup();
|
|
3233
|
+
if (result.actions.length > 0) {
|
|
3234
|
+
executeAction(result.actions[0]);
|
|
3235
|
+
}
|
|
3236
|
+
},
|
|
3237
|
+
onMouseEnter: () => setSelectedIndex(index),
|
|
3238
|
+
children: [
|
|
3239
|
+
/* @__PURE__ */ jsx("span", { className: "kbd-lookup-binding", children: renderKeySeq(result.keySeq) }),
|
|
3240
|
+
/* @__PURE__ */ jsx("span", { className: "kbd-lookup-labels", children: result.labels.join(", ") })
|
|
3241
|
+
]
|
|
3242
|
+
},
|
|
3243
|
+
result.binding
|
|
3244
|
+
)) }),
|
|
3245
|
+
pendingKeys.length > 0 && groupedByNextKey.size > 1 && /* @__PURE__ */ jsxs("div", { className: "kbd-lookup-continuations", children: [
|
|
3246
|
+
/* @__PURE__ */ jsx("span", { className: "kbd-lookup-continuations-label", children: "Continue with:" }),
|
|
3247
|
+
Array.from(groupedByNextKey.keys()).filter((k) => k !== "").slice(0, 8).map((nextKey) => /* @__PURE__ */ jsx("kbd", { className: "kbd-kbd kbd-small", children: nextKey }, nextKey)),
|
|
3248
|
+
groupedByNextKey.size > 9 && /* @__PURE__ */ jsx("span", { children: "..." })
|
|
3249
|
+
] })
|
|
3250
|
+
] }) });
|
|
1833
3251
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
case "meta":
|
|
1838
|
-
return CommandIcon;
|
|
1839
|
-
case "ctrl":
|
|
1840
|
-
return CtrlIcon;
|
|
1841
|
-
case "shift":
|
|
1842
|
-
return ShiftIcon;
|
|
1843
|
-
case "opt":
|
|
1844
|
-
return OptIcon;
|
|
1845
|
-
case "alt":
|
|
1846
|
-
return isMac2 ? OptIcon : AltIcon;
|
|
3252
|
+
function SeqElemBadge({ elem }) {
|
|
3253
|
+
if (elem.type === "digit") {
|
|
3254
|
+
return /* @__PURE__ */ jsx("span", { className: "kbd-placeholder", title: "Any single digit (0-9)", children: "#" });
|
|
1847
3255
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
return /* @__PURE__ */
|
|
3256
|
+
if (elem.type === "digits") {
|
|
3257
|
+
return /* @__PURE__ */ jsx("span", { className: "kbd-placeholder", title: "One or more digits (0-9)", children: "##" });
|
|
3258
|
+
}
|
|
3259
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3260
|
+
elem.modifiers.meta && /* @__PURE__ */ jsx(ModifierIcon, { modifier: "meta", className: "kbd-modifier-icon" }),
|
|
3261
|
+
elem.modifiers.ctrl && /* @__PURE__ */ jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }),
|
|
3262
|
+
elem.modifiers.alt && /* @__PURE__ */ jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }),
|
|
3263
|
+
elem.modifiers.shift && /* @__PURE__ */ jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }),
|
|
3264
|
+
/* @__PURE__ */ jsx("span", { children: formatKeyForDisplay(elem.key) })
|
|
3265
|
+
] });
|
|
1852
3266
|
}
|
|
1853
3267
|
function BindingBadge({ binding }) {
|
|
1854
|
-
const
|
|
1855
|
-
return /* @__PURE__ */ jsx("kbd", { className: "kbd-kbd", children:
|
|
3268
|
+
const keySeq = parseKeySeq(binding);
|
|
3269
|
+
return /* @__PURE__ */ jsx("kbd", { className: "kbd-kbd", children: keySeq.map((elem, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1856
3270
|
i > 0 && /* @__PURE__ */ jsx("span", { className: "kbd-sequence-sep", children: " " }),
|
|
1857
|
-
|
|
1858
|
-
combo.modifiers.ctrl && /* @__PURE__ */ jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }),
|
|
1859
|
-
combo.modifiers.alt && /* @__PURE__ */ jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }),
|
|
1860
|
-
combo.modifiers.shift && /* @__PURE__ */ jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }),
|
|
1861
|
-
/* @__PURE__ */ jsx("span", { children: combo.key.length === 1 ? combo.key.toUpperCase() : combo.key })
|
|
3271
|
+
/* @__PURE__ */ jsx(SeqElemBadge, { elem })
|
|
1862
3272
|
] }, i)) });
|
|
1863
3273
|
}
|
|
1864
3274
|
function Omnibar({
|
|
1865
3275
|
actions: actionsProp,
|
|
1866
3276
|
handlers: handlersProp,
|
|
1867
3277
|
keymap: keymapProp,
|
|
1868
|
-
|
|
1869
|
-
enabled: enabledProp,
|
|
3278
|
+
defaultBinding = "meta+k",
|
|
1870
3279
|
isOpen: isOpenProp,
|
|
1871
3280
|
onOpen: onOpenProp,
|
|
1872
3281
|
onClose: onCloseProp,
|
|
1873
3282
|
onExecute: onExecuteProp,
|
|
3283
|
+
onExecuteRemote: onExecuteRemoteProp,
|
|
1874
3284
|
maxResults = 10,
|
|
1875
3285
|
placeholder = "Type a command...",
|
|
1876
3286
|
children,
|
|
@@ -1881,7 +3291,12 @@ function Omnibar({
|
|
|
1881
3291
|
const ctx = useMaybeHotkeysContext();
|
|
1882
3292
|
const actions = actionsProp ?? ctx?.registry.actionRegistry ?? {};
|
|
1883
3293
|
const keymap = keymapProp ?? ctx?.registry.keymap ?? {};
|
|
1884
|
-
|
|
3294
|
+
useAction(ACTION_OMNIBAR, {
|
|
3295
|
+
label: "Command palette",
|
|
3296
|
+
group: "Global",
|
|
3297
|
+
defaultBindings: defaultBinding ? [defaultBinding] : [],
|
|
3298
|
+
handler: useCallback(() => ctx?.toggleOmnibar(), [ctx?.toggleOmnibar])
|
|
3299
|
+
});
|
|
1885
3300
|
const handleExecute = useCallback((actionId) => {
|
|
1886
3301
|
if (onExecuteProp) {
|
|
1887
3302
|
onExecuteProp(actionId);
|
|
@@ -1903,13 +3318,25 @@ function Omnibar({
|
|
|
1903
3318
|
ctx.openOmnibar();
|
|
1904
3319
|
}
|
|
1905
3320
|
}, [onOpenProp, ctx]);
|
|
3321
|
+
const handleExecuteRemote = useCallback((entry) => {
|
|
3322
|
+
if (onExecuteRemoteProp) {
|
|
3323
|
+
onExecuteRemoteProp(entry);
|
|
3324
|
+
} else if ("href" in entry && entry.href) {
|
|
3325
|
+
window.location.href = entry.href;
|
|
3326
|
+
}
|
|
3327
|
+
}, [onExecuteRemoteProp]);
|
|
1906
3328
|
const {
|
|
1907
3329
|
isOpen: internalIsOpen,
|
|
1908
3330
|
close,
|
|
1909
3331
|
query,
|
|
1910
3332
|
setQuery,
|
|
1911
3333
|
results,
|
|
3334
|
+
remoteResults,
|
|
3335
|
+
isLoadingRemote,
|
|
3336
|
+
endpointPagination,
|
|
3337
|
+
loadMore,
|
|
1912
3338
|
selectedIndex,
|
|
3339
|
+
totalResults,
|
|
1913
3340
|
selectNext,
|
|
1914
3341
|
selectPrev,
|
|
1915
3342
|
execute,
|
|
@@ -1920,15 +3347,28 @@ function Omnibar({
|
|
|
1920
3347
|
actions,
|
|
1921
3348
|
handlers: handlersProp,
|
|
1922
3349
|
keymap,
|
|
1923
|
-
openKey,
|
|
1924
|
-
|
|
1925
|
-
|
|
3350
|
+
openKey: "",
|
|
3351
|
+
// Trigger is handled via useAction, not useOmnibar
|
|
3352
|
+
enabled: false,
|
|
1926
3353
|
onOpen: handleOpen,
|
|
1927
3354
|
onClose: handleClose,
|
|
1928
3355
|
onExecute: handleExecute,
|
|
1929
|
-
|
|
3356
|
+
onExecuteRemote: handleExecuteRemote,
|
|
3357
|
+
maxResults,
|
|
3358
|
+
endpointsRegistry: ctx?.endpointsRegistry
|
|
1930
3359
|
});
|
|
1931
3360
|
const isOpen = isOpenProp ?? ctx?.isOmnibarOpen ?? internalIsOpen;
|
|
3361
|
+
const resultsContainerRef = useRef(null);
|
|
3362
|
+
const sentinelRefs = useRef(/* @__PURE__ */ new Map());
|
|
3363
|
+
const remoteResultsByEndpoint = useMemo(() => {
|
|
3364
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3365
|
+
for (const result of remoteResults) {
|
|
3366
|
+
const existing = grouped.get(result.endpointId) ?? [];
|
|
3367
|
+
existing.push(result);
|
|
3368
|
+
grouped.set(result.endpointId, existing);
|
|
3369
|
+
}
|
|
3370
|
+
return grouped;
|
|
3371
|
+
}, [remoteResults]);
|
|
1932
3372
|
useEffect(() => {
|
|
1933
3373
|
if (isOpen) {
|
|
1934
3374
|
requestAnimationFrame(() => {
|
|
@@ -1936,6 +3376,50 @@ function Omnibar({
|
|
|
1936
3376
|
});
|
|
1937
3377
|
}
|
|
1938
3378
|
}, [isOpen]);
|
|
3379
|
+
useEffect(() => {
|
|
3380
|
+
if (!isOpen) return;
|
|
3381
|
+
const container = resultsContainerRef.current;
|
|
3382
|
+
if (!container) return;
|
|
3383
|
+
const observer = new IntersectionObserver(
|
|
3384
|
+
(entries) => {
|
|
3385
|
+
for (const entry of entries) {
|
|
3386
|
+
if (!entry.isIntersecting) continue;
|
|
3387
|
+
const endpointId = entry.target.dataset.endpointId;
|
|
3388
|
+
if (!endpointId) continue;
|
|
3389
|
+
const paginationInfo = endpointPagination.get(endpointId);
|
|
3390
|
+
if (!paginationInfo) continue;
|
|
3391
|
+
if (paginationInfo.mode !== "scroll") continue;
|
|
3392
|
+
if (!paginationInfo.hasMore) continue;
|
|
3393
|
+
if (paginationInfo.isLoading) continue;
|
|
3394
|
+
loadMore(endpointId);
|
|
3395
|
+
}
|
|
3396
|
+
},
|
|
3397
|
+
{
|
|
3398
|
+
root: container,
|
|
3399
|
+
rootMargin: "100px",
|
|
3400
|
+
// Trigger slightly before sentinel is visible
|
|
3401
|
+
threshold: 0
|
|
3402
|
+
}
|
|
3403
|
+
);
|
|
3404
|
+
for (const [_endpointId, sentinel] of sentinelRefs.current) {
|
|
3405
|
+
if (sentinel) {
|
|
3406
|
+
observer.observe(sentinel);
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
return () => observer.disconnect();
|
|
3410
|
+
}, [isOpen, endpointPagination, loadMore]);
|
|
3411
|
+
useEffect(() => {
|
|
3412
|
+
if (!isOpen) return;
|
|
3413
|
+
const handleGlobalKeyDown = (e) => {
|
|
3414
|
+
if (e.key === "Escape") {
|
|
3415
|
+
e.preventDefault();
|
|
3416
|
+
e.stopPropagation();
|
|
3417
|
+
close();
|
|
3418
|
+
}
|
|
3419
|
+
};
|
|
3420
|
+
document.addEventListener("keydown", handleGlobalKeyDown, true);
|
|
3421
|
+
return () => document.removeEventListener("keydown", handleGlobalKeyDown, true);
|
|
3422
|
+
}, [isOpen, close]);
|
|
1939
3423
|
const handleKeyDown = useCallback(
|
|
1940
3424
|
(e) => {
|
|
1941
3425
|
switch (e.key) {
|
|
@@ -1973,7 +3457,12 @@ function Omnibar({
|
|
|
1973
3457
|
query,
|
|
1974
3458
|
setQuery,
|
|
1975
3459
|
results,
|
|
3460
|
+
remoteResults,
|
|
3461
|
+
isLoadingRemote,
|
|
3462
|
+
endpointPagination,
|
|
3463
|
+
loadMore,
|
|
1976
3464
|
selectedIndex,
|
|
3465
|
+
totalResults,
|
|
1977
3466
|
selectNext,
|
|
1978
3467
|
selectPrev,
|
|
1979
3468
|
execute,
|
|
@@ -2001,65 +3490,164 @@ function Omnibar({
|
|
|
2001
3490
|
spellCheck: false
|
|
2002
3491
|
}
|
|
2003
3492
|
),
|
|
2004
|
-
/* @__PURE__ */ jsx("div", { className: "kbd-omnibar-results", children:
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
3493
|
+
/* @__PURE__ */ jsx("div", { className: "kbd-omnibar-results", ref: resultsContainerRef, children: totalResults === 0 && !isLoadingRemote ? /* @__PURE__ */ jsx("div", { className: "kbd-omnibar-no-results", children: query ? "No matching commands" : "Start typing to search commands..." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3494
|
+
results.map((result, i) => /* @__PURE__ */ jsxs(
|
|
3495
|
+
"div",
|
|
3496
|
+
{
|
|
3497
|
+
className: `kbd-omnibar-result ${i === selectedIndex ? "selected" : ""}`,
|
|
3498
|
+
onClick: () => execute(result.id),
|
|
3499
|
+
children: [
|
|
3500
|
+
/* @__PURE__ */ jsx("span", { className: "kbd-omnibar-result-label", children: result.action.label }),
|
|
3501
|
+
result.action.group && /* @__PURE__ */ jsx("span", { className: "kbd-omnibar-result-category", children: result.action.group }),
|
|
3502
|
+
result.bindings.length > 0 && /* @__PURE__ */ jsx("div", { className: "kbd-omnibar-result-bindings", children: result.bindings.slice(0, 2).map((binding) => /* @__PURE__ */ jsx(BindingBadge, { binding }, binding)) })
|
|
3503
|
+
]
|
|
2010
3504
|
},
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
]
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
3505
|
+
result.id
|
|
3506
|
+
)),
|
|
3507
|
+
(() => {
|
|
3508
|
+
let remoteIndex = 0;
|
|
3509
|
+
return Array.from(remoteResultsByEndpoint.entries()).map(([endpointId, endpointResults]) => {
|
|
3510
|
+
const paginationInfo = endpointPagination.get(endpointId);
|
|
3511
|
+
const showPagination = paginationInfo?.mode === "scroll" && paginationInfo.total !== void 0;
|
|
3512
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3513
|
+
endpointResults.map((result) => {
|
|
3514
|
+
const absoluteIndex = results.length + remoteIndex;
|
|
3515
|
+
remoteIndex++;
|
|
3516
|
+
return /* @__PURE__ */ jsxs(
|
|
3517
|
+
"div",
|
|
3518
|
+
{
|
|
3519
|
+
className: `kbd-omnibar-result ${absoluteIndex === selectedIndex ? "selected" : ""}`,
|
|
3520
|
+
onClick: () => execute(result.id),
|
|
3521
|
+
children: [
|
|
3522
|
+
/* @__PURE__ */ jsx("span", { className: "kbd-omnibar-result-label", children: result.entry.label }),
|
|
3523
|
+
result.entry.group && /* @__PURE__ */ jsx("span", { className: "kbd-omnibar-result-category", children: result.entry.group }),
|
|
3524
|
+
result.entry.description && /* @__PURE__ */ jsx("span", { className: "kbd-omnibar-result-description", children: result.entry.description })
|
|
3525
|
+
]
|
|
3526
|
+
},
|
|
3527
|
+
result.id
|
|
3528
|
+
);
|
|
3529
|
+
}),
|
|
3530
|
+
paginationInfo?.mode === "scroll" && /* @__PURE__ */ jsx(
|
|
3531
|
+
"div",
|
|
3532
|
+
{
|
|
3533
|
+
className: "kbd-omnibar-pagination",
|
|
3534
|
+
ref: (el) => sentinelRefs.current.set(endpointId, el),
|
|
3535
|
+
"data-endpoint-id": endpointId,
|
|
3536
|
+
children: paginationInfo.isLoading ? /* @__PURE__ */ jsx("span", { className: "kbd-omnibar-pagination-loading", children: "Loading more..." }) : showPagination ? /* @__PURE__ */ jsxs("span", { className: "kbd-omnibar-pagination-info", children: [
|
|
3537
|
+
paginationInfo.loaded,
|
|
3538
|
+
" of ",
|
|
3539
|
+
paginationInfo.total
|
|
3540
|
+
] }) : paginationInfo.hasMore ? /* @__PURE__ */ jsx("span", { className: "kbd-omnibar-pagination-more", children: "Scroll for more..." }) : null
|
|
3541
|
+
}
|
|
3542
|
+
)
|
|
3543
|
+
] }, endpointId);
|
|
3544
|
+
});
|
|
3545
|
+
})(),
|
|
3546
|
+
isLoadingRemote && remoteResults.length === 0 && /* @__PURE__ */ jsx("div", { className: "kbd-omnibar-loading", children: "Searching..." })
|
|
3547
|
+
] }) })
|
|
2019
3548
|
] }) });
|
|
2020
3549
|
}
|
|
2021
3550
|
function SequenceModal() {
|
|
2022
3551
|
const {
|
|
2023
3552
|
pendingKeys,
|
|
2024
3553
|
isAwaitingSequence,
|
|
3554
|
+
cancelSequence,
|
|
2025
3555
|
sequenceTimeoutStartedAt: timeoutStartedAt,
|
|
2026
3556
|
sequenceTimeout,
|
|
2027
3557
|
getCompletions,
|
|
2028
|
-
registry
|
|
3558
|
+
registry,
|
|
3559
|
+
executeAction
|
|
2029
3560
|
} = useHotkeysContext();
|
|
3561
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
3562
|
+
const [hasInteracted, setHasInteracted] = useState(false);
|
|
2030
3563
|
const completions = useMemo(() => {
|
|
2031
3564
|
if (pendingKeys.length === 0) return [];
|
|
2032
3565
|
return getCompletions(pendingKeys);
|
|
2033
3566
|
}, [getCompletions, pendingKeys]);
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
return formatCombination(pendingKeys).display;
|
|
2037
|
-
}, [pendingKeys]);
|
|
2038
|
-
const getActionLabel = (actionId) => {
|
|
2039
|
-
const action = registry.actions.get(actionId);
|
|
2040
|
-
return action?.config.label || actionId;
|
|
2041
|
-
};
|
|
2042
|
-
const groupedCompletions = useMemo(() => {
|
|
2043
|
-
const byNextKey = /* @__PURE__ */ new Map();
|
|
3567
|
+
const flatCompletions = useMemo(() => {
|
|
3568
|
+
const items = [];
|
|
2044
3569
|
for (const c of completions) {
|
|
2045
|
-
const
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
3570
|
+
for (const action of c.actions) {
|
|
3571
|
+
const displayKey = c.isComplete ? "\u21B5" : c.nextKeys;
|
|
3572
|
+
items.push({
|
|
3573
|
+
completion: c,
|
|
3574
|
+
action,
|
|
3575
|
+
displayKey,
|
|
3576
|
+
isComplete: c.isComplete
|
|
3577
|
+
});
|
|
2050
3578
|
}
|
|
2051
3579
|
}
|
|
2052
|
-
return
|
|
3580
|
+
return items;
|
|
2053
3581
|
}, [completions]);
|
|
3582
|
+
const itemCount = flatCompletions.length;
|
|
3583
|
+
const shouldShowTimeout = timeoutStartedAt !== null && completions.length === 1 && !hasInteracted;
|
|
3584
|
+
useEffect(() => {
|
|
3585
|
+
setSelectedIndex(0);
|
|
3586
|
+
setHasInteracted(false);
|
|
3587
|
+
}, [pendingKeys]);
|
|
3588
|
+
const executeSelected = useCallback(() => {
|
|
3589
|
+
if (selectedIndex >= 0 && selectedIndex < flatCompletions.length) {
|
|
3590
|
+
const item = flatCompletions[selectedIndex];
|
|
3591
|
+
executeAction(item.action, item.completion.captures);
|
|
3592
|
+
cancelSequence();
|
|
3593
|
+
}
|
|
3594
|
+
}, [selectedIndex, flatCompletions, executeAction, cancelSequence]);
|
|
3595
|
+
useEffect(() => {
|
|
3596
|
+
if (!isAwaitingSequence || pendingKeys.length === 0) return;
|
|
3597
|
+
const handleKeyDown = (e) => {
|
|
3598
|
+
switch (e.key) {
|
|
3599
|
+
case "ArrowDown":
|
|
3600
|
+
e.preventDefault();
|
|
3601
|
+
e.stopPropagation();
|
|
3602
|
+
setSelectedIndex((prev) => Math.min(prev + 1, itemCount - 1));
|
|
3603
|
+
setHasInteracted(true);
|
|
3604
|
+
break;
|
|
3605
|
+
case "ArrowUp":
|
|
3606
|
+
e.preventDefault();
|
|
3607
|
+
e.stopPropagation();
|
|
3608
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
3609
|
+
setHasInteracted(true);
|
|
3610
|
+
break;
|
|
3611
|
+
case "Enter":
|
|
3612
|
+
e.preventDefault();
|
|
3613
|
+
e.stopPropagation();
|
|
3614
|
+
executeSelected();
|
|
3615
|
+
break;
|
|
3616
|
+
}
|
|
3617
|
+
};
|
|
3618
|
+
document.addEventListener("keydown", handleKeyDown, true);
|
|
3619
|
+
return () => document.removeEventListener("keydown", handleKeyDown, true);
|
|
3620
|
+
}, [isAwaitingSequence, pendingKeys.length, itemCount, executeSelected]);
|
|
3621
|
+
const renderKey = useCallback((combo, index) => {
|
|
3622
|
+
const { key, modifiers } = combo;
|
|
3623
|
+
return /* @__PURE__ */ jsxs("kbd", { className: "kbd-kbd", children: [
|
|
3624
|
+
renderModifierIcons(modifiers),
|
|
3625
|
+
renderKeyContent(key)
|
|
3626
|
+
] }, index);
|
|
3627
|
+
}, []);
|
|
3628
|
+
const getActionLabel = (actionId, captures) => {
|
|
3629
|
+
const action = registry.actions.get(actionId);
|
|
3630
|
+
let label = action?.config.label || actionId;
|
|
3631
|
+
if (captures && captures.length > 0) {
|
|
3632
|
+
let captureIdx = 0;
|
|
3633
|
+
label = label.replace(/\bN\b/g, () => {
|
|
3634
|
+
if (captureIdx < captures.length) {
|
|
3635
|
+
return String(captures[captureIdx++]);
|
|
3636
|
+
}
|
|
3637
|
+
return "N";
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
3640
|
+
return label;
|
|
3641
|
+
};
|
|
2054
3642
|
if (!isAwaitingSequence || pendingKeys.length === 0) {
|
|
2055
3643
|
return null;
|
|
2056
3644
|
}
|
|
2057
|
-
return /* @__PURE__ */ jsx("div", { className: "kbd-sequence-backdrop", children: /* @__PURE__ */ jsxs("div", { className: "kbd-sequence", children: [
|
|
3645
|
+
return /* @__PURE__ */ jsx("div", { className: "kbd-sequence-backdrop", onClick: cancelSequence, children: /* @__PURE__ */ jsxs("div", { className: "kbd-sequence", onClick: (e) => e.stopPropagation(), children: [
|
|
2058
3646
|
/* @__PURE__ */ jsxs("div", { className: "kbd-sequence-current", children: [
|
|
2059
|
-
/* @__PURE__ */ jsx("
|
|
3647
|
+
/* @__PURE__ */ jsx("div", { className: "kbd-sequence-keys", children: pendingKeys.map((combo, i) => renderKey(combo, i)) }),
|
|
2060
3648
|
/* @__PURE__ */ jsx("span", { className: "kbd-sequence-ellipsis", children: "\u2026" })
|
|
2061
3649
|
] }),
|
|
2062
|
-
|
|
3650
|
+
shouldShowTimeout && /* @__PURE__ */ jsx(
|
|
2063
3651
|
"div",
|
|
2064
3652
|
{
|
|
2065
3653
|
className: "kbd-sequence-timeout",
|
|
@@ -2067,17 +3655,23 @@ function SequenceModal() {
|
|
|
2067
3655
|
},
|
|
2068
3656
|
timeoutStartedAt
|
|
2069
3657
|
),
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
3658
|
+
flatCompletions.length > 0 && /* @__PURE__ */ jsx("div", { className: "kbd-sequence-completions", children: flatCompletions.map((item, index) => /* @__PURE__ */ jsxs(
|
|
3659
|
+
"div",
|
|
3660
|
+
{
|
|
3661
|
+
className: `kbd-sequence-completion ${index === selectedIndex ? "selected" : ""} ${item.isComplete ? "complete" : ""}`,
|
|
3662
|
+
children: [
|
|
3663
|
+
item.isComplete ? /* @__PURE__ */ jsx("kbd", { className: "kbd-kbd", children: "\u21B5" }) : item.completion.nextKeySeq ? renderKeySeq(item.completion.nextKeySeq) : /* @__PURE__ */ jsx("kbd", { className: "kbd-kbd", children: item.displayKey }),
|
|
3664
|
+
/* @__PURE__ */ jsx("span", { className: "kbd-sequence-arrow", children: "\u2192" }),
|
|
3665
|
+
/* @__PURE__ */ jsx("span", { className: "kbd-sequence-actions", children: getActionLabel(item.action, item.completion.captures) })
|
|
3666
|
+
]
|
|
3667
|
+
},
|
|
3668
|
+
`${item.completion.fullSequence}-${item.action}`
|
|
3669
|
+
)) }),
|
|
3670
|
+
flatCompletions.length === 0 && /* @__PURE__ */ jsx("div", { className: "kbd-sequence-empty", children: "No matching shortcuts" })
|
|
2079
3671
|
] }) });
|
|
2080
3672
|
}
|
|
3673
|
+
var DefaultTooltip = ({ children }) => /* @__PURE__ */ jsx(Fragment, { children });
|
|
3674
|
+
var TooltipContext = createContext(DefaultTooltip);
|
|
2081
3675
|
function parseActionId(actionId) {
|
|
2082
3676
|
const colonIndex = actionId.indexOf(":");
|
|
2083
3677
|
if (colonIndex > 0) {
|
|
@@ -2085,22 +3679,49 @@ function parseActionId(actionId) {
|
|
|
2085
3679
|
}
|
|
2086
3680
|
return { group: "General", name: actionId };
|
|
2087
3681
|
}
|
|
2088
|
-
function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder) {
|
|
3682
|
+
function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder, actionRegistry, showUnbound = true) {
|
|
2089
3683
|
const actionBindings = getActionBindings(keymap);
|
|
2090
3684
|
const groupMap = /* @__PURE__ */ new Map();
|
|
3685
|
+
const includedActions = /* @__PURE__ */ new Set();
|
|
3686
|
+
const getGroupName = (actionId) => {
|
|
3687
|
+
const registeredGroup = actionRegistry?.[actionId]?.group;
|
|
3688
|
+
if (registeredGroup) return registeredGroup;
|
|
3689
|
+
const { group: groupKey } = parseActionId(actionId);
|
|
3690
|
+
return groupNames?.[groupKey] ?? groupKey;
|
|
3691
|
+
};
|
|
2091
3692
|
for (const [actionId, bindings] of actionBindings) {
|
|
2092
|
-
|
|
2093
|
-
|
|
3693
|
+
if (actionRegistry?.[actionId]?.hideFromModal) continue;
|
|
3694
|
+
includedActions.add(actionId);
|
|
3695
|
+
const { name } = parseActionId(actionId);
|
|
3696
|
+
const groupName = getGroupName(actionId);
|
|
2094
3697
|
if (!groupMap.has(groupName)) {
|
|
2095
3698
|
groupMap.set(groupName, { name: groupName, shortcuts: [] });
|
|
2096
3699
|
}
|
|
2097
3700
|
groupMap.get(groupName).shortcuts.push({
|
|
2098
3701
|
actionId,
|
|
2099
|
-
label: labels?.[actionId] ?? name,
|
|
3702
|
+
label: labels?.[actionId] ?? actionRegistry?.[actionId]?.label ?? name,
|
|
2100
3703
|
description: descriptions?.[actionId],
|
|
2101
3704
|
bindings
|
|
2102
3705
|
});
|
|
2103
3706
|
}
|
|
3707
|
+
if (actionRegistry && showUnbound) {
|
|
3708
|
+
for (const [actionId, action] of Object.entries(actionRegistry)) {
|
|
3709
|
+
if (includedActions.has(actionId)) continue;
|
|
3710
|
+
if (action.hideFromModal) continue;
|
|
3711
|
+
const { name } = parseActionId(actionId);
|
|
3712
|
+
const groupName = getGroupName(actionId);
|
|
3713
|
+
if (!groupMap.has(groupName)) {
|
|
3714
|
+
groupMap.set(groupName, { name: groupName, shortcuts: [] });
|
|
3715
|
+
}
|
|
3716
|
+
groupMap.get(groupName).shortcuts.push({
|
|
3717
|
+
actionId,
|
|
3718
|
+
label: labels?.[actionId] ?? action.label ?? name,
|
|
3719
|
+
description: descriptions?.[actionId],
|
|
3720
|
+
bindings: []
|
|
3721
|
+
// No bindings
|
|
3722
|
+
});
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
2104
3725
|
for (const group of groupMap.values()) {
|
|
2105
3726
|
group.shortcuts.sort((a, b) => a.actionId.localeCompare(b.actionId));
|
|
2106
3727
|
}
|
|
@@ -2127,25 +3748,22 @@ function KeyDisplay({
|
|
|
2127
3748
|
combo,
|
|
2128
3749
|
className
|
|
2129
3750
|
}) {
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }, "alt"));
|
|
3751
|
+
return /* @__PURE__ */ jsxs("span", { className, children: [
|
|
3752
|
+
renderModifierIcons(combo.modifiers),
|
|
3753
|
+
renderKeyContent(combo.key)
|
|
3754
|
+
] });
|
|
3755
|
+
}
|
|
3756
|
+
function SeqElemDisplay2({ elem, className }) {
|
|
3757
|
+
const Tooltip = useContext(TooltipContext);
|
|
3758
|
+
if (elem.type === "digit") {
|
|
3759
|
+
return /* @__PURE__ */ jsx(Tooltip, { title: "Any single digit (0-9)", children: /* @__PURE__ */ jsx("span", { className: `kbd-placeholder ${className || ""}`, children: "#" }) });
|
|
2140
3760
|
}
|
|
2141
|
-
if (
|
|
2142
|
-
|
|
3761
|
+
if (elem.type === "digits") {
|
|
3762
|
+
return /* @__PURE__ */ jsx(Tooltip, { title: "One or more digits (0-9)", children: /* @__PURE__ */ jsx("span", { className: `kbd-placeholder ${className || ""}`, children: "##" }) });
|
|
2143
3763
|
}
|
|
2144
|
-
|
|
2145
|
-
parts.push(/* @__PURE__ */ jsx("span", { children: keyDisplay }, "key"));
|
|
2146
|
-
return /* @__PURE__ */ jsx("span", { className, children: parts });
|
|
3764
|
+
return /* @__PURE__ */ jsx(KeyDisplay, { combo: { key: elem.key, modifiers: elem.modifiers }, className });
|
|
2147
3765
|
}
|
|
2148
|
-
function
|
|
3766
|
+
function BindingDisplay2({
|
|
2149
3767
|
binding,
|
|
2150
3768
|
className,
|
|
2151
3769
|
editable,
|
|
@@ -2156,10 +3774,11 @@ function BindingDisplay({
|
|
|
2156
3774
|
onEdit,
|
|
2157
3775
|
onRemove,
|
|
2158
3776
|
pendingKeys,
|
|
2159
|
-
activeKeys
|
|
3777
|
+
activeKeys,
|
|
3778
|
+
timeoutDuration = DEFAULT_SEQUENCE_TIMEOUT
|
|
2160
3779
|
}) {
|
|
2161
3780
|
const sequence = parseHotkeyString(binding);
|
|
2162
|
-
const
|
|
3781
|
+
const keySeq = parseKeySeq(binding);
|
|
2163
3782
|
let kbdClassName = "kbd-kbd";
|
|
2164
3783
|
if (editable && !isEditing) kbdClassName += " editable";
|
|
2165
3784
|
if (isEditing) kbdClassName += " editing";
|
|
@@ -2190,7 +3809,17 @@ function BindingDisplay({
|
|
|
2190
3809
|
} else {
|
|
2191
3810
|
content = "...";
|
|
2192
3811
|
}
|
|
2193
|
-
return /* @__PURE__ */
|
|
3812
|
+
return /* @__PURE__ */ jsxs("kbd", { className: kbdClassName, tabIndex: editable ? 0 : void 0, children: [
|
|
3813
|
+
content,
|
|
3814
|
+
pendingKeys && pendingKeys.length > 0 && Number.isFinite(timeoutDuration) && /* @__PURE__ */ jsx(
|
|
3815
|
+
"span",
|
|
3816
|
+
{
|
|
3817
|
+
className: "kbd-timeout-bar",
|
|
3818
|
+
style: { animationDuration: `${timeoutDuration}ms` }
|
|
3819
|
+
},
|
|
3820
|
+
pendingKeys.length
|
|
3821
|
+
)
|
|
3822
|
+
] });
|
|
2194
3823
|
}
|
|
2195
3824
|
return /* @__PURE__ */ jsxs("kbd", { className: kbdClassName, onClick: handleClick, tabIndex: editable ? 0 : void 0, onKeyDown: editable && onEdit ? (e) => {
|
|
2196
3825
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -2198,10 +3827,13 @@ function BindingDisplay({
|
|
|
2198
3827
|
onEdit();
|
|
2199
3828
|
}
|
|
2200
3829
|
} : void 0, children: [
|
|
2201
|
-
|
|
3830
|
+
keySeq.length > 1 ? keySeq.map((elem, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2202
3831
|
i > 0 && /* @__PURE__ */ jsx("span", { className: "kbd-sequence-sep", children: " " }),
|
|
2203
|
-
/* @__PURE__ */ jsx(
|
|
2204
|
-
] }, i)) : /* @__PURE__ */ jsx(
|
|
3832
|
+
/* @__PURE__ */ jsx(SeqElemDisplay2, { elem })
|
|
3833
|
+
] }, i)) : keySeq.length === 1 ? /* @__PURE__ */ jsx(SeqElemDisplay2, { elem: keySeq[0] }) : (
|
|
3834
|
+
// Fallback for legacy parsing
|
|
3835
|
+
/* @__PURE__ */ jsx(KeyDisplay, { combo: sequence[0] })
|
|
3836
|
+
),
|
|
2205
3837
|
editable && onRemove && /* @__PURE__ */ jsx(
|
|
2206
3838
|
"button",
|
|
2207
3839
|
{
|
|
@@ -2226,8 +3858,7 @@ function ShortcutsModal({
|
|
|
2226
3858
|
groupRenderers,
|
|
2227
3859
|
isOpen: isOpenProp,
|
|
2228
3860
|
onClose: onCloseProp,
|
|
2229
|
-
|
|
2230
|
-
autoRegisterOpen,
|
|
3861
|
+
defaultBinding = "?",
|
|
2231
3862
|
editable = false,
|
|
2232
3863
|
onBindingChange,
|
|
2233
3864
|
onBindingAdd,
|
|
@@ -2238,7 +3869,9 @@ function ShortcutsModal({
|
|
|
2238
3869
|
backdropClassName = "kbd-backdrop",
|
|
2239
3870
|
modalClassName = "kbd-modal",
|
|
2240
3871
|
title = "Keyboard Shortcuts",
|
|
2241
|
-
hint
|
|
3872
|
+
hint,
|
|
3873
|
+
showUnbound,
|
|
3874
|
+
TooltipComponent: TooltipComponentProp = DefaultTooltip
|
|
2242
3875
|
}) {
|
|
2243
3876
|
const ctx = useMaybeHotkeysContext();
|
|
2244
3877
|
const contextLabels = useMemo(() => {
|
|
@@ -2277,28 +3910,30 @@ function ShortcutsModal({
|
|
|
2277
3910
|
const descriptions = descriptionsProp ?? contextDescriptions;
|
|
2278
3911
|
const groupNames = groupNamesProp ?? contextGroups;
|
|
2279
3912
|
const handleBindingChange = onBindingChange ?? (ctx ? (action, oldKey, newKey) => {
|
|
2280
|
-
if (oldKey) ctx.registry.removeBinding(oldKey);
|
|
3913
|
+
if (oldKey) ctx.registry.removeBinding(action, oldKey);
|
|
2281
3914
|
ctx.registry.setBinding(action, newKey);
|
|
2282
3915
|
} : void 0);
|
|
2283
3916
|
const handleBindingAdd = onBindingAdd ?? (ctx ? (action, key) => {
|
|
2284
3917
|
ctx.registry.setBinding(action, key);
|
|
2285
3918
|
} : void 0);
|
|
2286
|
-
const handleBindingRemove = onBindingRemove ?? (ctx ? (
|
|
2287
|
-
ctx.registry.removeBinding(key);
|
|
3919
|
+
const handleBindingRemove = onBindingRemove ?? (ctx ? (action, key) => {
|
|
3920
|
+
ctx.registry.removeBinding(action, key);
|
|
2288
3921
|
} : void 0);
|
|
2289
3922
|
const handleReset = onReset ?? (ctx ? () => {
|
|
2290
3923
|
ctx.registry.resetOverrides();
|
|
2291
3924
|
} : void 0);
|
|
2292
|
-
const shouldAutoRegisterOpen = autoRegisterOpen ?? !ctx;
|
|
2293
3925
|
const [internalIsOpen, setInternalIsOpen] = useState(false);
|
|
2294
3926
|
const isOpen = isOpenProp ?? ctx?.isModalOpen ?? internalIsOpen;
|
|
2295
3927
|
const [editingAction, setEditingAction] = useState(null);
|
|
2296
3928
|
const [editingKey, setEditingKey] = useState(null);
|
|
2297
3929
|
const [addingAction, setAddingAction] = useState(null);
|
|
2298
3930
|
const [pendingConflict, setPendingConflict] = useState(null);
|
|
3931
|
+
const [hasPendingConflictState, setHasPendingConflictState] = useState(false);
|
|
2299
3932
|
const editingActionRef = useRef(null);
|
|
2300
3933
|
const editingKeyRef = useRef(null);
|
|
2301
3934
|
const addingActionRef = useRef(null);
|
|
3935
|
+
const setIsEditingBindingRef = useRef(ctx?.setIsEditingBinding);
|
|
3936
|
+
setIsEditingBindingRef.current = ctx?.setIsEditingBinding;
|
|
2302
3937
|
const conflicts = useMemo(() => findConflicts(keymap), [keymap]);
|
|
2303
3938
|
const actionBindings = useMemo(() => getActionBindings(keymap), [keymap]);
|
|
2304
3939
|
const close = useCallback(() => {
|
|
@@ -2316,13 +3951,18 @@ function ShortcutsModal({
|
|
|
2316
3951
|
ctx.closeModal();
|
|
2317
3952
|
}
|
|
2318
3953
|
}, [onCloseProp, ctx]);
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
3954
|
+
useAction(ACTION_MODAL, {
|
|
3955
|
+
label: "Show shortcuts",
|
|
3956
|
+
group: "Global",
|
|
3957
|
+
defaultBindings: defaultBinding ? [defaultBinding] : [],
|
|
3958
|
+
handler: useCallback(() => {
|
|
3959
|
+
if (ctx) {
|
|
3960
|
+
ctx.toggleModal();
|
|
3961
|
+
} else {
|
|
3962
|
+
setInternalIsOpen((prev) => !prev);
|
|
3963
|
+
}
|
|
3964
|
+
}, [ctx])
|
|
3965
|
+
});
|
|
2326
3966
|
const checkConflict = useCallback((newKey, forAction) => {
|
|
2327
3967
|
const existingActions = keymap[newKey];
|
|
2328
3968
|
if (!existingActions) return null;
|
|
@@ -2330,7 +3970,24 @@ function ShortcutsModal({
|
|
|
2330
3970
|
const conflicts2 = actions.filter((a) => a !== forAction);
|
|
2331
3971
|
return conflicts2.length > 0 ? conflicts2 : null;
|
|
2332
3972
|
}, [keymap]);
|
|
2333
|
-
const
|
|
3973
|
+
const combinationsEqual2 = useCallback((a, b) => {
|
|
3974
|
+
return a.key === b.key && a.modifiers.ctrl === b.modifiers.ctrl && a.modifiers.alt === b.modifiers.alt && a.modifiers.shift === b.modifiers.shift && a.modifiers.meta === b.modifiers.meta;
|
|
3975
|
+
}, []);
|
|
3976
|
+
const isSequencePrefix = useCallback((a, b) => {
|
|
3977
|
+
if (a.length >= b.length) return false;
|
|
3978
|
+
for (let i = 0; i < a.length; i++) {
|
|
3979
|
+
if (!combinationsEqual2(a[i], b[i])) return false;
|
|
3980
|
+
}
|
|
3981
|
+
return true;
|
|
3982
|
+
}, [combinationsEqual2]);
|
|
3983
|
+
const sequencesEqual = useCallback((a, b) => {
|
|
3984
|
+
if (a.length !== b.length) return false;
|
|
3985
|
+
for (let i = 0; i < a.length; i++) {
|
|
3986
|
+
if (!combinationsEqual2(a[i], b[i])) return false;
|
|
3987
|
+
}
|
|
3988
|
+
return true;
|
|
3989
|
+
}, [combinationsEqual2]);
|
|
3990
|
+
const { isRecording, startRecording, cancel, pendingKeys, activeKeys, sequenceTimeout } = useRecordHotkey({
|
|
2334
3991
|
onCapture: useCallback(
|
|
2335
3992
|
(_sequence, display) => {
|
|
2336
3993
|
const currentAddingAction = addingActionRef.current;
|
|
@@ -2358,6 +4015,7 @@ function ShortcutsModal({
|
|
|
2358
4015
|
setEditingAction(null);
|
|
2359
4016
|
setEditingKey(null);
|
|
2360
4017
|
setAddingAction(null);
|
|
4018
|
+
setIsEditingBindingRef.current?.(false);
|
|
2361
4019
|
},
|
|
2362
4020
|
[checkConflict, handleBindingChange, handleBindingAdd]
|
|
2363
4021
|
),
|
|
@@ -2369,6 +4027,7 @@ function ShortcutsModal({
|
|
|
2369
4027
|
setEditingKey(null);
|
|
2370
4028
|
setAddingAction(null);
|
|
2371
4029
|
setPendingConflict(null);
|
|
4030
|
+
setIsEditingBindingRef.current?.(false);
|
|
2372
4031
|
}, []),
|
|
2373
4032
|
// Tab to next/prev editable kbd and start editing
|
|
2374
4033
|
onTab: useCallback(() => {
|
|
@@ -2393,7 +4052,7 @@ function ShortcutsModal({
|
|
|
2393
4052
|
prev.click();
|
|
2394
4053
|
}
|
|
2395
4054
|
}, []),
|
|
2396
|
-
pauseTimeout: pendingConflict !== null
|
|
4055
|
+
pauseTimeout: pendingConflict !== null || hasPendingConflictState
|
|
2397
4056
|
});
|
|
2398
4057
|
const startEditingBinding = useCallback(
|
|
2399
4058
|
(action, key) => {
|
|
@@ -2404,9 +4063,10 @@ function ShortcutsModal({
|
|
|
2404
4063
|
setEditingAction(action);
|
|
2405
4064
|
setEditingKey(key);
|
|
2406
4065
|
setPendingConflict(null);
|
|
4066
|
+
ctx?.setIsEditingBinding(true);
|
|
2407
4067
|
startRecording();
|
|
2408
4068
|
},
|
|
2409
|
-
[startRecording]
|
|
4069
|
+
[startRecording, ctx?.setIsEditingBinding]
|
|
2410
4070
|
);
|
|
2411
4071
|
const startAddingBinding = useCallback(
|
|
2412
4072
|
(action) => {
|
|
@@ -2417,9 +4077,10 @@ function ShortcutsModal({
|
|
|
2417
4077
|
setEditingKey(null);
|
|
2418
4078
|
setAddingAction(action);
|
|
2419
4079
|
setPendingConflict(null);
|
|
4080
|
+
ctx?.setIsEditingBinding(true);
|
|
2420
4081
|
startRecording();
|
|
2421
4082
|
},
|
|
2422
|
-
[startRecording]
|
|
4083
|
+
[startRecording, ctx?.setIsEditingBinding]
|
|
2423
4084
|
);
|
|
2424
4085
|
const startEditing = useCallback(
|
|
2425
4086
|
(action, bindingIndex) => {
|
|
@@ -2441,7 +4102,8 @@ function ShortcutsModal({
|
|
|
2441
4102
|
setEditingKey(null);
|
|
2442
4103
|
setAddingAction(null);
|
|
2443
4104
|
setPendingConflict(null);
|
|
2444
|
-
|
|
4105
|
+
ctx?.setIsEditingBinding(false);
|
|
4106
|
+
}, [cancel, ctx?.setIsEditingBinding]);
|
|
2445
4107
|
const removeBinding = useCallback(
|
|
2446
4108
|
(action, key) => {
|
|
2447
4109
|
handleBindingRemove?.(action, key);
|
|
@@ -2451,6 +4113,31 @@ function ShortcutsModal({
|
|
|
2451
4113
|
const reset = useCallback(() => {
|
|
2452
4114
|
handleReset?.();
|
|
2453
4115
|
}, [handleReset]);
|
|
4116
|
+
const pendingConflictInfo = useMemo(() => {
|
|
4117
|
+
if (!isRecording || pendingKeys.length === 0) {
|
|
4118
|
+
return { hasConflict: false, conflictingKeys: /* @__PURE__ */ new Set() };
|
|
4119
|
+
}
|
|
4120
|
+
const conflictingKeys = /* @__PURE__ */ new Set();
|
|
4121
|
+
for (const key of Object.keys(keymap)) {
|
|
4122
|
+
if (editingKey && key.toLowerCase() === editingKey.toLowerCase()) continue;
|
|
4123
|
+
const keySequence = parseHotkeyString(key);
|
|
4124
|
+
if (sequencesEqual(pendingKeys, keySequence)) {
|
|
4125
|
+
conflictingKeys.add(key);
|
|
4126
|
+
continue;
|
|
4127
|
+
}
|
|
4128
|
+
if (isSequencePrefix(pendingKeys, keySequence)) {
|
|
4129
|
+
conflictingKeys.add(key);
|
|
4130
|
+
continue;
|
|
4131
|
+
}
|
|
4132
|
+
if (isSequencePrefix(keySequence, pendingKeys)) {
|
|
4133
|
+
conflictingKeys.add(key);
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
return { hasConflict: conflictingKeys.size > 0, conflictingKeys };
|
|
4137
|
+
}, [isRecording, pendingKeys, keymap, editingKey, sequencesEqual, isSequencePrefix]);
|
|
4138
|
+
useEffect(() => {
|
|
4139
|
+
setHasPendingConflictState(pendingConflictInfo.hasConflict);
|
|
4140
|
+
}, [pendingConflictInfo.hasConflict]);
|
|
2454
4141
|
const renderEditableKbd = useCallback(
|
|
2455
4142
|
(actionId, key, showRemove = false) => {
|
|
2456
4143
|
const isEditingThis = editingAction === actionId && editingKey === key && !addingAction;
|
|
@@ -2462,13 +4149,15 @@ function ShortcutsModal({
|
|
|
2462
4149
|
const defaultActions = Array.isArray(defaultAction) ? defaultAction : [defaultAction];
|
|
2463
4150
|
return defaultActions.includes(actionId);
|
|
2464
4151
|
})() : true;
|
|
4152
|
+
const isPendingConflict = pendingConflictInfo.conflictingKeys.has(key);
|
|
2465
4153
|
return /* @__PURE__ */ jsx(
|
|
2466
|
-
|
|
4154
|
+
BindingDisplay2,
|
|
2467
4155
|
{
|
|
2468
4156
|
binding: key,
|
|
2469
4157
|
editable,
|
|
2470
4158
|
isEditing: isEditingThis,
|
|
2471
4159
|
isConflict,
|
|
4160
|
+
isPendingConflict,
|
|
2472
4161
|
isDefault,
|
|
2473
4162
|
onEdit: () => {
|
|
2474
4163
|
if (isRecording && !(editingAction === actionId && editingKey === key)) {
|
|
@@ -2489,24 +4178,27 @@ function ShortcutsModal({
|
|
|
2489
4178
|
},
|
|
2490
4179
|
onRemove: editable && showRemove ? () => removeBinding(actionId, key) : void 0,
|
|
2491
4180
|
pendingKeys,
|
|
2492
|
-
activeKeys
|
|
4181
|
+
activeKeys,
|
|
4182
|
+
timeoutDuration: pendingConflictInfo.hasConflict ? Infinity : sequenceTimeout
|
|
2493
4183
|
},
|
|
2494
4184
|
key
|
|
2495
4185
|
);
|
|
2496
4186
|
},
|
|
2497
|
-
[editingAction, editingKey, addingAction, conflicts, defaults, editable, startEditingBinding, removeBinding, pendingKeys, activeKeys, isRecording, cancel, handleBindingAdd, handleBindingChange]
|
|
4187
|
+
[editingAction, editingKey, addingAction, conflicts, defaults, editable, startEditingBinding, removeBinding, pendingKeys, activeKeys, isRecording, cancel, handleBindingAdd, handleBindingChange, sequenceTimeout, pendingConflictInfo]
|
|
2498
4188
|
);
|
|
2499
4189
|
const renderAddButton = useCallback(
|
|
2500
4190
|
(actionId) => {
|
|
2501
4191
|
const isAddingThis = addingAction === actionId;
|
|
2502
4192
|
if (isAddingThis) {
|
|
2503
4193
|
return /* @__PURE__ */ jsx(
|
|
2504
|
-
|
|
4194
|
+
BindingDisplay2,
|
|
2505
4195
|
{
|
|
2506
4196
|
binding: "",
|
|
2507
4197
|
isEditing: true,
|
|
4198
|
+
isPendingConflict: pendingConflictInfo.hasConflict,
|
|
2508
4199
|
pendingKeys,
|
|
2509
|
-
activeKeys
|
|
4200
|
+
activeKeys,
|
|
4201
|
+
timeoutDuration: pendingConflictInfo.hasConflict ? Infinity : sequenceTimeout
|
|
2510
4202
|
}
|
|
2511
4203
|
);
|
|
2512
4204
|
}
|
|
@@ -2535,13 +4227,14 @@ function ShortcutsModal({
|
|
|
2535
4227
|
}
|
|
2536
4228
|
);
|
|
2537
4229
|
},
|
|
2538
|
-
[addingAction, pendingKeys, activeKeys, startAddingBinding, isRecording, cancel, handleBindingAdd, handleBindingChange]
|
|
4230
|
+
[addingAction, pendingKeys, activeKeys, startAddingBinding, isRecording, cancel, handleBindingAdd, handleBindingChange, sequenceTimeout, pendingConflictInfo]
|
|
2539
4231
|
);
|
|
2540
4232
|
const renderCell = useCallback(
|
|
2541
4233
|
(actionId, keys) => {
|
|
4234
|
+
const showAddButton = editable && (multipleBindings || keys.length === 0);
|
|
2542
4235
|
return /* @__PURE__ */ jsxs("span", { className: "kbd-action-bindings", children: [
|
|
2543
4236
|
keys.map((key) => /* @__PURE__ */ jsx(Fragment$1, { children: renderEditableKbd(actionId, key, true) }, key)),
|
|
2544
|
-
|
|
4237
|
+
showAddButton && renderAddButton(actionId)
|
|
2545
4238
|
] });
|
|
2546
4239
|
},
|
|
2547
4240
|
[renderEditableKbd, renderAddButton, editable, multipleBindings]
|
|
@@ -2558,14 +4251,10 @@ function ShortcutsModal({
|
|
|
2558
4251
|
editingKey,
|
|
2559
4252
|
addingAction
|
|
2560
4253
|
}), [renderCell, renderEditableKbd, renderAddButton, startEditingBinding, startAddingBinding, removeBinding, isRecording, editingAction, editingKey, addingAction]);
|
|
2561
|
-
const modalKeymap = shouldAutoRegisterOpen ? { [openKey]: "openShortcuts" } : {};
|
|
2562
4254
|
useHotkeys(
|
|
2563
|
-
{
|
|
2564
|
-
{
|
|
2565
|
-
|
|
2566
|
-
closeShortcuts: close
|
|
2567
|
-
},
|
|
2568
|
-
{ enabled: shouldAutoRegisterOpen || isOpen }
|
|
4255
|
+
{ escape: "closeShortcuts" },
|
|
4256
|
+
{ closeShortcuts: close },
|
|
4257
|
+
{ enabled: isOpen }
|
|
2569
4258
|
);
|
|
2570
4259
|
useEffect(() => {
|
|
2571
4260
|
if (!isOpen || !editingAction && !addingAction) return;
|
|
@@ -2579,6 +4268,19 @@ function ShortcutsModal({
|
|
|
2579
4268
|
window.addEventListener("keydown", handleEscape, true);
|
|
2580
4269
|
return () => window.removeEventListener("keydown", handleEscape, true);
|
|
2581
4270
|
}, [isOpen, editingAction, addingAction, cancelEditing]);
|
|
4271
|
+
useEffect(() => {
|
|
4272
|
+
if (!isOpen || !ctx) return;
|
|
4273
|
+
const handleMetaK = (e) => {
|
|
4274
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
4275
|
+
e.preventDefault();
|
|
4276
|
+
e.stopPropagation();
|
|
4277
|
+
close();
|
|
4278
|
+
ctx.openOmnibar();
|
|
4279
|
+
}
|
|
4280
|
+
};
|
|
4281
|
+
window.addEventListener("keydown", handleMetaK, true);
|
|
4282
|
+
return () => window.removeEventListener("keydown", handleMetaK, true);
|
|
4283
|
+
}, [isOpen, ctx, close]);
|
|
2582
4284
|
const handleBackdropClick = useCallback(
|
|
2583
4285
|
(e) => {
|
|
2584
4286
|
if (e.target === e.currentTarget) {
|
|
@@ -2598,9 +4300,10 @@ function ShortcutsModal({
|
|
|
2598
4300
|
},
|
|
2599
4301
|
[editingAction, addingAction, cancelEditing]
|
|
2600
4302
|
);
|
|
4303
|
+
const effectiveShowUnbound = showUnbound ?? editable;
|
|
2601
4304
|
const shortcutGroups = useMemo(
|
|
2602
|
-
() => organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder),
|
|
2603
|
-
[keymap, labels, descriptions, groupNames, groupOrder]
|
|
4305
|
+
() => organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder, ctx?.registry.actionRegistry, effectiveShowUnbound),
|
|
4306
|
+
[keymap, labels, descriptions, groupNames, groupOrder, ctx?.registry.actionRegistry, effectiveShowUnbound]
|
|
2604
4307
|
);
|
|
2605
4308
|
if (!isOpen) return null;
|
|
2606
4309
|
if (children) {
|
|
@@ -2630,7 +4333,7 @@ function ShortcutsModal({
|
|
|
2630
4333
|
renderCell(actionId, bindings)
|
|
2631
4334
|
] }, actionId));
|
|
2632
4335
|
};
|
|
2633
|
-
return /* @__PURE__ */ jsx("div", { className: backdropClassName, onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs("div", { className: modalClassName, role: "dialog", "aria-modal": "true", "aria-label": "Keyboard shortcuts", onClick: handleModalClick, children: [
|
|
4336
|
+
return /* @__PURE__ */ jsx(TooltipContext.Provider, { value: TooltipComponentProp, children: /* @__PURE__ */ jsx("div", { className: backdropClassName, onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs("div", { className: modalClassName, role: "dialog", "aria-modal": "true", "aria-label": "Keyboard shortcuts", onClick: handleModalClick, children: [
|
|
2634
4337
|
/* @__PURE__ */ jsxs("div", { className: "kbd-modal-header", children: [
|
|
2635
4338
|
/* @__PURE__ */ jsx("h2", { className: "kbd-modal-title", children: title }),
|
|
2636
4339
|
/* @__PURE__ */ jsxs("div", { className: "kbd-modal-header-buttons", children: [
|
|
@@ -2699,9 +4402,9 @@ function ShortcutsModal({
|
|
|
2699
4402
|
)
|
|
2700
4403
|
] })
|
|
2701
4404
|
] })
|
|
2702
|
-
] }) });
|
|
4405
|
+
] }) }) });
|
|
2703
4406
|
}
|
|
2704
4407
|
|
|
2705
|
-
export { ActionsRegistryContext,
|
|
4408
|
+
export { ACTION_LOOKUP, ACTION_MODAL, ACTION_OMNIBAR, ActionsRegistryContext, Alt, Backspace, Command, Ctrl, DEFAULT_SEQUENCE_TIMEOUT, DIGITS_PLACEHOLDER, DIGIT_PLACEHOLDER, Down, Enter, HotkeysProvider, Kbd, KbdLookup, KbdModal, KbdOmnibar, Kbds, Key, KeybindingEditor, Left, LookupModal, ModifierIcon, Omnibar, OmnibarEndpointsRegistryContext, Option, Right, SequenceModal, Shift, ShortcutsModal, Up, countPlaceholders, createTwoColumnRenderer, extractCaptures, findConflicts, formatBinding, formatCombination, formatKeyForDisplay, formatKeySeq, fuzzyMatch, getActionBindings, getConflictsArray, getKeyIcon, getModifierIcon, getSequenceCompletions, hasConflicts, hasDigitPlaceholders, hotkeySequenceToKeySeq, isDigitPlaceholder, isMac, isModifierKey, isPlaceholderSentinel, isSequence, isShiftedSymbol, keySeqToHotkeySequence, normalizeKey, parseHotkeyString, parseKeySeq, searchActions, useAction, useActions, useActionsRegistry, useEditableHotkeys, useHotkeys, useHotkeysContext, useMaybeHotkeysContext, useOmnibar, useOmnibarEndpoint, useOmnibarEndpointsRegistry, useRecordHotkey };
|
|
2706
4409
|
//# sourceMappingURL=index.js.map
|
|
2707
4410
|
//# sourceMappingURL=index.js.map
|