use-kbd 0.4.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/dist/index.cjs +812 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +254 -33
- package/dist/index.d.ts +254 -33
- package/dist/index.js +810 -165
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/styles.css +48 -3
package/dist/index.js
CHANGED
|
@@ -70,9 +70,7 @@ function useActionsRegistry(options = {}) {
|
|
|
70
70
|
const filterRedundantOverrides = useCallback((overrides2) => {
|
|
71
71
|
const filtered = {};
|
|
72
72
|
for (const [key, actionOrActions] of Object.entries(overrides2)) {
|
|
73
|
-
if (actionOrActions === "") {
|
|
74
|
-
continue;
|
|
75
|
-
} else if (Array.isArray(actionOrActions)) {
|
|
73
|
+
if (actionOrActions === "") ; else if (Array.isArray(actionOrActions)) {
|
|
76
74
|
const nonDefaultActions = actionOrActions.filter((a) => !isDefaultBinding(key, a));
|
|
77
75
|
if (nonDefaultActions.length > 0) {
|
|
78
76
|
filtered[key] = nonDefaultActions.length === 1 ? nonDefaultActions[0] : nonDefaultActions;
|
|
@@ -136,10 +134,10 @@ function useActionsRegistry(options = {}) {
|
|
|
136
134
|
actionsRef.current.delete(id);
|
|
137
135
|
setActionsVersion((v) => v + 1);
|
|
138
136
|
}, []);
|
|
139
|
-
const execute = useCallback((id) => {
|
|
137
|
+
const execute = useCallback((id, captures) => {
|
|
140
138
|
const action = actionsRef.current.get(id);
|
|
141
139
|
if (action && (action.config.enabled ?? true)) {
|
|
142
|
-
action.config.handler();
|
|
140
|
+
action.config.handler(void 0, captures);
|
|
143
141
|
}
|
|
144
142
|
}, []);
|
|
145
143
|
const keymap = useMemo(() => {
|
|
@@ -163,9 +161,7 @@ function useActionsRegistry(options = {}) {
|
|
|
163
161
|
}
|
|
164
162
|
}
|
|
165
163
|
for (const [key, actionOrActions] of Object.entries(overrides)) {
|
|
166
|
-
if (actionOrActions === "") {
|
|
167
|
-
continue;
|
|
168
|
-
} else {
|
|
164
|
+
if (actionOrActions === "") ; else {
|
|
169
165
|
const actions2 = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
170
166
|
for (const actionId of actions2) {
|
|
171
167
|
addToKey(key, actionId);
|
|
@@ -281,6 +277,70 @@ function useActionsRegistry(options = {}) {
|
|
|
281
277
|
resetOverrides
|
|
282
278
|
]);
|
|
283
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
|
+
}
|
|
284
344
|
|
|
285
345
|
// src/constants.ts
|
|
286
346
|
var DEFAULT_SEQUENCE_TIMEOUT = Infinity;
|
|
@@ -318,7 +378,11 @@ function isShiftedSymbol(key) {
|
|
|
318
378
|
}
|
|
319
379
|
function isMac() {
|
|
320
380
|
if (typeof navigator === "undefined") return false;
|
|
321
|
-
|
|
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);
|
|
322
386
|
}
|
|
323
387
|
function normalizeKey(key) {
|
|
324
388
|
const keyMap = {
|
|
@@ -353,7 +417,7 @@ function formatKeyForDisplay(key) {
|
|
|
353
417
|
"space": "Space",
|
|
354
418
|
"escape": "Esc",
|
|
355
419
|
"enter": "\u21B5",
|
|
356
|
-
"tab": "
|
|
420
|
+
"tab": "\u21E5",
|
|
357
421
|
"backspace": "\u232B",
|
|
358
422
|
"delete": "Del",
|
|
359
423
|
"arrowup": "\u2191",
|
|
@@ -493,13 +557,6 @@ function parseHotkeyString(hotkeyStr) {
|
|
|
493
557
|
const parts = hotkeyStr.trim().split(/\s+/);
|
|
494
558
|
return parts.map(parseSingleCombination);
|
|
495
559
|
}
|
|
496
|
-
function parseCombinationId(id) {
|
|
497
|
-
const sequence = parseHotkeyString(id);
|
|
498
|
-
if (sequence.length === 0) {
|
|
499
|
-
return { key: "", modifiers: { ctrl: false, alt: false, shift: false, meta: false } };
|
|
500
|
-
}
|
|
501
|
-
return sequence[0];
|
|
502
|
-
}
|
|
503
560
|
var NO_MODIFIERS = { ctrl: false, alt: false, shift: false, meta: false };
|
|
504
561
|
function parseSeqElem(str) {
|
|
505
562
|
if (str === "\\d") {
|
|
@@ -615,6 +672,13 @@ function isPrefix(a, b) {
|
|
|
615
672
|
function combinationsEqual(a, b) {
|
|
616
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;
|
|
617
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
|
+
}
|
|
618
682
|
function isDigitKey(key) {
|
|
619
683
|
return /^[0-9]$/.test(key);
|
|
620
684
|
}
|
|
@@ -735,28 +799,77 @@ function getSequenceCompletions(pendingKeys, keymap) {
|
|
|
735
799
|
if (pendingKeys.length === 0) return [];
|
|
736
800
|
const completions = [];
|
|
737
801
|
for (const [hotkeyStr, actionOrActions] of Object.entries(keymap)) {
|
|
738
|
-
const sequence = parseHotkeyString(hotkeyStr);
|
|
739
802
|
const keySeq = parseKeySeq(hotkeyStr);
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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++;
|
|
746
839
|
}
|
|
747
840
|
}
|
|
748
|
-
if (
|
|
749
|
-
|
|
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);
|
|
750
857
|
const nextKeys = formatKeySeq(remainingKeySeq).display;
|
|
751
|
-
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
752
858
|
completions.push({
|
|
753
859
|
nextKeys,
|
|
860
|
+
nextKeySeq: remainingKeySeq,
|
|
754
861
|
fullSequence: hotkeyStr,
|
|
755
862
|
display: formatKeySeq(keySeq),
|
|
756
|
-
actions
|
|
863
|
+
actions,
|
|
864
|
+
isComplete: false,
|
|
865
|
+
captures: captures.length > 0 ? captures : void 0
|
|
757
866
|
});
|
|
758
867
|
}
|
|
759
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
|
+
});
|
|
760
873
|
return completions;
|
|
761
874
|
}
|
|
762
875
|
function getActionBindings(keymap) {
|
|
@@ -847,7 +960,7 @@ function searchActions(query, actions, keymap) {
|
|
|
847
960
|
}
|
|
848
961
|
const matched = labelMatch.matched || descMatch.matched || groupMatch.matched || idMatch.matched || keywordScore > 0;
|
|
849
962
|
if (!matched && query) continue;
|
|
850
|
-
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;
|
|
851
964
|
results.push({
|
|
852
965
|
id,
|
|
853
966
|
action,
|
|
@@ -925,6 +1038,9 @@ function advanceMatchState(state, pattern, combo) {
|
|
|
925
1038
|
const digitValue = parseInt(elem.partial, 10);
|
|
926
1039
|
newState[i] = { type: "digits", value: digitValue };
|
|
927
1040
|
pos = i + 1;
|
|
1041
|
+
if (pos >= pattern.length) {
|
|
1042
|
+
return { status: "failed" };
|
|
1043
|
+
}
|
|
928
1044
|
break;
|
|
929
1045
|
}
|
|
930
1046
|
}
|
|
@@ -1047,7 +1163,7 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1047
1163
|
}
|
|
1048
1164
|
return false;
|
|
1049
1165
|
}, [preventDefault, stopPropagation]);
|
|
1050
|
-
const tryExecuteKeySeq = useCallback((matchKey,
|
|
1166
|
+
const tryExecuteKeySeq = useCallback((matchKey, captures, e) => {
|
|
1051
1167
|
for (const entry of parsedKeymapRef.current) {
|
|
1052
1168
|
if (entry.key === matchKey) {
|
|
1053
1169
|
for (const action of entry.actions) {
|
|
@@ -1103,7 +1219,24 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1103
1219
|
}
|
|
1104
1220
|
if (e.key === "Enter" && pendingKeysRef.current.length > 0) {
|
|
1105
1221
|
e.preventDefault();
|
|
1106
|
-
|
|
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
|
+
}
|
|
1107
1240
|
clearPending();
|
|
1108
1241
|
if (!executed) {
|
|
1109
1242
|
onSequenceCancel?.();
|
|
@@ -1116,14 +1249,57 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1116
1249
|
return;
|
|
1117
1250
|
}
|
|
1118
1251
|
const currentCombo = eventToCombination(e);
|
|
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);
|
|
1258
|
+
}
|
|
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);
|
|
1282
|
+
}
|
|
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
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1119
1295
|
const newSequence = [...pendingKeysRef.current, currentCombo];
|
|
1120
|
-
|
|
1121
|
-
let
|
|
1296
|
+
const completeMatches = [];
|
|
1297
|
+
let hasPartials = false;
|
|
1122
1298
|
const matchStates = matchStatesRef.current;
|
|
1123
|
-
const
|
|
1299
|
+
const hadPartialMatches = matchStates.size > 0;
|
|
1124
1300
|
for (const entry of parsedKeymapRef.current) {
|
|
1125
1301
|
let state = matchStates.get(entry.key);
|
|
1126
|
-
if (
|
|
1302
|
+
if (hadPartialMatches && !state) {
|
|
1127
1303
|
continue;
|
|
1128
1304
|
}
|
|
1129
1305
|
if (!state) {
|
|
@@ -1132,22 +1308,27 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1132
1308
|
}
|
|
1133
1309
|
const result = advanceMatchState(state, entry.keySeq, currentCombo);
|
|
1134
1310
|
if (result.status === "matched") {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1311
|
+
completeMatches.push({
|
|
1312
|
+
key: entry.key,
|
|
1313
|
+
state: result.state,
|
|
1314
|
+
captures: result.captures
|
|
1315
|
+
});
|
|
1316
|
+
matchStates.delete(entry.key);
|
|
1140
1317
|
} else if (result.status === "partial") {
|
|
1141
1318
|
matchStates.set(entry.key, result.state);
|
|
1142
|
-
|
|
1319
|
+
hasPartials = true;
|
|
1143
1320
|
} else {
|
|
1144
1321
|
matchStates.delete(entry.key);
|
|
1145
1322
|
}
|
|
1146
1323
|
}
|
|
1147
|
-
if (
|
|
1148
|
-
|
|
1324
|
+
if (completeMatches.length === 1 && !hasPartials) {
|
|
1325
|
+
const match = completeMatches[0];
|
|
1326
|
+
if (tryExecuteKeySeq(match.key, match.captures, e)) {
|
|
1327
|
+
clearPending();
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1149
1330
|
}
|
|
1150
|
-
if (
|
|
1331
|
+
if (completeMatches.length > 0 || hasPartials) {
|
|
1151
1332
|
setPendingKeys(newSequence);
|
|
1152
1333
|
setIsAwaitingSequence(true);
|
|
1153
1334
|
if (pendingKeysRef.current.length === 0) {
|
|
@@ -1230,8 +1411,11 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1230
1411
|
}
|
|
1231
1412
|
}
|
|
1232
1413
|
if (pendingKeysRef.current.length > 0) {
|
|
1233
|
-
|
|
1234
|
-
|
|
1414
|
+
setPendingKeys(newSequence);
|
|
1415
|
+
if (preventDefault) {
|
|
1416
|
+
e.preventDefault();
|
|
1417
|
+
}
|
|
1418
|
+
return;
|
|
1235
1419
|
}
|
|
1236
1420
|
const singleMatch = tryExecute([currentCombo], e);
|
|
1237
1421
|
if (!singleMatch) {
|
|
@@ -1293,7 +1477,8 @@ var HotkeysContext = createContext(null);
|
|
|
1293
1477
|
var DEFAULT_CONFIG = {
|
|
1294
1478
|
storageKey: "use-kbd",
|
|
1295
1479
|
sequenceTimeout: DEFAULT_SEQUENCE_TIMEOUT,
|
|
1296
|
-
disableConflicts:
|
|
1480
|
+
disableConflicts: false,
|
|
1481
|
+
// Keep conflicting bindings active; SeqM handles disambiguation
|
|
1297
1482
|
minViewportWidth: 768,
|
|
1298
1483
|
enableOnTouch: false
|
|
1299
1484
|
};
|
|
@@ -1306,6 +1491,7 @@ function HotkeysProvider({
|
|
|
1306
1491
|
...configProp
|
|
1307
1492
|
}), [configProp]);
|
|
1308
1493
|
const registry = useActionsRegistry({ storageKey: config.storageKey });
|
|
1494
|
+
const endpointsRegistry = useOmnibarEndpointsRegistry();
|
|
1309
1495
|
const [isEnabled, setIsEnabled] = useState(true);
|
|
1310
1496
|
useEffect(() => {
|
|
1311
1497
|
if (typeof window === "undefined") return;
|
|
@@ -1397,6 +1583,7 @@ function HotkeysProvider({
|
|
|
1397
1583
|
);
|
|
1398
1584
|
const value = useMemo(() => ({
|
|
1399
1585
|
registry,
|
|
1586
|
+
endpointsRegistry,
|
|
1400
1587
|
isEnabled,
|
|
1401
1588
|
isModalOpen,
|
|
1402
1589
|
openModal,
|
|
@@ -1424,6 +1611,7 @@ function HotkeysProvider({
|
|
|
1424
1611
|
getCompletions
|
|
1425
1612
|
}), [
|
|
1426
1613
|
registry,
|
|
1614
|
+
endpointsRegistry,
|
|
1427
1615
|
isEnabled,
|
|
1428
1616
|
isModalOpen,
|
|
1429
1617
|
openModal,
|
|
@@ -1448,7 +1636,7 @@ function HotkeysProvider({
|
|
|
1448
1636
|
searchActionsHelper,
|
|
1449
1637
|
getCompletions
|
|
1450
1638
|
]);
|
|
1451
|
-
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 }) }) });
|
|
1452
1640
|
}
|
|
1453
1641
|
function useHotkeysContext() {
|
|
1454
1642
|
const context = useContext(HotkeysContext);
|
|
@@ -1536,6 +1724,38 @@ function useActions(actions) {
|
|
|
1536
1724
|
)
|
|
1537
1725
|
]);
|
|
1538
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
|
+
}
|
|
1539
1759
|
function useEventCallback(fn) {
|
|
1540
1760
|
const ref = useRef(fn);
|
|
1541
1761
|
ref.current = fn;
|
|
@@ -1784,7 +2004,6 @@ function useRecordHotkey(options = {}) {
|
|
|
1784
2004
|
};
|
|
1785
2005
|
}, [isRecording, preventDefault, sequenceTimeout, clearTimeout_, submit, cancel, onCapture, onTab, onShiftTab]);
|
|
1786
2006
|
const display = sequence ? formatCombination(sequence) : null;
|
|
1787
|
-
const combination = sequence && sequence.length > 0 ? sequence[0] : null;
|
|
1788
2007
|
return {
|
|
1789
2008
|
isRecording,
|
|
1790
2009
|
startRecording,
|
|
@@ -1794,13 +2013,11 @@ function useRecordHotkey(options = {}) {
|
|
|
1794
2013
|
display,
|
|
1795
2014
|
pendingKeys,
|
|
1796
2015
|
activeKeys,
|
|
1797
|
-
sequenceTimeout
|
|
1798
|
-
combination
|
|
1799
|
-
// deprecated
|
|
2016
|
+
sequenceTimeout
|
|
1800
2017
|
};
|
|
1801
2018
|
}
|
|
1802
2019
|
function useEditableHotkeys(defaults, handlers, options = {}) {
|
|
1803
|
-
const { storageKey, disableConflicts =
|
|
2020
|
+
const { storageKey, disableConflicts = false, ...hotkeyOptions } = options;
|
|
1804
2021
|
const [overrides, setOverrides] = useState(() => {
|
|
1805
2022
|
if (!storageKey || typeof window === "undefined") return {};
|
|
1806
2023
|
try {
|
|
@@ -1896,6 +2113,7 @@ function useEditableHotkeys(defaults, handlers, options = {}) {
|
|
|
1896
2113
|
};
|
|
1897
2114
|
}
|
|
1898
2115
|
var { max: max2, min } = Math;
|
|
2116
|
+
var DEFAULT_DEBOUNCE_MS = 150;
|
|
1899
2117
|
function useOmnibar(options) {
|
|
1900
2118
|
const {
|
|
1901
2119
|
actions,
|
|
@@ -1904,17 +2122,27 @@ function useOmnibar(options) {
|
|
|
1904
2122
|
openKey = "meta+k",
|
|
1905
2123
|
enabled = true,
|
|
1906
2124
|
onExecute,
|
|
2125
|
+
onExecuteRemote,
|
|
1907
2126
|
onOpen,
|
|
1908
2127
|
onClose,
|
|
1909
|
-
maxResults = 10
|
|
2128
|
+
maxResults = 10,
|
|
2129
|
+
endpointsRegistry,
|
|
2130
|
+
debounceMs = DEFAULT_DEBOUNCE_MS
|
|
1910
2131
|
} = options;
|
|
1911
2132
|
const [isOpen, setIsOpen] = useState(false);
|
|
1912
2133
|
const [query, setQuery] = useState("");
|
|
1913
2134
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
2135
|
+
const [endpointStates, setEndpointStates] = useState(/* @__PURE__ */ new Map());
|
|
1914
2136
|
const handlersRef = useRef(handlers);
|
|
1915
2137
|
handlersRef.current = handlers;
|
|
1916
2138
|
const onExecuteRef = useRef(onExecute);
|
|
1917
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;
|
|
1918
2146
|
const omnibarKeymap = useMemo(() => {
|
|
1919
2147
|
if (!enabled) return {};
|
|
1920
2148
|
return { [openKey]: "omnibar:toggle" };
|
|
@@ -1940,12 +2168,189 @@ function useOmnibar(options) {
|
|
|
1940
2168
|
const allResults = searchActions(query, actions, keymap);
|
|
1941
2169
|
return allResults.slice(0, maxResults);
|
|
1942
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;
|
|
1943
2348
|
const completions = useMemo(() => {
|
|
1944
2349
|
return getSequenceCompletions(pendingKeys, keymap);
|
|
1945
2350
|
}, [pendingKeys, keymap]);
|
|
1946
2351
|
useEffect(() => {
|
|
1947
2352
|
setSelectedIndex(0);
|
|
1948
|
-
}, [results]);
|
|
2353
|
+
}, [results, remoteResults]);
|
|
1949
2354
|
const open = useCallback(() => {
|
|
1950
2355
|
setIsOpen(true);
|
|
1951
2356
|
setQuery("");
|
|
@@ -1972,8 +2377,8 @@ function useOmnibar(options) {
|
|
|
1972
2377
|
});
|
|
1973
2378
|
}, [onOpen, onClose]);
|
|
1974
2379
|
const selectNext = useCallback(() => {
|
|
1975
|
-
setSelectedIndex((prev) => min(prev + 1,
|
|
1976
|
-
}, [
|
|
2380
|
+
setSelectedIndex((prev) => min(prev + 1, totalResults - 1));
|
|
2381
|
+
}, [totalResults]);
|
|
1977
2382
|
const selectPrev = useCallback(() => {
|
|
1978
2383
|
setSelectedIndex((prev) => max2(prev - 1, 0));
|
|
1979
2384
|
}, []);
|
|
@@ -1981,15 +2386,47 @@ function useOmnibar(options) {
|
|
|
1981
2386
|
setSelectedIndex(0);
|
|
1982
2387
|
}, []);
|
|
1983
2388
|
const execute = useCallback((actionId) => {
|
|
1984
|
-
const
|
|
1985
|
-
if (
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
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]);
|
|
1993
2430
|
useEffect(() => {
|
|
1994
2431
|
if (!isOpen) return;
|
|
1995
2432
|
const handleKeyDown = (e) => {
|
|
@@ -2031,7 +2468,12 @@ function useOmnibar(options) {
|
|
|
2031
2468
|
query,
|
|
2032
2469
|
setQuery,
|
|
2033
2470
|
results,
|
|
2471
|
+
remoteResults,
|
|
2472
|
+
isLoadingRemote,
|
|
2473
|
+
endpointPagination,
|
|
2474
|
+
loadMore,
|
|
2034
2475
|
selectedIndex,
|
|
2476
|
+
totalResults,
|
|
2035
2477
|
selectNext,
|
|
2036
2478
|
selectPrev,
|
|
2037
2479
|
execute,
|
|
@@ -2149,6 +2591,26 @@ function Backspace({ className, style }) {
|
|
|
2149
2591
|
}
|
|
2150
2592
|
);
|
|
2151
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
|
+
}
|
|
2152
2614
|
function getKeyIcon(key) {
|
|
2153
2615
|
switch (key.toLowerCase()) {
|
|
2154
2616
|
case "arrowup":
|
|
@@ -2163,6 +2625,8 @@ function getKeyIcon(key) {
|
|
|
2163
2625
|
return Enter;
|
|
2164
2626
|
case "backspace":
|
|
2165
2627
|
return Backspace;
|
|
2628
|
+
case "tab":
|
|
2629
|
+
return Tab;
|
|
2166
2630
|
default:
|
|
2167
2631
|
return null;
|
|
2168
2632
|
}
|
|
@@ -2267,7 +2731,6 @@ var Alt = forwardRef(
|
|
|
2267
2731
|
)
|
|
2268
2732
|
);
|
|
2269
2733
|
Alt.displayName = "Alt";
|
|
2270
|
-
var isMac2 = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
2271
2734
|
function getModifierIcon(modifier) {
|
|
2272
2735
|
switch (modifier) {
|
|
2273
2736
|
case "meta":
|
|
@@ -2279,7 +2742,7 @@ function getModifierIcon(modifier) {
|
|
|
2279
2742
|
case "opt":
|
|
2280
2743
|
return Option;
|
|
2281
2744
|
case "alt":
|
|
2282
|
-
return
|
|
2745
|
+
return isMac() ? Option : Alt;
|
|
2283
2746
|
}
|
|
2284
2747
|
}
|
|
2285
2748
|
var ModifierIcon = forwardRef(
|
|
@@ -2289,28 +2752,47 @@ var ModifierIcon = forwardRef(
|
|
|
2289
2752
|
}
|
|
2290
2753
|
);
|
|
2291
2754
|
ModifierIcon.displayName = "ModifierIcon";
|
|
2292
|
-
function
|
|
2293
|
-
const
|
|
2294
|
-
const parts = [];
|
|
2755
|
+
function renderModifierIcons(modifiers, className = "kbd-modifier-icon") {
|
|
2756
|
+
const icons = [];
|
|
2295
2757
|
if (modifiers.meta) {
|
|
2296
|
-
|
|
2758
|
+
icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "meta", className }, "meta"));
|
|
2297
2759
|
}
|
|
2298
2760
|
if (modifiers.ctrl) {
|
|
2299
|
-
|
|
2761
|
+
icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "ctrl", className }, "ctrl"));
|
|
2300
2762
|
}
|
|
2301
2763
|
if (modifiers.alt) {
|
|
2302
|
-
|
|
2764
|
+
icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "alt", className }, "alt"));
|
|
2303
2765
|
}
|
|
2304
2766
|
if (modifiers.shift) {
|
|
2305
|
-
|
|
2767
|
+
icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "shift", className }, "shift"));
|
|
2306
2768
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
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);
|
|
2312
2782
|
}
|
|
2313
|
-
return /* @__PURE__ */
|
|
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
|
+
] });
|
|
2314
2796
|
}
|
|
2315
2797
|
function SeqElemDisplay({ elem }) {
|
|
2316
2798
|
if (elem.type === "digit") {
|
|
@@ -2462,7 +2944,7 @@ function KeybindingEditor({
|
|
|
2462
2944
|
return Array.from(allActions).map((action) => {
|
|
2463
2945
|
const key = actionMap.get(action) ?? defaultActionMap.get(action) ?? "";
|
|
2464
2946
|
const defaultKey = defaultActionMap.get(action) ?? "";
|
|
2465
|
-
const combo =
|
|
2947
|
+
const combo = parseHotkeyString(key);
|
|
2466
2948
|
const display = formatCombination(combo);
|
|
2467
2949
|
const conflictActions = conflicts.get(key);
|
|
2468
2950
|
return {
|
|
@@ -2619,15 +3101,30 @@ function LookupModal({ defaultBinding = "meta+shift+k" } = {}) {
|
|
|
2619
3101
|
const filteredBindings = useMemo(() => {
|
|
2620
3102
|
if (pendingKeys.length === 0) return allBindings;
|
|
2621
3103
|
return allBindings.filter((result) => {
|
|
2622
|
-
|
|
2623
|
-
|
|
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++) {
|
|
2624
3108
|
const pending = pendingKeys[i];
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
if (
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
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
|
+
}
|
|
2631
3128
|
}
|
|
2632
3129
|
return true;
|
|
2633
3130
|
});
|
|
@@ -2739,7 +3236,7 @@ function LookupModal({ defaultBinding = "meta+shift+k" } = {}) {
|
|
|
2739
3236
|
},
|
|
2740
3237
|
onMouseEnter: () => setSelectedIndex(index),
|
|
2741
3238
|
children: [
|
|
2742
|
-
/* @__PURE__ */ jsx("
|
|
3239
|
+
/* @__PURE__ */ jsx("span", { className: "kbd-lookup-binding", children: renderKeySeq(result.keySeq) }),
|
|
2743
3240
|
/* @__PURE__ */ jsx("span", { className: "kbd-lookup-labels", children: result.labels.join(", ") })
|
|
2744
3241
|
]
|
|
2745
3242
|
},
|
|
@@ -2783,6 +3280,7 @@ function Omnibar({
|
|
|
2783
3280
|
onOpen: onOpenProp,
|
|
2784
3281
|
onClose: onCloseProp,
|
|
2785
3282
|
onExecute: onExecuteProp,
|
|
3283
|
+
onExecuteRemote: onExecuteRemoteProp,
|
|
2786
3284
|
maxResults = 10,
|
|
2787
3285
|
placeholder = "Type a command...",
|
|
2788
3286
|
children,
|
|
@@ -2820,13 +3318,25 @@ function Omnibar({
|
|
|
2820
3318
|
ctx.openOmnibar();
|
|
2821
3319
|
}
|
|
2822
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]);
|
|
2823
3328
|
const {
|
|
2824
3329
|
isOpen: internalIsOpen,
|
|
2825
3330
|
close,
|
|
2826
3331
|
query,
|
|
2827
3332
|
setQuery,
|
|
2828
3333
|
results,
|
|
3334
|
+
remoteResults,
|
|
3335
|
+
isLoadingRemote,
|
|
3336
|
+
endpointPagination,
|
|
3337
|
+
loadMore,
|
|
2829
3338
|
selectedIndex,
|
|
3339
|
+
totalResults,
|
|
2830
3340
|
selectNext,
|
|
2831
3341
|
selectPrev,
|
|
2832
3342
|
execute,
|
|
@@ -2843,9 +3353,22 @@ function Omnibar({
|
|
|
2843
3353
|
onOpen: handleOpen,
|
|
2844
3354
|
onClose: handleClose,
|
|
2845
3355
|
onExecute: handleExecute,
|
|
2846
|
-
|
|
3356
|
+
onExecuteRemote: handleExecuteRemote,
|
|
3357
|
+
maxResults,
|
|
3358
|
+
endpointsRegistry: ctx?.endpointsRegistry
|
|
2847
3359
|
});
|
|
2848
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]);
|
|
2849
3372
|
useEffect(() => {
|
|
2850
3373
|
if (isOpen) {
|
|
2851
3374
|
requestAnimationFrame(() => {
|
|
@@ -2853,6 +3376,38 @@ function Omnibar({
|
|
|
2853
3376
|
});
|
|
2854
3377
|
}
|
|
2855
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]);
|
|
2856
3411
|
useEffect(() => {
|
|
2857
3412
|
if (!isOpen) return;
|
|
2858
3413
|
const handleGlobalKeyDown = (e) => {
|
|
@@ -2902,7 +3457,12 @@ function Omnibar({
|
|
|
2902
3457
|
query,
|
|
2903
3458
|
setQuery,
|
|
2904
3459
|
results,
|
|
3460
|
+
remoteResults,
|
|
3461
|
+
isLoadingRemote,
|
|
3462
|
+
endpointPagination,
|
|
3463
|
+
loadMore,
|
|
2905
3464
|
selectedIndex,
|
|
3465
|
+
totalResults,
|
|
2906
3466
|
selectNext,
|
|
2907
3467
|
selectPrev,
|
|
2908
3468
|
execute,
|
|
@@ -2930,21 +3490,61 @@ function Omnibar({
|
|
|
2930
3490
|
spellCheck: false
|
|
2931
3491
|
}
|
|
2932
3492
|
),
|
|
2933
|
-
/* @__PURE__ */ jsx("div", { className: "kbd-omnibar-results", children:
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
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
|
+
]
|
|
2939
3504
|
},
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
]
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
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
|
+
] }) })
|
|
2948
3548
|
] }) });
|
|
2949
3549
|
}
|
|
2950
3550
|
function SequenceModal() {
|
|
@@ -2955,41 +3555,99 @@ function SequenceModal() {
|
|
|
2955
3555
|
sequenceTimeoutStartedAt: timeoutStartedAt,
|
|
2956
3556
|
sequenceTimeout,
|
|
2957
3557
|
getCompletions,
|
|
2958
|
-
registry
|
|
3558
|
+
registry,
|
|
3559
|
+
executeAction
|
|
2959
3560
|
} = useHotkeysContext();
|
|
3561
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
3562
|
+
const [hasInteracted, setHasInteracted] = useState(false);
|
|
2960
3563
|
const completions = useMemo(() => {
|
|
2961
3564
|
if (pendingKeys.length === 0) return [];
|
|
2962
3565
|
return getCompletions(pendingKeys);
|
|
2963
3566
|
}, [getCompletions, pendingKeys]);
|
|
2964
|
-
const
|
|
2965
|
-
|
|
2966
|
-
return formatCombination(pendingKeys).display;
|
|
2967
|
-
}, [pendingKeys]);
|
|
2968
|
-
const getActionLabel = (actionId) => {
|
|
2969
|
-
const action = registry.actions.get(actionId);
|
|
2970
|
-
return action?.config.label || actionId;
|
|
2971
|
-
};
|
|
2972
|
-
const groupedCompletions = useMemo(() => {
|
|
2973
|
-
const byNextKey = /* @__PURE__ */ new Map();
|
|
3567
|
+
const flatCompletions = useMemo(() => {
|
|
3568
|
+
const items = [];
|
|
2974
3569
|
for (const c of completions) {
|
|
2975
|
-
const
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
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
|
+
});
|
|
2980
3578
|
}
|
|
2981
3579
|
}
|
|
2982
|
-
return
|
|
3580
|
+
return items;
|
|
2983
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
|
+
};
|
|
2984
3642
|
if (!isAwaitingSequence || pendingKeys.length === 0) {
|
|
2985
3643
|
return null;
|
|
2986
3644
|
}
|
|
2987
3645
|
return /* @__PURE__ */ jsx("div", { className: "kbd-sequence-backdrop", onClick: cancelSequence, children: /* @__PURE__ */ jsxs("div", { className: "kbd-sequence", onClick: (e) => e.stopPropagation(), children: [
|
|
2988
3646
|
/* @__PURE__ */ jsxs("div", { className: "kbd-sequence-current", children: [
|
|
2989
|
-
/* @__PURE__ */ jsx("
|
|
3647
|
+
/* @__PURE__ */ jsx("div", { className: "kbd-sequence-keys", children: pendingKeys.map((combo, i) => renderKey(combo, i)) }),
|
|
2990
3648
|
/* @__PURE__ */ jsx("span", { className: "kbd-sequence-ellipsis", children: "\u2026" })
|
|
2991
3649
|
] }),
|
|
2992
|
-
|
|
3650
|
+
shouldShowTimeout && /* @__PURE__ */ jsx(
|
|
2993
3651
|
"div",
|
|
2994
3652
|
{
|
|
2995
3653
|
className: "kbd-sequence-timeout",
|
|
@@ -2997,15 +3655,19 @@ function SequenceModal() {
|
|
|
2997
3655
|
},
|
|
2998
3656
|
timeoutStartedAt
|
|
2999
3657
|
),
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
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" })
|
|
3009
3671
|
] }) });
|
|
3010
3672
|
}
|
|
3011
3673
|
var DefaultTooltip = ({ children }) => /* @__PURE__ */ jsx(Fragment, { children });
|
|
@@ -3028,6 +3690,7 @@ function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder,
|
|
|
3028
3690
|
return groupNames?.[groupKey] ?? groupKey;
|
|
3029
3691
|
};
|
|
3030
3692
|
for (const [actionId, bindings] of actionBindings) {
|
|
3693
|
+
if (actionRegistry?.[actionId]?.hideFromModal) continue;
|
|
3031
3694
|
includedActions.add(actionId);
|
|
3032
3695
|
const { name } = parseActionId(actionId);
|
|
3033
3696
|
const groupName = getGroupName(actionId);
|
|
@@ -3044,6 +3707,7 @@ function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder,
|
|
|
3044
3707
|
if (actionRegistry && showUnbound) {
|
|
3045
3708
|
for (const [actionId, action] of Object.entries(actionRegistry)) {
|
|
3046
3709
|
if (includedActions.has(actionId)) continue;
|
|
3710
|
+
if (action.hideFromModal) continue;
|
|
3047
3711
|
const { name } = parseActionId(actionId);
|
|
3048
3712
|
const groupName = getGroupName(actionId);
|
|
3049
3713
|
if (!groupMap.has(groupName)) {
|
|
@@ -3084,27 +3748,10 @@ function KeyDisplay({
|
|
|
3084
3748
|
combo,
|
|
3085
3749
|
className
|
|
3086
3750
|
}) {
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
}
|
|
3092
|
-
if (modifiers.ctrl) {
|
|
3093
|
-
parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }, "ctrl"));
|
|
3094
|
-
}
|
|
3095
|
-
if (modifiers.alt) {
|
|
3096
|
-
parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }, "alt"));
|
|
3097
|
-
}
|
|
3098
|
-
if (modifiers.shift) {
|
|
3099
|
-
parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }, "shift"));
|
|
3100
|
-
}
|
|
3101
|
-
const KeyIcon = getKeyIcon(key);
|
|
3102
|
-
if (KeyIcon) {
|
|
3103
|
-
parts.push(/* @__PURE__ */ jsx(KeyIcon, { className: "kbd-key-icon" }, "key"));
|
|
3104
|
-
} else {
|
|
3105
|
-
parts.push(/* @__PURE__ */ jsx("span", { children: formatKeyForDisplay(key) }, "key"));
|
|
3106
|
-
}
|
|
3107
|
-
return /* @__PURE__ */ jsx("span", { className, children: parts });
|
|
3751
|
+
return /* @__PURE__ */ jsxs("span", { className, children: [
|
|
3752
|
+
renderModifierIcons(combo.modifiers),
|
|
3753
|
+
renderKeyContent(combo.key)
|
|
3754
|
+
] });
|
|
3108
3755
|
}
|
|
3109
3756
|
function SeqElemDisplay2({ elem, className }) {
|
|
3110
3757
|
const Tooltip = useContext(TooltipContext);
|
|
@@ -3132,7 +3779,6 @@ function BindingDisplay2({
|
|
|
3132
3779
|
}) {
|
|
3133
3780
|
const sequence = parseHotkeyString(binding);
|
|
3134
3781
|
const keySeq = parseKeySeq(binding);
|
|
3135
|
-
formatKeySeq(keySeq);
|
|
3136
3782
|
let kbdClassName = "kbd-kbd";
|
|
3137
3783
|
if (editable && !isEditing) kbdClassName += " editable";
|
|
3138
3784
|
if (isEditing) kbdClassName += " editing";
|
|
@@ -3305,18 +3951,17 @@ function ShortcutsModal({
|
|
|
3305
3951
|
ctx.closeModal();
|
|
3306
3952
|
}
|
|
3307
3953
|
}, [onCloseProp, ctx]);
|
|
3308
|
-
useCallback(() => {
|
|
3309
|
-
if (ctx?.openModal) {
|
|
3310
|
-
ctx.openModal();
|
|
3311
|
-
} else {
|
|
3312
|
-
setInternalIsOpen(true);
|
|
3313
|
-
}
|
|
3314
|
-
}, [ctx]);
|
|
3315
3954
|
useAction(ACTION_MODAL, {
|
|
3316
3955
|
label: "Show shortcuts",
|
|
3317
3956
|
group: "Global",
|
|
3318
3957
|
defaultBindings: defaultBinding ? [defaultBinding] : [],
|
|
3319
|
-
handler: useCallback(() =>
|
|
3958
|
+
handler: useCallback(() => {
|
|
3959
|
+
if (ctx) {
|
|
3960
|
+
ctx.toggleModal();
|
|
3961
|
+
} else {
|
|
3962
|
+
setInternalIsOpen((prev) => !prev);
|
|
3963
|
+
}
|
|
3964
|
+
}, [ctx])
|
|
3320
3965
|
});
|
|
3321
3966
|
const checkConflict = useCallback((newKey, forAction) => {
|
|
3322
3967
|
const existingActions = keymap[newKey];
|
|
@@ -3760,6 +4405,6 @@ function ShortcutsModal({
|
|
|
3760
4405
|
] }) }) });
|
|
3761
4406
|
}
|
|
3762
4407
|
|
|
3763
|
-
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, 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,
|
|
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 };
|
|
3764
4409
|
//# sourceMappingURL=index.js.map
|
|
3765
4410
|
//# sourceMappingURL=index.js.map
|