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