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.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
- return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
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": "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
- if (sequence.length <= pendingKeys.length) continue;
741
- let isPrefix2 = true;
742
- for (let i = 0; i < pendingKeys.length; i++) {
743
- if (!combinationsEqual(pendingKeys[i], sequence[i])) {
744
- isPrefix2 = false;
745
- break;
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 (isPrefix2) {
749
- const remainingKeySeq = keySeq.slice(pendingKeys.length);
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 * 1 : 0) + (idMatch.matched ? idMatch.score * 0.5 : 0) + keywordScore * 2;
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, matchState, captures, e) => {
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
- const executed = tryExecute(pendingKeysRef.current, e);
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
- let keySeqMatched = false;
1121
- let keySeqPartial = false;
1300
+ const completeMatches = [];
1301
+ let hasPartials = false;
1122
1302
  const matchStates = matchStatesRef.current;
1123
- const hasPartialMatches = matchStates.size > 0;
1303
+ const hadPartialMatches = matchStates.size > 0;
1124
1304
  for (const entry of parsedKeymapRef.current) {
1125
1305
  let state = matchStates.get(entry.key);
1126
- if (hasPartialMatches && !state) {
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
- if (tryExecuteKeySeq(entry.key, result.state, result.captures, e)) {
1136
- clearPending();
1137
- keySeqMatched = true;
1138
- break;
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
- keySeqPartial = true;
1323
+ hasPartials = true;
1143
1324
  } else {
1144
1325
  matchStates.delete(entry.key);
1145
1326
  }
1146
1327
  }
1147
- if (keySeqMatched) {
1148
- return;
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 (keySeqPartial) {
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
- clearPending();
1234
- onSequenceCancel?.();
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: true,
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 = true, ...hotkeyOptions } = options;
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, results.length - 1));
1976
- }, [results.length]);
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 id = actionId ?? results[selectedIndex]?.id;
1985
- if (!id) return;
1986
- close();
1987
- if (handlersRef.current?.[id]) {
1988
- const event = new KeyboardEvent("keydown", { key: "Enter" });
1989
- handlersRef.current[id](event);
1990
- }
1991
- onExecuteRef.current?.(id);
1992
- }, [results, selectedIndex, close]);
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 isMac2 ? Option : Alt;
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 KeyCombo({ combo }) {
2293
- const { key, modifiers } = combo;
2294
- const parts = [];
2826
+ function renderModifierIcons(modifiers, className = "kbd-modifier-icon") {
2827
+ const icons = [];
2295
2828
  if (modifiers.meta) {
2296
- parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "meta", className: "kbd-modifier-icon" }, "meta"));
2829
+ icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "meta", className }, "meta"));
2297
2830
  }
2298
2831
  if (modifiers.ctrl) {
2299
- parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }, "ctrl"));
2832
+ icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "ctrl", className }, "ctrl"));
2300
2833
  }
2301
2834
  if (modifiers.alt) {
2302
- parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }, "alt"));
2835
+ icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "alt", className }, "alt"));
2303
2836
  }
2304
2837
  if (modifiers.shift) {
2305
- parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }, "shift"));
2838
+ icons.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "shift", className }, "shift"));
2306
2839
  }
2307
- const KeyIcon = getKeyIcon(key);
2308
- if (KeyIcon) {
2309
- parts.push(/* @__PURE__ */ jsx(KeyIcon, { className: "kbd-key-icon" }, "key"));
2310
- } else {
2311
- parts.push(/* @__PURE__ */ jsx("span", { children: formatKeyForDisplay(key) }, "key"));
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__ */ jsx(Fragment, { children: parts });
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 = parseCombinationId(key);
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
- if (result.sequence.length < pendingKeys.length) return false;
2623
- for (let i = 0; i < pendingKeys.length; i++) {
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 target = result.sequence[i];
2626
- if (pending.key !== target.key) return false;
2627
- if (pending.modifiers.ctrl !== target.modifiers.ctrl) return false;
2628
- if (pending.modifiers.alt !== target.modifiers.alt) return false;
2629
- if (pending.modifiers.shift !== target.modifiers.shift) return false;
2630
- if (pending.modifiers.meta !== target.modifiers.meta) return false;
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("kbd", { className: "kbd-kbd", children: result.display }),
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
- maxResults
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: results.length === 0 ? /* @__PURE__ */ jsx("div", { className: "kbd-omnibar-no-results", children: query ? "No matching commands" : "Start typing to search commands..." }) : results.map((result, i) => /* @__PURE__ */ jsxs(
2934
- "div",
2935
- {
2936
- className: `kbd-omnibar-result ${i === selectedIndex ? "selected" : ""}`,
2937
- onClick: () => execute(result.id),
2938
- onMouseEnter: () => {
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
- children: [
2941
- /* @__PURE__ */ jsx("span", { className: "kbd-omnibar-result-label", children: result.action.label }),
2942
- result.action.group && /* @__PURE__ */ jsx("span", { className: "kbd-omnibar-result-category", children: result.action.group }),
2943
- 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)) })
2944
- ]
2945
- },
2946
- result.id
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 formattedPendingKeys = useMemo(() => {
2965
- if (pendingKeys.length === 0) return "";
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 existing = byNextKey.get(c.nextKeys);
2976
- if (existing) {
2977
- existing.push(c);
2978
- } else {
2979
- byNextKey.set(c.nextKeys, [c]);
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 byNextKey;
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("kbd", { className: "kbd-sequence-keys", children: formattedPendingKeys }),
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
- timeoutStartedAt && /* @__PURE__ */ jsx(
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
- completions.length > 0 && /* @__PURE__ */ jsx("div", { className: "kbd-sequence-completions", children: Array.from(groupedCompletions.entries()).map(([nextKey, comps]) => /* @__PURE__ */ jsxs("div", { className: "kbd-sequence-completion", children: [
3001
- /* @__PURE__ */ jsx("kbd", { className: "kbd-kbd", children: nextKey }),
3002
- /* @__PURE__ */ jsx("span", { className: "kbd-sequence-arrow", children: "\u2192" }),
3003
- /* @__PURE__ */ jsx("span", { className: "kbd-sequence-actions", children: comps.flatMap((c) => c.actions).map((action, i) => /* @__PURE__ */ jsxs("span", { children: [
3004
- i > 0 && ", ",
3005
- getActionLabel(action)
3006
- ] }, action)) })
3007
- ] }, nextKey)) }),
3008
- completions.length === 0 && /* @__PURE__ */ jsx("div", { className: "kbd-sequence-empty", children: "No matching shortcuts" })
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
- const { key, modifiers } = combo;
3088
- const parts = [];
3089
- if (modifiers.meta) {
3090
- parts.push(/* @__PURE__ */ jsx(ModifierIcon, { modifier: "meta", className: "kbd-modifier-icon" }, "meta"));
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(() => ctx?.toggleModal() ?? setInternalIsOpen((prev) => !prev), [ctx?.toggleModal])
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, parseCombinationId, parseHotkeyString, parseKeySeq, searchActions, useAction, useActions, useActionsRegistry, useEditableHotkeys, useHotkeys, useHotkeysContext, useMaybeHotkeysContext, useOmnibar, useRecordHotkey };
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