use-kbd 0.4.0 → 0.5.0

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