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.cjs
CHANGED
|
@@ -72,9 +72,7 @@ function useActionsRegistry(options = {}) {
|
|
|
72
72
|
const filterRedundantOverrides = react.useCallback((overrides2) => {
|
|
73
73
|
const filtered = {};
|
|
74
74
|
for (const [key, actionOrActions] of Object.entries(overrides2)) {
|
|
75
|
-
if (actionOrActions === "") {
|
|
76
|
-
continue;
|
|
77
|
-
} else if (Array.isArray(actionOrActions)) {
|
|
75
|
+
if (actionOrActions === "") ; else if (Array.isArray(actionOrActions)) {
|
|
78
76
|
const nonDefaultActions = actionOrActions.filter((a) => !isDefaultBinding(key, a));
|
|
79
77
|
if (nonDefaultActions.length > 0) {
|
|
80
78
|
filtered[key] = nonDefaultActions.length === 1 ? nonDefaultActions[0] : nonDefaultActions;
|
|
@@ -138,10 +136,10 @@ function useActionsRegistry(options = {}) {
|
|
|
138
136
|
actionsRef.current.delete(id);
|
|
139
137
|
setActionsVersion((v) => v + 1);
|
|
140
138
|
}, []);
|
|
141
|
-
const execute = react.useCallback((id) => {
|
|
139
|
+
const execute = react.useCallback((id, captures) => {
|
|
142
140
|
const action = actionsRef.current.get(id);
|
|
143
141
|
if (action && (action.config.enabled ?? true)) {
|
|
144
|
-
action.config.handler();
|
|
142
|
+
action.config.handler(void 0, captures);
|
|
145
143
|
}
|
|
146
144
|
}, []);
|
|
147
145
|
const keymap = react.useMemo(() => {
|
|
@@ -165,9 +163,7 @@ function useActionsRegistry(options = {}) {
|
|
|
165
163
|
}
|
|
166
164
|
}
|
|
167
165
|
for (const [key, actionOrActions] of Object.entries(overrides)) {
|
|
168
|
-
if (actionOrActions === "") {
|
|
169
|
-
continue;
|
|
170
|
-
} else {
|
|
166
|
+
if (actionOrActions === "") ; else {
|
|
171
167
|
const actions2 = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
172
168
|
for (const actionId of actions2) {
|
|
173
169
|
addToKey(key, actionId);
|
|
@@ -283,6 +279,70 @@ function useActionsRegistry(options = {}) {
|
|
|
283
279
|
resetOverrides
|
|
284
280
|
]);
|
|
285
281
|
}
|
|
282
|
+
var OmnibarEndpointsRegistryContext = react.createContext(null);
|
|
283
|
+
function useOmnibarEndpointsRegistry() {
|
|
284
|
+
const endpointsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
285
|
+
const [endpointsVersion, setEndpointsVersion] = react.useState(0);
|
|
286
|
+
const register = react.useCallback((id, config) => {
|
|
287
|
+
endpointsRef.current.set(id, {
|
|
288
|
+
id,
|
|
289
|
+
config,
|
|
290
|
+
registeredAt: Date.now()
|
|
291
|
+
});
|
|
292
|
+
setEndpointsVersion((v) => v + 1);
|
|
293
|
+
}, []);
|
|
294
|
+
const unregister = react.useCallback((id) => {
|
|
295
|
+
endpointsRef.current.delete(id);
|
|
296
|
+
setEndpointsVersion((v) => v + 1);
|
|
297
|
+
}, []);
|
|
298
|
+
const queryEndpoint = react.useCallback(async (endpointId, query, pagination, signal) => {
|
|
299
|
+
const ep = endpointsRef.current.get(endpointId);
|
|
300
|
+
if (!ep) return null;
|
|
301
|
+
if (ep.config.enabled === false) return null;
|
|
302
|
+
if (query.length < (ep.config.minQueryLength ?? 2)) return null;
|
|
303
|
+
try {
|
|
304
|
+
const response = await ep.config.fetch(query, signal, pagination);
|
|
305
|
+
const entriesWithGroup = response.entries.map((entry) => ({
|
|
306
|
+
...entry,
|
|
307
|
+
group: entry.group ?? ep.config.group
|
|
308
|
+
}));
|
|
309
|
+
return {
|
|
310
|
+
endpointId: ep.id,
|
|
311
|
+
entries: entriesWithGroup,
|
|
312
|
+
total: response.total,
|
|
313
|
+
hasMore: response.hasMore
|
|
314
|
+
};
|
|
315
|
+
} catch (error) {
|
|
316
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
317
|
+
return { endpointId: ep.id, entries: [] };
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
endpointId: ep.id,
|
|
321
|
+
entries: [],
|
|
322
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}, []);
|
|
326
|
+
const queryAll = react.useCallback(async (query, signal) => {
|
|
327
|
+
const endpoints2 = Array.from(endpointsRef.current.values());
|
|
328
|
+
const promises = endpoints2.filter((ep) => ep.config.enabled !== false).filter((ep) => query.length >= (ep.config.minQueryLength ?? 2)).map(async (ep) => {
|
|
329
|
+
const pageSize = ep.config.pageSize ?? 10;
|
|
330
|
+
const result = await queryEndpoint(ep.id, query, { offset: 0, limit: pageSize }, signal);
|
|
331
|
+
return result ?? { endpointId: ep.id, entries: [] };
|
|
332
|
+
});
|
|
333
|
+
return Promise.all(promises);
|
|
334
|
+
}, [queryEndpoint]);
|
|
335
|
+
const endpoints = react.useMemo(() => {
|
|
336
|
+
return new Map(endpointsRef.current);
|
|
337
|
+
}, [endpointsVersion]);
|
|
338
|
+
return react.useMemo(() => ({
|
|
339
|
+
register,
|
|
340
|
+
unregister,
|
|
341
|
+
endpoints,
|
|
342
|
+
queryAll,
|
|
343
|
+
queryEndpoint
|
|
344
|
+
}), [register, unregister, endpoints, queryAll, queryEndpoint]);
|
|
345
|
+
}
|
|
286
346
|
|
|
287
347
|
// src/constants.ts
|
|
288
348
|
var DEFAULT_SEQUENCE_TIMEOUT = Infinity;
|
|
@@ -320,7 +380,11 @@ function isShiftedSymbol(key) {
|
|
|
320
380
|
}
|
|
321
381
|
function isMac() {
|
|
322
382
|
if (typeof navigator === "undefined") return false;
|
|
323
|
-
|
|
383
|
+
const platform = navigator.userAgentData?.platform;
|
|
384
|
+
if (platform) {
|
|
385
|
+
return platform === "macOS" || platform === "iOS";
|
|
386
|
+
}
|
|
387
|
+
return /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
324
388
|
}
|
|
325
389
|
function normalizeKey(key) {
|
|
326
390
|
const keyMap = {
|
|
@@ -355,7 +419,7 @@ function formatKeyForDisplay(key) {
|
|
|
355
419
|
"space": "Space",
|
|
356
420
|
"escape": "Esc",
|
|
357
421
|
"enter": "\u21B5",
|
|
358
|
-
"tab": "
|
|
422
|
+
"tab": "\u21E5",
|
|
359
423
|
"backspace": "\u232B",
|
|
360
424
|
"delete": "Del",
|
|
361
425
|
"arrowup": "\u2191",
|
|
@@ -495,13 +559,6 @@ function parseHotkeyString(hotkeyStr) {
|
|
|
495
559
|
const parts = hotkeyStr.trim().split(/\s+/);
|
|
496
560
|
return parts.map(parseSingleCombination);
|
|
497
561
|
}
|
|
498
|
-
function parseCombinationId(id) {
|
|
499
|
-
const sequence = parseHotkeyString(id);
|
|
500
|
-
if (sequence.length === 0) {
|
|
501
|
-
return { key: "", modifiers: { ctrl: false, alt: false, shift: false, meta: false } };
|
|
502
|
-
}
|
|
503
|
-
return sequence[0];
|
|
504
|
-
}
|
|
505
562
|
var NO_MODIFIERS = { ctrl: false, alt: false, shift: false, meta: false };
|
|
506
563
|
function parseSeqElem(str) {
|
|
507
564
|
if (str === "\\d") {
|
|
@@ -617,6 +674,13 @@ function isPrefix(a, b) {
|
|
|
617
674
|
function combinationsEqual(a, b) {
|
|
618
675
|
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;
|
|
619
676
|
}
|
|
677
|
+
function keyMatchesPattern(pending, pattern) {
|
|
678
|
+
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) {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
if (pending.key === pattern.key) return true;
|
|
682
|
+
return /^[0-9]$/.test(pending.key) && (pattern.key === DIGIT_PLACEHOLDER || pattern.key === DIGITS_PLACEHOLDER);
|
|
683
|
+
}
|
|
620
684
|
function isDigitKey(key) {
|
|
621
685
|
return /^[0-9]$/.test(key);
|
|
622
686
|
}
|
|
@@ -737,28 +801,77 @@ function getSequenceCompletions(pendingKeys, keymap) {
|
|
|
737
801
|
if (pendingKeys.length === 0) return [];
|
|
738
802
|
const completions = [];
|
|
739
803
|
for (const [hotkeyStr, actionOrActions] of Object.entries(keymap)) {
|
|
740
|
-
const sequence = parseHotkeyString(hotkeyStr);
|
|
741
804
|
const keySeq = parseKeySeq(hotkeyStr);
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
805
|
+
const hasDigitsPlaceholder = keySeq.some((e) => e.type === "digits");
|
|
806
|
+
if (!hasDigitsPlaceholder && keySeq.length < pendingKeys.length) continue;
|
|
807
|
+
let keySeqIdx = 0;
|
|
808
|
+
let pendingIdx = 0;
|
|
809
|
+
let isMatch = true;
|
|
810
|
+
const captures = [];
|
|
811
|
+
let currentDigits = "";
|
|
812
|
+
for (; pendingIdx < pendingKeys.length && keySeqIdx < keySeq.length; pendingIdx++) {
|
|
813
|
+
const elem = keySeq[keySeqIdx];
|
|
814
|
+
if (elem.type === "digits") {
|
|
815
|
+
if (!/^[0-9]$/.test(pendingKeys[pendingIdx].key)) {
|
|
816
|
+
isMatch = false;
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
currentDigits += pendingKeys[pendingIdx].key;
|
|
820
|
+
if (pendingIdx + 1 < pendingKeys.length && /^[0-9]$/.test(pendingKeys[pendingIdx + 1].key)) {
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
captures.push(parseInt(currentDigits, 10));
|
|
824
|
+
currentDigits = "";
|
|
825
|
+
keySeqIdx++;
|
|
826
|
+
} else if (elem.type === "digit") {
|
|
827
|
+
if (!/^[0-9]$/.test(pendingKeys[pendingIdx].key)) {
|
|
828
|
+
isMatch = false;
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
captures.push(parseInt(pendingKeys[pendingIdx].key, 10));
|
|
832
|
+
keySeqIdx++;
|
|
833
|
+
} else {
|
|
834
|
+
const keyElem = elem;
|
|
835
|
+
const targetCombo = { key: keyElem.key, modifiers: keyElem.modifiers };
|
|
836
|
+
if (!keyMatchesPattern(pendingKeys[pendingIdx], targetCombo)) {
|
|
837
|
+
isMatch = false;
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
keySeqIdx++;
|
|
748
841
|
}
|
|
749
842
|
}
|
|
750
|
-
if (
|
|
751
|
-
|
|
843
|
+
if (pendingIdx < pendingKeys.length) {
|
|
844
|
+
isMatch = false;
|
|
845
|
+
}
|
|
846
|
+
if (!isMatch) continue;
|
|
847
|
+
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
848
|
+
if (keySeqIdx === keySeq.length) {
|
|
849
|
+
completions.push({
|
|
850
|
+
nextKeys: "",
|
|
851
|
+
fullSequence: hotkeyStr,
|
|
852
|
+
display: formatKeySeq(keySeq),
|
|
853
|
+
actions,
|
|
854
|
+
isComplete: true,
|
|
855
|
+
captures: captures.length > 0 ? captures : void 0
|
|
856
|
+
});
|
|
857
|
+
} else {
|
|
858
|
+
const remainingKeySeq = keySeq.slice(keySeqIdx);
|
|
752
859
|
const nextKeys = formatKeySeq(remainingKeySeq).display;
|
|
753
|
-
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
754
860
|
completions.push({
|
|
755
861
|
nextKeys,
|
|
862
|
+
nextKeySeq: remainingKeySeq,
|
|
756
863
|
fullSequence: hotkeyStr,
|
|
757
864
|
display: formatKeySeq(keySeq),
|
|
758
|
-
actions
|
|
865
|
+
actions,
|
|
866
|
+
isComplete: false,
|
|
867
|
+
captures: captures.length > 0 ? captures : void 0
|
|
759
868
|
});
|
|
760
869
|
}
|
|
761
870
|
}
|
|
871
|
+
completions.sort((a, b) => {
|
|
872
|
+
if (a.isComplete !== b.isComplete) return a.isComplete ? -1 : 1;
|
|
873
|
+
return a.fullSequence.localeCompare(b.fullSequence);
|
|
874
|
+
});
|
|
762
875
|
return completions;
|
|
763
876
|
}
|
|
764
877
|
function getActionBindings(keymap) {
|
|
@@ -849,7 +962,7 @@ function searchActions(query, actions, keymap) {
|
|
|
849
962
|
}
|
|
850
963
|
const matched = labelMatch.matched || descMatch.matched || groupMatch.matched || idMatch.matched || keywordScore > 0;
|
|
851
964
|
if (!matched && query) continue;
|
|
852
|
-
const score = (labelMatch.matched ? labelMatch.score * 3 : 0) + (descMatch.matched ? descMatch.score * 1.5 : 0) + (groupMatch.matched ? groupMatch.score
|
|
965
|
+
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;
|
|
853
966
|
results.push({
|
|
854
967
|
id,
|
|
855
968
|
action,
|
|
@@ -927,6 +1040,9 @@ function advanceMatchState(state, pattern, combo) {
|
|
|
927
1040
|
const digitValue = parseInt(elem.partial, 10);
|
|
928
1041
|
newState[i] = { type: "digits", value: digitValue };
|
|
929
1042
|
pos = i + 1;
|
|
1043
|
+
if (pos >= pattern.length) {
|
|
1044
|
+
return { status: "failed" };
|
|
1045
|
+
}
|
|
930
1046
|
break;
|
|
931
1047
|
}
|
|
932
1048
|
}
|
|
@@ -1049,7 +1165,7 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1049
1165
|
}
|
|
1050
1166
|
return false;
|
|
1051
1167
|
}, [preventDefault, stopPropagation]);
|
|
1052
|
-
const tryExecuteKeySeq = react.useCallback((matchKey,
|
|
1168
|
+
const tryExecuteKeySeq = react.useCallback((matchKey, captures, e) => {
|
|
1053
1169
|
for (const entry of parsedKeymapRef.current) {
|
|
1054
1170
|
if (entry.key === matchKey) {
|
|
1055
1171
|
for (const action of entry.actions) {
|
|
@@ -1105,7 +1221,24 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1105
1221
|
}
|
|
1106
1222
|
if (e.key === "Enter" && pendingKeysRef.current.length > 0) {
|
|
1107
1223
|
e.preventDefault();
|
|
1108
|
-
|
|
1224
|
+
let executed = false;
|
|
1225
|
+
for (const [key, state] of matchStatesRef.current.entries()) {
|
|
1226
|
+
const finalizedState = isCollectingDigits(state) ? finalizeDigits(state) : state;
|
|
1227
|
+
const isComplete = finalizedState.every((elem) => {
|
|
1228
|
+
if (elem.type === "key") return elem.matched === true;
|
|
1229
|
+
if (elem.type === "digit") return elem.value !== void 0;
|
|
1230
|
+
if (elem.type === "digits") return elem.value !== void 0;
|
|
1231
|
+
return false;
|
|
1232
|
+
});
|
|
1233
|
+
if (isComplete) {
|
|
1234
|
+
const captures = extractMatchCaptures(finalizedState);
|
|
1235
|
+
executed = tryExecuteKeySeq(key, captures, e);
|
|
1236
|
+
if (executed) break;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (!executed) {
|
|
1240
|
+
executed = tryExecute(pendingKeysRef.current, e);
|
|
1241
|
+
}
|
|
1109
1242
|
clearPending();
|
|
1110
1243
|
if (!executed) {
|
|
1111
1244
|
onSequenceCancel?.();
|
|
@@ -1118,14 +1251,57 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1118
1251
|
return;
|
|
1119
1252
|
}
|
|
1120
1253
|
const currentCombo = eventToCombination(e);
|
|
1254
|
+
if (e.key === "Backspace" && pendingKeysRef.current.length > 0) {
|
|
1255
|
+
let backspaceMatches = false;
|
|
1256
|
+
for (const entry of parsedKeymapRef.current) {
|
|
1257
|
+
let state = matchStatesRef.current.get(entry.key);
|
|
1258
|
+
if (!state) {
|
|
1259
|
+
state = initMatchState(entry.keySeq);
|
|
1260
|
+
}
|
|
1261
|
+
if (isCollectingDigits(state)) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
const result = advanceMatchState(state, entry.keySeq, currentCombo);
|
|
1265
|
+
if (result.status === "matched" || result.status === "partial") {
|
|
1266
|
+
backspaceMatches = true;
|
|
1267
|
+
break;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
if (!backspaceMatches) {
|
|
1271
|
+
e.preventDefault();
|
|
1272
|
+
const newPending = pendingKeysRef.current.slice(0, -1);
|
|
1273
|
+
if (newPending.length === 0) {
|
|
1274
|
+
clearPending();
|
|
1275
|
+
onSequenceCancel?.();
|
|
1276
|
+
} else {
|
|
1277
|
+
setPendingKeys(newPending);
|
|
1278
|
+
matchStatesRef.current.clear();
|
|
1279
|
+
for (const combo of newPending) {
|
|
1280
|
+
for (const entry of parsedKeymapRef.current) {
|
|
1281
|
+
let state = matchStatesRef.current.get(entry.key);
|
|
1282
|
+
if (!state) {
|
|
1283
|
+
state = initMatchState(entry.keySeq);
|
|
1284
|
+
}
|
|
1285
|
+
const result = advanceMatchState(state, entry.keySeq, combo);
|
|
1286
|
+
if (result.status === "partial") {
|
|
1287
|
+
matchStatesRef.current.set(entry.key, result.state);
|
|
1288
|
+
} else {
|
|
1289
|
+
matchStatesRef.current.delete(entry.key);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1121
1297
|
const newSequence = [...pendingKeysRef.current, currentCombo];
|
|
1122
|
-
|
|
1123
|
-
let
|
|
1298
|
+
const completeMatches = [];
|
|
1299
|
+
let hasPartials = false;
|
|
1124
1300
|
const matchStates = matchStatesRef.current;
|
|
1125
|
-
const
|
|
1301
|
+
const hadPartialMatches = matchStates.size > 0;
|
|
1126
1302
|
for (const entry of parsedKeymapRef.current) {
|
|
1127
1303
|
let state = matchStates.get(entry.key);
|
|
1128
|
-
if (
|
|
1304
|
+
if (hadPartialMatches && !state) {
|
|
1129
1305
|
continue;
|
|
1130
1306
|
}
|
|
1131
1307
|
if (!state) {
|
|
@@ -1134,22 +1310,27 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1134
1310
|
}
|
|
1135
1311
|
const result = advanceMatchState(state, entry.keySeq, currentCombo);
|
|
1136
1312
|
if (result.status === "matched") {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1313
|
+
completeMatches.push({
|
|
1314
|
+
key: entry.key,
|
|
1315
|
+
state: result.state,
|
|
1316
|
+
captures: result.captures
|
|
1317
|
+
});
|
|
1318
|
+
matchStates.delete(entry.key);
|
|
1142
1319
|
} else if (result.status === "partial") {
|
|
1143
1320
|
matchStates.set(entry.key, result.state);
|
|
1144
|
-
|
|
1321
|
+
hasPartials = true;
|
|
1145
1322
|
} else {
|
|
1146
1323
|
matchStates.delete(entry.key);
|
|
1147
1324
|
}
|
|
1148
1325
|
}
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1326
|
+
if (completeMatches.length === 1 && !hasPartials) {
|
|
1327
|
+
const match = completeMatches[0];
|
|
1328
|
+
if (tryExecuteKeySeq(match.key, match.captures, e)) {
|
|
1329
|
+
clearPending();
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1151
1332
|
}
|
|
1152
|
-
if (
|
|
1333
|
+
if (completeMatches.length > 0 || hasPartials) {
|
|
1153
1334
|
setPendingKeys(newSequence);
|
|
1154
1335
|
setIsAwaitingSequence(true);
|
|
1155
1336
|
if (pendingKeysRef.current.length === 0) {
|
|
@@ -1232,8 +1413,11 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1232
1413
|
}
|
|
1233
1414
|
}
|
|
1234
1415
|
if (pendingKeysRef.current.length > 0) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1416
|
+
setPendingKeys(newSequence);
|
|
1417
|
+
if (preventDefault) {
|
|
1418
|
+
e.preventDefault();
|
|
1419
|
+
}
|
|
1420
|
+
return;
|
|
1237
1421
|
}
|
|
1238
1422
|
const singleMatch = tryExecute([currentCombo], e);
|
|
1239
1423
|
if (!singleMatch) {
|
|
@@ -1295,7 +1479,8 @@ var HotkeysContext = react.createContext(null);
|
|
|
1295
1479
|
var DEFAULT_CONFIG = {
|
|
1296
1480
|
storageKey: "use-kbd",
|
|
1297
1481
|
sequenceTimeout: DEFAULT_SEQUENCE_TIMEOUT,
|
|
1298
|
-
disableConflicts:
|
|
1482
|
+
disableConflicts: false,
|
|
1483
|
+
// Keep conflicting bindings active; SeqM handles disambiguation
|
|
1299
1484
|
minViewportWidth: 768,
|
|
1300
1485
|
enableOnTouch: false
|
|
1301
1486
|
};
|
|
@@ -1308,6 +1493,7 @@ function HotkeysProvider({
|
|
|
1308
1493
|
...configProp
|
|
1309
1494
|
}), [configProp]);
|
|
1310
1495
|
const registry = useActionsRegistry({ storageKey: config.storageKey });
|
|
1496
|
+
const endpointsRegistry = useOmnibarEndpointsRegistry();
|
|
1311
1497
|
const [isEnabled, setIsEnabled] = react.useState(true);
|
|
1312
1498
|
react.useEffect(() => {
|
|
1313
1499
|
if (typeof window === "undefined") return;
|
|
@@ -1399,6 +1585,7 @@ function HotkeysProvider({
|
|
|
1399
1585
|
);
|
|
1400
1586
|
const value = react.useMemo(() => ({
|
|
1401
1587
|
registry,
|
|
1588
|
+
endpointsRegistry,
|
|
1402
1589
|
isEnabled,
|
|
1403
1590
|
isModalOpen,
|
|
1404
1591
|
openModal,
|
|
@@ -1426,6 +1613,7 @@ function HotkeysProvider({
|
|
|
1426
1613
|
getCompletions
|
|
1427
1614
|
}), [
|
|
1428
1615
|
registry,
|
|
1616
|
+
endpointsRegistry,
|
|
1429
1617
|
isEnabled,
|
|
1430
1618
|
isModalOpen,
|
|
1431
1619
|
openModal,
|
|
@@ -1450,7 +1638,7 @@ function HotkeysProvider({
|
|
|
1450
1638
|
searchActionsHelper,
|
|
1451
1639
|
getCompletions
|
|
1452
1640
|
]);
|
|
1453
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ActionsRegistryContext.Provider, { value: registry, children: /* @__PURE__ */ jsxRuntime.jsx(HotkeysContext.Provider, { value, children }) });
|
|
1641
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ActionsRegistryContext.Provider, { value: registry, children: /* @__PURE__ */ jsxRuntime.jsx(OmnibarEndpointsRegistryContext.Provider, { value: endpointsRegistry, children: /* @__PURE__ */ jsxRuntime.jsx(HotkeysContext.Provider, { value, children }) }) });
|
|
1454
1642
|
}
|
|
1455
1643
|
function useHotkeysContext() {
|
|
1456
1644
|
const context = react.useContext(HotkeysContext);
|
|
@@ -1538,6 +1726,38 @@ function useActions(actions) {
|
|
|
1538
1726
|
)
|
|
1539
1727
|
]);
|
|
1540
1728
|
}
|
|
1729
|
+
function useOmnibarEndpoint(id, config) {
|
|
1730
|
+
const registry = react.useContext(OmnibarEndpointsRegistryContext);
|
|
1731
|
+
if (!registry) {
|
|
1732
|
+
throw new Error("useOmnibarEndpoint must be used within a HotkeysProvider");
|
|
1733
|
+
}
|
|
1734
|
+
const registryRef = react.useRef(registry);
|
|
1735
|
+
registryRef.current = registry;
|
|
1736
|
+
const fetchRef = react.useRef(config.fetch);
|
|
1737
|
+
fetchRef.current = config.fetch;
|
|
1738
|
+
const enabledRef = react.useRef(config.enabled ?? true);
|
|
1739
|
+
enabledRef.current = config.enabled ?? true;
|
|
1740
|
+
react.useEffect(() => {
|
|
1741
|
+
registryRef.current.register(id, {
|
|
1742
|
+
...config,
|
|
1743
|
+
fetch: async (query, signal, pagination) => {
|
|
1744
|
+
if (!enabledRef.current) return { entries: [] };
|
|
1745
|
+
return fetchRef.current(query, signal, pagination);
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
return () => {
|
|
1749
|
+
registryRef.current.unregister(id);
|
|
1750
|
+
};
|
|
1751
|
+
}, [
|
|
1752
|
+
id,
|
|
1753
|
+
config.group,
|
|
1754
|
+
config.priority,
|
|
1755
|
+
config.minQueryLength,
|
|
1756
|
+
config.pageSize,
|
|
1757
|
+
config.pagination
|
|
1758
|
+
// Note: we use refs for fetch and enabled, so they don't cause re-registration
|
|
1759
|
+
]);
|
|
1760
|
+
}
|
|
1541
1761
|
function useEventCallback(fn) {
|
|
1542
1762
|
const ref = react.useRef(fn);
|
|
1543
1763
|
ref.current = fn;
|
|
@@ -1786,7 +2006,6 @@ function useRecordHotkey(options = {}) {
|
|
|
1786
2006
|
};
|
|
1787
2007
|
}, [isRecording, preventDefault, sequenceTimeout, clearTimeout_, submit, cancel, onCapture, onTab, onShiftTab]);
|
|
1788
2008
|
const display = sequence ? formatCombination(sequence) : null;
|
|
1789
|
-
const combination = sequence && sequence.length > 0 ? sequence[0] : null;
|
|
1790
2009
|
return {
|
|
1791
2010
|
isRecording,
|
|
1792
2011
|
startRecording,
|
|
@@ -1796,13 +2015,11 @@ function useRecordHotkey(options = {}) {
|
|
|
1796
2015
|
display,
|
|
1797
2016
|
pendingKeys,
|
|
1798
2017
|
activeKeys,
|
|
1799
|
-
sequenceTimeout
|
|
1800
|
-
combination
|
|
1801
|
-
// deprecated
|
|
2018
|
+
sequenceTimeout
|
|
1802
2019
|
};
|
|
1803
2020
|
}
|
|
1804
2021
|
function useEditableHotkeys(defaults, handlers, options = {}) {
|
|
1805
|
-
const { storageKey, disableConflicts =
|
|
2022
|
+
const { storageKey, disableConflicts = false, ...hotkeyOptions } = options;
|
|
1806
2023
|
const [overrides, setOverrides] = react.useState(() => {
|
|
1807
2024
|
if (!storageKey || typeof window === "undefined") return {};
|
|
1808
2025
|
try {
|
|
@@ -1898,6 +2115,7 @@ function useEditableHotkeys(defaults, handlers, options = {}) {
|
|
|
1898
2115
|
};
|
|
1899
2116
|
}
|
|
1900
2117
|
var { max: max2, min } = Math;
|
|
2118
|
+
var DEFAULT_DEBOUNCE_MS = 150;
|
|
1901
2119
|
function useOmnibar(options) {
|
|
1902
2120
|
const {
|
|
1903
2121
|
actions,
|
|
@@ -1906,17 +2124,27 @@ function useOmnibar(options) {
|
|
|
1906
2124
|
openKey = "meta+k",
|
|
1907
2125
|
enabled = true,
|
|
1908
2126
|
onExecute,
|
|
2127
|
+
onExecuteRemote,
|
|
1909
2128
|
onOpen,
|
|
1910
2129
|
onClose,
|
|
1911
|
-
maxResults = 10
|
|
2130
|
+
maxResults = 10,
|
|
2131
|
+
endpointsRegistry,
|
|
2132
|
+
debounceMs = DEFAULT_DEBOUNCE_MS
|
|
1912
2133
|
} = options;
|
|
1913
2134
|
const [isOpen, setIsOpen] = react.useState(false);
|
|
1914
2135
|
const [query, setQuery] = react.useState("");
|
|
1915
2136
|
const [selectedIndex, setSelectedIndex] = react.useState(0);
|
|
2137
|
+
const [endpointStates, setEndpointStates] = react.useState(/* @__PURE__ */ new Map());
|
|
1916
2138
|
const handlersRef = react.useRef(handlers);
|
|
1917
2139
|
handlersRef.current = handlers;
|
|
1918
2140
|
const onExecuteRef = react.useRef(onExecute);
|
|
1919
2141
|
onExecuteRef.current = onExecute;
|
|
2142
|
+
const onExecuteRemoteRef = react.useRef(onExecuteRemote);
|
|
2143
|
+
onExecuteRemoteRef.current = onExecuteRemote;
|
|
2144
|
+
const abortControllerRef = react.useRef(null);
|
|
2145
|
+
const debounceTimerRef = react.useRef(null);
|
|
2146
|
+
const currentQueryRef = react.useRef(query);
|
|
2147
|
+
currentQueryRef.current = query;
|
|
1920
2148
|
const omnibarKeymap = react.useMemo(() => {
|
|
1921
2149
|
if (!enabled) return {};
|
|
1922
2150
|
return { [openKey]: "omnibar:toggle" };
|
|
@@ -1942,12 +2170,189 @@ function useOmnibar(options) {
|
|
|
1942
2170
|
const allResults = searchActions(query, actions, keymap);
|
|
1943
2171
|
return allResults.slice(0, maxResults);
|
|
1944
2172
|
}, [query, actions, keymap, maxResults]);
|
|
2173
|
+
react.useEffect(() => {
|
|
2174
|
+
if (debounceTimerRef.current) {
|
|
2175
|
+
clearTimeout(debounceTimerRef.current);
|
|
2176
|
+
debounceTimerRef.current = null;
|
|
2177
|
+
}
|
|
2178
|
+
if (abortControllerRef.current) {
|
|
2179
|
+
abortControllerRef.current.abort();
|
|
2180
|
+
abortControllerRef.current = null;
|
|
2181
|
+
}
|
|
2182
|
+
if (!endpointsRegistry || !query.trim()) {
|
|
2183
|
+
setEndpointStates(/* @__PURE__ */ new Map());
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
setEndpointStates((prev) => {
|
|
2187
|
+
const next = new Map(prev);
|
|
2188
|
+
for (const [id] of endpointsRegistry.endpoints) {
|
|
2189
|
+
next.set(id, { entries: [], offset: 0, isLoading: true });
|
|
2190
|
+
}
|
|
2191
|
+
return next;
|
|
2192
|
+
});
|
|
2193
|
+
debounceTimerRef.current = setTimeout(async () => {
|
|
2194
|
+
const controller = new AbortController();
|
|
2195
|
+
abortControllerRef.current = controller;
|
|
2196
|
+
try {
|
|
2197
|
+
const endpointResults = await endpointsRegistry.queryAll(query, controller.signal);
|
|
2198
|
+
if (controller.signal.aborted) return;
|
|
2199
|
+
setEndpointStates(() => {
|
|
2200
|
+
const next = /* @__PURE__ */ new Map();
|
|
2201
|
+
for (const epResult of endpointResults) {
|
|
2202
|
+
const ep = endpointsRegistry.endpoints.get(epResult.endpointId);
|
|
2203
|
+
const pageSize = ep?.config.pageSize ?? 10;
|
|
2204
|
+
next.set(epResult.endpointId, {
|
|
2205
|
+
entries: epResult.entries,
|
|
2206
|
+
offset: pageSize,
|
|
2207
|
+
total: epResult.total,
|
|
2208
|
+
hasMore: epResult.hasMore ?? (epResult.total !== void 0 ? epResult.entries.length < epResult.total : void 0),
|
|
2209
|
+
isLoading: false
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
return next;
|
|
2213
|
+
});
|
|
2214
|
+
} catch (error) {
|
|
2215
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
2216
|
+
console.error("Omnibar endpoint query failed:", error);
|
|
2217
|
+
setEndpointStates((prev) => {
|
|
2218
|
+
const next = new Map(prev);
|
|
2219
|
+
for (const [id, state] of next) {
|
|
2220
|
+
next.set(id, { ...state, isLoading: false });
|
|
2221
|
+
}
|
|
2222
|
+
return next;
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
}, debounceMs);
|
|
2226
|
+
return () => {
|
|
2227
|
+
if (debounceTimerRef.current) {
|
|
2228
|
+
clearTimeout(debounceTimerRef.current);
|
|
2229
|
+
}
|
|
2230
|
+
if (abortControllerRef.current) {
|
|
2231
|
+
abortControllerRef.current.abort();
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
}, [query, endpointsRegistry, debounceMs]);
|
|
2235
|
+
const loadMore = react.useCallback(async (endpointId) => {
|
|
2236
|
+
if (!endpointsRegistry) return;
|
|
2237
|
+
const currentState = endpointStates.get(endpointId);
|
|
2238
|
+
if (!currentState || currentState.isLoading) return;
|
|
2239
|
+
if (currentState.hasMore === false) return;
|
|
2240
|
+
const ep = endpointsRegistry.endpoints.get(endpointId);
|
|
2241
|
+
if (!ep) return;
|
|
2242
|
+
const pageSize = ep.config.pageSize ?? 10;
|
|
2243
|
+
setEndpointStates((prev) => {
|
|
2244
|
+
const next = new Map(prev);
|
|
2245
|
+
const state = next.get(endpointId);
|
|
2246
|
+
if (state) {
|
|
2247
|
+
next.set(endpointId, { ...state, isLoading: true });
|
|
2248
|
+
}
|
|
2249
|
+
return next;
|
|
2250
|
+
});
|
|
2251
|
+
try {
|
|
2252
|
+
const controller = new AbortController();
|
|
2253
|
+
const result = await endpointsRegistry.queryEndpoint(
|
|
2254
|
+
endpointId,
|
|
2255
|
+
currentQueryRef.current,
|
|
2256
|
+
{ offset: currentState.offset, limit: pageSize },
|
|
2257
|
+
controller.signal
|
|
2258
|
+
);
|
|
2259
|
+
if (!result) return;
|
|
2260
|
+
setEndpointStates((prev) => {
|
|
2261
|
+
const next = new Map(prev);
|
|
2262
|
+
const state = next.get(endpointId);
|
|
2263
|
+
if (state) {
|
|
2264
|
+
next.set(endpointId, {
|
|
2265
|
+
entries: [...state.entries, ...result.entries],
|
|
2266
|
+
offset: state.offset + pageSize,
|
|
2267
|
+
total: result.total ?? state.total,
|
|
2268
|
+
hasMore: result.hasMore ?? (result.total !== void 0 ? state.entries.length + result.entries.length < result.total : void 0),
|
|
2269
|
+
isLoading: false
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
return next;
|
|
2273
|
+
});
|
|
2274
|
+
} catch (error) {
|
|
2275
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
2276
|
+
console.error(`Omnibar loadMore failed for ${endpointId}:`, error);
|
|
2277
|
+
setEndpointStates((prev) => {
|
|
2278
|
+
const next = new Map(prev);
|
|
2279
|
+
const state = next.get(endpointId);
|
|
2280
|
+
if (state) {
|
|
2281
|
+
next.set(endpointId, { ...state, isLoading: false });
|
|
2282
|
+
}
|
|
2283
|
+
return next;
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
}, [endpointsRegistry, endpointStates]);
|
|
2287
|
+
const remoteResults = react.useMemo(() => {
|
|
2288
|
+
if (!endpointsRegistry) return [];
|
|
2289
|
+
const processed = [];
|
|
2290
|
+
for (const [endpointId, state] of endpointStates) {
|
|
2291
|
+
const endpoint = endpointsRegistry.endpoints.get(endpointId);
|
|
2292
|
+
const priority = endpoint?.config.priority ?? 0;
|
|
2293
|
+
for (const entry of state.entries) {
|
|
2294
|
+
const labelMatch = fuzzyMatch(query, entry.label);
|
|
2295
|
+
const descMatch = entry.description ? fuzzyMatch(query, entry.description) : null;
|
|
2296
|
+
const keywordsMatch = entry.keywords?.map((k) => fuzzyMatch(query, k)) ?? [];
|
|
2297
|
+
let score = 0;
|
|
2298
|
+
let labelMatches = [];
|
|
2299
|
+
if (labelMatch.matched) {
|
|
2300
|
+
score = Math.max(score, labelMatch.score * 3);
|
|
2301
|
+
labelMatches = labelMatch.ranges;
|
|
2302
|
+
}
|
|
2303
|
+
if (descMatch?.matched) {
|
|
2304
|
+
score = Math.max(score, descMatch.score * 1.5);
|
|
2305
|
+
}
|
|
2306
|
+
for (const km of keywordsMatch) {
|
|
2307
|
+
if (km.matched) {
|
|
2308
|
+
score = Math.max(score, km.score * 2);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
processed.push({
|
|
2312
|
+
id: `${endpointId}:${entry.id}`,
|
|
2313
|
+
entry,
|
|
2314
|
+
endpointId,
|
|
2315
|
+
priority,
|
|
2316
|
+
score: score || 1,
|
|
2317
|
+
labelMatches
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
processed.sort((a, b) => {
|
|
2322
|
+
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
2323
|
+
return b.score - a.score;
|
|
2324
|
+
});
|
|
2325
|
+
return processed;
|
|
2326
|
+
}, [endpointStates, endpointsRegistry, query]);
|
|
2327
|
+
const isLoadingRemote = react.useMemo(() => {
|
|
2328
|
+
for (const [, state] of endpointStates) {
|
|
2329
|
+
if (state.isLoading) return true;
|
|
2330
|
+
}
|
|
2331
|
+
return false;
|
|
2332
|
+
}, [endpointStates]);
|
|
2333
|
+
const endpointPagination = react.useMemo(() => {
|
|
2334
|
+
const info = /* @__PURE__ */ new Map();
|
|
2335
|
+
if (!endpointsRegistry) return info;
|
|
2336
|
+
for (const [endpointId, state] of endpointStates) {
|
|
2337
|
+
const ep = endpointsRegistry.endpoints.get(endpointId);
|
|
2338
|
+
info.set(endpointId, {
|
|
2339
|
+
endpointId,
|
|
2340
|
+
loaded: state.entries.length,
|
|
2341
|
+
total: state.total,
|
|
2342
|
+
hasMore: state.hasMore ?? false,
|
|
2343
|
+
isLoading: state.isLoading,
|
|
2344
|
+
mode: ep?.config.pagination ?? "none"
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
return info;
|
|
2348
|
+
}, [endpointStates, endpointsRegistry]);
|
|
2349
|
+
const totalResults = results.length + remoteResults.length;
|
|
1945
2350
|
const completions = react.useMemo(() => {
|
|
1946
2351
|
return getSequenceCompletions(pendingKeys, keymap);
|
|
1947
2352
|
}, [pendingKeys, keymap]);
|
|
1948
2353
|
react.useEffect(() => {
|
|
1949
2354
|
setSelectedIndex(0);
|
|
1950
|
-
}, [results]);
|
|
2355
|
+
}, [results, remoteResults]);
|
|
1951
2356
|
const open = react.useCallback(() => {
|
|
1952
2357
|
setIsOpen(true);
|
|
1953
2358
|
setQuery("");
|
|
@@ -1974,8 +2379,8 @@ function useOmnibar(options) {
|
|
|
1974
2379
|
});
|
|
1975
2380
|
}, [onOpen, onClose]);
|
|
1976
2381
|
const selectNext = react.useCallback(() => {
|
|
1977
|
-
setSelectedIndex((prev) => min(prev + 1,
|
|
1978
|
-
}, [
|
|
2382
|
+
setSelectedIndex((prev) => min(prev + 1, totalResults - 1));
|
|
2383
|
+
}, [totalResults]);
|
|
1979
2384
|
const selectPrev = react.useCallback(() => {
|
|
1980
2385
|
setSelectedIndex((prev) => max2(prev - 1, 0));
|
|
1981
2386
|
}, []);
|
|
@@ -1983,15 +2388,47 @@ function useOmnibar(options) {
|
|
|
1983
2388
|
setSelectedIndex(0);
|
|
1984
2389
|
}, []);
|
|
1985
2390
|
const execute = react.useCallback((actionId) => {
|
|
1986
|
-
const
|
|
1987
|
-
if (
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2391
|
+
const localCount = results.length;
|
|
2392
|
+
if (actionId) {
|
|
2393
|
+
const remoteResult = remoteResults.find((r) => r.id === actionId);
|
|
2394
|
+
if (remoteResult) {
|
|
2395
|
+
close();
|
|
2396
|
+
const entry = remoteResult.entry;
|
|
2397
|
+
if ("handler" in entry && entry.handler) {
|
|
2398
|
+
entry.handler();
|
|
2399
|
+
}
|
|
2400
|
+
onExecuteRemoteRef.current?.(entry);
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
close();
|
|
2404
|
+
if (handlersRef.current?.[actionId]) {
|
|
2405
|
+
const event = new KeyboardEvent("keydown", { key: "Enter" });
|
|
2406
|
+
handlersRef.current[actionId](event);
|
|
2407
|
+
}
|
|
2408
|
+
onExecuteRef.current?.(actionId);
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
if (selectedIndex < localCount) {
|
|
2412
|
+
const id = results[selectedIndex]?.id;
|
|
2413
|
+
if (!id) return;
|
|
2414
|
+
close();
|
|
2415
|
+
if (handlersRef.current?.[id]) {
|
|
2416
|
+
const event = new KeyboardEvent("keydown", { key: "Enter" });
|
|
2417
|
+
handlersRef.current[id](event);
|
|
2418
|
+
}
|
|
2419
|
+
onExecuteRef.current?.(id);
|
|
2420
|
+
} else {
|
|
2421
|
+
const remoteIndex = selectedIndex - localCount;
|
|
2422
|
+
const remoteResult = remoteResults[remoteIndex];
|
|
2423
|
+
if (!remoteResult) return;
|
|
2424
|
+
close();
|
|
2425
|
+
const entry = remoteResult.entry;
|
|
2426
|
+
if ("handler" in entry && entry.handler) {
|
|
2427
|
+
entry.handler();
|
|
2428
|
+
}
|
|
2429
|
+
onExecuteRemoteRef.current?.(entry);
|
|
2430
|
+
}
|
|
2431
|
+
}, [results, remoteResults, selectedIndex, close]);
|
|
1995
2432
|
react.useEffect(() => {
|
|
1996
2433
|
if (!isOpen) return;
|
|
1997
2434
|
const handleKeyDown = (e) => {
|
|
@@ -2033,7 +2470,12 @@ function useOmnibar(options) {
|
|
|
2033
2470
|
query,
|
|
2034
2471
|
setQuery,
|
|
2035
2472
|
results,
|
|
2473
|
+
remoteResults,
|
|
2474
|
+
isLoadingRemote,
|
|
2475
|
+
endpointPagination,
|
|
2476
|
+
loadMore,
|
|
2036
2477
|
selectedIndex,
|
|
2478
|
+
totalResults,
|
|
2037
2479
|
selectNext,
|
|
2038
2480
|
selectPrev,
|
|
2039
2481
|
execute,
|
|
@@ -2151,6 +2593,26 @@ function Backspace({ className, style }) {
|
|
|
2151
2593
|
}
|
|
2152
2594
|
);
|
|
2153
2595
|
}
|
|
2596
|
+
function Tab({ className, style }) {
|
|
2597
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2598
|
+
"svg",
|
|
2599
|
+
{
|
|
2600
|
+
className,
|
|
2601
|
+
style: { ...baseStyle, ...style },
|
|
2602
|
+
viewBox: "0 0 24 24",
|
|
2603
|
+
fill: "none",
|
|
2604
|
+
stroke: "currentColor",
|
|
2605
|
+
strokeWidth: "2",
|
|
2606
|
+
strokeLinecap: "round",
|
|
2607
|
+
strokeLinejoin: "round",
|
|
2608
|
+
children: [
|
|
2609
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "12", x2: "16", y2: "12" }),
|
|
2610
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "12 8 16 12 12 16" }),
|
|
2611
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "20", y1: "6", x2: "20", y2: "18" })
|
|
2612
|
+
]
|
|
2613
|
+
}
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2154
2616
|
function getKeyIcon(key) {
|
|
2155
2617
|
switch (key.toLowerCase()) {
|
|
2156
2618
|
case "arrowup":
|
|
@@ -2165,6 +2627,8 @@ function getKeyIcon(key) {
|
|
|
2165
2627
|
return Enter;
|
|
2166
2628
|
case "backspace":
|
|
2167
2629
|
return Backspace;
|
|
2630
|
+
case "tab":
|
|
2631
|
+
return Tab;
|
|
2168
2632
|
default:
|
|
2169
2633
|
return null;
|
|
2170
2634
|
}
|
|
@@ -2269,7 +2733,6 @@ var Alt = react.forwardRef(
|
|
|
2269
2733
|
)
|
|
2270
2734
|
);
|
|
2271
2735
|
Alt.displayName = "Alt";
|
|
2272
|
-
var isMac2 = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
2273
2736
|
function getModifierIcon(modifier) {
|
|
2274
2737
|
switch (modifier) {
|
|
2275
2738
|
case "meta":
|
|
@@ -2281,7 +2744,7 @@ function getModifierIcon(modifier) {
|
|
|
2281
2744
|
case "opt":
|
|
2282
2745
|
return Option;
|
|
2283
2746
|
case "alt":
|
|
2284
|
-
return
|
|
2747
|
+
return isMac() ? Option : Alt;
|
|
2285
2748
|
}
|
|
2286
2749
|
}
|
|
2287
2750
|
var ModifierIcon = react.forwardRef(
|
|
@@ -2291,28 +2754,47 @@ var ModifierIcon = react.forwardRef(
|
|
|
2291
2754
|
}
|
|
2292
2755
|
);
|
|
2293
2756
|
ModifierIcon.displayName = "ModifierIcon";
|
|
2294
|
-
function
|
|
2295
|
-
const
|
|
2296
|
-
const parts = [];
|
|
2757
|
+
function renderModifierIcons(modifiers, className = "kbd-modifier-icon") {
|
|
2758
|
+
const icons = [];
|
|
2297
2759
|
if (modifiers.meta) {
|
|
2298
|
-
|
|
2760
|
+
icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className }, "meta"));
|
|
2299
2761
|
}
|
|
2300
2762
|
if (modifiers.ctrl) {
|
|
2301
|
-
|
|
2763
|
+
icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className }, "ctrl"));
|
|
2302
2764
|
}
|
|
2303
2765
|
if (modifiers.alt) {
|
|
2304
|
-
|
|
2766
|
+
icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className }, "alt"));
|
|
2305
2767
|
}
|
|
2306
2768
|
if (modifiers.shift) {
|
|
2307
|
-
|
|
2769
|
+
icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className }, "shift"));
|
|
2308
2770
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2771
|
+
return icons;
|
|
2772
|
+
}
|
|
2773
|
+
function renderKeyContent(key, iconClassName = "kbd-key-icon") {
|
|
2774
|
+
const Icon = getKeyIcon(key);
|
|
2775
|
+
const displayKey = formatKeyForDisplay(key);
|
|
2776
|
+
return Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: iconClassName }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: displayKey });
|
|
2777
|
+
}
|
|
2778
|
+
function renderSeqElem(elem, index, kbdClassName = "kbd-kbd") {
|
|
2779
|
+
if (elem.type === "digit") {
|
|
2780
|
+
return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: kbdClassName, children: "\u27E8#\u27E9" }, index);
|
|
2781
|
+
}
|
|
2782
|
+
if (elem.type === "digits") {
|
|
2783
|
+
return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: kbdClassName, children: "\u27E8##\u27E9" }, index);
|
|
2314
2784
|
}
|
|
2315
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
2785
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("kbd", { className: kbdClassName, children: [
|
|
2786
|
+
renderModifierIcons(elem.modifiers),
|
|
2787
|
+
renderKeyContent(elem.key)
|
|
2788
|
+
] }, index);
|
|
2789
|
+
}
|
|
2790
|
+
function renderKeySeq(keySeq, kbdClassName = "kbd-kbd") {
|
|
2791
|
+
return keySeq.map((elem, i) => renderSeqElem(elem, i, kbdClassName));
|
|
2792
|
+
}
|
|
2793
|
+
function KeyCombo({ combo }) {
|
|
2794
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2795
|
+
renderModifierIcons(combo.modifiers),
|
|
2796
|
+
renderKeyContent(combo.key)
|
|
2797
|
+
] });
|
|
2316
2798
|
}
|
|
2317
2799
|
function SeqElemDisplay({ elem }) {
|
|
2318
2800
|
if (elem.type === "digit") {
|
|
@@ -2464,7 +2946,7 @@ function KeybindingEditor({
|
|
|
2464
2946
|
return Array.from(allActions).map((action) => {
|
|
2465
2947
|
const key = actionMap.get(action) ?? defaultActionMap.get(action) ?? "";
|
|
2466
2948
|
const defaultKey = defaultActionMap.get(action) ?? "";
|
|
2467
|
-
const combo =
|
|
2949
|
+
const combo = parseHotkeyString(key);
|
|
2468
2950
|
const display = formatCombination(combo);
|
|
2469
2951
|
const conflictActions = conflicts.get(key);
|
|
2470
2952
|
return {
|
|
@@ -2621,15 +3103,30 @@ function LookupModal({ defaultBinding = "meta+shift+k" } = {}) {
|
|
|
2621
3103
|
const filteredBindings = react.useMemo(() => {
|
|
2622
3104
|
if (pendingKeys.length === 0) return allBindings;
|
|
2623
3105
|
return allBindings.filter((result) => {
|
|
2624
|
-
|
|
2625
|
-
|
|
3106
|
+
const keySeq = result.keySeq;
|
|
3107
|
+
if (keySeq.length < pendingKeys.length) return false;
|
|
3108
|
+
let keySeqIdx = 0;
|
|
3109
|
+
for (let i = 0; i < pendingKeys.length && keySeqIdx < keySeq.length; i++) {
|
|
2626
3110
|
const pending = pendingKeys[i];
|
|
2627
|
-
const
|
|
2628
|
-
|
|
2629
|
-
if (
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
3111
|
+
const elem = keySeq[keySeqIdx];
|
|
3112
|
+
const isDigit2 = /^[0-9]$/.test(pending.key);
|
|
3113
|
+
if (elem.type === "digits") {
|
|
3114
|
+
if (!isDigit2) return false;
|
|
3115
|
+
if (i + 1 < pendingKeys.length && /^[0-9]$/.test(pendingKeys[i + 1].key)) {
|
|
3116
|
+
continue;
|
|
3117
|
+
}
|
|
3118
|
+
keySeqIdx++;
|
|
3119
|
+
} else if (elem.type === "digit") {
|
|
3120
|
+
if (!isDigit2) return false;
|
|
3121
|
+
keySeqIdx++;
|
|
3122
|
+
} else {
|
|
3123
|
+
if (pending.key !== elem.key) return false;
|
|
3124
|
+
if (pending.modifiers.ctrl !== elem.modifiers.ctrl) return false;
|
|
3125
|
+
if (pending.modifiers.alt !== elem.modifiers.alt) return false;
|
|
3126
|
+
if (pending.modifiers.shift !== elem.modifiers.shift) return false;
|
|
3127
|
+
if (pending.modifiers.meta !== elem.modifiers.meta) return false;
|
|
3128
|
+
keySeqIdx++;
|
|
3129
|
+
}
|
|
2633
3130
|
}
|
|
2634
3131
|
return true;
|
|
2635
3132
|
});
|
|
@@ -2741,7 +3238,7 @@ function LookupModal({ defaultBinding = "meta+shift+k" } = {}) {
|
|
|
2741
3238
|
},
|
|
2742
3239
|
onMouseEnter: () => setSelectedIndex(index),
|
|
2743
3240
|
children: [
|
|
2744
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
3241
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-lookup-binding", children: renderKeySeq(result.keySeq) }),
|
|
2745
3242
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-lookup-labels", children: result.labels.join(", ") })
|
|
2746
3243
|
]
|
|
2747
3244
|
},
|
|
@@ -2785,6 +3282,7 @@ function Omnibar({
|
|
|
2785
3282
|
onOpen: onOpenProp,
|
|
2786
3283
|
onClose: onCloseProp,
|
|
2787
3284
|
onExecute: onExecuteProp,
|
|
3285
|
+
onExecuteRemote: onExecuteRemoteProp,
|
|
2788
3286
|
maxResults = 10,
|
|
2789
3287
|
placeholder = "Type a command...",
|
|
2790
3288
|
children,
|
|
@@ -2822,13 +3320,25 @@ function Omnibar({
|
|
|
2822
3320
|
ctx.openOmnibar();
|
|
2823
3321
|
}
|
|
2824
3322
|
}, [onOpenProp, ctx]);
|
|
3323
|
+
const handleExecuteRemote = react.useCallback((entry) => {
|
|
3324
|
+
if (onExecuteRemoteProp) {
|
|
3325
|
+
onExecuteRemoteProp(entry);
|
|
3326
|
+
} else if ("href" in entry && entry.href) {
|
|
3327
|
+
window.location.href = entry.href;
|
|
3328
|
+
}
|
|
3329
|
+
}, [onExecuteRemoteProp]);
|
|
2825
3330
|
const {
|
|
2826
3331
|
isOpen: internalIsOpen,
|
|
2827
3332
|
close,
|
|
2828
3333
|
query,
|
|
2829
3334
|
setQuery,
|
|
2830
3335
|
results,
|
|
3336
|
+
remoteResults,
|
|
3337
|
+
isLoadingRemote,
|
|
3338
|
+
endpointPagination,
|
|
3339
|
+
loadMore,
|
|
2831
3340
|
selectedIndex,
|
|
3341
|
+
totalResults,
|
|
2832
3342
|
selectNext,
|
|
2833
3343
|
selectPrev,
|
|
2834
3344
|
execute,
|
|
@@ -2845,9 +3355,22 @@ function Omnibar({
|
|
|
2845
3355
|
onOpen: handleOpen,
|
|
2846
3356
|
onClose: handleClose,
|
|
2847
3357
|
onExecute: handleExecute,
|
|
2848
|
-
|
|
3358
|
+
onExecuteRemote: handleExecuteRemote,
|
|
3359
|
+
maxResults,
|
|
3360
|
+
endpointsRegistry: ctx?.endpointsRegistry
|
|
2849
3361
|
});
|
|
2850
3362
|
const isOpen = isOpenProp ?? ctx?.isOmnibarOpen ?? internalIsOpen;
|
|
3363
|
+
const resultsContainerRef = react.useRef(null);
|
|
3364
|
+
const sentinelRefs = react.useRef(/* @__PURE__ */ new Map());
|
|
3365
|
+
const remoteResultsByEndpoint = react.useMemo(() => {
|
|
3366
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3367
|
+
for (const result of remoteResults) {
|
|
3368
|
+
const existing = grouped.get(result.endpointId) ?? [];
|
|
3369
|
+
existing.push(result);
|
|
3370
|
+
grouped.set(result.endpointId, existing);
|
|
3371
|
+
}
|
|
3372
|
+
return grouped;
|
|
3373
|
+
}, [remoteResults]);
|
|
2851
3374
|
react.useEffect(() => {
|
|
2852
3375
|
if (isOpen) {
|
|
2853
3376
|
requestAnimationFrame(() => {
|
|
@@ -2855,6 +3378,38 @@ function Omnibar({
|
|
|
2855
3378
|
});
|
|
2856
3379
|
}
|
|
2857
3380
|
}, [isOpen]);
|
|
3381
|
+
react.useEffect(() => {
|
|
3382
|
+
if (!isOpen) return;
|
|
3383
|
+
const container = resultsContainerRef.current;
|
|
3384
|
+
if (!container) return;
|
|
3385
|
+
const observer = new IntersectionObserver(
|
|
3386
|
+
(entries) => {
|
|
3387
|
+
for (const entry of entries) {
|
|
3388
|
+
if (!entry.isIntersecting) continue;
|
|
3389
|
+
const endpointId = entry.target.dataset.endpointId;
|
|
3390
|
+
if (!endpointId) continue;
|
|
3391
|
+
const paginationInfo = endpointPagination.get(endpointId);
|
|
3392
|
+
if (!paginationInfo) continue;
|
|
3393
|
+
if (paginationInfo.mode !== "scroll") continue;
|
|
3394
|
+
if (!paginationInfo.hasMore) continue;
|
|
3395
|
+
if (paginationInfo.isLoading) continue;
|
|
3396
|
+
loadMore(endpointId);
|
|
3397
|
+
}
|
|
3398
|
+
},
|
|
3399
|
+
{
|
|
3400
|
+
root: container,
|
|
3401
|
+
rootMargin: "100px",
|
|
3402
|
+
// Trigger slightly before sentinel is visible
|
|
3403
|
+
threshold: 0
|
|
3404
|
+
}
|
|
3405
|
+
);
|
|
3406
|
+
for (const [_endpointId, sentinel] of sentinelRefs.current) {
|
|
3407
|
+
if (sentinel) {
|
|
3408
|
+
observer.observe(sentinel);
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
return () => observer.disconnect();
|
|
3412
|
+
}, [isOpen, endpointPagination, loadMore]);
|
|
2858
3413
|
react.useEffect(() => {
|
|
2859
3414
|
if (!isOpen) return;
|
|
2860
3415
|
const handleGlobalKeyDown = (e) => {
|
|
@@ -2904,7 +3459,12 @@ function Omnibar({
|
|
|
2904
3459
|
query,
|
|
2905
3460
|
setQuery,
|
|
2906
3461
|
results,
|
|
3462
|
+
remoteResults,
|
|
3463
|
+
isLoadingRemote,
|
|
3464
|
+
endpointPagination,
|
|
3465
|
+
loadMore,
|
|
2907
3466
|
selectedIndex,
|
|
3467
|
+
totalResults,
|
|
2908
3468
|
selectNext,
|
|
2909
3469
|
selectPrev,
|
|
2910
3470
|
execute,
|
|
@@ -2932,21 +3492,61 @@ function Omnibar({
|
|
|
2932
3492
|
spellCheck: false
|
|
2933
3493
|
}
|
|
2934
3494
|
),
|
|
2935
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-omnibar-results", children:
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
3495
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-omnibar-results", ref: resultsContainerRef, children: totalResults === 0 && !isLoadingRemote ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-omnibar-no-results", children: query ? "No matching commands" : "Start typing to search commands..." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3496
|
+
results.map((result, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3497
|
+
"div",
|
|
3498
|
+
{
|
|
3499
|
+
className: `kbd-omnibar-result ${i === selectedIndex ? "selected" : ""}`,
|
|
3500
|
+
onClick: () => execute(result.id),
|
|
3501
|
+
children: [
|
|
3502
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-label", children: result.action.label }),
|
|
3503
|
+
result.action.group && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-category", children: result.action.group }),
|
|
3504
|
+
result.bindings.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-omnibar-result-bindings", children: result.bindings.slice(0, 2).map((binding) => /* @__PURE__ */ jsxRuntime.jsx(BindingBadge, { binding }, binding)) })
|
|
3505
|
+
]
|
|
2941
3506
|
},
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
]
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
3507
|
+
result.id
|
|
3508
|
+
)),
|
|
3509
|
+
(() => {
|
|
3510
|
+
let remoteIndex = 0;
|
|
3511
|
+
return Array.from(remoteResultsByEndpoint.entries()).map(([endpointId, endpointResults]) => {
|
|
3512
|
+
const paginationInfo = endpointPagination.get(endpointId);
|
|
3513
|
+
const showPagination = paginationInfo?.mode === "scroll" && paginationInfo.total !== void 0;
|
|
3514
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
3515
|
+
endpointResults.map((result) => {
|
|
3516
|
+
const absoluteIndex = results.length + remoteIndex;
|
|
3517
|
+
remoteIndex++;
|
|
3518
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3519
|
+
"div",
|
|
3520
|
+
{
|
|
3521
|
+
className: `kbd-omnibar-result ${absoluteIndex === selectedIndex ? "selected" : ""}`,
|
|
3522
|
+
onClick: () => execute(result.id),
|
|
3523
|
+
children: [
|
|
3524
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-label", children: result.entry.label }),
|
|
3525
|
+
result.entry.group && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-category", children: result.entry.group }),
|
|
3526
|
+
result.entry.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-description", children: result.entry.description })
|
|
3527
|
+
]
|
|
3528
|
+
},
|
|
3529
|
+
result.id
|
|
3530
|
+
);
|
|
3531
|
+
}),
|
|
3532
|
+
paginationInfo?.mode === "scroll" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3533
|
+
"div",
|
|
3534
|
+
{
|
|
3535
|
+
className: "kbd-omnibar-pagination",
|
|
3536
|
+
ref: (el) => sentinelRefs.current.set(endpointId, el),
|
|
3537
|
+
"data-endpoint-id": endpointId,
|
|
3538
|
+
children: paginationInfo.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-pagination-loading", children: "Loading more..." }) : showPagination ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "kbd-omnibar-pagination-info", children: [
|
|
3539
|
+
paginationInfo.loaded,
|
|
3540
|
+
" of ",
|
|
3541
|
+
paginationInfo.total
|
|
3542
|
+
] }) : paginationInfo.hasMore ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-pagination-more", children: "Scroll for more..." }) : null
|
|
3543
|
+
}
|
|
3544
|
+
)
|
|
3545
|
+
] }, endpointId);
|
|
3546
|
+
});
|
|
3547
|
+
})(),
|
|
3548
|
+
isLoadingRemote && remoteResults.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-omnibar-loading", children: "Searching..." })
|
|
3549
|
+
] }) })
|
|
2950
3550
|
] }) });
|
|
2951
3551
|
}
|
|
2952
3552
|
function SequenceModal() {
|
|
@@ -2957,41 +3557,99 @@ function SequenceModal() {
|
|
|
2957
3557
|
sequenceTimeoutStartedAt: timeoutStartedAt,
|
|
2958
3558
|
sequenceTimeout,
|
|
2959
3559
|
getCompletions,
|
|
2960
|
-
registry
|
|
3560
|
+
registry,
|
|
3561
|
+
executeAction
|
|
2961
3562
|
} = useHotkeysContext();
|
|
3563
|
+
const [selectedIndex, setSelectedIndex] = react.useState(0);
|
|
3564
|
+
const [hasInteracted, setHasInteracted] = react.useState(false);
|
|
2962
3565
|
const completions = react.useMemo(() => {
|
|
2963
3566
|
if (pendingKeys.length === 0) return [];
|
|
2964
3567
|
return getCompletions(pendingKeys);
|
|
2965
3568
|
}, [getCompletions, pendingKeys]);
|
|
2966
|
-
const
|
|
2967
|
-
|
|
2968
|
-
return formatCombination(pendingKeys).display;
|
|
2969
|
-
}, [pendingKeys]);
|
|
2970
|
-
const getActionLabel = (actionId) => {
|
|
2971
|
-
const action = registry.actions.get(actionId);
|
|
2972
|
-
return action?.config.label || actionId;
|
|
2973
|
-
};
|
|
2974
|
-
const groupedCompletions = react.useMemo(() => {
|
|
2975
|
-
const byNextKey = /* @__PURE__ */ new Map();
|
|
3569
|
+
const flatCompletions = react.useMemo(() => {
|
|
3570
|
+
const items = [];
|
|
2976
3571
|
for (const c of completions) {
|
|
2977
|
-
const
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3572
|
+
for (const action of c.actions) {
|
|
3573
|
+
const displayKey = c.isComplete ? "\u21B5" : c.nextKeys;
|
|
3574
|
+
items.push({
|
|
3575
|
+
completion: c,
|
|
3576
|
+
action,
|
|
3577
|
+
displayKey,
|
|
3578
|
+
isComplete: c.isComplete
|
|
3579
|
+
});
|
|
2982
3580
|
}
|
|
2983
3581
|
}
|
|
2984
|
-
return
|
|
3582
|
+
return items;
|
|
2985
3583
|
}, [completions]);
|
|
3584
|
+
const itemCount = flatCompletions.length;
|
|
3585
|
+
const shouldShowTimeout = timeoutStartedAt !== null && completions.length === 1 && !hasInteracted;
|
|
3586
|
+
react.useEffect(() => {
|
|
3587
|
+
setSelectedIndex(0);
|
|
3588
|
+
setHasInteracted(false);
|
|
3589
|
+
}, [pendingKeys]);
|
|
3590
|
+
const executeSelected = react.useCallback(() => {
|
|
3591
|
+
if (selectedIndex >= 0 && selectedIndex < flatCompletions.length) {
|
|
3592
|
+
const item = flatCompletions[selectedIndex];
|
|
3593
|
+
executeAction(item.action, item.completion.captures);
|
|
3594
|
+
cancelSequence();
|
|
3595
|
+
}
|
|
3596
|
+
}, [selectedIndex, flatCompletions, executeAction, cancelSequence]);
|
|
3597
|
+
react.useEffect(() => {
|
|
3598
|
+
if (!isAwaitingSequence || pendingKeys.length === 0) return;
|
|
3599
|
+
const handleKeyDown = (e) => {
|
|
3600
|
+
switch (e.key) {
|
|
3601
|
+
case "ArrowDown":
|
|
3602
|
+
e.preventDefault();
|
|
3603
|
+
e.stopPropagation();
|
|
3604
|
+
setSelectedIndex((prev) => Math.min(prev + 1, itemCount - 1));
|
|
3605
|
+
setHasInteracted(true);
|
|
3606
|
+
break;
|
|
3607
|
+
case "ArrowUp":
|
|
3608
|
+
e.preventDefault();
|
|
3609
|
+
e.stopPropagation();
|
|
3610
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
3611
|
+
setHasInteracted(true);
|
|
3612
|
+
break;
|
|
3613
|
+
case "Enter":
|
|
3614
|
+
e.preventDefault();
|
|
3615
|
+
e.stopPropagation();
|
|
3616
|
+
executeSelected();
|
|
3617
|
+
break;
|
|
3618
|
+
}
|
|
3619
|
+
};
|
|
3620
|
+
document.addEventListener("keydown", handleKeyDown, true);
|
|
3621
|
+
return () => document.removeEventListener("keydown", handleKeyDown, true);
|
|
3622
|
+
}, [isAwaitingSequence, pendingKeys.length, itemCount, executeSelected]);
|
|
3623
|
+
const renderKey = react.useCallback((combo, index) => {
|
|
3624
|
+
const { key, modifiers } = combo;
|
|
3625
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("kbd", { className: "kbd-kbd", children: [
|
|
3626
|
+
renderModifierIcons(modifiers),
|
|
3627
|
+
renderKeyContent(key)
|
|
3628
|
+
] }, index);
|
|
3629
|
+
}, []);
|
|
3630
|
+
const getActionLabel = (actionId, captures) => {
|
|
3631
|
+
const action = registry.actions.get(actionId);
|
|
3632
|
+
let label = action?.config.label || actionId;
|
|
3633
|
+
if (captures && captures.length > 0) {
|
|
3634
|
+
let captureIdx = 0;
|
|
3635
|
+
label = label.replace(/\bN\b/g, () => {
|
|
3636
|
+
if (captureIdx < captures.length) {
|
|
3637
|
+
return String(captures[captureIdx++]);
|
|
3638
|
+
}
|
|
3639
|
+
return "N";
|
|
3640
|
+
});
|
|
3641
|
+
}
|
|
3642
|
+
return label;
|
|
3643
|
+
};
|
|
2986
3644
|
if (!isAwaitingSequence || pendingKeys.length === 0) {
|
|
2987
3645
|
return null;
|
|
2988
3646
|
}
|
|
2989
3647
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-backdrop", onClick: cancelSequence, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-sequence", onClick: (e) => e.stopPropagation(), children: [
|
|
2990
3648
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-sequence-current", children: [
|
|
2991
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
3649
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-keys", children: pendingKeys.map((combo, i) => renderKey(combo, i)) }),
|
|
2992
3650
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-ellipsis", children: "\u2026" })
|
|
2993
3651
|
] }),
|
|
2994
|
-
|
|
3652
|
+
shouldShowTimeout && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2995
3653
|
"div",
|
|
2996
3654
|
{
|
|
2997
3655
|
className: "kbd-sequence-timeout",
|
|
@@ -2999,15 +3657,19 @@ function SequenceModal() {
|
|
|
2999
3657
|
},
|
|
3000
3658
|
timeoutStartedAt
|
|
3001
3659
|
),
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3660
|
+
flatCompletions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-completions", children: flatCompletions.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3661
|
+
"div",
|
|
3662
|
+
{
|
|
3663
|
+
className: `kbd-sequence-completion ${index === selectedIndex ? "selected" : ""} ${item.isComplete ? "complete" : ""}`,
|
|
3664
|
+
children: [
|
|
3665
|
+
item.isComplete ? /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "kbd-kbd", children: "\u21B5" }) : item.completion.nextKeySeq ? renderKeySeq(item.completion.nextKeySeq) : /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "kbd-kbd", children: item.displayKey }),
|
|
3666
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-arrow", children: "\u2192" }),
|
|
3667
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-actions", children: getActionLabel(item.action, item.completion.captures) })
|
|
3668
|
+
]
|
|
3669
|
+
},
|
|
3670
|
+
`${item.completion.fullSequence}-${item.action}`
|
|
3671
|
+
)) }),
|
|
3672
|
+
flatCompletions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-empty", children: "No matching shortcuts" })
|
|
3011
3673
|
] }) });
|
|
3012
3674
|
}
|
|
3013
3675
|
var DefaultTooltip = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
@@ -3030,6 +3692,7 @@ function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder,
|
|
|
3030
3692
|
return groupNames?.[groupKey] ?? groupKey;
|
|
3031
3693
|
};
|
|
3032
3694
|
for (const [actionId, bindings] of actionBindings) {
|
|
3695
|
+
if (actionRegistry?.[actionId]?.hideFromModal) continue;
|
|
3033
3696
|
includedActions.add(actionId);
|
|
3034
3697
|
const { name } = parseActionId(actionId);
|
|
3035
3698
|
const groupName = getGroupName(actionId);
|
|
@@ -3046,6 +3709,7 @@ function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder,
|
|
|
3046
3709
|
if (actionRegistry && showUnbound) {
|
|
3047
3710
|
for (const [actionId, action] of Object.entries(actionRegistry)) {
|
|
3048
3711
|
if (includedActions.has(actionId)) continue;
|
|
3712
|
+
if (action.hideFromModal) continue;
|
|
3049
3713
|
const { name } = parseActionId(actionId);
|
|
3050
3714
|
const groupName = getGroupName(actionId);
|
|
3051
3715
|
if (!groupMap.has(groupName)) {
|
|
@@ -3086,27 +3750,10 @@ function KeyDisplay({
|
|
|
3086
3750
|
combo,
|
|
3087
3751
|
className
|
|
3088
3752
|
}) {
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
}
|
|
3094
|
-
if (modifiers.ctrl) {
|
|
3095
|
-
parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }, "ctrl"));
|
|
3096
|
-
}
|
|
3097
|
-
if (modifiers.alt) {
|
|
3098
|
-
parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }, "alt"));
|
|
3099
|
-
}
|
|
3100
|
-
if (modifiers.shift) {
|
|
3101
|
-
parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }, "shift"));
|
|
3102
|
-
}
|
|
3103
|
-
const KeyIcon = getKeyIcon(key);
|
|
3104
|
-
if (KeyIcon) {
|
|
3105
|
-
parts.push(/* @__PURE__ */ jsxRuntime.jsx(KeyIcon, { className: "kbd-key-icon" }, "key"));
|
|
3106
|
-
} else {
|
|
3107
|
-
parts.push(/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatKeyForDisplay(key) }, "key"));
|
|
3108
|
-
}
|
|
3109
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: parts });
|
|
3753
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className, children: [
|
|
3754
|
+
renderModifierIcons(combo.modifiers),
|
|
3755
|
+
renderKeyContent(combo.key)
|
|
3756
|
+
] });
|
|
3110
3757
|
}
|
|
3111
3758
|
function SeqElemDisplay2({ elem, className }) {
|
|
3112
3759
|
const Tooltip = react.useContext(TooltipContext);
|
|
@@ -3134,7 +3781,6 @@ function BindingDisplay2({
|
|
|
3134
3781
|
}) {
|
|
3135
3782
|
const sequence = parseHotkeyString(binding);
|
|
3136
3783
|
const keySeq = parseKeySeq(binding);
|
|
3137
|
-
formatKeySeq(keySeq);
|
|
3138
3784
|
let kbdClassName = "kbd-kbd";
|
|
3139
3785
|
if (editable && !isEditing) kbdClassName += " editable";
|
|
3140
3786
|
if (isEditing) kbdClassName += " editing";
|
|
@@ -3307,18 +3953,17 @@ function ShortcutsModal({
|
|
|
3307
3953
|
ctx.closeModal();
|
|
3308
3954
|
}
|
|
3309
3955
|
}, [onCloseProp, ctx]);
|
|
3310
|
-
react.useCallback(() => {
|
|
3311
|
-
if (ctx?.openModal) {
|
|
3312
|
-
ctx.openModal();
|
|
3313
|
-
} else {
|
|
3314
|
-
setInternalIsOpen(true);
|
|
3315
|
-
}
|
|
3316
|
-
}, [ctx]);
|
|
3317
3956
|
useAction(ACTION_MODAL, {
|
|
3318
3957
|
label: "Show shortcuts",
|
|
3319
3958
|
group: "Global",
|
|
3320
3959
|
defaultBindings: defaultBinding ? [defaultBinding] : [],
|
|
3321
|
-
handler: react.useCallback(() =>
|
|
3960
|
+
handler: react.useCallback(() => {
|
|
3961
|
+
if (ctx) {
|
|
3962
|
+
ctx.toggleModal();
|
|
3963
|
+
} else {
|
|
3964
|
+
setInternalIsOpen((prev) => !prev);
|
|
3965
|
+
}
|
|
3966
|
+
}, [ctx])
|
|
3322
3967
|
});
|
|
3323
3968
|
const checkConflict = react.useCallback((newKey, forAction) => {
|
|
3324
3969
|
const existingActions = keymap[newKey];
|
|
@@ -3787,6 +4432,7 @@ exports.Left = Left;
|
|
|
3787
4432
|
exports.LookupModal = LookupModal;
|
|
3788
4433
|
exports.ModifierIcon = ModifierIcon;
|
|
3789
4434
|
exports.Omnibar = Omnibar;
|
|
4435
|
+
exports.OmnibarEndpointsRegistryContext = OmnibarEndpointsRegistryContext;
|
|
3790
4436
|
exports.Option = Option;
|
|
3791
4437
|
exports.Right = Right;
|
|
3792
4438
|
exports.SequenceModal = SequenceModal;
|
|
@@ -3818,7 +4464,6 @@ exports.isSequence = isSequence;
|
|
|
3818
4464
|
exports.isShiftedSymbol = isShiftedSymbol;
|
|
3819
4465
|
exports.keySeqToHotkeySequence = keySeqToHotkeySequence;
|
|
3820
4466
|
exports.normalizeKey = normalizeKey;
|
|
3821
|
-
exports.parseCombinationId = parseCombinationId;
|
|
3822
4467
|
exports.parseHotkeyString = parseHotkeyString;
|
|
3823
4468
|
exports.parseKeySeq = parseKeySeq;
|
|
3824
4469
|
exports.searchActions = searchActions;
|
|
@@ -3830,6 +4475,8 @@ exports.useHotkeys = useHotkeys;
|
|
|
3830
4475
|
exports.useHotkeysContext = useHotkeysContext;
|
|
3831
4476
|
exports.useMaybeHotkeysContext = useMaybeHotkeysContext;
|
|
3832
4477
|
exports.useOmnibar = useOmnibar;
|
|
4478
|
+
exports.useOmnibarEndpoint = useOmnibarEndpoint;
|
|
4479
|
+
exports.useOmnibarEndpointsRegistry = useOmnibarEndpointsRegistry;
|
|
3833
4480
|
exports.useRecordHotkey = useRecordHotkey;
|
|
3834
4481
|
//# sourceMappingURL=index.cjs.map
|
|
3835
4482
|
//# sourceMappingURL=index.cjs.map
|