use-kbd 0.4.0 → 0.6.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 +884 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +288 -33
- package/dist/index.d.ts +288 -33
- package/dist/index.js +882 -166
- 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);
|
|
@@ -182,7 +178,8 @@ function useActionsRegistry(options = {}) {
|
|
|
182
178
|
registry[id] = {
|
|
183
179
|
label: config.label,
|
|
184
180
|
group: config.group,
|
|
185
|
-
keywords: config.keywords
|
|
181
|
+
keywords: config.keywords,
|
|
182
|
+
hideFromModal: config.hideFromModal
|
|
186
183
|
};
|
|
187
184
|
}
|
|
188
185
|
return registry;
|
|
@@ -283,6 +280,73 @@ function useActionsRegistry(options = {}) {
|
|
|
283
280
|
resetOverrides
|
|
284
281
|
]);
|
|
285
282
|
}
|
|
283
|
+
var OmnibarEndpointsRegistryContext = react.createContext(null);
|
|
284
|
+
function useOmnibarEndpointsRegistry() {
|
|
285
|
+
const endpointsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
286
|
+
const [endpointsVersion, setEndpointsVersion] = react.useState(0);
|
|
287
|
+
const register = react.useCallback((id, config) => {
|
|
288
|
+
endpointsRef.current.set(id, {
|
|
289
|
+
id,
|
|
290
|
+
config,
|
|
291
|
+
registeredAt: Date.now()
|
|
292
|
+
});
|
|
293
|
+
setEndpointsVersion((v) => v + 1);
|
|
294
|
+
}, []);
|
|
295
|
+
const unregister = react.useCallback((id) => {
|
|
296
|
+
endpointsRef.current.delete(id);
|
|
297
|
+
setEndpointsVersion((v) => v + 1);
|
|
298
|
+
}, []);
|
|
299
|
+
const queryEndpoint = react.useCallback(async (endpointId, query, pagination, signal) => {
|
|
300
|
+
const ep = endpointsRef.current.get(endpointId);
|
|
301
|
+
if (!ep) 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 filteredByMinQuery = endpoints2.filter((ep) => {
|
|
329
|
+
const minLen = ep.config.minQueryLength ?? 2;
|
|
330
|
+
return query.length >= minLen;
|
|
331
|
+
});
|
|
332
|
+
const promises = filteredByMinQuery.map(async (ep) => {
|
|
333
|
+
const pageSize = ep.config.pageSize ?? 10;
|
|
334
|
+
const result = await queryEndpoint(ep.id, query, { offset: 0, limit: pageSize }, signal);
|
|
335
|
+
return result ?? { endpointId: ep.id, entries: [] };
|
|
336
|
+
});
|
|
337
|
+
return Promise.all(promises);
|
|
338
|
+
}, [queryEndpoint]);
|
|
339
|
+
const endpoints = react.useMemo(() => {
|
|
340
|
+
return new Map(endpointsRef.current);
|
|
341
|
+
}, [endpointsVersion]);
|
|
342
|
+
return react.useMemo(() => ({
|
|
343
|
+
register,
|
|
344
|
+
unregister,
|
|
345
|
+
endpoints,
|
|
346
|
+
queryAll,
|
|
347
|
+
queryEndpoint
|
|
348
|
+
}), [register, unregister, endpoints, queryAll, queryEndpoint]);
|
|
349
|
+
}
|
|
286
350
|
|
|
287
351
|
// src/constants.ts
|
|
288
352
|
var DEFAULT_SEQUENCE_TIMEOUT = Infinity;
|
|
@@ -320,7 +384,11 @@ function isShiftedSymbol(key) {
|
|
|
320
384
|
}
|
|
321
385
|
function isMac() {
|
|
322
386
|
if (typeof navigator === "undefined") return false;
|
|
323
|
-
|
|
387
|
+
const platform = navigator.userAgentData?.platform;
|
|
388
|
+
if (platform) {
|
|
389
|
+
return platform === "macOS" || platform === "iOS";
|
|
390
|
+
}
|
|
391
|
+
return /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
324
392
|
}
|
|
325
393
|
function normalizeKey(key) {
|
|
326
394
|
const keyMap = {
|
|
@@ -355,7 +423,7 @@ function formatKeyForDisplay(key) {
|
|
|
355
423
|
"space": "Space",
|
|
356
424
|
"escape": "Esc",
|
|
357
425
|
"enter": "\u21B5",
|
|
358
|
-
"tab": "
|
|
426
|
+
"tab": "\u21E5",
|
|
359
427
|
"backspace": "\u232B",
|
|
360
428
|
"delete": "Del",
|
|
361
429
|
"arrowup": "\u2191",
|
|
@@ -495,13 +563,6 @@ function parseHotkeyString(hotkeyStr) {
|
|
|
495
563
|
const parts = hotkeyStr.trim().split(/\s+/);
|
|
496
564
|
return parts.map(parseSingleCombination);
|
|
497
565
|
}
|
|
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
566
|
var NO_MODIFIERS = { ctrl: false, alt: false, shift: false, meta: false };
|
|
506
567
|
function parseSeqElem(str) {
|
|
507
568
|
if (str === "\\d") {
|
|
@@ -617,6 +678,13 @@ function isPrefix(a, b) {
|
|
|
617
678
|
function combinationsEqual(a, b) {
|
|
618
679
|
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
680
|
}
|
|
681
|
+
function keyMatchesPattern(pending, pattern) {
|
|
682
|
+
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) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
if (pending.key === pattern.key) return true;
|
|
686
|
+
return /^[0-9]$/.test(pending.key) && (pattern.key === DIGIT_PLACEHOLDER || pattern.key === DIGITS_PLACEHOLDER);
|
|
687
|
+
}
|
|
620
688
|
function isDigitKey(key) {
|
|
621
689
|
return /^[0-9]$/.test(key);
|
|
622
690
|
}
|
|
@@ -737,28 +805,77 @@ function getSequenceCompletions(pendingKeys, keymap) {
|
|
|
737
805
|
if (pendingKeys.length === 0) return [];
|
|
738
806
|
const completions = [];
|
|
739
807
|
for (const [hotkeyStr, actionOrActions] of Object.entries(keymap)) {
|
|
740
|
-
const sequence = parseHotkeyString(hotkeyStr);
|
|
741
808
|
const keySeq = parseKeySeq(hotkeyStr);
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
809
|
+
const hasDigitsPlaceholder = keySeq.some((e) => e.type === "digits");
|
|
810
|
+
if (!hasDigitsPlaceholder && keySeq.length < pendingKeys.length) continue;
|
|
811
|
+
let keySeqIdx = 0;
|
|
812
|
+
let pendingIdx = 0;
|
|
813
|
+
let isMatch = true;
|
|
814
|
+
const captures = [];
|
|
815
|
+
let currentDigits = "";
|
|
816
|
+
for (; pendingIdx < pendingKeys.length && keySeqIdx < keySeq.length; pendingIdx++) {
|
|
817
|
+
const elem = keySeq[keySeqIdx];
|
|
818
|
+
if (elem.type === "digits") {
|
|
819
|
+
if (!/^[0-9]$/.test(pendingKeys[pendingIdx].key)) {
|
|
820
|
+
isMatch = false;
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
currentDigits += pendingKeys[pendingIdx].key;
|
|
824
|
+
if (pendingIdx + 1 < pendingKeys.length && /^[0-9]$/.test(pendingKeys[pendingIdx + 1].key)) {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
captures.push(parseInt(currentDigits, 10));
|
|
828
|
+
currentDigits = "";
|
|
829
|
+
keySeqIdx++;
|
|
830
|
+
} else if (elem.type === "digit") {
|
|
831
|
+
if (!/^[0-9]$/.test(pendingKeys[pendingIdx].key)) {
|
|
832
|
+
isMatch = false;
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
captures.push(parseInt(pendingKeys[pendingIdx].key, 10));
|
|
836
|
+
keySeqIdx++;
|
|
837
|
+
} else {
|
|
838
|
+
const keyElem = elem;
|
|
839
|
+
const targetCombo = { key: keyElem.key, modifiers: keyElem.modifiers };
|
|
840
|
+
if (!keyMatchesPattern(pendingKeys[pendingIdx], targetCombo)) {
|
|
841
|
+
isMatch = false;
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
keySeqIdx++;
|
|
748
845
|
}
|
|
749
846
|
}
|
|
750
|
-
if (
|
|
751
|
-
|
|
847
|
+
if (pendingIdx < pendingKeys.length) {
|
|
848
|
+
isMatch = false;
|
|
849
|
+
}
|
|
850
|
+
if (!isMatch) continue;
|
|
851
|
+
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
852
|
+
if (keySeqIdx === keySeq.length) {
|
|
853
|
+
completions.push({
|
|
854
|
+
nextKeys: "",
|
|
855
|
+
fullSequence: hotkeyStr,
|
|
856
|
+
display: formatKeySeq(keySeq),
|
|
857
|
+
actions,
|
|
858
|
+
isComplete: true,
|
|
859
|
+
captures: captures.length > 0 ? captures : void 0
|
|
860
|
+
});
|
|
861
|
+
} else {
|
|
862
|
+
const remainingKeySeq = keySeq.slice(keySeqIdx);
|
|
752
863
|
const nextKeys = formatKeySeq(remainingKeySeq).display;
|
|
753
|
-
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
|
|
754
864
|
completions.push({
|
|
755
865
|
nextKeys,
|
|
866
|
+
nextKeySeq: remainingKeySeq,
|
|
756
867
|
fullSequence: hotkeyStr,
|
|
757
868
|
display: formatKeySeq(keySeq),
|
|
758
|
-
actions
|
|
869
|
+
actions,
|
|
870
|
+
isComplete: false,
|
|
871
|
+
captures: captures.length > 0 ? captures : void 0
|
|
759
872
|
});
|
|
760
873
|
}
|
|
761
874
|
}
|
|
875
|
+
completions.sort((a, b) => {
|
|
876
|
+
if (a.isComplete !== b.isComplete) return a.isComplete ? -1 : 1;
|
|
877
|
+
return a.fullSequence.localeCompare(b.fullSequence);
|
|
878
|
+
});
|
|
762
879
|
return completions;
|
|
763
880
|
}
|
|
764
881
|
function getActionBindings(keymap) {
|
|
@@ -849,7 +966,7 @@ function searchActions(query, actions, keymap) {
|
|
|
849
966
|
}
|
|
850
967
|
const matched = labelMatch.matched || descMatch.matched || groupMatch.matched || idMatch.matched || keywordScore > 0;
|
|
851
968
|
if (!matched && query) continue;
|
|
852
|
-
const score = (labelMatch.matched ? labelMatch.score * 3 : 0) + (descMatch.matched ? descMatch.score * 1.5 : 0) + (groupMatch.matched ? groupMatch.score
|
|
969
|
+
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
970
|
results.push({
|
|
854
971
|
id,
|
|
855
972
|
action,
|
|
@@ -927,6 +1044,9 @@ function advanceMatchState(state, pattern, combo) {
|
|
|
927
1044
|
const digitValue = parseInt(elem.partial, 10);
|
|
928
1045
|
newState[i] = { type: "digits", value: digitValue };
|
|
929
1046
|
pos = i + 1;
|
|
1047
|
+
if (pos >= pattern.length) {
|
|
1048
|
+
return { status: "failed" };
|
|
1049
|
+
}
|
|
930
1050
|
break;
|
|
931
1051
|
}
|
|
932
1052
|
}
|
|
@@ -1049,7 +1169,7 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1049
1169
|
}
|
|
1050
1170
|
return false;
|
|
1051
1171
|
}, [preventDefault, stopPropagation]);
|
|
1052
|
-
const tryExecuteKeySeq = react.useCallback((matchKey,
|
|
1172
|
+
const tryExecuteKeySeq = react.useCallback((matchKey, captures, e) => {
|
|
1053
1173
|
for (const entry of parsedKeymapRef.current) {
|
|
1054
1174
|
if (entry.key === matchKey) {
|
|
1055
1175
|
for (const action of entry.actions) {
|
|
@@ -1105,7 +1225,24 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1105
1225
|
}
|
|
1106
1226
|
if (e.key === "Enter" && pendingKeysRef.current.length > 0) {
|
|
1107
1227
|
e.preventDefault();
|
|
1108
|
-
|
|
1228
|
+
let executed = false;
|
|
1229
|
+
for (const [key, state] of matchStatesRef.current.entries()) {
|
|
1230
|
+
const finalizedState = isCollectingDigits(state) ? finalizeDigits(state) : state;
|
|
1231
|
+
const isComplete = finalizedState.every((elem) => {
|
|
1232
|
+
if (elem.type === "key") return elem.matched === true;
|
|
1233
|
+
if (elem.type === "digit") return elem.value !== void 0;
|
|
1234
|
+
if (elem.type === "digits") return elem.value !== void 0;
|
|
1235
|
+
return false;
|
|
1236
|
+
});
|
|
1237
|
+
if (isComplete) {
|
|
1238
|
+
const captures = extractMatchCaptures(finalizedState);
|
|
1239
|
+
executed = tryExecuteKeySeq(key, captures, e);
|
|
1240
|
+
if (executed) break;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (!executed) {
|
|
1244
|
+
executed = tryExecute(pendingKeysRef.current, e);
|
|
1245
|
+
}
|
|
1109
1246
|
clearPending();
|
|
1110
1247
|
if (!executed) {
|
|
1111
1248
|
onSequenceCancel?.();
|
|
@@ -1118,14 +1255,57 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1118
1255
|
return;
|
|
1119
1256
|
}
|
|
1120
1257
|
const currentCombo = eventToCombination(e);
|
|
1258
|
+
if (e.key === "Backspace" && pendingKeysRef.current.length > 0) {
|
|
1259
|
+
let backspaceMatches = false;
|
|
1260
|
+
for (const entry of parsedKeymapRef.current) {
|
|
1261
|
+
let state = matchStatesRef.current.get(entry.key);
|
|
1262
|
+
if (!state) {
|
|
1263
|
+
state = initMatchState(entry.keySeq);
|
|
1264
|
+
}
|
|
1265
|
+
if (isCollectingDigits(state)) {
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
const result = advanceMatchState(state, entry.keySeq, currentCombo);
|
|
1269
|
+
if (result.status === "matched" || result.status === "partial") {
|
|
1270
|
+
backspaceMatches = true;
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (!backspaceMatches) {
|
|
1275
|
+
e.preventDefault();
|
|
1276
|
+
const newPending = pendingKeysRef.current.slice(0, -1);
|
|
1277
|
+
if (newPending.length === 0) {
|
|
1278
|
+
clearPending();
|
|
1279
|
+
onSequenceCancel?.();
|
|
1280
|
+
} else {
|
|
1281
|
+
setPendingKeys(newPending);
|
|
1282
|
+
matchStatesRef.current.clear();
|
|
1283
|
+
for (const combo of newPending) {
|
|
1284
|
+
for (const entry of parsedKeymapRef.current) {
|
|
1285
|
+
let state = matchStatesRef.current.get(entry.key);
|
|
1286
|
+
if (!state) {
|
|
1287
|
+
state = initMatchState(entry.keySeq);
|
|
1288
|
+
}
|
|
1289
|
+
const result = advanceMatchState(state, entry.keySeq, combo);
|
|
1290
|
+
if (result.status === "partial") {
|
|
1291
|
+
matchStatesRef.current.set(entry.key, result.state);
|
|
1292
|
+
} else {
|
|
1293
|
+
matchStatesRef.current.delete(entry.key);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1121
1301
|
const newSequence = [...pendingKeysRef.current, currentCombo];
|
|
1122
|
-
|
|
1123
|
-
let
|
|
1302
|
+
const completeMatches = [];
|
|
1303
|
+
let hasPartials = false;
|
|
1124
1304
|
const matchStates = matchStatesRef.current;
|
|
1125
|
-
const
|
|
1305
|
+
const hadPartialMatches = matchStates.size > 0;
|
|
1126
1306
|
for (const entry of parsedKeymapRef.current) {
|
|
1127
1307
|
let state = matchStates.get(entry.key);
|
|
1128
|
-
if (
|
|
1308
|
+
if (hadPartialMatches && !state) {
|
|
1129
1309
|
continue;
|
|
1130
1310
|
}
|
|
1131
1311
|
if (!state) {
|
|
@@ -1134,22 +1314,27 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1134
1314
|
}
|
|
1135
1315
|
const result = advanceMatchState(state, entry.keySeq, currentCombo);
|
|
1136
1316
|
if (result.status === "matched") {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1317
|
+
completeMatches.push({
|
|
1318
|
+
key: entry.key,
|
|
1319
|
+
state: result.state,
|
|
1320
|
+
captures: result.captures
|
|
1321
|
+
});
|
|
1322
|
+
matchStates.delete(entry.key);
|
|
1142
1323
|
} else if (result.status === "partial") {
|
|
1143
1324
|
matchStates.set(entry.key, result.state);
|
|
1144
|
-
|
|
1325
|
+
hasPartials = true;
|
|
1145
1326
|
} else {
|
|
1146
1327
|
matchStates.delete(entry.key);
|
|
1147
1328
|
}
|
|
1148
1329
|
}
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1330
|
+
if (completeMatches.length === 1 && !hasPartials) {
|
|
1331
|
+
const match = completeMatches[0];
|
|
1332
|
+
if (tryExecuteKeySeq(match.key, match.captures, e)) {
|
|
1333
|
+
clearPending();
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1151
1336
|
}
|
|
1152
|
-
if (
|
|
1337
|
+
if (completeMatches.length > 0 || hasPartials) {
|
|
1153
1338
|
setPendingKeys(newSequence);
|
|
1154
1339
|
setIsAwaitingSequence(true);
|
|
1155
1340
|
if (pendingKeysRef.current.length === 0) {
|
|
@@ -1232,8 +1417,11 @@ function useHotkeys(keymap, handlers, options = {}) {
|
|
|
1232
1417
|
}
|
|
1233
1418
|
}
|
|
1234
1419
|
if (pendingKeysRef.current.length > 0) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1420
|
+
setPendingKeys(newSequence);
|
|
1421
|
+
if (preventDefault) {
|
|
1422
|
+
e.preventDefault();
|
|
1423
|
+
}
|
|
1424
|
+
return;
|
|
1237
1425
|
}
|
|
1238
1426
|
const singleMatch = tryExecute([currentCombo], e);
|
|
1239
1427
|
if (!singleMatch) {
|
|
@@ -1295,7 +1483,8 @@ var HotkeysContext = react.createContext(null);
|
|
|
1295
1483
|
var DEFAULT_CONFIG = {
|
|
1296
1484
|
storageKey: "use-kbd",
|
|
1297
1485
|
sequenceTimeout: DEFAULT_SEQUENCE_TIMEOUT,
|
|
1298
|
-
disableConflicts:
|
|
1486
|
+
disableConflicts: false,
|
|
1487
|
+
// Keep conflicting bindings active; SeqM handles disambiguation
|
|
1299
1488
|
minViewportWidth: 768,
|
|
1300
1489
|
enableOnTouch: false
|
|
1301
1490
|
};
|
|
@@ -1308,6 +1497,7 @@ function HotkeysProvider({
|
|
|
1308
1497
|
...configProp
|
|
1309
1498
|
}), [configProp]);
|
|
1310
1499
|
const registry = useActionsRegistry({ storageKey: config.storageKey });
|
|
1500
|
+
const endpointsRegistry = useOmnibarEndpointsRegistry();
|
|
1311
1501
|
const [isEnabled, setIsEnabled] = react.useState(true);
|
|
1312
1502
|
react.useEffect(() => {
|
|
1313
1503
|
if (typeof window === "undefined") return;
|
|
@@ -1399,6 +1589,7 @@ function HotkeysProvider({
|
|
|
1399
1589
|
);
|
|
1400
1590
|
const value = react.useMemo(() => ({
|
|
1401
1591
|
registry,
|
|
1592
|
+
endpointsRegistry,
|
|
1402
1593
|
isEnabled,
|
|
1403
1594
|
isModalOpen,
|
|
1404
1595
|
openModal,
|
|
@@ -1426,6 +1617,7 @@ function HotkeysProvider({
|
|
|
1426
1617
|
getCompletions
|
|
1427
1618
|
}), [
|
|
1428
1619
|
registry,
|
|
1620
|
+
endpointsRegistry,
|
|
1429
1621
|
isEnabled,
|
|
1430
1622
|
isModalOpen,
|
|
1431
1623
|
openModal,
|
|
@@ -1450,7 +1642,7 @@ function HotkeysProvider({
|
|
|
1450
1642
|
searchActionsHelper,
|
|
1451
1643
|
getCompletions
|
|
1452
1644
|
]);
|
|
1453
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ActionsRegistryContext.Provider, { value: registry, children: /* @__PURE__ */ jsxRuntime.jsx(HotkeysContext.Provider, { value, children }) });
|
|
1645
|
+
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
1646
|
}
|
|
1455
1647
|
function useHotkeysContext() {
|
|
1456
1648
|
const context = react.useContext(HotkeysContext);
|
|
@@ -1538,6 +1730,56 @@ function useActions(actions) {
|
|
|
1538
1730
|
)
|
|
1539
1731
|
]);
|
|
1540
1732
|
}
|
|
1733
|
+
function useOmnibarEndpoint(id, config) {
|
|
1734
|
+
const registry = react.useContext(OmnibarEndpointsRegistryContext);
|
|
1735
|
+
if (!registry) {
|
|
1736
|
+
throw new Error("useOmnibarEndpoint must be used within a HotkeysProvider");
|
|
1737
|
+
}
|
|
1738
|
+
const registryRef = react.useRef(registry);
|
|
1739
|
+
registryRef.current = registry;
|
|
1740
|
+
const isSync = "filter" in config && config.filter !== void 0;
|
|
1741
|
+
const fetchFn = isSync ? void 0 : config.fetch;
|
|
1742
|
+
const filterFn = isSync ? config.filter : void 0;
|
|
1743
|
+
const fetchRef = react.useRef(fetchFn);
|
|
1744
|
+
fetchRef.current = fetchFn;
|
|
1745
|
+
const filterRef = react.useRef(filterFn);
|
|
1746
|
+
filterRef.current = filterFn;
|
|
1747
|
+
const isSyncRef = react.useRef(isSync);
|
|
1748
|
+
isSyncRef.current = isSync;
|
|
1749
|
+
const enabledRef = react.useRef(config.enabled ?? true);
|
|
1750
|
+
enabledRef.current = config.enabled ?? true;
|
|
1751
|
+
react.useEffect(() => {
|
|
1752
|
+
const asyncConfig = {
|
|
1753
|
+
group: config.group,
|
|
1754
|
+
priority: config.priority,
|
|
1755
|
+
minQueryLength: config.minQueryLength,
|
|
1756
|
+
enabled: config.enabled,
|
|
1757
|
+
pageSize: config.pageSize,
|
|
1758
|
+
pagination: config.pagination,
|
|
1759
|
+
isSync: isSyncRef.current,
|
|
1760
|
+
// Track sync endpoints to skip debouncing
|
|
1761
|
+
fetch: async (query, signal, pagination) => {
|
|
1762
|
+
if (!enabledRef.current) return { entries: [] };
|
|
1763
|
+
if (isSyncRef.current && filterRef.current) {
|
|
1764
|
+
return filterRef.current(query, pagination);
|
|
1765
|
+
}
|
|
1766
|
+
return fetchRef.current(query, signal, pagination);
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
registryRef.current.register(id, asyncConfig);
|
|
1770
|
+
return () => {
|
|
1771
|
+
registryRef.current.unregister(id);
|
|
1772
|
+
};
|
|
1773
|
+
}, [
|
|
1774
|
+
id,
|
|
1775
|
+
config.group,
|
|
1776
|
+
config.priority,
|
|
1777
|
+
config.minQueryLength,
|
|
1778
|
+
config.pageSize,
|
|
1779
|
+
config.pagination
|
|
1780
|
+
// Note: we use refs for fetch/filter and enabled, so they don't cause re-registration
|
|
1781
|
+
]);
|
|
1782
|
+
}
|
|
1541
1783
|
function useEventCallback(fn) {
|
|
1542
1784
|
const ref = react.useRef(fn);
|
|
1543
1785
|
ref.current = fn;
|
|
@@ -1786,7 +2028,6 @@ function useRecordHotkey(options = {}) {
|
|
|
1786
2028
|
};
|
|
1787
2029
|
}, [isRecording, preventDefault, sequenceTimeout, clearTimeout_, submit, cancel, onCapture, onTab, onShiftTab]);
|
|
1788
2030
|
const display = sequence ? formatCombination(sequence) : null;
|
|
1789
|
-
const combination = sequence && sequence.length > 0 ? sequence[0] : null;
|
|
1790
2031
|
return {
|
|
1791
2032
|
isRecording,
|
|
1792
2033
|
startRecording,
|
|
@@ -1796,13 +2037,11 @@ function useRecordHotkey(options = {}) {
|
|
|
1796
2037
|
display,
|
|
1797
2038
|
pendingKeys,
|
|
1798
2039
|
activeKeys,
|
|
1799
|
-
sequenceTimeout
|
|
1800
|
-
combination
|
|
1801
|
-
// deprecated
|
|
2040
|
+
sequenceTimeout
|
|
1802
2041
|
};
|
|
1803
2042
|
}
|
|
1804
2043
|
function useEditableHotkeys(defaults, handlers, options = {}) {
|
|
1805
|
-
const { storageKey, disableConflicts =
|
|
2044
|
+
const { storageKey, disableConflicts = false, ...hotkeyOptions } = options;
|
|
1806
2045
|
const [overrides, setOverrides] = react.useState(() => {
|
|
1807
2046
|
if (!storageKey || typeof window === "undefined") return {};
|
|
1808
2047
|
try {
|
|
@@ -1898,6 +2137,7 @@ function useEditableHotkeys(defaults, handlers, options = {}) {
|
|
|
1898
2137
|
};
|
|
1899
2138
|
}
|
|
1900
2139
|
var { max: max2, min } = Math;
|
|
2140
|
+
var DEFAULT_DEBOUNCE_MS = 150;
|
|
1901
2141
|
function useOmnibar(options) {
|
|
1902
2142
|
const {
|
|
1903
2143
|
actions,
|
|
@@ -1906,17 +2146,27 @@ function useOmnibar(options) {
|
|
|
1906
2146
|
openKey = "meta+k",
|
|
1907
2147
|
enabled = true,
|
|
1908
2148
|
onExecute,
|
|
2149
|
+
onExecuteRemote,
|
|
1909
2150
|
onOpen,
|
|
1910
2151
|
onClose,
|
|
1911
|
-
maxResults = 10
|
|
2152
|
+
maxResults = 10,
|
|
2153
|
+
endpointsRegistry,
|
|
2154
|
+
debounceMs = DEFAULT_DEBOUNCE_MS
|
|
1912
2155
|
} = options;
|
|
1913
2156
|
const [isOpen, setIsOpen] = react.useState(false);
|
|
1914
2157
|
const [query, setQuery] = react.useState("");
|
|
1915
2158
|
const [selectedIndex, setSelectedIndex] = react.useState(0);
|
|
2159
|
+
const [endpointStates, setEndpointStates] = react.useState(/* @__PURE__ */ new Map());
|
|
1916
2160
|
const handlersRef = react.useRef(handlers);
|
|
1917
2161
|
handlersRef.current = handlers;
|
|
1918
2162
|
const onExecuteRef = react.useRef(onExecute);
|
|
1919
2163
|
onExecuteRef.current = onExecute;
|
|
2164
|
+
const onExecuteRemoteRef = react.useRef(onExecuteRemote);
|
|
2165
|
+
onExecuteRemoteRef.current = onExecuteRemote;
|
|
2166
|
+
const abortControllerRef = react.useRef(null);
|
|
2167
|
+
const debounceTimerRef = react.useRef(null);
|
|
2168
|
+
const currentQueryRef = react.useRef(query);
|
|
2169
|
+
currentQueryRef.current = query;
|
|
1920
2170
|
const omnibarKeymap = react.useMemo(() => {
|
|
1921
2171
|
if (!enabled) return {};
|
|
1922
2172
|
return { [openKey]: "omnibar:toggle" };
|
|
@@ -1942,12 +2192,238 @@ function useOmnibar(options) {
|
|
|
1942
2192
|
const allResults = searchActions(query, actions, keymap);
|
|
1943
2193
|
return allResults.slice(0, maxResults);
|
|
1944
2194
|
}, [query, actions, keymap, maxResults]);
|
|
2195
|
+
react.useEffect(() => {
|
|
2196
|
+
if (debounceTimerRef.current) {
|
|
2197
|
+
clearTimeout(debounceTimerRef.current);
|
|
2198
|
+
debounceTimerRef.current = null;
|
|
2199
|
+
}
|
|
2200
|
+
if (abortControllerRef.current) {
|
|
2201
|
+
abortControllerRef.current.abort();
|
|
2202
|
+
abortControllerRef.current = null;
|
|
2203
|
+
}
|
|
2204
|
+
if (!endpointsRegistry) {
|
|
2205
|
+
setEndpointStates(/* @__PURE__ */ new Map());
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
const syncEndpoints = [];
|
|
2209
|
+
const asyncEndpoints = [];
|
|
2210
|
+
for (const [id, ep] of endpointsRegistry.endpoints) {
|
|
2211
|
+
if (ep.config.isSync) {
|
|
2212
|
+
syncEndpoints.push(id);
|
|
2213
|
+
} else {
|
|
2214
|
+
asyncEndpoints.push(id);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
const updateEndpointState = (epResult) => {
|
|
2218
|
+
const ep = endpointsRegistry.endpoints.get(epResult.endpointId);
|
|
2219
|
+
const pageSize = ep?.config.pageSize ?? 10;
|
|
2220
|
+
return {
|
|
2221
|
+
entries: epResult.entries,
|
|
2222
|
+
offset: pageSize,
|
|
2223
|
+
total: epResult.total,
|
|
2224
|
+
hasMore: epResult.hasMore ?? (epResult.total !== void 0 ? epResult.entries.length < epResult.total : void 0),
|
|
2225
|
+
isLoading: false
|
|
2226
|
+
};
|
|
2227
|
+
};
|
|
2228
|
+
if (syncEndpoints.length > 0) {
|
|
2229
|
+
const syncController = new AbortController();
|
|
2230
|
+
Promise.all(
|
|
2231
|
+
syncEndpoints.map(
|
|
2232
|
+
(id) => endpointsRegistry.queryEndpoint(id, query, { offset: 0, limit: endpointsRegistry.endpoints.get(id)?.config.pageSize ?? 10 }, syncController.signal)
|
|
2233
|
+
)
|
|
2234
|
+
).then((results2) => {
|
|
2235
|
+
if (syncController.signal.aborted) return;
|
|
2236
|
+
setEndpointStates((prev) => {
|
|
2237
|
+
const next = new Map(prev);
|
|
2238
|
+
for (const result of results2) {
|
|
2239
|
+
if (result) {
|
|
2240
|
+
next.set(result.endpointId, updateEndpointState(result));
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
return next;
|
|
2244
|
+
});
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
if (asyncEndpoints.length > 0) {
|
|
2248
|
+
setEndpointStates((prev) => {
|
|
2249
|
+
const next = new Map(prev);
|
|
2250
|
+
for (const id of asyncEndpoints) {
|
|
2251
|
+
const existing = prev.get(id);
|
|
2252
|
+
next.set(id, {
|
|
2253
|
+
entries: existing?.entries ?? [],
|
|
2254
|
+
offset: existing?.offset ?? 0,
|
|
2255
|
+
total: existing?.total,
|
|
2256
|
+
hasMore: existing?.hasMore,
|
|
2257
|
+
isLoading: true
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
return next;
|
|
2261
|
+
});
|
|
2262
|
+
debounceTimerRef.current = setTimeout(async () => {
|
|
2263
|
+
const controller = new AbortController();
|
|
2264
|
+
abortControllerRef.current = controller;
|
|
2265
|
+
try {
|
|
2266
|
+
const results2 = await Promise.all(
|
|
2267
|
+
asyncEndpoints.map(
|
|
2268
|
+
(id) => endpointsRegistry.queryEndpoint(id, query, { offset: 0, limit: endpointsRegistry.endpoints.get(id)?.config.pageSize ?? 10 }, controller.signal)
|
|
2269
|
+
)
|
|
2270
|
+
);
|
|
2271
|
+
if (controller.signal.aborted) return;
|
|
2272
|
+
setEndpointStates((prev) => {
|
|
2273
|
+
const next = new Map(prev);
|
|
2274
|
+
for (const result of results2) {
|
|
2275
|
+
if (result) {
|
|
2276
|
+
next.set(result.endpointId, updateEndpointState(result));
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
return next;
|
|
2280
|
+
});
|
|
2281
|
+
} catch (error) {
|
|
2282
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
2283
|
+
console.error("Omnibar endpoint query failed:", error);
|
|
2284
|
+
setEndpointStates((prev) => {
|
|
2285
|
+
const next = new Map(prev);
|
|
2286
|
+
for (const id of asyncEndpoints) {
|
|
2287
|
+
const state = next.get(id);
|
|
2288
|
+
if (state) {
|
|
2289
|
+
next.set(id, { ...state, isLoading: false });
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
return next;
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
}, debounceMs);
|
|
2296
|
+
}
|
|
2297
|
+
return () => {
|
|
2298
|
+
if (debounceTimerRef.current) {
|
|
2299
|
+
clearTimeout(debounceTimerRef.current);
|
|
2300
|
+
}
|
|
2301
|
+
if (abortControllerRef.current) {
|
|
2302
|
+
abortControllerRef.current.abort();
|
|
2303
|
+
}
|
|
2304
|
+
};
|
|
2305
|
+
}, [query, endpointsRegistry, debounceMs]);
|
|
2306
|
+
const loadMore = react.useCallback(async (endpointId) => {
|
|
2307
|
+
if (!endpointsRegistry) return;
|
|
2308
|
+
const currentState = endpointStates.get(endpointId);
|
|
2309
|
+
if (!currentState || currentState.isLoading) return;
|
|
2310
|
+
if (currentState.hasMore === false) return;
|
|
2311
|
+
const ep = endpointsRegistry.endpoints.get(endpointId);
|
|
2312
|
+
if (!ep) return;
|
|
2313
|
+
const pageSize = ep.config.pageSize ?? 10;
|
|
2314
|
+
setEndpointStates((prev) => {
|
|
2315
|
+
const next = new Map(prev);
|
|
2316
|
+
const state = next.get(endpointId);
|
|
2317
|
+
if (state) {
|
|
2318
|
+
next.set(endpointId, { ...state, isLoading: true });
|
|
2319
|
+
}
|
|
2320
|
+
return next;
|
|
2321
|
+
});
|
|
2322
|
+
try {
|
|
2323
|
+
const controller = new AbortController();
|
|
2324
|
+
const result = await endpointsRegistry.queryEndpoint(
|
|
2325
|
+
endpointId,
|
|
2326
|
+
currentQueryRef.current,
|
|
2327
|
+
{ offset: currentState.offset, limit: pageSize },
|
|
2328
|
+
controller.signal
|
|
2329
|
+
);
|
|
2330
|
+
if (!result) return;
|
|
2331
|
+
setEndpointStates((prev) => {
|
|
2332
|
+
const next = new Map(prev);
|
|
2333
|
+
const state = next.get(endpointId);
|
|
2334
|
+
if (state) {
|
|
2335
|
+
next.set(endpointId, {
|
|
2336
|
+
entries: [...state.entries, ...result.entries],
|
|
2337
|
+
offset: state.offset + pageSize,
|
|
2338
|
+
total: result.total ?? state.total,
|
|
2339
|
+
hasMore: result.hasMore ?? (result.total !== void 0 ? state.entries.length + result.entries.length < result.total : void 0),
|
|
2340
|
+
isLoading: false
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
return next;
|
|
2344
|
+
});
|
|
2345
|
+
} catch (error) {
|
|
2346
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
2347
|
+
console.error(`Omnibar loadMore failed for ${endpointId}:`, error);
|
|
2348
|
+
setEndpointStates((prev) => {
|
|
2349
|
+
const next = new Map(prev);
|
|
2350
|
+
const state = next.get(endpointId);
|
|
2351
|
+
if (state) {
|
|
2352
|
+
next.set(endpointId, { ...state, isLoading: false });
|
|
2353
|
+
}
|
|
2354
|
+
return next;
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}, [endpointsRegistry, endpointStates]);
|
|
2358
|
+
const remoteResults = react.useMemo(() => {
|
|
2359
|
+
if (!endpointsRegistry) return [];
|
|
2360
|
+
const processed = [];
|
|
2361
|
+
for (const [endpointId, state] of endpointStates) {
|
|
2362
|
+
const endpoint = endpointsRegistry.endpoints.get(endpointId);
|
|
2363
|
+
const priority = endpoint?.config.priority ?? 0;
|
|
2364
|
+
for (const entry of state.entries) {
|
|
2365
|
+
const labelMatch = fuzzyMatch(query, entry.label);
|
|
2366
|
+
const descMatch = entry.description ? fuzzyMatch(query, entry.description) : null;
|
|
2367
|
+
const keywordsMatch = entry.keywords?.map((k) => fuzzyMatch(query, k)) ?? [];
|
|
2368
|
+
let score = 0;
|
|
2369
|
+
let labelMatches = [];
|
|
2370
|
+
if (labelMatch.matched) {
|
|
2371
|
+
score = Math.max(score, labelMatch.score * 3);
|
|
2372
|
+
labelMatches = labelMatch.ranges;
|
|
2373
|
+
}
|
|
2374
|
+
if (descMatch?.matched) {
|
|
2375
|
+
score = Math.max(score, descMatch.score * 1.5);
|
|
2376
|
+
}
|
|
2377
|
+
for (const km of keywordsMatch) {
|
|
2378
|
+
if (km.matched) {
|
|
2379
|
+
score = Math.max(score, km.score * 2);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
processed.push({
|
|
2383
|
+
id: `${endpointId}:${entry.id}`,
|
|
2384
|
+
entry,
|
|
2385
|
+
endpointId,
|
|
2386
|
+
priority,
|
|
2387
|
+
score: score || 1,
|
|
2388
|
+
labelMatches
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
processed.sort((a, b) => {
|
|
2393
|
+
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
2394
|
+
return b.score - a.score;
|
|
2395
|
+
});
|
|
2396
|
+
return processed;
|
|
2397
|
+
}, [endpointStates, endpointsRegistry, query]);
|
|
2398
|
+
const isLoadingRemote = react.useMemo(() => {
|
|
2399
|
+
for (const [, state] of endpointStates) {
|
|
2400
|
+
if (state.isLoading) return true;
|
|
2401
|
+
}
|
|
2402
|
+
return false;
|
|
2403
|
+
}, [endpointStates]);
|
|
2404
|
+
const endpointPagination = react.useMemo(() => {
|
|
2405
|
+
const info = /* @__PURE__ */ new Map();
|
|
2406
|
+
if (!endpointsRegistry) return info;
|
|
2407
|
+
for (const [endpointId, state] of endpointStates) {
|
|
2408
|
+
const ep = endpointsRegistry.endpoints.get(endpointId);
|
|
2409
|
+
info.set(endpointId, {
|
|
2410
|
+
endpointId,
|
|
2411
|
+
loaded: state.entries.length,
|
|
2412
|
+
total: state.total,
|
|
2413
|
+
hasMore: state.hasMore ?? false,
|
|
2414
|
+
isLoading: state.isLoading,
|
|
2415
|
+
mode: ep?.config.pagination ?? "none"
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
return info;
|
|
2419
|
+
}, [endpointStates, endpointsRegistry]);
|
|
2420
|
+
const totalResults = results.length + remoteResults.length;
|
|
1945
2421
|
const completions = react.useMemo(() => {
|
|
1946
2422
|
return getSequenceCompletions(pendingKeys, keymap);
|
|
1947
2423
|
}, [pendingKeys, keymap]);
|
|
1948
2424
|
react.useEffect(() => {
|
|
1949
2425
|
setSelectedIndex(0);
|
|
1950
|
-
}, [results]);
|
|
2426
|
+
}, [results, remoteResults]);
|
|
1951
2427
|
const open = react.useCallback(() => {
|
|
1952
2428
|
setIsOpen(true);
|
|
1953
2429
|
setQuery("");
|
|
@@ -1974,8 +2450,8 @@ function useOmnibar(options) {
|
|
|
1974
2450
|
});
|
|
1975
2451
|
}, [onOpen, onClose]);
|
|
1976
2452
|
const selectNext = react.useCallback(() => {
|
|
1977
|
-
setSelectedIndex((prev) => min(prev + 1,
|
|
1978
|
-
}, [
|
|
2453
|
+
setSelectedIndex((prev) => min(prev + 1, totalResults - 1));
|
|
2454
|
+
}, [totalResults]);
|
|
1979
2455
|
const selectPrev = react.useCallback(() => {
|
|
1980
2456
|
setSelectedIndex((prev) => max2(prev - 1, 0));
|
|
1981
2457
|
}, []);
|
|
@@ -1983,15 +2459,47 @@ function useOmnibar(options) {
|
|
|
1983
2459
|
setSelectedIndex(0);
|
|
1984
2460
|
}, []);
|
|
1985
2461
|
const execute = react.useCallback((actionId) => {
|
|
1986
|
-
const
|
|
1987
|
-
if (
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2462
|
+
const localCount = results.length;
|
|
2463
|
+
if (actionId) {
|
|
2464
|
+
const remoteResult = remoteResults.find((r) => r.id === actionId);
|
|
2465
|
+
if (remoteResult) {
|
|
2466
|
+
close();
|
|
2467
|
+
const entry = remoteResult.entry;
|
|
2468
|
+
if ("handler" in entry && entry.handler) {
|
|
2469
|
+
entry.handler();
|
|
2470
|
+
}
|
|
2471
|
+
onExecuteRemoteRef.current?.(entry);
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
close();
|
|
2475
|
+
if (handlersRef.current?.[actionId]) {
|
|
2476
|
+
const event = new KeyboardEvent("keydown", { key: "Enter" });
|
|
2477
|
+
handlersRef.current[actionId](event);
|
|
2478
|
+
}
|
|
2479
|
+
onExecuteRef.current?.(actionId);
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
if (selectedIndex < localCount) {
|
|
2483
|
+
const id = results[selectedIndex]?.id;
|
|
2484
|
+
if (!id) return;
|
|
2485
|
+
close();
|
|
2486
|
+
if (handlersRef.current?.[id]) {
|
|
2487
|
+
const event = new KeyboardEvent("keydown", { key: "Enter" });
|
|
2488
|
+
handlersRef.current[id](event);
|
|
2489
|
+
}
|
|
2490
|
+
onExecuteRef.current?.(id);
|
|
2491
|
+
} else {
|
|
2492
|
+
const remoteIndex = selectedIndex - localCount;
|
|
2493
|
+
const remoteResult = remoteResults[remoteIndex];
|
|
2494
|
+
if (!remoteResult) return;
|
|
2495
|
+
close();
|
|
2496
|
+
const entry = remoteResult.entry;
|
|
2497
|
+
if ("handler" in entry && entry.handler) {
|
|
2498
|
+
entry.handler();
|
|
2499
|
+
}
|
|
2500
|
+
onExecuteRemoteRef.current?.(entry);
|
|
2501
|
+
}
|
|
2502
|
+
}, [results, remoteResults, selectedIndex, close]);
|
|
1995
2503
|
react.useEffect(() => {
|
|
1996
2504
|
if (!isOpen) return;
|
|
1997
2505
|
const handleKeyDown = (e) => {
|
|
@@ -2033,7 +2541,12 @@ function useOmnibar(options) {
|
|
|
2033
2541
|
query,
|
|
2034
2542
|
setQuery,
|
|
2035
2543
|
results,
|
|
2544
|
+
remoteResults,
|
|
2545
|
+
isLoadingRemote,
|
|
2546
|
+
endpointPagination,
|
|
2547
|
+
loadMore,
|
|
2036
2548
|
selectedIndex,
|
|
2549
|
+
totalResults,
|
|
2037
2550
|
selectNext,
|
|
2038
2551
|
selectPrev,
|
|
2039
2552
|
execute,
|
|
@@ -2151,6 +2664,26 @@ function Backspace({ className, style }) {
|
|
|
2151
2664
|
}
|
|
2152
2665
|
);
|
|
2153
2666
|
}
|
|
2667
|
+
function Tab({ className, style }) {
|
|
2668
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2669
|
+
"svg",
|
|
2670
|
+
{
|
|
2671
|
+
className,
|
|
2672
|
+
style: { ...baseStyle, ...style },
|
|
2673
|
+
viewBox: "0 0 24 24",
|
|
2674
|
+
fill: "none",
|
|
2675
|
+
stroke: "currentColor",
|
|
2676
|
+
strokeWidth: "2",
|
|
2677
|
+
strokeLinecap: "round",
|
|
2678
|
+
strokeLinejoin: "round",
|
|
2679
|
+
children: [
|
|
2680
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "12", x2: "16", y2: "12" }),
|
|
2681
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "12 8 16 12 12 16" }),
|
|
2682
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "20", y1: "6", x2: "20", y2: "18" })
|
|
2683
|
+
]
|
|
2684
|
+
}
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2154
2687
|
function getKeyIcon(key) {
|
|
2155
2688
|
switch (key.toLowerCase()) {
|
|
2156
2689
|
case "arrowup":
|
|
@@ -2165,6 +2698,8 @@ function getKeyIcon(key) {
|
|
|
2165
2698
|
return Enter;
|
|
2166
2699
|
case "backspace":
|
|
2167
2700
|
return Backspace;
|
|
2701
|
+
case "tab":
|
|
2702
|
+
return Tab;
|
|
2168
2703
|
default:
|
|
2169
2704
|
return null;
|
|
2170
2705
|
}
|
|
@@ -2269,7 +2804,6 @@ var Alt = react.forwardRef(
|
|
|
2269
2804
|
)
|
|
2270
2805
|
);
|
|
2271
2806
|
Alt.displayName = "Alt";
|
|
2272
|
-
var isMac2 = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
2273
2807
|
function getModifierIcon(modifier) {
|
|
2274
2808
|
switch (modifier) {
|
|
2275
2809
|
case "meta":
|
|
@@ -2281,7 +2815,7 @@ function getModifierIcon(modifier) {
|
|
|
2281
2815
|
case "opt":
|
|
2282
2816
|
return Option;
|
|
2283
2817
|
case "alt":
|
|
2284
|
-
return
|
|
2818
|
+
return isMac() ? Option : Alt;
|
|
2285
2819
|
}
|
|
2286
2820
|
}
|
|
2287
2821
|
var ModifierIcon = react.forwardRef(
|
|
@@ -2291,28 +2825,47 @@ var ModifierIcon = react.forwardRef(
|
|
|
2291
2825
|
}
|
|
2292
2826
|
);
|
|
2293
2827
|
ModifierIcon.displayName = "ModifierIcon";
|
|
2294
|
-
function
|
|
2295
|
-
const
|
|
2296
|
-
const parts = [];
|
|
2828
|
+
function renderModifierIcons(modifiers, className = "kbd-modifier-icon") {
|
|
2829
|
+
const icons = [];
|
|
2297
2830
|
if (modifiers.meta) {
|
|
2298
|
-
|
|
2831
|
+
icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className }, "meta"));
|
|
2299
2832
|
}
|
|
2300
2833
|
if (modifiers.ctrl) {
|
|
2301
|
-
|
|
2834
|
+
icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className }, "ctrl"));
|
|
2302
2835
|
}
|
|
2303
2836
|
if (modifiers.alt) {
|
|
2304
|
-
|
|
2837
|
+
icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className }, "alt"));
|
|
2305
2838
|
}
|
|
2306
2839
|
if (modifiers.shift) {
|
|
2307
|
-
|
|
2840
|
+
icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className }, "shift"));
|
|
2308
2841
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2842
|
+
return icons;
|
|
2843
|
+
}
|
|
2844
|
+
function renderKeyContent(key, iconClassName = "kbd-key-icon") {
|
|
2845
|
+
const Icon = getKeyIcon(key);
|
|
2846
|
+
const displayKey = formatKeyForDisplay(key);
|
|
2847
|
+
return Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: iconClassName }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: displayKey });
|
|
2848
|
+
}
|
|
2849
|
+
function renderSeqElem(elem, index, kbdClassName = "kbd-kbd") {
|
|
2850
|
+
if (elem.type === "digit") {
|
|
2851
|
+
return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: kbdClassName, children: "\u27E8#\u27E9" }, index);
|
|
2852
|
+
}
|
|
2853
|
+
if (elem.type === "digits") {
|
|
2854
|
+
return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: kbdClassName, children: "\u27E8##\u27E9" }, index);
|
|
2314
2855
|
}
|
|
2315
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
2856
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("kbd", { className: kbdClassName, children: [
|
|
2857
|
+
renderModifierIcons(elem.modifiers),
|
|
2858
|
+
renderKeyContent(elem.key)
|
|
2859
|
+
] }, index);
|
|
2860
|
+
}
|
|
2861
|
+
function renderKeySeq(keySeq, kbdClassName = "kbd-kbd") {
|
|
2862
|
+
return keySeq.map((elem, i) => renderSeqElem(elem, i, kbdClassName));
|
|
2863
|
+
}
|
|
2864
|
+
function KeyCombo({ combo }) {
|
|
2865
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2866
|
+
renderModifierIcons(combo.modifiers),
|
|
2867
|
+
renderKeyContent(combo.key)
|
|
2868
|
+
] });
|
|
2316
2869
|
}
|
|
2317
2870
|
function SeqElemDisplay({ elem }) {
|
|
2318
2871
|
if (elem.type === "digit") {
|
|
@@ -2464,7 +3017,7 @@ function KeybindingEditor({
|
|
|
2464
3017
|
return Array.from(allActions).map((action) => {
|
|
2465
3018
|
const key = actionMap.get(action) ?? defaultActionMap.get(action) ?? "";
|
|
2466
3019
|
const defaultKey = defaultActionMap.get(action) ?? "";
|
|
2467
|
-
const combo =
|
|
3020
|
+
const combo = parseHotkeyString(key);
|
|
2468
3021
|
const display = formatCombination(combo);
|
|
2469
3022
|
const conflictActions = conflicts.get(key);
|
|
2470
3023
|
return {
|
|
@@ -2621,15 +3174,30 @@ function LookupModal({ defaultBinding = "meta+shift+k" } = {}) {
|
|
|
2621
3174
|
const filteredBindings = react.useMemo(() => {
|
|
2622
3175
|
if (pendingKeys.length === 0) return allBindings;
|
|
2623
3176
|
return allBindings.filter((result) => {
|
|
2624
|
-
|
|
2625
|
-
|
|
3177
|
+
const keySeq = result.keySeq;
|
|
3178
|
+
if (keySeq.length < pendingKeys.length) return false;
|
|
3179
|
+
let keySeqIdx = 0;
|
|
3180
|
+
for (let i = 0; i < pendingKeys.length && keySeqIdx < keySeq.length; i++) {
|
|
2626
3181
|
const pending = pendingKeys[i];
|
|
2627
|
-
const
|
|
2628
|
-
|
|
2629
|
-
if (
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
3182
|
+
const elem = keySeq[keySeqIdx];
|
|
3183
|
+
const isDigit2 = /^[0-9]$/.test(pending.key);
|
|
3184
|
+
if (elem.type === "digits") {
|
|
3185
|
+
if (!isDigit2) return false;
|
|
3186
|
+
if (i + 1 < pendingKeys.length && /^[0-9]$/.test(pendingKeys[i + 1].key)) {
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
keySeqIdx++;
|
|
3190
|
+
} else if (elem.type === "digit") {
|
|
3191
|
+
if (!isDigit2) return false;
|
|
3192
|
+
keySeqIdx++;
|
|
3193
|
+
} else {
|
|
3194
|
+
if (pending.key !== elem.key) return false;
|
|
3195
|
+
if (pending.modifiers.ctrl !== elem.modifiers.ctrl) return false;
|
|
3196
|
+
if (pending.modifiers.alt !== elem.modifiers.alt) return false;
|
|
3197
|
+
if (pending.modifiers.shift !== elem.modifiers.shift) return false;
|
|
3198
|
+
if (pending.modifiers.meta !== elem.modifiers.meta) return false;
|
|
3199
|
+
keySeqIdx++;
|
|
3200
|
+
}
|
|
2633
3201
|
}
|
|
2634
3202
|
return true;
|
|
2635
3203
|
});
|
|
@@ -2741,7 +3309,7 @@ function LookupModal({ defaultBinding = "meta+shift+k" } = {}) {
|
|
|
2741
3309
|
},
|
|
2742
3310
|
onMouseEnter: () => setSelectedIndex(index),
|
|
2743
3311
|
children: [
|
|
2744
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
3312
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-lookup-binding", children: renderKeySeq(result.keySeq) }),
|
|
2745
3313
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-lookup-labels", children: result.labels.join(", ") })
|
|
2746
3314
|
]
|
|
2747
3315
|
},
|
|
@@ -2785,6 +3353,7 @@ function Omnibar({
|
|
|
2785
3353
|
onOpen: onOpenProp,
|
|
2786
3354
|
onClose: onCloseProp,
|
|
2787
3355
|
onExecute: onExecuteProp,
|
|
3356
|
+
onExecuteRemote: onExecuteRemoteProp,
|
|
2788
3357
|
maxResults = 10,
|
|
2789
3358
|
placeholder = "Type a command...",
|
|
2790
3359
|
children,
|
|
@@ -2822,13 +3391,25 @@ function Omnibar({
|
|
|
2822
3391
|
ctx.openOmnibar();
|
|
2823
3392
|
}
|
|
2824
3393
|
}, [onOpenProp, ctx]);
|
|
3394
|
+
const handleExecuteRemote = react.useCallback((entry) => {
|
|
3395
|
+
if (onExecuteRemoteProp) {
|
|
3396
|
+
onExecuteRemoteProp(entry);
|
|
3397
|
+
} else if ("href" in entry && entry.href) {
|
|
3398
|
+
window.location.href = entry.href;
|
|
3399
|
+
}
|
|
3400
|
+
}, [onExecuteRemoteProp]);
|
|
2825
3401
|
const {
|
|
2826
3402
|
isOpen: internalIsOpen,
|
|
2827
3403
|
close,
|
|
2828
3404
|
query,
|
|
2829
3405
|
setQuery,
|
|
2830
3406
|
results,
|
|
3407
|
+
remoteResults,
|
|
3408
|
+
isLoadingRemote,
|
|
3409
|
+
endpointPagination,
|
|
3410
|
+
loadMore,
|
|
2831
3411
|
selectedIndex,
|
|
3412
|
+
totalResults,
|
|
2832
3413
|
selectNext,
|
|
2833
3414
|
selectPrev,
|
|
2834
3415
|
execute,
|
|
@@ -2845,9 +3426,22 @@ function Omnibar({
|
|
|
2845
3426
|
onOpen: handleOpen,
|
|
2846
3427
|
onClose: handleClose,
|
|
2847
3428
|
onExecute: handleExecute,
|
|
2848
|
-
|
|
3429
|
+
onExecuteRemote: handleExecuteRemote,
|
|
3430
|
+
maxResults,
|
|
3431
|
+
endpointsRegistry: ctx?.endpointsRegistry
|
|
2849
3432
|
});
|
|
2850
3433
|
const isOpen = isOpenProp ?? ctx?.isOmnibarOpen ?? internalIsOpen;
|
|
3434
|
+
const resultsContainerRef = react.useRef(null);
|
|
3435
|
+
const sentinelRefs = react.useRef(/* @__PURE__ */ new Map());
|
|
3436
|
+
const remoteResultsByEndpoint = react.useMemo(() => {
|
|
3437
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3438
|
+
for (const result of remoteResults) {
|
|
3439
|
+
const existing = grouped.get(result.endpointId) ?? [];
|
|
3440
|
+
existing.push(result);
|
|
3441
|
+
grouped.set(result.endpointId, existing);
|
|
3442
|
+
}
|
|
3443
|
+
return grouped;
|
|
3444
|
+
}, [remoteResults]);
|
|
2851
3445
|
react.useEffect(() => {
|
|
2852
3446
|
if (isOpen) {
|
|
2853
3447
|
requestAnimationFrame(() => {
|
|
@@ -2855,6 +3449,38 @@ function Omnibar({
|
|
|
2855
3449
|
});
|
|
2856
3450
|
}
|
|
2857
3451
|
}, [isOpen]);
|
|
3452
|
+
react.useEffect(() => {
|
|
3453
|
+
if (!isOpen) return;
|
|
3454
|
+
const container = resultsContainerRef.current;
|
|
3455
|
+
if (!container) return;
|
|
3456
|
+
const observer = new IntersectionObserver(
|
|
3457
|
+
(entries) => {
|
|
3458
|
+
for (const entry of entries) {
|
|
3459
|
+
if (!entry.isIntersecting) continue;
|
|
3460
|
+
const endpointId = entry.target.dataset.endpointId;
|
|
3461
|
+
if (!endpointId) continue;
|
|
3462
|
+
const paginationInfo = endpointPagination.get(endpointId);
|
|
3463
|
+
if (!paginationInfo) continue;
|
|
3464
|
+
if (paginationInfo.mode !== "scroll") continue;
|
|
3465
|
+
if (!paginationInfo.hasMore) continue;
|
|
3466
|
+
if (paginationInfo.isLoading) continue;
|
|
3467
|
+
loadMore(endpointId);
|
|
3468
|
+
}
|
|
3469
|
+
},
|
|
3470
|
+
{
|
|
3471
|
+
root: container,
|
|
3472
|
+
rootMargin: "100px",
|
|
3473
|
+
// Trigger slightly before sentinel is visible
|
|
3474
|
+
threshold: 0
|
|
3475
|
+
}
|
|
3476
|
+
);
|
|
3477
|
+
for (const [_endpointId, sentinel] of sentinelRefs.current) {
|
|
3478
|
+
if (sentinel) {
|
|
3479
|
+
observer.observe(sentinel);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
return () => observer.disconnect();
|
|
3483
|
+
}, [isOpen, endpointPagination, loadMore]);
|
|
2858
3484
|
react.useEffect(() => {
|
|
2859
3485
|
if (!isOpen) return;
|
|
2860
3486
|
const handleGlobalKeyDown = (e) => {
|
|
@@ -2904,7 +3530,12 @@ function Omnibar({
|
|
|
2904
3530
|
query,
|
|
2905
3531
|
setQuery,
|
|
2906
3532
|
results,
|
|
3533
|
+
remoteResults,
|
|
3534
|
+
isLoadingRemote,
|
|
3535
|
+
endpointPagination,
|
|
3536
|
+
loadMore,
|
|
2907
3537
|
selectedIndex,
|
|
3538
|
+
totalResults,
|
|
2908
3539
|
selectNext,
|
|
2909
3540
|
selectPrev,
|
|
2910
3541
|
execute,
|
|
@@ -2932,21 +3563,61 @@ function Omnibar({
|
|
|
2932
3563
|
spellCheck: false
|
|
2933
3564
|
}
|
|
2934
3565
|
),
|
|
2935
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-omnibar-results", children:
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
3566
|
+
/* @__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: [
|
|
3567
|
+
results.map((result, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3568
|
+
"div",
|
|
3569
|
+
{
|
|
3570
|
+
className: `kbd-omnibar-result ${i === selectedIndex ? "selected" : ""}`,
|
|
3571
|
+
onClick: () => execute(result.id),
|
|
3572
|
+
children: [
|
|
3573
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-label", children: result.action.label }),
|
|
3574
|
+
result.action.group && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-category", children: result.action.group }),
|
|
3575
|
+
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)) })
|
|
3576
|
+
]
|
|
2941
3577
|
},
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
]
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
3578
|
+
result.id
|
|
3579
|
+
)),
|
|
3580
|
+
(() => {
|
|
3581
|
+
let remoteIndex = 0;
|
|
3582
|
+
return Array.from(remoteResultsByEndpoint.entries()).map(([endpointId, endpointResults]) => {
|
|
3583
|
+
const paginationInfo = endpointPagination.get(endpointId);
|
|
3584
|
+
const showPagination = paginationInfo?.mode === "scroll" && paginationInfo.total !== void 0;
|
|
3585
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
3586
|
+
endpointResults.map((result) => {
|
|
3587
|
+
const absoluteIndex = results.length + remoteIndex;
|
|
3588
|
+
remoteIndex++;
|
|
3589
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3590
|
+
"div",
|
|
3591
|
+
{
|
|
3592
|
+
className: `kbd-omnibar-result ${absoluteIndex === selectedIndex ? "selected" : ""}`,
|
|
3593
|
+
onClick: () => execute(result.id),
|
|
3594
|
+
children: [
|
|
3595
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-label", children: result.entry.label }),
|
|
3596
|
+
result.entry.group && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-category", children: result.entry.group }),
|
|
3597
|
+
result.entry.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-description", children: result.entry.description })
|
|
3598
|
+
]
|
|
3599
|
+
},
|
|
3600
|
+
result.id
|
|
3601
|
+
);
|
|
3602
|
+
}),
|
|
3603
|
+
paginationInfo?.mode === "scroll" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3604
|
+
"div",
|
|
3605
|
+
{
|
|
3606
|
+
className: "kbd-omnibar-pagination",
|
|
3607
|
+
ref: (el) => sentinelRefs.current.set(endpointId, el),
|
|
3608
|
+
"data-endpoint-id": endpointId,
|
|
3609
|
+
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: [
|
|
3610
|
+
paginationInfo.loaded,
|
|
3611
|
+
" of ",
|
|
3612
|
+
paginationInfo.total
|
|
3613
|
+
] }) : paginationInfo.hasMore ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-pagination-more", children: "Scroll for more..." }) : null
|
|
3614
|
+
}
|
|
3615
|
+
)
|
|
3616
|
+
] }, endpointId);
|
|
3617
|
+
});
|
|
3618
|
+
})(),
|
|
3619
|
+
isLoadingRemote && remoteResults.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-omnibar-loading", children: "Searching..." })
|
|
3620
|
+
] }) })
|
|
2950
3621
|
] }) });
|
|
2951
3622
|
}
|
|
2952
3623
|
function SequenceModal() {
|
|
@@ -2957,41 +3628,99 @@ function SequenceModal() {
|
|
|
2957
3628
|
sequenceTimeoutStartedAt: timeoutStartedAt,
|
|
2958
3629
|
sequenceTimeout,
|
|
2959
3630
|
getCompletions,
|
|
2960
|
-
registry
|
|
3631
|
+
registry,
|
|
3632
|
+
executeAction
|
|
2961
3633
|
} = useHotkeysContext();
|
|
3634
|
+
const [selectedIndex, setSelectedIndex] = react.useState(0);
|
|
3635
|
+
const [hasInteracted, setHasInteracted] = react.useState(false);
|
|
2962
3636
|
const completions = react.useMemo(() => {
|
|
2963
3637
|
if (pendingKeys.length === 0) return [];
|
|
2964
3638
|
return getCompletions(pendingKeys);
|
|
2965
3639
|
}, [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();
|
|
3640
|
+
const flatCompletions = react.useMemo(() => {
|
|
3641
|
+
const items = [];
|
|
2976
3642
|
for (const c of completions) {
|
|
2977
|
-
const
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3643
|
+
for (const action of c.actions) {
|
|
3644
|
+
const displayKey = c.isComplete ? "\u21B5" : c.nextKeys;
|
|
3645
|
+
items.push({
|
|
3646
|
+
completion: c,
|
|
3647
|
+
action,
|
|
3648
|
+
displayKey,
|
|
3649
|
+
isComplete: c.isComplete
|
|
3650
|
+
});
|
|
2982
3651
|
}
|
|
2983
3652
|
}
|
|
2984
|
-
return
|
|
3653
|
+
return items;
|
|
2985
3654
|
}, [completions]);
|
|
3655
|
+
const itemCount = flatCompletions.length;
|
|
3656
|
+
const shouldShowTimeout = timeoutStartedAt !== null && completions.length === 1 && !hasInteracted;
|
|
3657
|
+
react.useEffect(() => {
|
|
3658
|
+
setSelectedIndex(0);
|
|
3659
|
+
setHasInteracted(false);
|
|
3660
|
+
}, [pendingKeys]);
|
|
3661
|
+
const executeSelected = react.useCallback(() => {
|
|
3662
|
+
if (selectedIndex >= 0 && selectedIndex < flatCompletions.length) {
|
|
3663
|
+
const item = flatCompletions[selectedIndex];
|
|
3664
|
+
executeAction(item.action, item.completion.captures);
|
|
3665
|
+
cancelSequence();
|
|
3666
|
+
}
|
|
3667
|
+
}, [selectedIndex, flatCompletions, executeAction, cancelSequence]);
|
|
3668
|
+
react.useEffect(() => {
|
|
3669
|
+
if (!isAwaitingSequence || pendingKeys.length === 0) return;
|
|
3670
|
+
const handleKeyDown = (e) => {
|
|
3671
|
+
switch (e.key) {
|
|
3672
|
+
case "ArrowDown":
|
|
3673
|
+
e.preventDefault();
|
|
3674
|
+
e.stopPropagation();
|
|
3675
|
+
setSelectedIndex((prev) => Math.min(prev + 1, itemCount - 1));
|
|
3676
|
+
setHasInteracted(true);
|
|
3677
|
+
break;
|
|
3678
|
+
case "ArrowUp":
|
|
3679
|
+
e.preventDefault();
|
|
3680
|
+
e.stopPropagation();
|
|
3681
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
3682
|
+
setHasInteracted(true);
|
|
3683
|
+
break;
|
|
3684
|
+
case "Enter":
|
|
3685
|
+
e.preventDefault();
|
|
3686
|
+
e.stopPropagation();
|
|
3687
|
+
executeSelected();
|
|
3688
|
+
break;
|
|
3689
|
+
}
|
|
3690
|
+
};
|
|
3691
|
+
document.addEventListener("keydown", handleKeyDown, true);
|
|
3692
|
+
return () => document.removeEventListener("keydown", handleKeyDown, true);
|
|
3693
|
+
}, [isAwaitingSequence, pendingKeys.length, itemCount, executeSelected]);
|
|
3694
|
+
const renderKey = react.useCallback((combo, index) => {
|
|
3695
|
+
const { key, modifiers } = combo;
|
|
3696
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("kbd", { className: "kbd-kbd", children: [
|
|
3697
|
+
renderModifierIcons(modifiers),
|
|
3698
|
+
renderKeyContent(key)
|
|
3699
|
+
] }, index);
|
|
3700
|
+
}, []);
|
|
3701
|
+
const getActionLabel = (actionId, captures) => {
|
|
3702
|
+
const action = registry.actions.get(actionId);
|
|
3703
|
+
let label = action?.config.label || actionId;
|
|
3704
|
+
if (captures && captures.length > 0) {
|
|
3705
|
+
let captureIdx = 0;
|
|
3706
|
+
label = label.replace(/\bN\b/g, () => {
|
|
3707
|
+
if (captureIdx < captures.length) {
|
|
3708
|
+
return String(captures[captureIdx++]);
|
|
3709
|
+
}
|
|
3710
|
+
return "N";
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3713
|
+
return label;
|
|
3714
|
+
};
|
|
2986
3715
|
if (!isAwaitingSequence || pendingKeys.length === 0) {
|
|
2987
3716
|
return null;
|
|
2988
3717
|
}
|
|
2989
3718
|
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
3719
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-sequence-current", children: [
|
|
2991
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
3720
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-keys", children: pendingKeys.map((combo, i) => renderKey(combo, i)) }),
|
|
2992
3721
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-ellipsis", children: "\u2026" })
|
|
2993
3722
|
] }),
|
|
2994
|
-
|
|
3723
|
+
shouldShowTimeout && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2995
3724
|
"div",
|
|
2996
3725
|
{
|
|
2997
3726
|
className: "kbd-sequence-timeout",
|
|
@@ -2999,15 +3728,19 @@ function SequenceModal() {
|
|
|
2999
3728
|
},
|
|
3000
3729
|
timeoutStartedAt
|
|
3001
3730
|
),
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3731
|
+
flatCompletions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-completions", children: flatCompletions.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3732
|
+
"div",
|
|
3733
|
+
{
|
|
3734
|
+
className: `kbd-sequence-completion ${index === selectedIndex ? "selected" : ""} ${item.isComplete ? "complete" : ""}`,
|
|
3735
|
+
children: [
|
|
3736
|
+
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 }),
|
|
3737
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-arrow", children: "\u2192" }),
|
|
3738
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-actions", children: getActionLabel(item.action, item.completion.captures) })
|
|
3739
|
+
]
|
|
3740
|
+
},
|
|
3741
|
+
`${item.completion.fullSequence}-${item.action}`
|
|
3742
|
+
)) }),
|
|
3743
|
+
flatCompletions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-empty", children: "No matching shortcuts" })
|
|
3011
3744
|
] }) });
|
|
3012
3745
|
}
|
|
3013
3746
|
var DefaultTooltip = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
@@ -3030,6 +3763,7 @@ function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder,
|
|
|
3030
3763
|
return groupNames?.[groupKey] ?? groupKey;
|
|
3031
3764
|
};
|
|
3032
3765
|
for (const [actionId, bindings] of actionBindings) {
|
|
3766
|
+
if (actionRegistry?.[actionId]?.hideFromModal) continue;
|
|
3033
3767
|
includedActions.add(actionId);
|
|
3034
3768
|
const { name } = parseActionId(actionId);
|
|
3035
3769
|
const groupName = getGroupName(actionId);
|
|
@@ -3046,6 +3780,7 @@ function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder,
|
|
|
3046
3780
|
if (actionRegistry && showUnbound) {
|
|
3047
3781
|
for (const [actionId, action] of Object.entries(actionRegistry)) {
|
|
3048
3782
|
if (includedActions.has(actionId)) continue;
|
|
3783
|
+
if (action.hideFromModal) continue;
|
|
3049
3784
|
const { name } = parseActionId(actionId);
|
|
3050
3785
|
const groupName = getGroupName(actionId);
|
|
3051
3786
|
if (!groupMap.has(groupName)) {
|
|
@@ -3086,27 +3821,10 @@ function KeyDisplay({
|
|
|
3086
3821
|
combo,
|
|
3087
3822
|
className
|
|
3088
3823
|
}) {
|
|
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 });
|
|
3824
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className, children: [
|
|
3825
|
+
renderModifierIcons(combo.modifiers),
|
|
3826
|
+
renderKeyContent(combo.key)
|
|
3827
|
+
] });
|
|
3110
3828
|
}
|
|
3111
3829
|
function SeqElemDisplay2({ elem, className }) {
|
|
3112
3830
|
const Tooltip = react.useContext(TooltipContext);
|
|
@@ -3134,7 +3852,6 @@ function BindingDisplay2({
|
|
|
3134
3852
|
}) {
|
|
3135
3853
|
const sequence = parseHotkeyString(binding);
|
|
3136
3854
|
const keySeq = parseKeySeq(binding);
|
|
3137
|
-
formatKeySeq(keySeq);
|
|
3138
3855
|
let kbdClassName = "kbd-kbd";
|
|
3139
3856
|
if (editable && !isEditing) kbdClassName += " editable";
|
|
3140
3857
|
if (isEditing) kbdClassName += " editing";
|
|
@@ -3307,18 +4024,17 @@ function ShortcutsModal({
|
|
|
3307
4024
|
ctx.closeModal();
|
|
3308
4025
|
}
|
|
3309
4026
|
}, [onCloseProp, ctx]);
|
|
3310
|
-
react.useCallback(() => {
|
|
3311
|
-
if (ctx?.openModal) {
|
|
3312
|
-
ctx.openModal();
|
|
3313
|
-
} else {
|
|
3314
|
-
setInternalIsOpen(true);
|
|
3315
|
-
}
|
|
3316
|
-
}, [ctx]);
|
|
3317
4027
|
useAction(ACTION_MODAL, {
|
|
3318
4028
|
label: "Show shortcuts",
|
|
3319
4029
|
group: "Global",
|
|
3320
4030
|
defaultBindings: defaultBinding ? [defaultBinding] : [],
|
|
3321
|
-
handler: react.useCallback(() =>
|
|
4031
|
+
handler: react.useCallback(() => {
|
|
4032
|
+
if (ctx) {
|
|
4033
|
+
ctx.toggleModal();
|
|
4034
|
+
} else {
|
|
4035
|
+
setInternalIsOpen((prev) => !prev);
|
|
4036
|
+
}
|
|
4037
|
+
}, [ctx])
|
|
3322
4038
|
});
|
|
3323
4039
|
const checkConflict = react.useCallback((newKey, forAction) => {
|
|
3324
4040
|
const existingActions = keymap[newKey];
|
|
@@ -3787,6 +4503,7 @@ exports.Left = Left;
|
|
|
3787
4503
|
exports.LookupModal = LookupModal;
|
|
3788
4504
|
exports.ModifierIcon = ModifierIcon;
|
|
3789
4505
|
exports.Omnibar = Omnibar;
|
|
4506
|
+
exports.OmnibarEndpointsRegistryContext = OmnibarEndpointsRegistryContext;
|
|
3790
4507
|
exports.Option = Option;
|
|
3791
4508
|
exports.Right = Right;
|
|
3792
4509
|
exports.SequenceModal = SequenceModal;
|
|
@@ -3818,7 +4535,6 @@ exports.isSequence = isSequence;
|
|
|
3818
4535
|
exports.isShiftedSymbol = isShiftedSymbol;
|
|
3819
4536
|
exports.keySeqToHotkeySequence = keySeqToHotkeySequence;
|
|
3820
4537
|
exports.normalizeKey = normalizeKey;
|
|
3821
|
-
exports.parseCombinationId = parseCombinationId;
|
|
3822
4538
|
exports.parseHotkeyString = parseHotkeyString;
|
|
3823
4539
|
exports.parseKeySeq = parseKeySeq;
|
|
3824
4540
|
exports.searchActions = searchActions;
|
|
@@ -3830,6 +4546,8 @@ exports.useHotkeys = useHotkeys;
|
|
|
3830
4546
|
exports.useHotkeysContext = useHotkeysContext;
|
|
3831
4547
|
exports.useMaybeHotkeysContext = useMaybeHotkeysContext;
|
|
3832
4548
|
exports.useOmnibar = useOmnibar;
|
|
4549
|
+
exports.useOmnibarEndpoint = useOmnibarEndpoint;
|
|
4550
|
+
exports.useOmnibarEndpointsRegistry = useOmnibarEndpointsRegistry;
|
|
3833
4551
|
exports.useRecordHotkey = useRecordHotkey;
|
|
3834
4552
|
//# sourceMappingURL=index.cjs.map
|
|
3835
4553
|
//# sourceMappingURL=index.cjs.map
|