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 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
- return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
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": "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
- if (sequence.length <= pendingKeys.length) continue;
743
- let isPrefix2 = true;
744
- for (let i = 0; i < pendingKeys.length; i++) {
745
- if (!combinationsEqual(pendingKeys[i], sequence[i])) {
746
- isPrefix2 = false;
747
- break;
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 (isPrefix2) {
751
- const remainingKeySeq = keySeq.slice(pendingKeys.length);
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 * 1 : 0) + (idMatch.matched ? idMatch.score * 0.5 : 0) + keywordScore * 2;
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, matchState, captures, e) => {
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
- const executed = tryExecute(pendingKeysRef.current, e);
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
- let keySeqMatched = false;
1123
- let keySeqPartial = false;
1302
+ const completeMatches = [];
1303
+ let hasPartials = false;
1124
1304
  const matchStates = matchStatesRef.current;
1125
- const hasPartialMatches = matchStates.size > 0;
1305
+ const hadPartialMatches = matchStates.size > 0;
1126
1306
  for (const entry of parsedKeymapRef.current) {
1127
1307
  let state = matchStates.get(entry.key);
1128
- if (hasPartialMatches && !state) {
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
- if (tryExecuteKeySeq(entry.key, result.state, result.captures, e)) {
1138
- clearPending();
1139
- keySeqMatched = true;
1140
- break;
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
- keySeqPartial = true;
1325
+ hasPartials = true;
1145
1326
  } else {
1146
1327
  matchStates.delete(entry.key);
1147
1328
  }
1148
1329
  }
1149
- if (keySeqMatched) {
1150
- return;
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 (keySeqPartial) {
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
- clearPending();
1236
- onSequenceCancel?.();
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: true,
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 = true, ...hotkeyOptions } = options;
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, results.length - 1));
1978
- }, [results.length]);
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 id = actionId ?? results[selectedIndex]?.id;
1987
- if (!id) return;
1988
- close();
1989
- if (handlersRef.current?.[id]) {
1990
- const event = new KeyboardEvent("keydown", { key: "Enter" });
1991
- handlersRef.current[id](event);
1992
- }
1993
- onExecuteRef.current?.(id);
1994
- }, [results, selectedIndex, close]);
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 isMac2 ? Option : Alt;
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 KeyCombo({ combo }) {
2295
- const { key, modifiers } = combo;
2296
- const parts = [];
2828
+ function renderModifierIcons(modifiers, className = "kbd-modifier-icon") {
2829
+ const icons = [];
2297
2830
  if (modifiers.meta) {
2298
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className: "kbd-modifier-icon" }, "meta"));
2831
+ icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className }, "meta"));
2299
2832
  }
2300
2833
  if (modifiers.ctrl) {
2301
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }, "ctrl"));
2834
+ icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className }, "ctrl"));
2302
2835
  }
2303
2836
  if (modifiers.alt) {
2304
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }, "alt"));
2837
+ icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className }, "alt"));
2305
2838
  }
2306
2839
  if (modifiers.shift) {
2307
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }, "shift"));
2840
+ icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className }, "shift"));
2308
2841
  }
2309
- const KeyIcon = getKeyIcon(key);
2310
- if (KeyIcon) {
2311
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(KeyIcon, { className: "kbd-key-icon" }, "key"));
2312
- } else {
2313
- parts.push(/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatKeyForDisplay(key) }, "key"));
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.jsx(jsxRuntime.Fragment, { children: parts });
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 = parseCombinationId(key);
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
- if (result.sequence.length < pendingKeys.length) return false;
2625
- for (let i = 0; i < pendingKeys.length; i++) {
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 target = result.sequence[i];
2628
- if (pending.key !== target.key) return false;
2629
- if (pending.modifiers.ctrl !== target.modifiers.ctrl) return false;
2630
- if (pending.modifiers.alt !== target.modifiers.alt) return false;
2631
- if (pending.modifiers.shift !== target.modifiers.shift) return false;
2632
- if (pending.modifiers.meta !== target.modifiers.meta) return false;
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("kbd", { className: "kbd-kbd", children: result.display }),
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
- maxResults
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: results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-omnibar-no-results", children: query ? "No matching commands" : "Start typing to search commands..." }) : results.map((result, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2936
- "div",
2937
- {
2938
- className: `kbd-omnibar-result ${i === selectedIndex ? "selected" : ""}`,
2939
- onClick: () => execute(result.id),
2940
- onMouseEnter: () => {
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
- children: [
2943
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-label", children: result.action.label }),
2944
- result.action.group && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-category", children: result.action.group }),
2945
- 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)) })
2946
- ]
2947
- },
2948
- result.id
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 formattedPendingKeys = react.useMemo(() => {
2967
- if (pendingKeys.length === 0) return "";
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 existing = byNextKey.get(c.nextKeys);
2978
- if (existing) {
2979
- existing.push(c);
2980
- } else {
2981
- byNextKey.set(c.nextKeys, [c]);
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 byNextKey;
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("kbd", { className: "kbd-sequence-keys", children: formattedPendingKeys }),
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
- timeoutStartedAt && /* @__PURE__ */ jsxRuntime.jsx(
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
- completions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-completions", children: Array.from(groupedCompletions.entries()).map(([nextKey, comps]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-sequence-completion", children: [
3003
- /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "kbd-kbd", children: nextKey }),
3004
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-arrow", children: "\u2192" }),
3005
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-actions", children: comps.flatMap((c) => c.actions).map((action, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3006
- i > 0 && ", ",
3007
- getActionLabel(action)
3008
- ] }, action)) })
3009
- ] }, nextKey)) }),
3010
- completions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-empty", children: "No matching shortcuts" })
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
- const { key, modifiers } = combo;
3090
- const parts = [];
3091
- if (modifiers.meta) {
3092
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className: "kbd-modifier-icon" }, "meta"));
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(() => ctx?.toggleModal() ?? setInternalIsOpen((prev) => !prev), [ctx?.toggleModal])
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