use-kbd 0.3.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
@@ -2,9 +2,19 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var react = require('react');
5
- var base = require('@rdub/base');
6
5
 
7
- // src/TwoColumnRenderer.tsx
6
+ // src/types.ts
7
+ function extractCaptures(state) {
8
+ return state.filter(
9
+ (e) => (e.type === "digit" || e.type === "digits") && e.value !== void 0
10
+ ).map((e) => e.value);
11
+ }
12
+ function isDigitPlaceholder(elem) {
13
+ return elem.type === "digit" || elem.type === "digits";
14
+ }
15
+ function countPlaceholders(seq) {
16
+ return seq.filter(isDigitPlaceholder).length;
17
+ }
8
18
  function createTwoColumnRenderer(config) {
9
19
  const { headers, getRows } = config;
10
20
  const [labelHeader, leftHeader, rightHeader] = headers;
@@ -62,9 +72,7 @@ function useActionsRegistry(options = {}) {
62
72
  const filterRedundantOverrides = react.useCallback((overrides2) => {
63
73
  const filtered = {};
64
74
  for (const [key, actionOrActions] of Object.entries(overrides2)) {
65
- if (actionOrActions === "") {
66
- continue;
67
- } else if (Array.isArray(actionOrActions)) {
75
+ if (actionOrActions === "") ; else if (Array.isArray(actionOrActions)) {
68
76
  const nonDefaultActions = actionOrActions.filter((a) => !isDefaultBinding(key, a));
69
77
  if (nonDefaultActions.length > 0) {
70
78
  filtered[key] = nonDefaultActions.length === 1 ? nonDefaultActions[0] : nonDefaultActions;
@@ -128,10 +136,10 @@ function useActionsRegistry(options = {}) {
128
136
  actionsRef.current.delete(id);
129
137
  setActionsVersion((v) => v + 1);
130
138
  }, []);
131
- const execute = react.useCallback((id) => {
139
+ const execute = react.useCallback((id, captures) => {
132
140
  const action = actionsRef.current.get(id);
133
141
  if (action && (action.config.enabled ?? true)) {
134
- action.config.handler();
142
+ action.config.handler(void 0, captures);
135
143
  }
136
144
  }, []);
137
145
  const keymap = react.useMemo(() => {
@@ -155,9 +163,7 @@ function useActionsRegistry(options = {}) {
155
163
  }
156
164
  }
157
165
  for (const [key, actionOrActions] of Object.entries(overrides)) {
158
- if (actionOrActions === "") {
159
- continue;
160
- } else {
166
+ if (actionOrActions === "") ; else {
161
167
  const actions2 = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
162
168
  for (const actionId of actions2) {
163
169
  addToKey(key, actionId);
@@ -187,34 +193,55 @@ function useActionsRegistry(options = {}) {
187
193
  }
188
194
  return bindings;
189
195
  }, [keymap]);
196
+ const getFirstBindingForAction = react.useCallback((actionId) => {
197
+ return getBindingsForAction(actionId)[0];
198
+ }, [getBindingsForAction]);
190
199
  const setBinding = react.useCallback((actionId, key) => {
191
- updateOverrides((prev) => ({
192
- ...prev,
193
- [key]: actionId
194
- }));
195
- }, [updateOverrides]);
196
- const removeBinding = react.useCallback((key) => {
197
- const actionsWithDefault = [];
198
- for (const [id, { config }] of actionsRef.current) {
199
- if (config.defaultBindings?.includes(key)) {
200
- actionsWithDefault.push(id);
201
- }
202
- }
203
- if (actionsWithDefault.length > 0) {
200
+ if (isDefaultBinding(key, actionId)) {
204
201
  updateRemovedDefaults((prev) => {
205
- const next = { ...prev };
206
- for (const actionId of actionsWithDefault) {
207
- const existing = next[actionId] ?? [];
208
- if (!existing.includes(key)) {
209
- next[actionId] = [...existing, key];
202
+ const existing = prev[actionId] ?? [];
203
+ if (existing.includes(key)) {
204
+ const filtered = existing.filter((k) => k !== key);
205
+ if (filtered.length === 0) {
206
+ const { [actionId]: _, ...rest } = prev;
207
+ return rest;
210
208
  }
209
+ return { ...prev, [actionId]: filtered };
211
210
  }
212
- return next;
211
+ return prev;
212
+ });
213
+ } else {
214
+ updateOverrides((prev) => ({
215
+ ...prev,
216
+ [key]: actionId
217
+ }));
218
+ }
219
+ }, [updateOverrides, updateRemovedDefaults, isDefaultBinding]);
220
+ const removeBinding = react.useCallback((actionId, key) => {
221
+ const action = actionsRef.current.get(actionId);
222
+ const isDefault = action?.config.defaultBindings?.includes(key);
223
+ if (isDefault) {
224
+ updateRemovedDefaults((prev) => {
225
+ const existing = prev[actionId] ?? [];
226
+ if (existing.includes(key)) return prev;
227
+ return { ...prev, [actionId]: [...existing, key] };
213
228
  });
214
229
  }
215
230
  updateOverrides((prev) => {
216
- const { [key]: _, ...rest } = prev;
217
- return rest;
231
+ const boundAction = prev[key];
232
+ if (boundAction === actionId) {
233
+ const { [key]: _, ...rest } = prev;
234
+ return rest;
235
+ }
236
+ if (Array.isArray(boundAction) && boundAction.includes(actionId)) {
237
+ const newActions = boundAction.filter((a) => a !== actionId);
238
+ if (newActions.length === 0) {
239
+ const { [key]: _, ...rest } = prev;
240
+ return rest;
241
+ }
242
+ return { ...prev, [key]: newActions.length === 1 ? newActions[0] : newActions };
243
+ }
244
+ return prev;
218
245
  });
219
246
  }, [updateOverrides, updateRemovedDefaults]);
220
247
  const resetOverrides = react.useCallback(() => {
@@ -232,6 +259,7 @@ function useActionsRegistry(options = {}) {
232
259
  keymap,
233
260
  actionRegistry,
234
261
  getBindingsForAction,
262
+ getFirstBindingForAction,
235
263
  overrides,
236
264
  setBinding,
237
265
  removeBinding,
@@ -244,15 +272,119 @@ function useActionsRegistry(options = {}) {
244
272
  keymap,
245
273
  actionRegistry,
246
274
  getBindingsForAction,
275
+ getFirstBindingForAction,
247
276
  overrides,
248
277
  setBinding,
249
278
  removeBinding,
250
279
  resetOverrides
251
280
  ]);
252
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
+ }
346
+
347
+ // src/constants.ts
348
+ var DEFAULT_SEQUENCE_TIMEOUT = Infinity;
349
+ var ACTION_MODAL = "__hotkeys:modal";
350
+ var ACTION_OMNIBAR = "__hotkeys:omnibar";
351
+ var ACTION_LOOKUP = "__hotkeys:lookup";
352
+
353
+ // src/utils.ts
354
+ var { max } = Math;
355
+ var SHIFTED_SYMBOLS = /* @__PURE__ */ new Set([
356
+ "!",
357
+ "@",
358
+ "#",
359
+ "$",
360
+ "%",
361
+ "^",
362
+ "&",
363
+ "*",
364
+ "(",
365
+ ")",
366
+ "_",
367
+ "+",
368
+ "{",
369
+ "}",
370
+ "|",
371
+ ":",
372
+ '"',
373
+ "<",
374
+ ">",
375
+ "?",
376
+ "~"
377
+ ]);
378
+ function isShiftedSymbol(key) {
379
+ return SHIFTED_SYMBOLS.has(key);
380
+ }
253
381
  function isMac() {
254
382
  if (typeof navigator === "undefined") return false;
255
- 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);
256
388
  }
257
389
  function normalizeKey(key) {
258
390
  const keyMap = {
@@ -287,7 +419,7 @@ function formatKeyForDisplay(key) {
287
419
  "space": "Space",
288
420
  "escape": "Esc",
289
421
  "enter": "\u21B5",
290
- "tab": "Tab",
422
+ "tab": "\u21E5",
291
423
  "backspace": "\u232B",
292
424
  "delete": "Del",
293
425
  "arrowup": "\u2191",
@@ -310,7 +442,18 @@ function formatKeyForDisplay(key) {
310
442
  }
311
443
  return key;
312
444
  }
445
+ var DIGIT_PLACEHOLDER = "__DIGIT__";
446
+ var DIGITS_PLACEHOLDER = "__DIGITS__";
447
+ function isPlaceholderSentinel(key) {
448
+ return key === DIGIT_PLACEHOLDER || key === DIGITS_PLACEHOLDER;
449
+ }
313
450
  function formatSingleCombination(combo) {
451
+ if (combo.key === DIGIT_PLACEHOLDER) {
452
+ return { display: "#", id: "\\d" };
453
+ }
454
+ if (combo.key === DIGITS_PLACEHOLDER) {
455
+ return { display: "##", id: "\\d+" };
456
+ }
314
457
  const mac = isMac();
315
458
  const parts = [];
316
459
  const idParts = [];
@@ -356,6 +499,10 @@ function formatCombination(input) {
356
499
  const single = formatSingleCombination(input);
357
500
  return { ...single, isSequence: false };
358
501
  }
502
+ function formatBinding(binding) {
503
+ const parsed = parseHotkeyString(binding);
504
+ return formatCombination(parsed).display;
505
+ }
359
506
  function isModifierKey(key) {
360
507
  return ["Control", "Alt", "Shift", "Meta"].includes(key);
361
508
  }
@@ -412,12 +559,110 @@ function parseHotkeyString(hotkeyStr) {
412
559
  const parts = hotkeyStr.trim().split(/\s+/);
413
560
  return parts.map(parseSingleCombination);
414
561
  }
415
- function parseCombinationId(id) {
416
- const sequence = parseHotkeyString(id);
417
- if (sequence.length === 0) {
418
- return { key: "", modifiers: { ctrl: false, alt: false, shift: false, meta: false } };
562
+ var NO_MODIFIERS = { ctrl: false, alt: false, shift: false, meta: false };
563
+ function parseSeqElem(str) {
564
+ if (str === "\\d") {
565
+ return { type: "digit" };
566
+ }
567
+ if (str === "\\d+") {
568
+ return { type: "digits" };
569
+ }
570
+ if (str.length === 1 && /^[A-Z]$/.test(str)) {
571
+ return {
572
+ type: "key",
573
+ key: str.toLowerCase(),
574
+ modifiers: { ctrl: false, alt: false, shift: true, meta: false }
575
+ };
576
+ }
577
+ const parts = str.toLowerCase().split("+");
578
+ const key = parts[parts.length - 1];
579
+ return {
580
+ type: "key",
581
+ key,
582
+ modifiers: {
583
+ ctrl: parts.includes("ctrl") || parts.includes("control"),
584
+ alt: parts.includes("alt") || parts.includes("option"),
585
+ shift: parts.includes("shift"),
586
+ meta: parts.includes("meta") || parts.includes("cmd") || parts.includes("command")
587
+ }
588
+ };
589
+ }
590
+ function parseKeySeq(hotkeyStr) {
591
+ if (!hotkeyStr.trim()) return [];
592
+ const parts = hotkeyStr.trim().split(/\s+/);
593
+ return parts.map(parseSeqElem);
594
+ }
595
+ function formatSeqElem(elem) {
596
+ if (elem.type === "digit") {
597
+ return { display: "\u27E8#\u27E9", id: "\\d" };
598
+ }
599
+ if (elem.type === "digits") {
600
+ return { display: "\u27E8##\u27E9", id: "\\d+" };
601
+ }
602
+ const mac = isMac();
603
+ const parts = [];
604
+ const idParts = [];
605
+ if (elem.modifiers.ctrl) {
606
+ parts.push(mac ? "\u2303" : "Ctrl");
607
+ idParts.push("ctrl");
608
+ }
609
+ if (elem.modifiers.meta) {
610
+ parts.push(mac ? "\u2318" : "Win");
611
+ idParts.push("meta");
612
+ }
613
+ if (elem.modifiers.alt) {
614
+ parts.push(mac ? "\u2325" : "Alt");
615
+ idParts.push("alt");
616
+ }
617
+ if (elem.modifiers.shift) {
618
+ parts.push(mac ? "\u21E7" : "Shift");
619
+ idParts.push("shift");
620
+ }
621
+ parts.push(formatKeyForDisplay(elem.key));
622
+ idParts.push(elem.key);
623
+ return {
624
+ display: mac ? parts.join("") : parts.join("+"),
625
+ id: idParts.join("+")
626
+ };
627
+ }
628
+ function formatKeySeq(seq) {
629
+ if (seq.length === 0) {
630
+ return { display: "", id: "", isSequence: false };
631
+ }
632
+ const formatted = seq.map(formatSeqElem);
633
+ if (seq.length === 1) {
634
+ return { ...formatted[0], isSequence: false };
419
635
  }
420
- return sequence[0];
636
+ return {
637
+ display: formatted.map((f) => f.display).join(" "),
638
+ id: formatted.map((f) => f.id).join(" "),
639
+ isSequence: true
640
+ };
641
+ }
642
+ function hasDigitPlaceholders(seq) {
643
+ return seq.some((elem) => elem.type === "digit" || elem.type === "digits");
644
+ }
645
+ function keySeqToHotkeySequence(seq) {
646
+ return seq.map((elem) => {
647
+ if (elem.type === "digit") {
648
+ return { key: "\\d", modifiers: NO_MODIFIERS };
649
+ }
650
+ if (elem.type === "digits") {
651
+ return { key: "\\d+", modifiers: NO_MODIFIERS };
652
+ }
653
+ return { key: elem.key, modifiers: elem.modifiers };
654
+ });
655
+ }
656
+ function hotkeySequenceToKeySeq(seq) {
657
+ return seq.map((combo) => {
658
+ if (combo.key === "\\d" && !combo.modifiers.ctrl && !combo.modifiers.alt && !combo.modifiers.shift && !combo.modifiers.meta) {
659
+ return { type: "digit" };
660
+ }
661
+ if (combo.key === "\\d+" && !combo.modifiers.ctrl && !combo.modifiers.alt && !combo.modifiers.shift && !combo.modifiers.meta) {
662
+ return { type: "digits" };
663
+ }
664
+ return { type: "key", key: combo.key, modifiers: combo.modifiers };
665
+ });
421
666
  }
422
667
  function isPrefix(a, b) {
423
668
  if (a.length >= b.length) return false;
@@ -429,11 +674,50 @@ function isPrefix(a, b) {
429
674
  function combinationsEqual(a, b) {
430
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;
431
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
+ }
684
+ function isDigitKey(key) {
685
+ return /^[0-9]$/.test(key);
686
+ }
687
+ function seqElemsCouldConflict(a, b) {
688
+ if (a.type === "digit" && b.type === "digit") return true;
689
+ if (a.type === "digit" && b.type === "key" && isDigitKey(b.key)) return true;
690
+ if (a.type === "key" && isDigitKey(a.key) && b.type === "digit") return true;
691
+ if (a.type === "digits" && b.type === "digits") return true;
692
+ if (a.type === "digits" && b.type === "digit") return true;
693
+ if (a.type === "digit" && b.type === "digits") return true;
694
+ if (a.type === "digits" && b.type === "key" && isDigitKey(b.key)) return true;
695
+ if (a.type === "key" && isDigitKey(a.key) && b.type === "digits") return true;
696
+ if (a.type === "key" && b.type === "key") {
697
+ 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;
698
+ }
699
+ return false;
700
+ }
701
+ function keySeqIsPrefix(a, b) {
702
+ if (a.length >= b.length) return false;
703
+ for (let i = 0; i < a.length; i++) {
704
+ if (!seqElemsCouldConflict(a[i], b[i])) return false;
705
+ }
706
+ return true;
707
+ }
708
+ function keySeqsCouldConflict(a, b) {
709
+ if (a.length !== b.length) return false;
710
+ for (let i = 0; i < a.length; i++) {
711
+ if (!seqElemsCouldConflict(a[i], b[i])) return false;
712
+ }
713
+ return true;
714
+ }
432
715
  function findConflicts(keymap) {
433
716
  const conflicts = /* @__PURE__ */ new Map();
434
717
  const entries = Object.entries(keymap).map(([key, actionOrActions]) => ({
435
718
  key,
436
719
  sequence: parseHotkeyString(key),
720
+ keySeq: parseKeySeq(key),
437
721
  actions: Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
438
722
  }));
439
723
  const keyToActions = /* @__PURE__ */ new Map();
@@ -450,7 +734,36 @@ function findConflicts(keymap) {
450
734
  for (let j = i + 1; j < entries.length; j++) {
451
735
  const a = entries[i];
452
736
  const b = entries[j];
453
- if (isPrefix(a.sequence, b.sequence)) {
737
+ if (keySeqsCouldConflict(a.keySeq, b.keySeq) && a.key !== b.key) {
738
+ const existingA = conflicts.get(a.key) ?? [];
739
+ if (!existingA.includes(`conflicts with: ${b.key}`)) {
740
+ conflicts.set(a.key, [...existingA, ...a.actions, `conflicts with: ${b.key}`]);
741
+ }
742
+ const existingB = conflicts.get(b.key) ?? [];
743
+ if (!existingB.includes(`conflicts with: ${a.key}`)) {
744
+ conflicts.set(b.key, [...existingB, ...b.actions, `conflicts with: ${a.key}`]);
745
+ }
746
+ continue;
747
+ }
748
+ if (keySeqIsPrefix(a.keySeq, b.keySeq)) {
749
+ const existingA = conflicts.get(a.key) ?? [];
750
+ if (!existingA.includes(`prefix of: ${b.key}`)) {
751
+ conflicts.set(a.key, [...existingA, ...a.actions, `prefix of: ${b.key}`]);
752
+ }
753
+ const existingB = conflicts.get(b.key) ?? [];
754
+ if (!existingB.includes(`has prefix: ${a.key}`)) {
755
+ conflicts.set(b.key, [...existingB, ...b.actions, `has prefix: ${a.key}`]);
756
+ }
757
+ } else if (keySeqIsPrefix(b.keySeq, a.keySeq)) {
758
+ const existingB = conflicts.get(b.key) ?? [];
759
+ if (!existingB.includes(`prefix of: ${a.key}`)) {
760
+ conflicts.set(b.key, [...existingB, ...b.actions, `prefix of: ${a.key}`]);
761
+ }
762
+ const existingA = conflicts.get(a.key) ?? [];
763
+ if (!existingA.includes(`has prefix: ${b.key}`)) {
764
+ conflicts.set(a.key, [...existingA, ...a.actions, `has prefix: ${b.key}`]);
765
+ }
766
+ } else if (isPrefix(a.sequence, b.sequence)) {
454
767
  const existingA = conflicts.get(a.key) ?? [];
455
768
  if (!existingA.includes(`prefix of: ${b.key}`)) {
456
769
  conflicts.set(a.key, [...existingA, ...a.actions, `prefix of: ${b.key}`]);
@@ -488,27 +801,77 @@ function getSequenceCompletions(pendingKeys, keymap) {
488
801
  if (pendingKeys.length === 0) return [];
489
802
  const completions = [];
490
803
  for (const [hotkeyStr, actionOrActions] of Object.entries(keymap)) {
491
- const sequence = parseHotkeyString(hotkeyStr);
492
- if (sequence.length <= pendingKeys.length) continue;
493
- let isPrefix2 = true;
494
- for (let i = 0; i < pendingKeys.length; i++) {
495
- if (!combinationsEqual(pendingKeys[i], sequence[i])) {
496
- isPrefix2 = false;
497
- break;
804
+ const keySeq = parseKeySeq(hotkeyStr);
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++;
498
841
  }
499
842
  }
500
- if (isPrefix2) {
501
- const remainingKeys = sequence.slice(pendingKeys.length);
502
- const nextKeys = formatCombination(remainingKeys).id;
503
- const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
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);
859
+ const nextKeys = formatKeySeq(remainingKeySeq).display;
504
860
  completions.push({
505
861
  nextKeys,
862
+ nextKeySeq: remainingKeySeq,
506
863
  fullSequence: hotkeyStr,
507
- display: formatCombination(sequence),
508
- actions
864
+ display: formatKeySeq(keySeq),
865
+ actions,
866
+ isComplete: false,
867
+ captures: captures.length > 0 ? captures : void 0
509
868
  });
510
869
  }
511
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
+ });
512
875
  return completions;
513
876
  }
514
877
  function getActionBindings(keymap) {
@@ -593,13 +956,13 @@ function searchActions(query, actions, keymap) {
593
956
  for (const keyword of action.keywords) {
594
957
  const kwMatch = fuzzyMatch(query, keyword);
595
958
  if (kwMatch.matched) {
596
- keywordScore = base.max(keywordScore, kwMatch.score);
959
+ keywordScore = max(keywordScore, kwMatch.score);
597
960
  }
598
961
  }
599
962
  }
600
963
  const matched = labelMatch.matched || descMatch.matched || groupMatch.matched || idMatch.matched || keywordScore > 0;
601
964
  if (!matched && query) continue;
602
- 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;
603
966
  results.push({
604
967
  id,
605
968
  action,
@@ -646,6 +1009,95 @@ function sequencesMatch(a, b) {
646
1009
  }
647
1010
  return true;
648
1011
  }
1012
+ function isDigit(key) {
1013
+ return /^[0-9]$/.test(key);
1014
+ }
1015
+ function initMatchState(seq) {
1016
+ return seq.map((elem) => {
1017
+ if (elem.type === "digit") return { type: "digit" };
1018
+ if (elem.type === "digits") return { type: "digits" };
1019
+ return { type: "key", key: elem.key, modifiers: elem.modifiers };
1020
+ });
1021
+ }
1022
+ function matchesKeyElem(combo, elem) {
1023
+ const shiftMatches = isShiftedChar(combo.key) ? elem.modifiers.shift ? combo.modifiers.shift : true : combo.modifiers.shift === elem.modifiers.shift;
1024
+ return combo.modifiers.ctrl === elem.modifiers.ctrl && combo.modifiers.alt === elem.modifiers.alt && shiftMatches && combo.modifiers.meta === elem.modifiers.meta && combo.key === elem.key;
1025
+ }
1026
+ function advanceMatchState(state, pattern, combo) {
1027
+ const newState = [...state];
1028
+ let pos = 0;
1029
+ for (let i = 0; i < state.length; i++) {
1030
+ const elem = state[i];
1031
+ if (elem.type === "key" && !elem.matched) break;
1032
+ if (elem.type === "digit" && elem.value === void 0) break;
1033
+ if (elem.type === "digits" && elem.value === void 0) {
1034
+ if (!elem.partial) break;
1035
+ if (isDigit(combo.key)) {
1036
+ const newPartial = (elem.partial || "") + combo.key;
1037
+ newState[i] = { type: "digits", partial: newPartial };
1038
+ return { status: "partial", state: newState };
1039
+ } else {
1040
+ const digitValue = parseInt(elem.partial, 10);
1041
+ newState[i] = { type: "digits", value: digitValue };
1042
+ pos = i + 1;
1043
+ if (pos >= pattern.length) {
1044
+ return { status: "failed" };
1045
+ }
1046
+ break;
1047
+ }
1048
+ }
1049
+ pos++;
1050
+ }
1051
+ if (pos >= pattern.length) {
1052
+ return { status: "failed" };
1053
+ }
1054
+ const currentPattern = pattern[pos];
1055
+ if (currentPattern.type === "digit") {
1056
+ if (!isDigit(combo.key) || combo.modifiers.ctrl || combo.modifiers.alt || combo.modifiers.meta) {
1057
+ return { status: "failed" };
1058
+ }
1059
+ newState[pos] = { type: "digit", value: parseInt(combo.key, 10) };
1060
+ } else if (currentPattern.type === "digits") {
1061
+ if (!isDigit(combo.key) || combo.modifiers.ctrl || combo.modifiers.alt || combo.modifiers.meta) {
1062
+ return { status: "failed" };
1063
+ }
1064
+ newState[pos] = { type: "digits", partial: combo.key };
1065
+ } else {
1066
+ if (!matchesKeyElem(combo, currentPattern)) {
1067
+ return { status: "failed" };
1068
+ }
1069
+ newState[pos] = { type: "key", key: currentPattern.key, modifiers: currentPattern.modifiers, matched: true };
1070
+ }
1071
+ const isComplete = newState.every((elem) => {
1072
+ if (elem.type === "key") return elem.matched === true;
1073
+ if (elem.type === "digit") return elem.value !== void 0;
1074
+ if (elem.type === "digits") return elem.value !== void 0;
1075
+ return false;
1076
+ });
1077
+ if (isComplete) {
1078
+ const captures = newState.filter(
1079
+ (e) => (e.type === "digit" || e.type === "digits") && e.value !== void 0
1080
+ ).map((e) => e.value);
1081
+ return { status: "matched", state: newState, captures };
1082
+ }
1083
+ return { status: "partial", state: newState };
1084
+ }
1085
+ function isCollectingDigits(state) {
1086
+ return state.some((elem) => elem.type === "digits" && elem.partial !== void 0 && elem.value === void 0);
1087
+ }
1088
+ function finalizeDigits(state) {
1089
+ return state.map((elem) => {
1090
+ if (elem.type === "digits" && elem.partial !== void 0 && elem.value === void 0) {
1091
+ return { type: "digits", value: parseInt(elem.partial, 10) };
1092
+ }
1093
+ return elem;
1094
+ });
1095
+ }
1096
+ function extractMatchCaptures(state) {
1097
+ return state.filter(
1098
+ (e) => (e.type === "digit" || e.type === "digits") && e.value !== void 0
1099
+ ).map((e) => e.value);
1100
+ }
649
1101
  function useHotkeys(keymap, handlers, options = {}) {
650
1102
  const {
651
1103
  enabled = true,
@@ -653,7 +1105,7 @@ function useHotkeys(keymap, handlers, options = {}) {
653
1105
  preventDefault = true,
654
1106
  stopPropagation = true,
655
1107
  enableOnFormTags = false,
656
- sequenceTimeout = 1e3,
1108
+ sequenceTimeout = DEFAULT_SEQUENCE_TIMEOUT,
657
1109
  onTimeout = "submit",
658
1110
  onSequenceStart,
659
1111
  onSequenceProgress,
@@ -669,11 +1121,13 @@ function useHotkeys(keymap, handlers, options = {}) {
669
1121
  const timeoutRef = react.useRef(null);
670
1122
  const pendingKeysRef = react.useRef([]);
671
1123
  pendingKeysRef.current = pendingKeys;
1124
+ const matchStatesRef = react.useRef(/* @__PURE__ */ new Map());
672
1125
  const parsedKeymapRef = react.useRef([]);
673
1126
  react.useEffect(() => {
674
1127
  parsedKeymapRef.current = Object.entries(keymap).map(([key, actionOrActions]) => ({
675
1128
  key,
676
1129
  sequence: parseHotkeyString(key),
1130
+ keySeq: parseKeySeq(key),
677
1131
  actions: Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
678
1132
  }));
679
1133
  }, [keymap]);
@@ -681,6 +1135,7 @@ function useHotkeys(keymap, handlers, options = {}) {
681
1135
  setPendingKeys([]);
682
1136
  setIsAwaitingSequence(false);
683
1137
  setTimeoutStartedAt(null);
1138
+ matchStatesRef.current.clear();
684
1139
  if (timeoutRef.current) {
685
1140
  clearTimeout(timeoutRef.current);
686
1141
  timeoutRef.current = null;
@@ -690,7 +1145,7 @@ function useHotkeys(keymap, handlers, options = {}) {
690
1145
  clearPending();
691
1146
  onSequenceCancel?.();
692
1147
  }, [clearPending, onSequenceCancel]);
693
- const tryExecute = react.useCallback((sequence, e) => {
1148
+ const tryExecute = react.useCallback((sequence, e, captures) => {
694
1149
  for (const entry of parsedKeymapRef.current) {
695
1150
  if (sequencesMatch(sequence, entry.sequence)) {
696
1151
  for (const action of entry.actions) {
@@ -702,7 +1157,27 @@ function useHotkeys(keymap, handlers, options = {}) {
702
1157
  if (stopPropagation) {
703
1158
  e.stopPropagation();
704
1159
  }
705
- handler(e);
1160
+ handler(e, captures);
1161
+ return true;
1162
+ }
1163
+ }
1164
+ }
1165
+ }
1166
+ return false;
1167
+ }, [preventDefault, stopPropagation]);
1168
+ const tryExecuteKeySeq = react.useCallback((matchKey, captures, e) => {
1169
+ for (const entry of parsedKeymapRef.current) {
1170
+ if (entry.key === matchKey) {
1171
+ for (const action of entry.actions) {
1172
+ const handler = handlersRef.current[action];
1173
+ if (handler) {
1174
+ if (preventDefault) {
1175
+ e.preventDefault();
1176
+ }
1177
+ if (stopPropagation) {
1178
+ e.stopPropagation();
1179
+ }
1180
+ handler(e, captures.length > 0 ? captures : void 0);
706
1181
  return true;
707
1182
  }
708
1183
  }
@@ -732,7 +1207,8 @@ function useHotkeys(keymap, handlers, options = {}) {
732
1207
  const handleKeyDown = (e) => {
733
1208
  if (!enableOnFormTags) {
734
1209
  const eventTarget = e.target;
735
- if (eventTarget instanceof HTMLInputElement || eventTarget instanceof HTMLTextAreaElement || eventTarget instanceof HTMLSelectElement || eventTarget.isContentEditable) {
1210
+ const isTextInput = eventTarget instanceof HTMLInputElement && ["text", "email", "password", "search", "tel", "url", "number", "date", "datetime-local", "month", "time", "week"].includes(eventTarget.type);
1211
+ if (isTextInput || eventTarget instanceof HTMLTextAreaElement || eventTarget instanceof HTMLSelectElement || eventTarget.isContentEditable) {
736
1212
  return;
737
1213
  }
738
1214
  }
@@ -745,7 +1221,24 @@ function useHotkeys(keymap, handlers, options = {}) {
745
1221
  }
746
1222
  if (e.key === "Enter" && pendingKeysRef.current.length > 0) {
747
1223
  e.preventDefault();
748
- 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
+ }
749
1242
  clearPending();
750
1243
  if (!executed) {
751
1244
  onSequenceCancel?.();
@@ -758,49 +1251,173 @@ function useHotkeys(keymap, handlers, options = {}) {
758
1251
  return;
759
1252
  }
760
1253
  const currentCombo = eventToCombination(e);
761
- const newSequence = [...pendingKeysRef.current, currentCombo];
762
- const exactMatch = tryExecute(newSequence, e);
763
- if (exactMatch) {
764
- clearPending();
765
- return;
766
- }
767
- if (hasPotentialMatch(newSequence)) {
768
- if (hasSequenceExtension(newSequence)) {
769
- setPendingKeys(newSequence);
770
- setIsAwaitingSequence(true);
771
- if (pendingKeysRef.current.length === 0) {
772
- onSequenceStart?.(newSequence);
773
- } else {
774
- onSequenceProgress?.(newSequence);
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);
775
1260
  }
776
- setTimeoutStartedAt(Date.now());
777
- timeoutRef.current = setTimeout(() => {
778
- if (onTimeout === "submit") {
779
- setPendingKeys((current) => {
780
- if (current.length > 0) {
781
- onSequenceCancel?.();
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);
782
1284
  }
783
- return [];
784
- });
785
- setIsAwaitingSequence(false);
786
- setTimeoutStartedAt(null);
787
- } else {
788
- setPendingKeys([]);
789
- setIsAwaitingSequence(false);
790
- setTimeoutStartedAt(null);
791
- onSequenceCancel?.();
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
+ }
792
1292
  }
793
- timeoutRef.current = null;
794
- }, sequenceTimeout);
795
- if (preventDefault) {
796
- e.preventDefault();
797
1293
  }
798
1294
  return;
799
1295
  }
800
1296
  }
801
- if (pendingKeysRef.current.length > 0) {
802
- clearPending();
803
- onSequenceCancel?.();
1297
+ const newSequence = [...pendingKeysRef.current, currentCombo];
1298
+ const completeMatches = [];
1299
+ let hasPartials = false;
1300
+ const matchStates = matchStatesRef.current;
1301
+ const hadPartialMatches = matchStates.size > 0;
1302
+ for (const entry of parsedKeymapRef.current) {
1303
+ let state = matchStates.get(entry.key);
1304
+ if (hadPartialMatches && !state) {
1305
+ continue;
1306
+ }
1307
+ if (!state) {
1308
+ state = initMatchState(entry.keySeq);
1309
+ matchStates.set(entry.key, state);
1310
+ }
1311
+ const result = advanceMatchState(state, entry.keySeq, currentCombo);
1312
+ if (result.status === "matched") {
1313
+ completeMatches.push({
1314
+ key: entry.key,
1315
+ state: result.state,
1316
+ captures: result.captures
1317
+ });
1318
+ matchStates.delete(entry.key);
1319
+ } else if (result.status === "partial") {
1320
+ matchStates.set(entry.key, result.state);
1321
+ hasPartials = true;
1322
+ } else {
1323
+ matchStates.delete(entry.key);
1324
+ }
1325
+ }
1326
+ if (completeMatches.length === 1 && !hasPartials) {
1327
+ const match = completeMatches[0];
1328
+ if (tryExecuteKeySeq(match.key, match.captures, e)) {
1329
+ clearPending();
1330
+ return;
1331
+ }
1332
+ }
1333
+ if (completeMatches.length > 0 || hasPartials) {
1334
+ setPendingKeys(newSequence);
1335
+ setIsAwaitingSequence(true);
1336
+ if (pendingKeysRef.current.length === 0) {
1337
+ onSequenceStart?.(newSequence);
1338
+ } else {
1339
+ onSequenceProgress?.(newSequence);
1340
+ }
1341
+ if (preventDefault) {
1342
+ e.preventDefault();
1343
+ }
1344
+ if (Number.isFinite(sequenceTimeout)) {
1345
+ setTimeoutStartedAt(Date.now());
1346
+ timeoutRef.current = setTimeout(() => {
1347
+ for (const [key, state] of matchStates.entries()) {
1348
+ if (isCollectingDigits(state)) {
1349
+ const finalizedState = finalizeDigits(state);
1350
+ const entry = parsedKeymapRef.current.find((e2) => e2.key === key);
1351
+ if (entry) {
1352
+ const isComplete = finalizedState.every((elem) => {
1353
+ if (elem.type === "key") return elem.matched === true;
1354
+ if (elem.type === "digit") return elem.value !== void 0;
1355
+ if (elem.type === "digits") return elem.value !== void 0;
1356
+ return false;
1357
+ });
1358
+ if (isComplete) {
1359
+ void extractMatchCaptures(finalizedState);
1360
+ }
1361
+ }
1362
+ }
1363
+ }
1364
+ setPendingKeys([]);
1365
+ setIsAwaitingSequence(false);
1366
+ setTimeoutStartedAt(null);
1367
+ matchStatesRef.current.clear();
1368
+ onSequenceCancel?.();
1369
+ timeoutRef.current = null;
1370
+ }, sequenceTimeout);
1371
+ }
1372
+ return;
1373
+ }
1374
+ const exactMatch = tryExecute(newSequence, e);
1375
+ if (exactMatch) {
1376
+ clearPending();
1377
+ return;
1378
+ }
1379
+ if (hasPotentialMatch(newSequence)) {
1380
+ if (hasSequenceExtension(newSequence)) {
1381
+ setPendingKeys(newSequence);
1382
+ setIsAwaitingSequence(true);
1383
+ if (pendingKeysRef.current.length === 0) {
1384
+ onSequenceStart?.(newSequence);
1385
+ } else {
1386
+ onSequenceProgress?.(newSequence);
1387
+ }
1388
+ if (Number.isFinite(sequenceTimeout)) {
1389
+ setTimeoutStartedAt(Date.now());
1390
+ timeoutRef.current = setTimeout(() => {
1391
+ if (onTimeout === "submit") {
1392
+ setPendingKeys((current) => {
1393
+ if (current.length > 0) {
1394
+ onSequenceCancel?.();
1395
+ }
1396
+ return [];
1397
+ });
1398
+ setIsAwaitingSequence(false);
1399
+ setTimeoutStartedAt(null);
1400
+ } else {
1401
+ setPendingKeys([]);
1402
+ setIsAwaitingSequence(false);
1403
+ setTimeoutStartedAt(null);
1404
+ onSequenceCancel?.();
1405
+ }
1406
+ timeoutRef.current = null;
1407
+ }, sequenceTimeout);
1408
+ }
1409
+ if (preventDefault) {
1410
+ e.preventDefault();
1411
+ }
1412
+ return;
1413
+ }
1414
+ }
1415
+ if (pendingKeysRef.current.length > 0) {
1416
+ setPendingKeys(newSequence);
1417
+ if (preventDefault) {
1418
+ e.preventDefault();
1419
+ }
1420
+ return;
804
1421
  }
805
1422
  const singleMatch = tryExecute([currentCombo], e);
806
1423
  if (!singleMatch) {
@@ -811,21 +1428,23 @@ function useHotkeys(keymap, handlers, options = {}) {
811
1428
  if (preventDefault) {
812
1429
  e.preventDefault();
813
1430
  }
814
- setTimeoutStartedAt(Date.now());
815
- timeoutRef.current = setTimeout(() => {
816
- if (onTimeout === "submit") {
817
- setPendingKeys([]);
818
- setIsAwaitingSequence(false);
819
- setTimeoutStartedAt(null);
820
- onSequenceCancel?.();
821
- } else {
822
- setPendingKeys([]);
823
- setIsAwaitingSequence(false);
824
- setTimeoutStartedAt(null);
825
- onSequenceCancel?.();
826
- }
827
- timeoutRef.current = null;
828
- }, sequenceTimeout);
1431
+ if (Number.isFinite(sequenceTimeout)) {
1432
+ setTimeoutStartedAt(Date.now());
1433
+ timeoutRef.current = setTimeout(() => {
1434
+ if (onTimeout === "submit") {
1435
+ setPendingKeys([]);
1436
+ setIsAwaitingSequence(false);
1437
+ setTimeoutStartedAt(null);
1438
+ onSequenceCancel?.();
1439
+ } else {
1440
+ setPendingKeys([]);
1441
+ setIsAwaitingSequence(false);
1442
+ setTimeoutStartedAt(null);
1443
+ onSequenceCancel?.();
1444
+ }
1445
+ timeoutRef.current = null;
1446
+ }, sequenceTimeout);
1447
+ }
829
1448
  }
830
1449
  }
831
1450
  };
@@ -847,6 +1466,7 @@ function useHotkeys(keymap, handlers, options = {}) {
847
1466
  clearPending,
848
1467
  cancelSequence,
849
1468
  tryExecute,
1469
+ tryExecuteKeySeq,
850
1470
  hasPotentialMatch,
851
1471
  hasSequenceExtension,
852
1472
  onSequenceStart,
@@ -858,12 +1478,11 @@ function useHotkeys(keymap, handlers, options = {}) {
858
1478
  var HotkeysContext = react.createContext(null);
859
1479
  var DEFAULT_CONFIG = {
860
1480
  storageKey: "use-kbd",
861
- sequenceTimeout: 1e3,
862
- disableConflicts: true,
1481
+ sequenceTimeout: DEFAULT_SEQUENCE_TIMEOUT,
1482
+ disableConflicts: false,
1483
+ // Keep conflicting bindings active; SeqM handles disambiguation
863
1484
  minViewportWidth: 768,
864
- enableOnTouch: false,
865
- modalTrigger: "?",
866
- omnibarTrigger: "meta+k"
1485
+ enableOnTouch: false
867
1486
  };
868
1487
  function HotkeysProvider({
869
1488
  config: configProp = {},
@@ -874,6 +1493,7 @@ function HotkeysProvider({
874
1493
  ...configProp
875
1494
  }), [configProp]);
876
1495
  const registry = useActionsRegistry({ storageKey: config.storageKey });
1496
+ const endpointsRegistry = useOmnibarEndpointsRegistry();
877
1497
  const [isEnabled, setIsEnabled] = react.useState(true);
878
1498
  react.useEffect(() => {
879
1499
  if (typeof window === "undefined") return;
@@ -912,16 +1532,12 @@ function HotkeysProvider({
912
1532
  const openOmnibar = react.useCallback(() => setIsOmnibarOpen(true), []);
913
1533
  const closeOmnibar = react.useCallback(() => setIsOmnibarOpen(false), []);
914
1534
  const toggleOmnibar = react.useCallback(() => setIsOmnibarOpen((prev) => !prev), []);
915
- const keymap = react.useMemo(() => {
916
- const map = { ...registry.keymap };
917
- if (config.modalTrigger !== false) {
918
- map[config.modalTrigger] = "__hotkeys:modal";
919
- }
920
- if (config.omnibarTrigger !== false) {
921
- map[config.omnibarTrigger] = "__hotkeys:omnibar";
922
- }
923
- return map;
924
- }, [registry.keymap, config.modalTrigger, config.omnibarTrigger]);
1535
+ const [isLookupOpen, setIsLookupOpen] = react.useState(false);
1536
+ const openLookup = react.useCallback(() => setIsLookupOpen(true), []);
1537
+ const closeLookup = react.useCallback(() => setIsLookupOpen(false), []);
1538
+ const toggleLookup = react.useCallback(() => setIsLookupOpen((prev) => !prev), []);
1539
+ const [isEditingBinding, setIsEditingBinding] = react.useState(false);
1540
+ const keymap = registry.keymap;
925
1541
  const conflicts = react.useMemo(() => findConflicts(keymap), [keymap]);
926
1542
  const hasConflicts2 = conflicts.size > 0;
927
1543
  const effectiveKeymap = react.useMemo(() => {
@@ -941,20 +1557,24 @@ function HotkeysProvider({
941
1557
  for (const [id, action] of registry.actions) {
942
1558
  map[id] = action.config.handler;
943
1559
  }
944
- map["__hotkeys:modal"] = toggleModal;
945
- map["__hotkeys:omnibar"] = toggleOmnibar;
946
1560
  return map;
947
- }, [registry.actions, toggleModal, toggleOmnibar]);
948
- const hotkeysEnabled = isEnabled && !isModalOpen && !isOmnibarOpen;
1561
+ }, [registry.actions]);
1562
+ const hotkeysEnabled = isEnabled && !isEditingBinding && !isOmnibarOpen && !isLookupOpen;
949
1563
  const {
950
1564
  pendingKeys,
951
1565
  isAwaitingSequence,
1566
+ cancelSequence,
952
1567
  timeoutStartedAt: sequenceTimeoutStartedAt,
953
1568
  sequenceTimeout
954
1569
  } = useHotkeys(effectiveKeymap, handlers, {
955
1570
  enabled: hotkeysEnabled,
956
1571
  sequenceTimeout: config.sequenceTimeout
957
1572
  });
1573
+ react.useEffect(() => {
1574
+ if (isAwaitingSequence && isModalOpen) {
1575
+ closeModal();
1576
+ }
1577
+ }, [isAwaitingSequence, isModalOpen, closeModal]);
958
1578
  const searchActionsHelper = react.useCallback(
959
1579
  (query) => searchActions(query, registry.actionRegistry, keymap),
960
1580
  [registry.actionRegistry, keymap]
@@ -965,6 +1585,7 @@ function HotkeysProvider({
965
1585
  );
966
1586
  const value = react.useMemo(() => ({
967
1587
  registry,
1588
+ endpointsRegistry,
968
1589
  isEnabled,
969
1590
  isModalOpen,
970
1591
  openModal,
@@ -974,9 +1595,16 @@ function HotkeysProvider({
974
1595
  openOmnibar,
975
1596
  closeOmnibar,
976
1597
  toggleOmnibar,
1598
+ isLookupOpen,
1599
+ openLookup,
1600
+ closeLookup,
1601
+ toggleLookup,
1602
+ isEditingBinding,
1603
+ setIsEditingBinding,
977
1604
  executeAction: registry.execute,
978
1605
  pendingKeys,
979
1606
  isAwaitingSequence,
1607
+ cancelSequence,
980
1608
  sequenceTimeoutStartedAt,
981
1609
  sequenceTimeout,
982
1610
  conflicts,
@@ -985,6 +1613,7 @@ function HotkeysProvider({
985
1613
  getCompletions
986
1614
  }), [
987
1615
  registry,
1616
+ endpointsRegistry,
988
1617
  isEnabled,
989
1618
  isModalOpen,
990
1619
  openModal,
@@ -994,8 +1623,14 @@ function HotkeysProvider({
994
1623
  openOmnibar,
995
1624
  closeOmnibar,
996
1625
  toggleOmnibar,
1626
+ isLookupOpen,
1627
+ openLookup,
1628
+ closeLookup,
1629
+ toggleLookup,
1630
+ isEditingBinding,
997
1631
  pendingKeys,
998
1632
  isAwaitingSequence,
1633
+ cancelSequence,
999
1634
  sequenceTimeoutStartedAt,
1000
1635
  sequenceTimeout,
1001
1636
  conflicts,
@@ -1003,7 +1638,7 @@ function HotkeysProvider({
1003
1638
  searchActionsHelper,
1004
1639
  getCompletions
1005
1640
  ]);
1006
- 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 }) }) });
1007
1642
  }
1008
1643
  function useHotkeysContext() {
1009
1644
  const context = react.useContext(HotkeysContext);
@@ -1029,9 +1664,9 @@ function useAction(id, config) {
1029
1664
  react.useEffect(() => {
1030
1665
  registryRef.current.register(id, {
1031
1666
  ...config,
1032
- handler: () => {
1667
+ handler: (e, captures) => {
1033
1668
  if (enabledRef.current) {
1034
- handlerRef.current();
1669
+ handlerRef.current(e, captures);
1035
1670
  }
1036
1671
  }
1037
1672
  });
@@ -1065,9 +1700,9 @@ function useActions(actions) {
1065
1700
  for (const [id, config] of Object.entries(actions)) {
1066
1701
  registryRef.current.register(id, {
1067
1702
  ...config,
1068
- handler: () => {
1703
+ handler: (e, captures) => {
1069
1704
  if (enabledRef.current[id]) {
1070
- handlersRef.current[id]?.();
1705
+ handlersRef.current[id]?.(e, captures);
1071
1706
  }
1072
1707
  }
1073
1708
  });
@@ -1091,6 +1726,38 @@ function useActions(actions) {
1091
1726
  )
1092
1727
  ]);
1093
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
+ }
1094
1761
  function useEventCallback(fn) {
1095
1762
  const ref = react.useRef(fn);
1096
1763
  ref.current = fn;
@@ -1103,7 +1770,7 @@ function useRecordHotkey(options = {}) {
1103
1770
  onTab: onTabProp,
1104
1771
  onShiftTab: onShiftTabProp,
1105
1772
  preventDefault = true,
1106
- sequenceTimeout = 1e3,
1773
+ sequenceTimeout = DEFAULT_SEQUENCE_TIMEOUT,
1107
1774
  pauseTimeout = false
1108
1775
  } = options;
1109
1776
  const onCapture = useEventCallback(onCaptureProp);
@@ -1121,6 +1788,7 @@ function useRecordHotkey(options = {}) {
1121
1788
  const pauseTimeoutRef = react.useRef(pauseTimeout);
1122
1789
  pauseTimeoutRef.current = pauseTimeout;
1123
1790
  const pendingKeysRef = react.useRef([]);
1791
+ const hashCycleRef = react.useRef(0);
1124
1792
  const clearTimeout_ = react.useCallback(() => {
1125
1793
  if (timeoutRef.current) {
1126
1794
  clearTimeout(timeoutRef.current);
@@ -1150,6 +1818,7 @@ function useRecordHotkey(options = {}) {
1150
1818
  pressedKeysRef.current.clear();
1151
1819
  hasNonModifierRef.current = false;
1152
1820
  currentComboRef.current = null;
1821
+ hashCycleRef.current = 0;
1153
1822
  onCancel?.();
1154
1823
  }, [clearTimeout_, onCancel]);
1155
1824
  const commit = react.useCallback(() => {
@@ -1170,6 +1839,7 @@ function useRecordHotkey(options = {}) {
1170
1839
  pressedKeysRef.current.clear();
1171
1840
  hasNonModifierRef.current = false;
1172
1841
  currentComboRef.current = null;
1842
+ hashCycleRef.current = 0;
1173
1843
  return cancel;
1174
1844
  }, [cancel, clearTimeout_]);
1175
1845
  react.useEffect(() => {
@@ -1180,9 +1850,13 @@ function useRecordHotkey(options = {}) {
1180
1850
  }
1181
1851
  } else if (isRecording && pendingKeysRef.current.length > 0 && !timeoutRef.current) {
1182
1852
  const currentSequence = pendingKeysRef.current;
1183
- timeoutRef.current = setTimeout(() => {
1853
+ if (sequenceTimeout === 0) {
1184
1854
  submit(currentSequence);
1185
- }, sequenceTimeout);
1855
+ } else if (Number.isFinite(sequenceTimeout)) {
1856
+ timeoutRef.current = setTimeout(() => {
1857
+ submit(currentSequence);
1858
+ }, sequenceTimeout);
1859
+ }
1186
1860
  }
1187
1861
  }, [pauseTimeout, isRecording, sequenceTimeout, submit]);
1188
1862
  react.useEffect(() => {
@@ -1241,22 +1915,23 @@ function useRecordHotkey(options = {}) {
1241
1915
  key = e.code.slice(5);
1242
1916
  }
1243
1917
  pressedKeysRef.current.add(key);
1918
+ let nonModifierKey = "";
1919
+ for (const k of pressedKeysRef.current) {
1920
+ if (!isModifierKey(k)) {
1921
+ nonModifierKey = normalizeKey(k);
1922
+ hasNonModifierRef.current = true;
1923
+ break;
1924
+ }
1925
+ }
1244
1926
  const combo = {
1245
- key: "",
1927
+ key: nonModifierKey,
1246
1928
  modifiers: {
1247
1929
  ctrl: e.ctrlKey,
1248
1930
  alt: e.altKey,
1249
- shift: e.shiftKey,
1931
+ shift: e.shiftKey && !isShiftedSymbol(nonModifierKey),
1250
1932
  meta: e.metaKey
1251
1933
  }
1252
1934
  };
1253
- for (const k of pressedKeysRef.current) {
1254
- if (!isModifierKey(k)) {
1255
- combo.key = normalizeKey(k);
1256
- hasNonModifierRef.current = true;
1257
- break;
1258
- }
1259
- }
1260
1935
  if (combo.key) {
1261
1936
  currentComboRef.current = combo;
1262
1937
  setActiveKeys(combo);
@@ -1281,16 +1956,41 @@ function useRecordHotkey(options = {}) {
1281
1956
  pressedKeysRef.current.delete(key);
1282
1957
  const shouldComplete = pressedKeysRef.current.size === 0 || e.key === "Meta" && hasNonModifierRef.current;
1283
1958
  if (shouldComplete && hasNonModifierRef.current && currentComboRef.current) {
1284
- const combo = currentComboRef.current;
1959
+ let combo = currentComboRef.current;
1285
1960
  pressedKeysRef.current.clear();
1286
1961
  hasNonModifierRef.current = false;
1287
1962
  currentComboRef.current = null;
1288
1963
  setActiveKeys(null);
1289
- const newSequence = [...pendingKeysRef.current, combo];
1964
+ let newSequence;
1965
+ const noModifiers = !combo.modifiers.ctrl && !combo.modifiers.alt && !combo.modifiers.meta && !combo.modifiers.shift;
1966
+ if (combo.key === "#" && noModifiers) {
1967
+ const pending = pendingKeysRef.current;
1968
+ const lastCombo = pending[pending.length - 1];
1969
+ if (hashCycleRef.current === 0) {
1970
+ combo = { key: DIGIT_PLACEHOLDER, modifiers: { ctrl: false, alt: false, shift: false, meta: false } };
1971
+ newSequence = [...pending, combo];
1972
+ hashCycleRef.current = 1;
1973
+ } else if (hashCycleRef.current === 1 && lastCombo?.key === DIGIT_PLACEHOLDER) {
1974
+ newSequence = [...pending.slice(0, -1), { key: DIGITS_PLACEHOLDER, modifiers: { ctrl: false, alt: false, shift: false, meta: false } }];
1975
+ hashCycleRef.current = 2;
1976
+ } else if (hashCycleRef.current === 2 && lastCombo?.key === DIGITS_PLACEHOLDER) {
1977
+ newSequence = [...pending.slice(0, -1), { key: "#", modifiers: { ctrl: false, alt: false, shift: false, meta: false } }];
1978
+ hashCycleRef.current = 3;
1979
+ } else {
1980
+ combo = { key: DIGIT_PLACEHOLDER, modifiers: { ctrl: false, alt: false, shift: false, meta: false } };
1981
+ newSequence = [...pending, combo];
1982
+ hashCycleRef.current = 1;
1983
+ }
1984
+ } else {
1985
+ hashCycleRef.current = 0;
1986
+ newSequence = [...pendingKeysRef.current, combo];
1987
+ }
1290
1988
  pendingKeysRef.current = newSequence;
1291
1989
  setPendingKeys(newSequence);
1292
1990
  clearTimeout_();
1293
- if (!pauseTimeoutRef.current) {
1991
+ if (sequenceTimeout === 0) {
1992
+ submit(newSequence);
1993
+ } else if (!pauseTimeoutRef.current && Number.isFinite(sequenceTimeout)) {
1294
1994
  timeoutRef.current = setTimeout(() => {
1295
1995
  submit(newSequence);
1296
1996
  }, sequenceTimeout);
@@ -1306,7 +2006,6 @@ function useRecordHotkey(options = {}) {
1306
2006
  };
1307
2007
  }, [isRecording, preventDefault, sequenceTimeout, clearTimeout_, submit, cancel, onCapture, onTab, onShiftTab]);
1308
2008
  const display = sequence ? formatCombination(sequence) : null;
1309
- const combination = sequence && sequence.length > 0 ? sequence[0] : null;
1310
2009
  return {
1311
2010
  isRecording,
1312
2011
  startRecording,
@@ -1316,12 +2015,11 @@ function useRecordHotkey(options = {}) {
1316
2015
  display,
1317
2016
  pendingKeys,
1318
2017
  activeKeys,
1319
- combination
1320
- // deprecated
2018
+ sequenceTimeout
1321
2019
  };
1322
2020
  }
1323
2021
  function useEditableHotkeys(defaults, handlers, options = {}) {
1324
- const { storageKey, disableConflicts = true, ...hotkeyOptions } = options;
2022
+ const { storageKey, disableConflicts = false, ...hotkeyOptions } = options;
1325
2023
  const [overrides, setOverrides] = react.useState(() => {
1326
2024
  if (!storageKey || typeof window === "undefined") return {};
1327
2025
  try {
@@ -1416,6 +2114,8 @@ function useEditableHotkeys(defaults, handlers, options = {}) {
1416
2114
  sequenceTimeout
1417
2115
  };
1418
2116
  }
2117
+ var { max: max2, min } = Math;
2118
+ var DEFAULT_DEBOUNCE_MS = 150;
1419
2119
  function useOmnibar(options) {
1420
2120
  const {
1421
2121
  actions,
@@ -1424,17 +2124,27 @@ function useOmnibar(options) {
1424
2124
  openKey = "meta+k",
1425
2125
  enabled = true,
1426
2126
  onExecute,
2127
+ onExecuteRemote,
1427
2128
  onOpen,
1428
2129
  onClose,
1429
- maxResults = 10
2130
+ maxResults = 10,
2131
+ endpointsRegistry,
2132
+ debounceMs = DEFAULT_DEBOUNCE_MS
1430
2133
  } = options;
1431
2134
  const [isOpen, setIsOpen] = react.useState(false);
1432
2135
  const [query, setQuery] = react.useState("");
1433
2136
  const [selectedIndex, setSelectedIndex] = react.useState(0);
2137
+ const [endpointStates, setEndpointStates] = react.useState(/* @__PURE__ */ new Map());
1434
2138
  const handlersRef = react.useRef(handlers);
1435
2139
  handlersRef.current = handlers;
1436
2140
  const onExecuteRef = react.useRef(onExecute);
1437
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;
1438
2148
  const omnibarKeymap = react.useMemo(() => {
1439
2149
  if (!enabled) return {};
1440
2150
  return { [openKey]: "omnibar:toggle" };
@@ -1460,12 +2170,189 @@ function useOmnibar(options) {
1460
2170
  const allResults = searchActions(query, actions, keymap);
1461
2171
  return allResults.slice(0, maxResults);
1462
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;
1463
2350
  const completions = react.useMemo(() => {
1464
2351
  return getSequenceCompletions(pendingKeys, keymap);
1465
2352
  }, [pendingKeys, keymap]);
1466
2353
  react.useEffect(() => {
1467
2354
  setSelectedIndex(0);
1468
- }, [results]);
2355
+ }, [results, remoteResults]);
1469
2356
  const open = react.useCallback(() => {
1470
2357
  setIsOpen(true);
1471
2358
  setQuery("");
@@ -1492,24 +2379,56 @@ function useOmnibar(options) {
1492
2379
  });
1493
2380
  }, [onOpen, onClose]);
1494
2381
  const selectNext = react.useCallback(() => {
1495
- setSelectedIndex((prev) => base.min(prev + 1, results.length - 1));
1496
- }, [results.length]);
2382
+ setSelectedIndex((prev) => min(prev + 1, totalResults - 1));
2383
+ }, [totalResults]);
1497
2384
  const selectPrev = react.useCallback(() => {
1498
- setSelectedIndex((prev) => base.max(prev - 1, 0));
2385
+ setSelectedIndex((prev) => max2(prev - 1, 0));
1499
2386
  }, []);
1500
2387
  const resetSelection = react.useCallback(() => {
1501
2388
  setSelectedIndex(0);
1502
2389
  }, []);
1503
2390
  const execute = react.useCallback((actionId) => {
1504
- const id = actionId ?? results[selectedIndex]?.id;
1505
- if (!id) return;
1506
- close();
1507
- if (handlersRef.current?.[id]) {
1508
- const event = new KeyboardEvent("keydown", { key: "Enter" });
1509
- handlersRef.current[id](event);
1510
- }
1511
- onExecuteRef.current?.(id);
1512
- }, [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]);
1513
2432
  react.useEffect(() => {
1514
2433
  if (!isOpen) return;
1515
2434
  const handleKeyDown = (e) => {
@@ -1551,7 +2470,12 @@ function useOmnibar(options) {
1551
2470
  query,
1552
2471
  setQuery,
1553
2472
  results,
2473
+ remoteResults,
2474
+ isLoadingRemote,
2475
+ endpointPagination,
2476
+ loadMore,
1554
2477
  selectedIndex,
2478
+ totalResults,
1555
2479
  selectNext,
1556
2480
  selectPrev,
1557
2481
  execute,
@@ -1561,6 +2485,400 @@ function useOmnibar(options) {
1561
2485
  isAwaitingSequence
1562
2486
  };
1563
2487
  }
2488
+ var baseStyle = {
2489
+ width: "1em",
2490
+ height: "1em",
2491
+ verticalAlign: "middle"
2492
+ };
2493
+ function Up({ className, style }) {
2494
+ return /* @__PURE__ */ jsxRuntime.jsx(
2495
+ "svg",
2496
+ {
2497
+ className,
2498
+ style: { ...baseStyle, ...style },
2499
+ viewBox: "0 0 24 24",
2500
+ fill: "none",
2501
+ stroke: "currentColor",
2502
+ strokeWidth: "3",
2503
+ strokeLinecap: "round",
2504
+ strokeLinejoin: "round",
2505
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 19V5M5 12l7-7 7 7" })
2506
+ }
2507
+ );
2508
+ }
2509
+ function Down({ className, style }) {
2510
+ return /* @__PURE__ */ jsxRuntime.jsx(
2511
+ "svg",
2512
+ {
2513
+ className,
2514
+ style: { ...baseStyle, ...style },
2515
+ viewBox: "0 0 24 24",
2516
+ fill: "none",
2517
+ stroke: "currentColor",
2518
+ strokeWidth: "3",
2519
+ strokeLinecap: "round",
2520
+ strokeLinejoin: "round",
2521
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5v14M5 12l7 7 7-7" })
2522
+ }
2523
+ );
2524
+ }
2525
+ function Left({ className, style }) {
2526
+ return /* @__PURE__ */ jsxRuntime.jsx(
2527
+ "svg",
2528
+ {
2529
+ className,
2530
+ style: { ...baseStyle, ...style },
2531
+ viewBox: "0 0 24 24",
2532
+ fill: "none",
2533
+ stroke: "currentColor",
2534
+ strokeWidth: "3",
2535
+ strokeLinecap: "round",
2536
+ strokeLinejoin: "round",
2537
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 12H5M12 5l-7 7 7 7" })
2538
+ }
2539
+ );
2540
+ }
2541
+ function Right({ className, style }) {
2542
+ return /* @__PURE__ */ jsxRuntime.jsx(
2543
+ "svg",
2544
+ {
2545
+ className,
2546
+ style: { ...baseStyle, ...style },
2547
+ viewBox: "0 0 24 24",
2548
+ fill: "none",
2549
+ stroke: "currentColor",
2550
+ strokeWidth: "3",
2551
+ strokeLinecap: "round",
2552
+ strokeLinejoin: "round",
2553
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12h14M12 5l7 7-7 7" })
2554
+ }
2555
+ );
2556
+ }
2557
+ function Enter({ className, style }) {
2558
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2559
+ "svg",
2560
+ {
2561
+ className,
2562
+ style: { ...baseStyle, ...style },
2563
+ viewBox: "0 0 24 24",
2564
+ fill: "none",
2565
+ stroke: "currentColor",
2566
+ strokeWidth: "3",
2567
+ strokeLinecap: "round",
2568
+ strokeLinejoin: "round",
2569
+ children: [
2570
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 10l-4 4 4 4" }),
2571
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6v8a2 2 0 01-2 2H5" })
2572
+ ]
2573
+ }
2574
+ );
2575
+ }
2576
+ function Backspace({ className, style }) {
2577
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2578
+ "svg",
2579
+ {
2580
+ className,
2581
+ style: { ...baseStyle, ...style },
2582
+ viewBox: "0 0 24 24",
2583
+ fill: "none",
2584
+ stroke: "currentColor",
2585
+ strokeWidth: "2",
2586
+ strokeLinecap: "round",
2587
+ strokeLinejoin: "round",
2588
+ children: [
2589
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 4H8l-7 8 7 8h13a2 2 0 002-2V6a2 2 0 00-2-2z" }),
2590
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "9", x2: "12", y2: "15" }),
2591
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "9", x2: "18", y2: "15" })
2592
+ ]
2593
+ }
2594
+ );
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
+ }
2616
+ function getKeyIcon(key) {
2617
+ switch (key.toLowerCase()) {
2618
+ case "arrowup":
2619
+ return Up;
2620
+ case "arrowdown":
2621
+ return Down;
2622
+ case "arrowleft":
2623
+ return Left;
2624
+ case "arrowright":
2625
+ return Right;
2626
+ case "enter":
2627
+ return Enter;
2628
+ case "backspace":
2629
+ return Backspace;
2630
+ case "tab":
2631
+ return Tab;
2632
+ default:
2633
+ return null;
2634
+ }
2635
+ }
2636
+ var baseStyle2 = {
2637
+ width: "1.2em",
2638
+ height: "1.2em",
2639
+ marginRight: "2px",
2640
+ verticalAlign: "middle"
2641
+ };
2642
+ var wideStyle = {
2643
+ ...baseStyle2,
2644
+ width: "1.4em"
2645
+ };
2646
+ var Command = react.forwardRef(
2647
+ ({ className, style, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2648
+ "svg",
2649
+ {
2650
+ ref,
2651
+ className,
2652
+ style: { ...baseStyle2, ...style },
2653
+ viewBox: "0 0 24 24",
2654
+ fill: "currentColor",
2655
+ ...props,
2656
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2v4H6a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2h4v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-2v-4h2a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v2h-4V6a2 2 0 0 0-2-2H6zm4 6h4v4h-4v-4z" })
2657
+ }
2658
+ )
2659
+ );
2660
+ Command.displayName = "Command";
2661
+ var Ctrl = react.forwardRef(
2662
+ ({ className, style, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2663
+ "svg",
2664
+ {
2665
+ ref,
2666
+ className,
2667
+ style: { ...baseStyle2, ...style },
2668
+ viewBox: "0 0 24 24",
2669
+ fill: "none",
2670
+ stroke: "currentColor",
2671
+ strokeWidth: "3",
2672
+ strokeLinecap: "round",
2673
+ strokeLinejoin: "round",
2674
+ ...props,
2675
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 15l6-6 6 6" })
2676
+ }
2677
+ )
2678
+ );
2679
+ Ctrl.displayName = "Ctrl";
2680
+ var Shift = react.forwardRef(
2681
+ ({ className, style, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2682
+ "svg",
2683
+ {
2684
+ ref,
2685
+ className,
2686
+ style: { ...wideStyle, ...style },
2687
+ viewBox: "0 0 28 24",
2688
+ fill: "none",
2689
+ stroke: "currentColor",
2690
+ strokeWidth: "2",
2691
+ strokeLinejoin: "round",
2692
+ ...props,
2693
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 3L3 14h6v7h10v-7h6L14 3z" })
2694
+ }
2695
+ )
2696
+ );
2697
+ Shift.displayName = "Shift";
2698
+ var Option = react.forwardRef(
2699
+ ({ className, style, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2700
+ "svg",
2701
+ {
2702
+ ref,
2703
+ className,
2704
+ style: { ...baseStyle2, ...style },
2705
+ viewBox: "0 0 24 24",
2706
+ fill: "none",
2707
+ stroke: "currentColor",
2708
+ strokeWidth: "2.5",
2709
+ strokeLinecap: "round",
2710
+ strokeLinejoin: "round",
2711
+ ...props,
2712
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 6h6l8 12h6M14 6h6" })
2713
+ }
2714
+ )
2715
+ );
2716
+ Option.displayName = "Option";
2717
+ var Alt = react.forwardRef(
2718
+ ({ className, style, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2719
+ "svg",
2720
+ {
2721
+ ref,
2722
+ className,
2723
+ style: { ...baseStyle2, ...style },
2724
+ viewBox: "0 0 24 24",
2725
+ fill: "none",
2726
+ stroke: "currentColor",
2727
+ strokeWidth: "2.5",
2728
+ strokeLinecap: "round",
2729
+ strokeLinejoin: "round",
2730
+ ...props,
2731
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 18h8M12 18l4-6M12 18l4 0M16 12l4-6h-8" })
2732
+ }
2733
+ )
2734
+ );
2735
+ Alt.displayName = "Alt";
2736
+ function getModifierIcon(modifier) {
2737
+ switch (modifier) {
2738
+ case "meta":
2739
+ return Command;
2740
+ case "ctrl":
2741
+ return Ctrl;
2742
+ case "shift":
2743
+ return Shift;
2744
+ case "opt":
2745
+ return Option;
2746
+ case "alt":
2747
+ return isMac() ? Option : Alt;
2748
+ }
2749
+ }
2750
+ var ModifierIcon = react.forwardRef(
2751
+ ({ modifier, ...props }, ref) => {
2752
+ const Icon = getModifierIcon(modifier);
2753
+ return /* @__PURE__ */ jsxRuntime.jsx(Icon, { ref, ...props });
2754
+ }
2755
+ );
2756
+ ModifierIcon.displayName = "ModifierIcon";
2757
+ function renderModifierIcons(modifiers, className = "kbd-modifier-icon") {
2758
+ const icons = [];
2759
+ if (modifiers.meta) {
2760
+ icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className }, "meta"));
2761
+ }
2762
+ if (modifiers.ctrl) {
2763
+ icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className }, "ctrl"));
2764
+ }
2765
+ if (modifiers.alt) {
2766
+ icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className }, "alt"));
2767
+ }
2768
+ if (modifiers.shift) {
2769
+ icons.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className }, "shift"));
2770
+ }
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);
2784
+ }
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
+ ] });
2798
+ }
2799
+ function SeqElemDisplay({ elem }) {
2800
+ if (elem.type === "digit") {
2801
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-placeholder", title: "Any single digit (0-9)", children: "#" });
2802
+ }
2803
+ if (elem.type === "digits") {
2804
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-placeholder", title: "One or more digits (0-9)", children: "##" });
2805
+ }
2806
+ return /* @__PURE__ */ jsxRuntime.jsx(KeyCombo, { combo: { key: elem.key, modifiers: elem.modifiers } });
2807
+ }
2808
+ function BindingDisplay({ binding }) {
2809
+ const sequence = parseKeySeq(binding);
2810
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: sequence.map((elem, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
2811
+ i > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-sep", children: " " }),
2812
+ /* @__PURE__ */ jsxRuntime.jsx(SeqElemDisplay, { elem })
2813
+ ] }, i)) });
2814
+ }
2815
+ function Kbd({
2816
+ action,
2817
+ separator = " / ",
2818
+ all = false,
2819
+ fallback = null,
2820
+ className,
2821
+ clickable = true
2822
+ }) {
2823
+ const ctx = useMaybeHotkeysContext();
2824
+ const warnedRef = react.useRef(false);
2825
+ const bindings = ctx ? all ? ctx.registry.getBindingsForAction(action) : [ctx.registry.getFirstBindingForAction(action)].filter(Boolean) : [];
2826
+ react.useEffect(() => {
2827
+ if (!ctx) return;
2828
+ if (warnedRef.current) return;
2829
+ const timer = setTimeout(() => {
2830
+ if (!ctx.registry.actions.has(action)) {
2831
+ console.warn(`Kbd: Action "${action}" not found in registry`);
2832
+ warnedRef.current = true;
2833
+ }
2834
+ }, 100);
2835
+ return () => clearTimeout(timer);
2836
+ }, [ctx, action]);
2837
+ if (!ctx) {
2838
+ return null;
2839
+ }
2840
+ if (bindings.length === 0) {
2841
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
2842
+ }
2843
+ const content = bindings.map((binding, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
2844
+ i > 0 && separator,
2845
+ /* @__PURE__ */ jsxRuntime.jsx(BindingDisplay, { binding })
2846
+ ] }, binding));
2847
+ if (clickable) {
2848
+ return /* @__PURE__ */ jsxRuntime.jsx(
2849
+ "kbd",
2850
+ {
2851
+ className: `${className || ""} kbd-clickable`.trim(),
2852
+ onClick: () => ctx.executeAction(action),
2853
+ role: "button",
2854
+ tabIndex: 0,
2855
+ onKeyDown: (e) => {
2856
+ if (e.key === "Enter" || e.key === " ") {
2857
+ e.preventDefault();
2858
+ ctx.executeAction(action);
2859
+ }
2860
+ },
2861
+ children: content
2862
+ }
2863
+ );
2864
+ }
2865
+ return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className, children: content });
2866
+ }
2867
+ function Key(props) {
2868
+ return /* @__PURE__ */ jsxRuntime.jsx(Kbd, { ...props, clickable: false });
2869
+ }
2870
+ function Kbds(props) {
2871
+ return /* @__PURE__ */ jsxRuntime.jsx(Kbd, { ...props, all: true });
2872
+ }
2873
+ function KbdModal(props) {
2874
+ return /* @__PURE__ */ jsxRuntime.jsx(Kbd, { ...props, action: ACTION_MODAL });
2875
+ }
2876
+ function KbdOmnibar(props) {
2877
+ return /* @__PURE__ */ jsxRuntime.jsx(Kbd, { ...props, action: ACTION_OMNIBAR });
2878
+ }
2879
+ function KbdLookup(props) {
2880
+ return /* @__PURE__ */ jsxRuntime.jsx(Kbd, { ...props, action: ACTION_LOOKUP });
2881
+ }
1564
2882
  function buildActionMap(keymap) {
1565
2883
  const map = /* @__PURE__ */ new Map();
1566
2884
  for (const [key, actionOrActions] of Object.entries(keymap)) {
@@ -1628,7 +2946,7 @@ function KeybindingEditor({
1628
2946
  return Array.from(allActions).map((action) => {
1629
2947
  const key = actionMap.get(action) ?? defaultActionMap.get(action) ?? "";
1630
2948
  const defaultKey = defaultActionMap.get(action) ?? "";
1631
- const combo = parseCombinationId(key);
2949
+ const combo = parseHotkeyString(key);
1632
2950
  const display = formatCombination(combo);
1633
2951
  const conflictActions = conflicts.get(key);
1634
2952
  return {
@@ -1730,149 +3048,241 @@ function KeybindingEditor({
1730
3048
  "button",
1731
3049
  {
1732
3050
  onClick: () => startEditing(action),
1733
- disabled: isRecording,
1734
- style: {
1735
- padding: "4px 8px",
1736
- backgroundColor: "#f5f5f5",
1737
- border: "1px solid #ddd",
1738
- borderRadius: "4px",
1739
- cursor: isRecording ? "not-allowed" : "pointer",
1740
- fontSize: "0.875rem",
1741
- opacity: isRecording ? 0.5 : 1
1742
- },
1743
- children: "Edit"
1744
- }
1745
- ) })
1746
- ] }, action);
1747
- }) })
1748
- ] })
1749
- ] });
1750
- }
1751
- var baseStyle = {
1752
- width: "1.2em",
1753
- height: "1.2em",
1754
- marginRight: "2px",
1755
- verticalAlign: "middle"
1756
- };
1757
- var wideStyle = {
1758
- ...baseStyle,
1759
- width: "1.4em"
1760
- };
1761
- function CommandIcon({ className, style }) {
1762
- return /* @__PURE__ */ jsxRuntime.jsx(
1763
- "svg",
1764
- {
1765
- className,
1766
- style: { ...baseStyle, ...style },
1767
- viewBox: "0 0 24 24",
1768
- fill: "currentColor",
1769
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2v4H6a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2h4v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-2v-4h2a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v2h-4V6a2 2 0 0 0-2-2H6zm4 6h4v4h-4v-4z" })
1770
- }
1771
- );
1772
- }
1773
- function CtrlIcon({ className, style }) {
1774
- return /* @__PURE__ */ jsxRuntime.jsx(
1775
- "svg",
1776
- {
1777
- className,
1778
- style: { ...baseStyle, ...style },
1779
- viewBox: "0 0 24 24",
1780
- fill: "none",
1781
- stroke: "currentColor",
1782
- strokeWidth: "3",
1783
- strokeLinecap: "round",
1784
- strokeLinejoin: "round",
1785
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 15l6-6 6 6" })
1786
- }
1787
- );
3051
+ disabled: isRecording,
3052
+ style: {
3053
+ padding: "4px 8px",
3054
+ backgroundColor: "#f5f5f5",
3055
+ border: "1px solid #ddd",
3056
+ borderRadius: "4px",
3057
+ cursor: isRecording ? "not-allowed" : "pointer",
3058
+ fontSize: "0.875rem",
3059
+ opacity: isRecording ? 0.5 : 1
3060
+ },
3061
+ children: "Edit"
3062
+ }
3063
+ ) })
3064
+ ] }, action);
3065
+ }) })
3066
+ ] })
3067
+ ] });
1788
3068
  }
1789
- function ShiftIcon({ className, style }) {
1790
- return /* @__PURE__ */ jsxRuntime.jsx(
1791
- "svg",
1792
- {
1793
- className,
1794
- style: { ...wideStyle, ...style },
1795
- viewBox: "0 0 28 24",
1796
- fill: "none",
1797
- stroke: "currentColor",
1798
- strokeWidth: "2",
1799
- strokeLinejoin: "round",
1800
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 3L3 14h6v7h10v-7h6L14 3z" })
3069
+ function LookupModal({ defaultBinding = "meta+shift+k" } = {}) {
3070
+ const {
3071
+ isLookupOpen,
3072
+ closeLookup,
3073
+ toggleLookup,
3074
+ registry,
3075
+ executeAction
3076
+ } = useHotkeysContext();
3077
+ useAction(ACTION_LOOKUP, {
3078
+ label: "Key lookup",
3079
+ group: "Global",
3080
+ defaultBindings: defaultBinding ? [defaultBinding] : [],
3081
+ handler: react.useCallback(() => toggleLookup(), [toggleLookup])
3082
+ });
3083
+ const [pendingKeys, setPendingKeys] = react.useState([]);
3084
+ const [selectedIndex, setSelectedIndex] = react.useState(0);
3085
+ const allBindings = react.useMemo(() => {
3086
+ const results = [];
3087
+ const keymap = registry.keymap;
3088
+ for (const [binding, actionOrActions] of Object.entries(keymap)) {
3089
+ if (binding.startsWith("__")) continue;
3090
+ const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions];
3091
+ const sequence = parseHotkeyString(binding);
3092
+ const keySeq = parseKeySeq(binding);
3093
+ const display = formatKeySeq(keySeq).display;
3094
+ const labels = actions.map((actionId) => {
3095
+ const action = registry.actions.get(actionId);
3096
+ return action?.config.label || actionId;
3097
+ });
3098
+ results.push({ binding, sequence, keySeq, display, actions, labels });
1801
3099
  }
1802
- );
1803
- }
1804
- function OptIcon({ className, style }) {
1805
- return /* @__PURE__ */ jsxRuntime.jsx(
1806
- "svg",
1807
- {
1808
- className,
1809
- style: { ...baseStyle, ...style },
1810
- viewBox: "0 0 24 24",
1811
- fill: "none",
1812
- stroke: "currentColor",
1813
- strokeWidth: "2.5",
1814
- strokeLinecap: "round",
1815
- strokeLinejoin: "round",
1816
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 6h6l8 12h6M14 6h6" })
3100
+ results.sort((a, b) => a.binding.localeCompare(b.binding));
3101
+ return results;
3102
+ }, [registry.keymap, registry.actions]);
3103
+ const filteredBindings = react.useMemo(() => {
3104
+ if (pendingKeys.length === 0) return allBindings;
3105
+ return allBindings.filter((result) => {
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++) {
3110
+ const pending = pendingKeys[i];
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
+ }
3130
+ }
3131
+ return true;
3132
+ });
3133
+ }, [allBindings, pendingKeys]);
3134
+ const groupedByNextKey = react.useMemo(() => {
3135
+ const groups = /* @__PURE__ */ new Map();
3136
+ for (const result of filteredBindings) {
3137
+ if (result.sequence.length > pendingKeys.length) {
3138
+ const nextCombo = result.sequence[pendingKeys.length];
3139
+ const nextKey = formatCombination([nextCombo]).display;
3140
+ const existing = groups.get(nextKey) || [];
3141
+ existing.push(result);
3142
+ groups.set(nextKey, existing);
3143
+ } else {
3144
+ const existing = groups.get("") || [];
3145
+ existing.push(result);
3146
+ groups.set("", existing);
3147
+ }
1817
3148
  }
1818
- );
1819
- }
1820
- function AltIcon({ className, style }) {
1821
- return /* @__PURE__ */ jsxRuntime.jsx(
1822
- "svg",
1823
- {
1824
- className,
1825
- style: { ...baseStyle, ...style },
1826
- viewBox: "0 0 24 24",
1827
- fill: "none",
1828
- stroke: "currentColor",
1829
- strokeWidth: "2.5",
1830
- strokeLinecap: "round",
1831
- strokeLinejoin: "round",
1832
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 18h8M12 18l4-6M12 18l4 0M16 12l4-6h-8" })
3149
+ return groups;
3150
+ }, [filteredBindings, pendingKeys]);
3151
+ const formattedPendingKeys = react.useMemo(() => {
3152
+ if (pendingKeys.length === 0) return "";
3153
+ return formatCombination(pendingKeys).display;
3154
+ }, [pendingKeys]);
3155
+ react.useEffect(() => {
3156
+ if (isLookupOpen) {
3157
+ setPendingKeys([]);
3158
+ setSelectedIndex(0);
1833
3159
  }
1834
- );
3160
+ }, [isLookupOpen]);
3161
+ react.useEffect(() => {
3162
+ setSelectedIndex(0);
3163
+ }, [filteredBindings.length]);
3164
+ react.useEffect(() => {
3165
+ if (!isLookupOpen) return;
3166
+ const handleKeyDown = (e) => {
3167
+ if (e.key === "Escape") {
3168
+ e.preventDefault();
3169
+ if (pendingKeys.length > 0) {
3170
+ setPendingKeys([]);
3171
+ } else {
3172
+ closeLookup();
3173
+ }
3174
+ return;
3175
+ }
3176
+ if (e.key === "Backspace") {
3177
+ e.preventDefault();
3178
+ setPendingKeys((prev) => prev.slice(0, -1));
3179
+ return;
3180
+ }
3181
+ if (e.key === "ArrowDown") {
3182
+ e.preventDefault();
3183
+ setSelectedIndex((prev) => Math.min(prev + 1, filteredBindings.length - 1));
3184
+ return;
3185
+ }
3186
+ if (e.key === "ArrowUp") {
3187
+ e.preventDefault();
3188
+ setSelectedIndex((prev) => Math.max(prev - 1, 0));
3189
+ return;
3190
+ }
3191
+ if (e.key === "Enter") {
3192
+ e.preventDefault();
3193
+ const selected = filteredBindings[selectedIndex];
3194
+ if (selected && selected.actions.length > 0) {
3195
+ closeLookup();
3196
+ executeAction(selected.actions[0]);
3197
+ }
3198
+ return;
3199
+ }
3200
+ if (isModifierKey(e.key)) return;
3201
+ e.preventDefault();
3202
+ const newCombo = {
3203
+ key: normalizeKey(e.key),
3204
+ modifiers: {
3205
+ ctrl: e.ctrlKey,
3206
+ alt: e.altKey,
3207
+ shift: e.shiftKey,
3208
+ meta: e.metaKey
3209
+ }
3210
+ };
3211
+ setPendingKeys((prev) => [...prev, newCombo]);
3212
+ };
3213
+ window.addEventListener("keydown", handleKeyDown);
3214
+ return () => window.removeEventListener("keydown", handleKeyDown);
3215
+ }, [isLookupOpen, pendingKeys, filteredBindings, selectedIndex, closeLookup, executeAction]);
3216
+ const handleBackdropClick = react.useCallback(() => {
3217
+ closeLookup();
3218
+ }, [closeLookup]);
3219
+ if (!isLookupOpen) return null;
3220
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-lookup-backdrop", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-lookup", onClick: (e) => e.stopPropagation(), children: [
3221
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-lookup-header", children: [
3222
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-lookup-search", children: formattedPendingKeys ? /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "kbd-sequence-keys", children: formattedPendingKeys }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-lookup-placeholder", children: "Type keys to filter..." }) }),
3223
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "kbd-lookup-hint", children: [
3224
+ "\u2191\u2193 navigate \xB7 Enter select \xB7 Esc ",
3225
+ pendingKeys.length > 0 ? "clear" : "close",
3226
+ " \xB7 \u232B back"
3227
+ ] })
3228
+ ] }),
3229
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-lookup-results", children: filteredBindings.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-lookup-empty", children: "No matching shortcuts" }) : filteredBindings.map((result, index) => /* @__PURE__ */ jsxRuntime.jsxs(
3230
+ "div",
3231
+ {
3232
+ className: `kbd-lookup-result ${index === selectedIndex ? "selected" : ""}`,
3233
+ onClick: () => {
3234
+ closeLookup();
3235
+ if (result.actions.length > 0) {
3236
+ executeAction(result.actions[0]);
3237
+ }
3238
+ },
3239
+ onMouseEnter: () => setSelectedIndex(index),
3240
+ children: [
3241
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-lookup-binding", children: renderKeySeq(result.keySeq) }),
3242
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-lookup-labels", children: result.labels.join(", ") })
3243
+ ]
3244
+ },
3245
+ result.binding
3246
+ )) }),
3247
+ pendingKeys.length > 0 && groupedByNextKey.size > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-lookup-continuations", children: [
3248
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-lookup-continuations-label", children: "Continue with:" }),
3249
+ Array.from(groupedByNextKey.keys()).filter((k) => k !== "").slice(0, 8).map((nextKey) => /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "kbd-kbd kbd-small", children: nextKey }, nextKey)),
3250
+ groupedByNextKey.size > 9 && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "..." })
3251
+ ] })
3252
+ ] }) });
1835
3253
  }
1836
- var isMac2 = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
1837
- function getModifierIcon(modifier) {
1838
- switch (modifier) {
1839
- case "meta":
1840
- return CommandIcon;
1841
- case "ctrl":
1842
- return CtrlIcon;
1843
- case "shift":
1844
- return ShiftIcon;
1845
- case "opt":
1846
- return OptIcon;
1847
- case "alt":
1848
- return isMac2 ? OptIcon : AltIcon;
3254
+ function SeqElemBadge({ elem }) {
3255
+ if (elem.type === "digit") {
3256
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-placeholder", title: "Any single digit (0-9)", children: "#" });
1849
3257
  }
1850
- }
1851
- function ModifierIcon({ modifier, ...props }) {
1852
- const Icon = getModifierIcon(modifier);
1853
- return /* @__PURE__ */ jsxRuntime.jsx(Icon, { ...props });
3258
+ if (elem.type === "digits") {
3259
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-placeholder", title: "One or more digits (0-9)", children: "##" });
3260
+ }
3261
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3262
+ elem.modifiers.meta && /* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className: "kbd-modifier-icon" }),
3263
+ elem.modifiers.ctrl && /* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }),
3264
+ elem.modifiers.alt && /* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }),
3265
+ elem.modifiers.shift && /* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }),
3266
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatKeyForDisplay(elem.key) })
3267
+ ] });
1854
3268
  }
1855
3269
  function BindingBadge({ binding }) {
1856
- const sequence = parseHotkeyString(binding);
1857
- return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "kbd-kbd", children: sequence.map((combo, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
3270
+ const keySeq = parseKeySeq(binding);
3271
+ return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "kbd-kbd", children: keySeq.map((elem, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
1858
3272
  i > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-sep", children: " " }),
1859
- combo.modifiers.meta && /* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className: "kbd-modifier-icon" }),
1860
- combo.modifiers.ctrl && /* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }),
1861
- combo.modifiers.alt && /* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }),
1862
- combo.modifiers.shift && /* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }),
1863
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: combo.key.length === 1 ? combo.key.toUpperCase() : combo.key })
3273
+ /* @__PURE__ */ jsxRuntime.jsx(SeqElemBadge, { elem })
1864
3274
  ] }, i)) });
1865
3275
  }
1866
3276
  function Omnibar({
1867
3277
  actions: actionsProp,
1868
3278
  handlers: handlersProp,
1869
3279
  keymap: keymapProp,
1870
- openKey = "meta+k",
1871
- enabled: enabledProp,
3280
+ defaultBinding = "meta+k",
1872
3281
  isOpen: isOpenProp,
1873
3282
  onOpen: onOpenProp,
1874
3283
  onClose: onCloseProp,
1875
3284
  onExecute: onExecuteProp,
3285
+ onExecuteRemote: onExecuteRemoteProp,
1876
3286
  maxResults = 10,
1877
3287
  placeholder = "Type a command...",
1878
3288
  children,
@@ -1883,7 +3293,12 @@ function Omnibar({
1883
3293
  const ctx = useMaybeHotkeysContext();
1884
3294
  const actions = actionsProp ?? ctx?.registry.actionRegistry ?? {};
1885
3295
  const keymap = keymapProp ?? ctx?.registry.keymap ?? {};
1886
- const enabled = enabledProp ?? !ctx;
3296
+ useAction(ACTION_OMNIBAR, {
3297
+ label: "Command palette",
3298
+ group: "Global",
3299
+ defaultBindings: defaultBinding ? [defaultBinding] : [],
3300
+ handler: react.useCallback(() => ctx?.toggleOmnibar(), [ctx?.toggleOmnibar])
3301
+ });
1887
3302
  const handleExecute = react.useCallback((actionId) => {
1888
3303
  if (onExecuteProp) {
1889
3304
  onExecuteProp(actionId);
@@ -1905,13 +3320,25 @@ function Omnibar({
1905
3320
  ctx.openOmnibar();
1906
3321
  }
1907
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]);
1908
3330
  const {
1909
3331
  isOpen: internalIsOpen,
1910
3332
  close,
1911
3333
  query,
1912
3334
  setQuery,
1913
3335
  results,
3336
+ remoteResults,
3337
+ isLoadingRemote,
3338
+ endpointPagination,
3339
+ loadMore,
1914
3340
  selectedIndex,
3341
+ totalResults,
1915
3342
  selectNext,
1916
3343
  selectPrev,
1917
3344
  execute,
@@ -1922,15 +3349,28 @@ function Omnibar({
1922
3349
  actions,
1923
3350
  handlers: handlersProp,
1924
3351
  keymap,
1925
- openKey,
1926
- enabled: isOpenProp === void 0 && ctx === null ? enabled : false,
1927
- // Disable hotkey if controlled or using context
3352
+ openKey: "",
3353
+ // Trigger is handled via useAction, not useOmnibar
3354
+ enabled: false,
1928
3355
  onOpen: handleOpen,
1929
3356
  onClose: handleClose,
1930
3357
  onExecute: handleExecute,
1931
- maxResults
3358
+ onExecuteRemote: handleExecuteRemote,
3359
+ maxResults,
3360
+ endpointsRegistry: ctx?.endpointsRegistry
1932
3361
  });
1933
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]);
1934
3374
  react.useEffect(() => {
1935
3375
  if (isOpen) {
1936
3376
  requestAnimationFrame(() => {
@@ -1938,6 +3378,50 @@ function Omnibar({
1938
3378
  });
1939
3379
  }
1940
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]);
3413
+ react.useEffect(() => {
3414
+ if (!isOpen) return;
3415
+ const handleGlobalKeyDown = (e) => {
3416
+ if (e.key === "Escape") {
3417
+ e.preventDefault();
3418
+ e.stopPropagation();
3419
+ close();
3420
+ }
3421
+ };
3422
+ document.addEventListener("keydown", handleGlobalKeyDown, true);
3423
+ return () => document.removeEventListener("keydown", handleGlobalKeyDown, true);
3424
+ }, [isOpen, close]);
1941
3425
  const handleKeyDown = react.useCallback(
1942
3426
  (e) => {
1943
3427
  switch (e.key) {
@@ -1975,7 +3459,12 @@ function Omnibar({
1975
3459
  query,
1976
3460
  setQuery,
1977
3461
  results,
3462
+ remoteResults,
3463
+ isLoadingRemote,
3464
+ endpointPagination,
3465
+ loadMore,
1978
3466
  selectedIndex,
3467
+ totalResults,
1979
3468
  selectNext,
1980
3469
  selectPrev,
1981
3470
  execute,
@@ -2003,65 +3492,164 @@ function Omnibar({
2003
3492
  spellCheck: false
2004
3493
  }
2005
3494
  ),
2006
- /* @__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(
2007
- "div",
2008
- {
2009
- className: `kbd-omnibar-result ${i === selectedIndex ? "selected" : ""}`,
2010
- onClick: () => execute(result.id),
2011
- 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
+ ]
2012
3506
  },
2013
- children: [
2014
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-label", children: result.action.label }),
2015
- result.action.group && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-omnibar-result-category", children: result.action.group }),
2016
- 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)) })
2017
- ]
2018
- },
2019
- result.id
2020
- )) })
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
+ ] }) })
2021
3550
  ] }) });
2022
3551
  }
2023
3552
  function SequenceModal() {
2024
3553
  const {
2025
3554
  pendingKeys,
2026
3555
  isAwaitingSequence,
3556
+ cancelSequence,
2027
3557
  sequenceTimeoutStartedAt: timeoutStartedAt,
2028
3558
  sequenceTimeout,
2029
3559
  getCompletions,
2030
- registry
3560
+ registry,
3561
+ executeAction
2031
3562
  } = useHotkeysContext();
3563
+ const [selectedIndex, setSelectedIndex] = react.useState(0);
3564
+ const [hasInteracted, setHasInteracted] = react.useState(false);
2032
3565
  const completions = react.useMemo(() => {
2033
3566
  if (pendingKeys.length === 0) return [];
2034
3567
  return getCompletions(pendingKeys);
2035
3568
  }, [getCompletions, pendingKeys]);
2036
- const formattedPendingKeys = react.useMemo(() => {
2037
- if (pendingKeys.length === 0) return "";
2038
- return formatCombination(pendingKeys).display;
2039
- }, [pendingKeys]);
2040
- const getActionLabel = (actionId) => {
2041
- const action = registry.actions.get(actionId);
2042
- return action?.config.label || actionId;
2043
- };
2044
- const groupedCompletions = react.useMemo(() => {
2045
- const byNextKey = /* @__PURE__ */ new Map();
3569
+ const flatCompletions = react.useMemo(() => {
3570
+ const items = [];
2046
3571
  for (const c of completions) {
2047
- const existing = byNextKey.get(c.nextKeys);
2048
- if (existing) {
2049
- existing.push(c);
2050
- } else {
2051
- 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
+ });
2052
3580
  }
2053
3581
  }
2054
- return byNextKey;
3582
+ return items;
2055
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
+ };
2056
3644
  if (!isAwaitingSequence || pendingKeys.length === 0) {
2057
3645
  return null;
2058
3646
  }
2059
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kbd-sequence-backdrop", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-sequence", children: [
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: [
2060
3648
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-sequence-current", children: [
2061
- /* @__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)) }),
2062
3650
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-ellipsis", children: "\u2026" })
2063
3651
  ] }),
2064
- timeoutStartedAt && /* @__PURE__ */ jsxRuntime.jsx(
3652
+ shouldShowTimeout && /* @__PURE__ */ jsxRuntime.jsx(
2065
3653
  "div",
2066
3654
  {
2067
3655
  className: "kbd-sequence-timeout",
@@ -2069,17 +3657,23 @@ function SequenceModal() {
2069
3657
  },
2070
3658
  timeoutStartedAt
2071
3659
  ),
2072
- 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: [
2073
- /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "kbd-kbd", children: nextKey.toUpperCase() }),
2074
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-arrow", children: "\u2192" }),
2075
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-actions", children: comps.flatMap((c) => c.actions).map((action, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2076
- i > 0 && ", ",
2077
- getActionLabel(action)
2078
- ] }, action)) })
2079
- ] }, nextKey)) }),
2080
- 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" })
2081
3673
  ] }) });
2082
3674
  }
3675
+ var DefaultTooltip = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
3676
+ var TooltipContext = react.createContext(DefaultTooltip);
2083
3677
  function parseActionId(actionId) {
2084
3678
  const colonIndex = actionId.indexOf(":");
2085
3679
  if (colonIndex > 0) {
@@ -2087,22 +3681,49 @@ function parseActionId(actionId) {
2087
3681
  }
2088
3682
  return { group: "General", name: actionId };
2089
3683
  }
2090
- function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder) {
3684
+ function organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder, actionRegistry, showUnbound = true) {
2091
3685
  const actionBindings = getActionBindings(keymap);
2092
3686
  const groupMap = /* @__PURE__ */ new Map();
3687
+ const includedActions = /* @__PURE__ */ new Set();
3688
+ const getGroupName = (actionId) => {
3689
+ const registeredGroup = actionRegistry?.[actionId]?.group;
3690
+ if (registeredGroup) return registeredGroup;
3691
+ const { group: groupKey } = parseActionId(actionId);
3692
+ return groupNames?.[groupKey] ?? groupKey;
3693
+ };
2093
3694
  for (const [actionId, bindings] of actionBindings) {
2094
- const { group: groupKey, name } = parseActionId(actionId);
2095
- const groupName = groupNames?.[groupKey] ?? groupKey;
3695
+ if (actionRegistry?.[actionId]?.hideFromModal) continue;
3696
+ includedActions.add(actionId);
3697
+ const { name } = parseActionId(actionId);
3698
+ const groupName = getGroupName(actionId);
2096
3699
  if (!groupMap.has(groupName)) {
2097
3700
  groupMap.set(groupName, { name: groupName, shortcuts: [] });
2098
3701
  }
2099
3702
  groupMap.get(groupName).shortcuts.push({
2100
3703
  actionId,
2101
- label: labels?.[actionId] ?? name,
3704
+ label: labels?.[actionId] ?? actionRegistry?.[actionId]?.label ?? name,
2102
3705
  description: descriptions?.[actionId],
2103
3706
  bindings
2104
3707
  });
2105
3708
  }
3709
+ if (actionRegistry && showUnbound) {
3710
+ for (const [actionId, action] of Object.entries(actionRegistry)) {
3711
+ if (includedActions.has(actionId)) continue;
3712
+ if (action.hideFromModal) continue;
3713
+ const { name } = parseActionId(actionId);
3714
+ const groupName = getGroupName(actionId);
3715
+ if (!groupMap.has(groupName)) {
3716
+ groupMap.set(groupName, { name: groupName, shortcuts: [] });
3717
+ }
3718
+ groupMap.get(groupName).shortcuts.push({
3719
+ actionId,
3720
+ label: labels?.[actionId] ?? action.label ?? name,
3721
+ description: descriptions?.[actionId],
3722
+ bindings: []
3723
+ // No bindings
3724
+ });
3725
+ }
3726
+ }
2106
3727
  for (const group of groupMap.values()) {
2107
3728
  group.shortcuts.sort((a, b) => a.actionId.localeCompare(b.actionId));
2108
3729
  }
@@ -2129,25 +3750,22 @@ function KeyDisplay({
2129
3750
  combo,
2130
3751
  className
2131
3752
  }) {
2132
- const { key, modifiers } = combo;
2133
- const parts = [];
2134
- if (modifiers.meta) {
2135
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "meta", className: "kbd-modifier-icon" }, "meta"));
2136
- }
2137
- if (modifiers.ctrl) {
2138
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "ctrl", className: "kbd-modifier-icon" }, "ctrl"));
2139
- }
2140
- if (modifiers.alt) {
2141
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "alt", className: "kbd-modifier-icon" }, "alt"));
3753
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className, children: [
3754
+ renderModifierIcons(combo.modifiers),
3755
+ renderKeyContent(combo.key)
3756
+ ] });
3757
+ }
3758
+ function SeqElemDisplay2({ elem, className }) {
3759
+ const Tooltip = react.useContext(TooltipContext);
3760
+ if (elem.type === "digit") {
3761
+ return /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { title: "Any single digit (0-9)", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `kbd-placeholder ${className || ""}`, children: "#" }) });
2142
3762
  }
2143
- if (modifiers.shift) {
2144
- parts.push(/* @__PURE__ */ jsxRuntime.jsx(ModifierIcon, { modifier: "shift", className: "kbd-modifier-icon" }, "shift"));
3763
+ if (elem.type === "digits") {
3764
+ return /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { title: "One or more digits (0-9)", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `kbd-placeholder ${className || ""}`, children: "##" }) });
2145
3765
  }
2146
- const keyDisplay = key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1);
2147
- parts.push(/* @__PURE__ */ jsxRuntime.jsx("span", { children: keyDisplay }, "key"));
2148
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: parts });
3766
+ return /* @__PURE__ */ jsxRuntime.jsx(KeyDisplay, { combo: { key: elem.key, modifiers: elem.modifiers }, className });
2149
3767
  }
2150
- function BindingDisplay({
3768
+ function BindingDisplay2({
2151
3769
  binding,
2152
3770
  className,
2153
3771
  editable,
@@ -2158,10 +3776,11 @@ function BindingDisplay({
2158
3776
  onEdit,
2159
3777
  onRemove,
2160
3778
  pendingKeys,
2161
- activeKeys
3779
+ activeKeys,
3780
+ timeoutDuration = DEFAULT_SEQUENCE_TIMEOUT
2162
3781
  }) {
2163
3782
  const sequence = parseHotkeyString(binding);
2164
- const display = formatCombination(sequence);
3783
+ const keySeq = parseKeySeq(binding);
2165
3784
  let kbdClassName = "kbd-kbd";
2166
3785
  if (editable && !isEditing) kbdClassName += " editable";
2167
3786
  if (isEditing) kbdClassName += " editing";
@@ -2192,7 +3811,17 @@ function BindingDisplay({
2192
3811
  } else {
2193
3812
  content = "...";
2194
3813
  }
2195
- return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: kbdClassName, tabIndex: editable ? 0 : void 0, children: content });
3814
+ return /* @__PURE__ */ jsxRuntime.jsxs("kbd", { className: kbdClassName, tabIndex: editable ? 0 : void 0, children: [
3815
+ content,
3816
+ pendingKeys && pendingKeys.length > 0 && Number.isFinite(timeoutDuration) && /* @__PURE__ */ jsxRuntime.jsx(
3817
+ "span",
3818
+ {
3819
+ className: "kbd-timeout-bar",
3820
+ style: { animationDuration: `${timeoutDuration}ms` }
3821
+ },
3822
+ pendingKeys.length
3823
+ )
3824
+ ] });
2196
3825
  }
2197
3826
  return /* @__PURE__ */ jsxRuntime.jsxs("kbd", { className: kbdClassName, onClick: handleClick, tabIndex: editable ? 0 : void 0, onKeyDown: editable && onEdit ? (e) => {
2198
3827
  if (e.key === "Enter" || e.key === " ") {
@@ -2200,10 +3829,13 @@ function BindingDisplay({
2200
3829
  onEdit();
2201
3830
  }
2202
3831
  } : void 0, children: [
2203
- display.isSequence ? sequence.map((combo, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
3832
+ keySeq.length > 1 ? keySeq.map((elem, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
2204
3833
  i > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "kbd-sequence-sep", children: " " }),
2205
- /* @__PURE__ */ jsxRuntime.jsx(KeyDisplay, { combo })
2206
- ] }, i)) : /* @__PURE__ */ jsxRuntime.jsx(KeyDisplay, { combo: sequence[0] }),
3834
+ /* @__PURE__ */ jsxRuntime.jsx(SeqElemDisplay2, { elem })
3835
+ ] }, i)) : keySeq.length === 1 ? /* @__PURE__ */ jsxRuntime.jsx(SeqElemDisplay2, { elem: keySeq[0] }) : (
3836
+ // Fallback for legacy parsing
3837
+ /* @__PURE__ */ jsxRuntime.jsx(KeyDisplay, { combo: sequence[0] })
3838
+ ),
2207
3839
  editable && onRemove && /* @__PURE__ */ jsxRuntime.jsx(
2208
3840
  "button",
2209
3841
  {
@@ -2228,8 +3860,7 @@ function ShortcutsModal({
2228
3860
  groupRenderers,
2229
3861
  isOpen: isOpenProp,
2230
3862
  onClose: onCloseProp,
2231
- openKey = "?",
2232
- autoRegisterOpen,
3863
+ defaultBinding = "?",
2233
3864
  editable = false,
2234
3865
  onBindingChange,
2235
3866
  onBindingAdd,
@@ -2240,7 +3871,9 @@ function ShortcutsModal({
2240
3871
  backdropClassName = "kbd-backdrop",
2241
3872
  modalClassName = "kbd-modal",
2242
3873
  title = "Keyboard Shortcuts",
2243
- hint
3874
+ hint,
3875
+ showUnbound,
3876
+ TooltipComponent: TooltipComponentProp = DefaultTooltip
2244
3877
  }) {
2245
3878
  const ctx = useMaybeHotkeysContext();
2246
3879
  const contextLabels = react.useMemo(() => {
@@ -2279,28 +3912,30 @@ function ShortcutsModal({
2279
3912
  const descriptions = descriptionsProp ?? contextDescriptions;
2280
3913
  const groupNames = groupNamesProp ?? contextGroups;
2281
3914
  const handleBindingChange = onBindingChange ?? (ctx ? (action, oldKey, newKey) => {
2282
- if (oldKey) ctx.registry.removeBinding(oldKey);
3915
+ if (oldKey) ctx.registry.removeBinding(action, oldKey);
2283
3916
  ctx.registry.setBinding(action, newKey);
2284
3917
  } : void 0);
2285
3918
  const handleBindingAdd = onBindingAdd ?? (ctx ? (action, key) => {
2286
3919
  ctx.registry.setBinding(action, key);
2287
3920
  } : void 0);
2288
- const handleBindingRemove = onBindingRemove ?? (ctx ? (_action, key) => {
2289
- ctx.registry.removeBinding(key);
3921
+ const handleBindingRemove = onBindingRemove ?? (ctx ? (action, key) => {
3922
+ ctx.registry.removeBinding(action, key);
2290
3923
  } : void 0);
2291
3924
  const handleReset = onReset ?? (ctx ? () => {
2292
3925
  ctx.registry.resetOverrides();
2293
3926
  } : void 0);
2294
- const shouldAutoRegisterOpen = autoRegisterOpen ?? !ctx;
2295
3927
  const [internalIsOpen, setInternalIsOpen] = react.useState(false);
2296
3928
  const isOpen = isOpenProp ?? ctx?.isModalOpen ?? internalIsOpen;
2297
3929
  const [editingAction, setEditingAction] = react.useState(null);
2298
3930
  const [editingKey, setEditingKey] = react.useState(null);
2299
3931
  const [addingAction, setAddingAction] = react.useState(null);
2300
3932
  const [pendingConflict, setPendingConflict] = react.useState(null);
3933
+ const [hasPendingConflictState, setHasPendingConflictState] = react.useState(false);
2301
3934
  const editingActionRef = react.useRef(null);
2302
3935
  const editingKeyRef = react.useRef(null);
2303
3936
  const addingActionRef = react.useRef(null);
3937
+ const setIsEditingBindingRef = react.useRef(ctx?.setIsEditingBinding);
3938
+ setIsEditingBindingRef.current = ctx?.setIsEditingBinding;
2304
3939
  const conflicts = react.useMemo(() => findConflicts(keymap), [keymap]);
2305
3940
  const actionBindings = react.useMemo(() => getActionBindings(keymap), [keymap]);
2306
3941
  const close = react.useCallback(() => {
@@ -2318,13 +3953,18 @@ function ShortcutsModal({
2318
3953
  ctx.closeModal();
2319
3954
  }
2320
3955
  }, [onCloseProp, ctx]);
2321
- const open = react.useCallback(() => {
2322
- if (ctx?.openModal) {
2323
- ctx.openModal();
2324
- } else {
2325
- setInternalIsOpen(true);
2326
- }
2327
- }, [ctx]);
3956
+ useAction(ACTION_MODAL, {
3957
+ label: "Show shortcuts",
3958
+ group: "Global",
3959
+ defaultBindings: defaultBinding ? [defaultBinding] : [],
3960
+ handler: react.useCallback(() => {
3961
+ if (ctx) {
3962
+ ctx.toggleModal();
3963
+ } else {
3964
+ setInternalIsOpen((prev) => !prev);
3965
+ }
3966
+ }, [ctx])
3967
+ });
2328
3968
  const checkConflict = react.useCallback((newKey, forAction) => {
2329
3969
  const existingActions = keymap[newKey];
2330
3970
  if (!existingActions) return null;
@@ -2332,7 +3972,24 @@ function ShortcutsModal({
2332
3972
  const conflicts2 = actions.filter((a) => a !== forAction);
2333
3973
  return conflicts2.length > 0 ? conflicts2 : null;
2334
3974
  }, [keymap]);
2335
- const { isRecording, startRecording, cancel, pendingKeys, activeKeys } = useRecordHotkey({
3975
+ const combinationsEqual2 = react.useCallback((a, b) => {
3976
+ 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;
3977
+ }, []);
3978
+ const isSequencePrefix = react.useCallback((a, b) => {
3979
+ if (a.length >= b.length) return false;
3980
+ for (let i = 0; i < a.length; i++) {
3981
+ if (!combinationsEqual2(a[i], b[i])) return false;
3982
+ }
3983
+ return true;
3984
+ }, [combinationsEqual2]);
3985
+ const sequencesEqual = react.useCallback((a, b) => {
3986
+ if (a.length !== b.length) return false;
3987
+ for (let i = 0; i < a.length; i++) {
3988
+ if (!combinationsEqual2(a[i], b[i])) return false;
3989
+ }
3990
+ return true;
3991
+ }, [combinationsEqual2]);
3992
+ const { isRecording, startRecording, cancel, pendingKeys, activeKeys, sequenceTimeout } = useRecordHotkey({
2336
3993
  onCapture: react.useCallback(
2337
3994
  (_sequence, display) => {
2338
3995
  const currentAddingAction = addingActionRef.current;
@@ -2360,6 +4017,7 @@ function ShortcutsModal({
2360
4017
  setEditingAction(null);
2361
4018
  setEditingKey(null);
2362
4019
  setAddingAction(null);
4020
+ setIsEditingBindingRef.current?.(false);
2363
4021
  },
2364
4022
  [checkConflict, handleBindingChange, handleBindingAdd]
2365
4023
  ),
@@ -2371,6 +4029,7 @@ function ShortcutsModal({
2371
4029
  setEditingKey(null);
2372
4030
  setAddingAction(null);
2373
4031
  setPendingConflict(null);
4032
+ setIsEditingBindingRef.current?.(false);
2374
4033
  }, []),
2375
4034
  // Tab to next/prev editable kbd and start editing
2376
4035
  onTab: react.useCallback(() => {
@@ -2395,7 +4054,7 @@ function ShortcutsModal({
2395
4054
  prev.click();
2396
4055
  }
2397
4056
  }, []),
2398
- pauseTimeout: pendingConflict !== null
4057
+ pauseTimeout: pendingConflict !== null || hasPendingConflictState
2399
4058
  });
2400
4059
  const startEditingBinding = react.useCallback(
2401
4060
  (action, key) => {
@@ -2406,9 +4065,10 @@ function ShortcutsModal({
2406
4065
  setEditingAction(action);
2407
4066
  setEditingKey(key);
2408
4067
  setPendingConflict(null);
4068
+ ctx?.setIsEditingBinding(true);
2409
4069
  startRecording();
2410
4070
  },
2411
- [startRecording]
4071
+ [startRecording, ctx?.setIsEditingBinding]
2412
4072
  );
2413
4073
  const startAddingBinding = react.useCallback(
2414
4074
  (action) => {
@@ -2419,9 +4079,10 @@ function ShortcutsModal({
2419
4079
  setEditingKey(null);
2420
4080
  setAddingAction(action);
2421
4081
  setPendingConflict(null);
4082
+ ctx?.setIsEditingBinding(true);
2422
4083
  startRecording();
2423
4084
  },
2424
- [startRecording]
4085
+ [startRecording, ctx?.setIsEditingBinding]
2425
4086
  );
2426
4087
  const startEditing = react.useCallback(
2427
4088
  (action, bindingIndex) => {
@@ -2443,7 +4104,8 @@ function ShortcutsModal({
2443
4104
  setEditingKey(null);
2444
4105
  setAddingAction(null);
2445
4106
  setPendingConflict(null);
2446
- }, [cancel]);
4107
+ ctx?.setIsEditingBinding(false);
4108
+ }, [cancel, ctx?.setIsEditingBinding]);
2447
4109
  const removeBinding = react.useCallback(
2448
4110
  (action, key) => {
2449
4111
  handleBindingRemove?.(action, key);
@@ -2453,6 +4115,31 @@ function ShortcutsModal({
2453
4115
  const reset = react.useCallback(() => {
2454
4116
  handleReset?.();
2455
4117
  }, [handleReset]);
4118
+ const pendingConflictInfo = react.useMemo(() => {
4119
+ if (!isRecording || pendingKeys.length === 0) {
4120
+ return { hasConflict: false, conflictingKeys: /* @__PURE__ */ new Set() };
4121
+ }
4122
+ const conflictingKeys = /* @__PURE__ */ new Set();
4123
+ for (const key of Object.keys(keymap)) {
4124
+ if (editingKey && key.toLowerCase() === editingKey.toLowerCase()) continue;
4125
+ const keySequence = parseHotkeyString(key);
4126
+ if (sequencesEqual(pendingKeys, keySequence)) {
4127
+ conflictingKeys.add(key);
4128
+ continue;
4129
+ }
4130
+ if (isSequencePrefix(pendingKeys, keySequence)) {
4131
+ conflictingKeys.add(key);
4132
+ continue;
4133
+ }
4134
+ if (isSequencePrefix(keySequence, pendingKeys)) {
4135
+ conflictingKeys.add(key);
4136
+ }
4137
+ }
4138
+ return { hasConflict: conflictingKeys.size > 0, conflictingKeys };
4139
+ }, [isRecording, pendingKeys, keymap, editingKey, sequencesEqual, isSequencePrefix]);
4140
+ react.useEffect(() => {
4141
+ setHasPendingConflictState(pendingConflictInfo.hasConflict);
4142
+ }, [pendingConflictInfo.hasConflict]);
2456
4143
  const renderEditableKbd = react.useCallback(
2457
4144
  (actionId, key, showRemove = false) => {
2458
4145
  const isEditingThis = editingAction === actionId && editingKey === key && !addingAction;
@@ -2464,13 +4151,15 @@ function ShortcutsModal({
2464
4151
  const defaultActions = Array.isArray(defaultAction) ? defaultAction : [defaultAction];
2465
4152
  return defaultActions.includes(actionId);
2466
4153
  })() : true;
4154
+ const isPendingConflict = pendingConflictInfo.conflictingKeys.has(key);
2467
4155
  return /* @__PURE__ */ jsxRuntime.jsx(
2468
- BindingDisplay,
4156
+ BindingDisplay2,
2469
4157
  {
2470
4158
  binding: key,
2471
4159
  editable,
2472
4160
  isEditing: isEditingThis,
2473
4161
  isConflict,
4162
+ isPendingConflict,
2474
4163
  isDefault,
2475
4164
  onEdit: () => {
2476
4165
  if (isRecording && !(editingAction === actionId && editingKey === key)) {
@@ -2491,24 +4180,27 @@ function ShortcutsModal({
2491
4180
  },
2492
4181
  onRemove: editable && showRemove ? () => removeBinding(actionId, key) : void 0,
2493
4182
  pendingKeys,
2494
- activeKeys
4183
+ activeKeys,
4184
+ timeoutDuration: pendingConflictInfo.hasConflict ? Infinity : sequenceTimeout
2495
4185
  },
2496
4186
  key
2497
4187
  );
2498
4188
  },
2499
- [editingAction, editingKey, addingAction, conflicts, defaults, editable, startEditingBinding, removeBinding, pendingKeys, activeKeys, isRecording, cancel, handleBindingAdd, handleBindingChange]
4189
+ [editingAction, editingKey, addingAction, conflicts, defaults, editable, startEditingBinding, removeBinding, pendingKeys, activeKeys, isRecording, cancel, handleBindingAdd, handleBindingChange, sequenceTimeout, pendingConflictInfo]
2500
4190
  );
2501
4191
  const renderAddButton = react.useCallback(
2502
4192
  (actionId) => {
2503
4193
  const isAddingThis = addingAction === actionId;
2504
4194
  if (isAddingThis) {
2505
4195
  return /* @__PURE__ */ jsxRuntime.jsx(
2506
- BindingDisplay,
4196
+ BindingDisplay2,
2507
4197
  {
2508
4198
  binding: "",
2509
4199
  isEditing: true,
4200
+ isPendingConflict: pendingConflictInfo.hasConflict,
2510
4201
  pendingKeys,
2511
- activeKeys
4202
+ activeKeys,
4203
+ timeoutDuration: pendingConflictInfo.hasConflict ? Infinity : sequenceTimeout
2512
4204
  }
2513
4205
  );
2514
4206
  }
@@ -2537,13 +4229,14 @@ function ShortcutsModal({
2537
4229
  }
2538
4230
  );
2539
4231
  },
2540
- [addingAction, pendingKeys, activeKeys, startAddingBinding, isRecording, cancel, handleBindingAdd, handleBindingChange]
4232
+ [addingAction, pendingKeys, activeKeys, startAddingBinding, isRecording, cancel, handleBindingAdd, handleBindingChange, sequenceTimeout, pendingConflictInfo]
2541
4233
  );
2542
4234
  const renderCell = react.useCallback(
2543
4235
  (actionId, keys) => {
4236
+ const showAddButton = editable && (multipleBindings || keys.length === 0);
2544
4237
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "kbd-action-bindings", children: [
2545
4238
  keys.map((key) => /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: renderEditableKbd(actionId, key, true) }, key)),
2546
- editable && multipleBindings && renderAddButton(actionId)
4239
+ showAddButton && renderAddButton(actionId)
2547
4240
  ] });
2548
4241
  },
2549
4242
  [renderEditableKbd, renderAddButton, editable, multipleBindings]
@@ -2560,14 +4253,10 @@ function ShortcutsModal({
2560
4253
  editingKey,
2561
4254
  addingAction
2562
4255
  }), [renderCell, renderEditableKbd, renderAddButton, startEditingBinding, startAddingBinding, removeBinding, isRecording, editingAction, editingKey, addingAction]);
2563
- const modalKeymap = shouldAutoRegisterOpen ? { [openKey]: "openShortcuts" } : {};
2564
4256
  useHotkeys(
2565
- { ...modalKeymap, escape: "closeShortcuts" },
2566
- {
2567
- openShortcuts: open,
2568
- closeShortcuts: close
2569
- },
2570
- { enabled: shouldAutoRegisterOpen || isOpen }
4257
+ { escape: "closeShortcuts" },
4258
+ { closeShortcuts: close },
4259
+ { enabled: isOpen }
2571
4260
  );
2572
4261
  react.useEffect(() => {
2573
4262
  if (!isOpen || !editingAction && !addingAction) return;
@@ -2581,6 +4270,19 @@ function ShortcutsModal({
2581
4270
  window.addEventListener("keydown", handleEscape, true);
2582
4271
  return () => window.removeEventListener("keydown", handleEscape, true);
2583
4272
  }, [isOpen, editingAction, addingAction, cancelEditing]);
4273
+ react.useEffect(() => {
4274
+ if (!isOpen || !ctx) return;
4275
+ const handleMetaK = (e) => {
4276
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
4277
+ e.preventDefault();
4278
+ e.stopPropagation();
4279
+ close();
4280
+ ctx.openOmnibar();
4281
+ }
4282
+ };
4283
+ window.addEventListener("keydown", handleMetaK, true);
4284
+ return () => window.removeEventListener("keydown", handleMetaK, true);
4285
+ }, [isOpen, ctx, close]);
2584
4286
  const handleBackdropClick = react.useCallback(
2585
4287
  (e) => {
2586
4288
  if (e.target === e.currentTarget) {
@@ -2600,9 +4302,10 @@ function ShortcutsModal({
2600
4302
  },
2601
4303
  [editingAction, addingAction, cancelEditing]
2602
4304
  );
4305
+ const effectiveShowUnbound = showUnbound ?? editable;
2603
4306
  const shortcutGroups = react.useMemo(
2604
- () => organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder),
2605
- [keymap, labels, descriptions, groupNames, groupOrder]
4307
+ () => organizeShortcuts(keymap, labels, descriptions, groupNames, groupOrder, ctx?.registry.actionRegistry, effectiveShowUnbound),
4308
+ [keymap, labels, descriptions, groupNames, groupOrder, ctx?.registry.actionRegistry, effectiveShowUnbound]
2606
4309
  );
2607
4310
  if (!isOpen) return null;
2608
4311
  if (children) {
@@ -2632,7 +4335,7 @@ function ShortcutsModal({
2632
4335
  renderCell(actionId, bindings)
2633
4336
  ] }, actionId));
2634
4337
  };
2635
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: backdropClassName, onClick: handleBackdropClick, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: modalClassName, role: "dialog", "aria-modal": "true", "aria-label": "Keyboard shortcuts", onClick: handleModalClick, children: [
4338
+ return /* @__PURE__ */ jsxRuntime.jsx(TooltipContext.Provider, { value: TooltipComponentProp, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: backdropClassName, onClick: handleBackdropClick, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: modalClassName, role: "dialog", "aria-modal": "true", "aria-label": "Keyboard shortcuts", onClick: handleModalClick, children: [
2636
4339
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-modal-header", children: [
2637
4340
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "kbd-modal-title", children: title }),
2638
4341
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kbd-modal-header-buttons", children: [
@@ -2701,37 +4404,68 @@ function ShortcutsModal({
2701
4404
  )
2702
4405
  ] })
2703
4406
  ] })
2704
- ] }) });
4407
+ ] }) }) });
2705
4408
  }
2706
4409
 
4410
+ exports.ACTION_LOOKUP = ACTION_LOOKUP;
4411
+ exports.ACTION_MODAL = ACTION_MODAL;
4412
+ exports.ACTION_OMNIBAR = ACTION_OMNIBAR;
2707
4413
  exports.ActionsRegistryContext = ActionsRegistryContext;
2708
- exports.AltIcon = AltIcon;
2709
- exports.CommandIcon = CommandIcon;
2710
- exports.CtrlIcon = CtrlIcon;
4414
+ exports.Alt = Alt;
4415
+ exports.Backspace = Backspace;
4416
+ exports.Command = Command;
4417
+ exports.Ctrl = Ctrl;
4418
+ exports.DEFAULT_SEQUENCE_TIMEOUT = DEFAULT_SEQUENCE_TIMEOUT;
4419
+ exports.DIGITS_PLACEHOLDER = DIGITS_PLACEHOLDER;
4420
+ exports.DIGIT_PLACEHOLDER = DIGIT_PLACEHOLDER;
4421
+ exports.Down = Down;
4422
+ exports.Enter = Enter;
2711
4423
  exports.HotkeysProvider = HotkeysProvider;
4424
+ exports.Kbd = Kbd;
4425
+ exports.KbdLookup = KbdLookup;
4426
+ exports.KbdModal = KbdModal;
4427
+ exports.KbdOmnibar = KbdOmnibar;
4428
+ exports.Kbds = Kbds;
4429
+ exports.Key = Key;
2712
4430
  exports.KeybindingEditor = KeybindingEditor;
4431
+ exports.Left = Left;
4432
+ exports.LookupModal = LookupModal;
2713
4433
  exports.ModifierIcon = ModifierIcon;
2714
4434
  exports.Omnibar = Omnibar;
2715
- exports.OptIcon = OptIcon;
4435
+ exports.OmnibarEndpointsRegistryContext = OmnibarEndpointsRegistryContext;
4436
+ exports.Option = Option;
4437
+ exports.Right = Right;
2716
4438
  exports.SequenceModal = SequenceModal;
2717
- exports.ShiftIcon = ShiftIcon;
4439
+ exports.Shift = Shift;
2718
4440
  exports.ShortcutsModal = ShortcutsModal;
4441
+ exports.Up = Up;
4442
+ exports.countPlaceholders = countPlaceholders;
2719
4443
  exports.createTwoColumnRenderer = createTwoColumnRenderer;
4444
+ exports.extractCaptures = extractCaptures;
2720
4445
  exports.findConflicts = findConflicts;
4446
+ exports.formatBinding = formatBinding;
2721
4447
  exports.formatCombination = formatCombination;
2722
4448
  exports.formatKeyForDisplay = formatKeyForDisplay;
4449
+ exports.formatKeySeq = formatKeySeq;
2723
4450
  exports.fuzzyMatch = fuzzyMatch;
2724
4451
  exports.getActionBindings = getActionBindings;
2725
4452
  exports.getConflictsArray = getConflictsArray;
4453
+ exports.getKeyIcon = getKeyIcon;
2726
4454
  exports.getModifierIcon = getModifierIcon;
2727
4455
  exports.getSequenceCompletions = getSequenceCompletions;
2728
4456
  exports.hasConflicts = hasConflicts;
4457
+ exports.hasDigitPlaceholders = hasDigitPlaceholders;
4458
+ exports.hotkeySequenceToKeySeq = hotkeySequenceToKeySeq;
4459
+ exports.isDigitPlaceholder = isDigitPlaceholder;
2729
4460
  exports.isMac = isMac;
2730
4461
  exports.isModifierKey = isModifierKey;
4462
+ exports.isPlaceholderSentinel = isPlaceholderSentinel;
2731
4463
  exports.isSequence = isSequence;
4464
+ exports.isShiftedSymbol = isShiftedSymbol;
4465
+ exports.keySeqToHotkeySequence = keySeqToHotkeySequence;
2732
4466
  exports.normalizeKey = normalizeKey;
2733
- exports.parseCombinationId = parseCombinationId;
2734
4467
  exports.parseHotkeyString = parseHotkeyString;
4468
+ exports.parseKeySeq = parseKeySeq;
2735
4469
  exports.searchActions = searchActions;
2736
4470
  exports.useAction = useAction;
2737
4471
  exports.useActions = useActions;
@@ -2741,6 +4475,8 @@ exports.useHotkeys = useHotkeys;
2741
4475
  exports.useHotkeysContext = useHotkeysContext;
2742
4476
  exports.useMaybeHotkeysContext = useMaybeHotkeysContext;
2743
4477
  exports.useOmnibar = useOmnibar;
4478
+ exports.useOmnibarEndpoint = useOmnibarEndpoint;
4479
+ exports.useOmnibarEndpointsRegistry = useOmnibarEndpointsRegistry;
2744
4480
  exports.useRecordHotkey = useRecordHotkey;
2745
4481
  //# sourceMappingURL=index.cjs.map
2746
4482
  //# sourceMappingURL=index.cjs.map