@opentui/keymap 0.2.1 → 0.2.3

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.
Files changed (86) hide show
  1. package/README.md +64 -30
  2. package/chunks/index-frk6sdcd.js +409 -0
  3. package/chunks/index-frk6sdcd.js.map +14 -0
  4. package/package.json +43 -23
  5. package/src/addons/index.js +1130 -0
  6. package/src/addons/index.js.map +25 -0
  7. package/src/addons/opentui/edit-buffer-bindings.d.ts +6 -2
  8. package/src/addons/opentui/index.d.ts +2 -2
  9. package/src/addons/opentui/index.js +467 -0
  10. package/src/addons/opentui/index.js.map +12 -0
  11. package/src/addons/universal/binding-overrides.d.ts +6 -0
  12. package/src/addons/universal/dead-bindings.d.ts +1 -1
  13. package/src/addons/universal/default-parser.d.ts +2 -2
  14. package/src/addons/universal/ex-commands.d.ts +11 -8
  15. package/src/addons/universal/index.d.ts +3 -1
  16. package/src/addons/universal/leader.d.ts +1 -1
  17. package/src/addons/universal/metadata.d.ts +2 -2
  18. package/src/addons/universal/mod-bindings.d.ts +6 -0
  19. package/src/addons/universal/unresolved-commands.d.ts +1 -1
  20. package/src/extras/binding-sections.d.ts +18 -0
  21. package/src/extras/command-bindings.d.ts +19 -0
  22. package/src/extras/formatting.d.ts +27 -0
  23. package/src/extras/graph.d.ts +9 -0
  24. package/src/extras/graph.js +373 -0
  25. package/src/extras/graph.js.map +11 -0
  26. package/src/extras/index.d.ts +6 -0
  27. package/src/extras/index.js +239 -0
  28. package/src/extras/index.js.map +12 -0
  29. package/src/extras/lib/graph-snapshot.d.ts +14 -0
  30. package/src/extras/lib/graph-types.d.ts +83 -0
  31. package/src/html.d.ts +3 -3
  32. package/src/html.js +297 -0
  33. package/src/html.js.map +10 -0
  34. package/src/index.d.ts +3 -1
  35. package/src/index.js +4492 -0
  36. package/src/index.js.map +34 -0
  37. package/src/keymap.d.ts +23 -35
  38. package/src/lib/emitter.d.ts +1 -2
  39. package/src/lib/registry.d.ts +2 -2
  40. package/src/lib/runtime-utils.d.ts +34 -0
  41. package/src/opentui.d.ts +1 -3
  42. package/src/opentui.js +133 -0
  43. package/src/opentui.js.map +10 -0
  44. package/src/react/index.d.ts +5 -19
  45. package/{react → src/react}/index.js +3 -0
  46. package/src/react/index.js.map +10 -0
  47. package/src/runtime-modules.d.ts +20 -0
  48. package/src/runtime-modules.js +28 -0
  49. package/src/runtime-modules.js.map +10 -0
  50. package/src/services/activation.d.ts +7 -33
  51. package/src/services/active-key-cache.d.ts +29 -0
  52. package/src/services/command-catalog.d.ts +28 -45
  53. package/src/services/command-executor.d.ts +7 -13
  54. package/src/services/compiler.d.ts +6 -12
  55. package/src/services/conditions.d.ts +4 -16
  56. package/src/services/dispatch-decisions.d.ts +21 -0
  57. package/src/services/dispatch-patterns.d.ts +5 -0
  58. package/src/services/dispatch.d.ts +6 -42
  59. package/src/services/environment.d.ts +6 -21
  60. package/src/services/extension-context.d.ts +16 -0
  61. package/src/services/layer-diagnostics.d.ts +10 -0
  62. package/src/services/layers.d.ts +15 -23
  63. package/src/services/notify.d.ts +6 -8
  64. package/src/services/pending-sequence.d.ts +4 -0
  65. package/src/services/primitives/active-layers.d.ts +2 -3
  66. package/src/services/primitives/bindings.d.ts +4 -0
  67. package/src/services/primitives/command-normalization.d.ts +3 -0
  68. package/src/services/primitives/field-invariants.d.ts +16 -1
  69. package/src/services/primitives/pending-captures.d.ts +5 -0
  70. package/src/services/runtime-view.d.ts +5 -0
  71. package/src/services/runtime.d.ts +2 -7
  72. package/src/services/sequence-index.d.ts +24 -0
  73. package/src/services/state.d.ts +46 -91
  74. package/src/solid/index.d.ts +5 -19
  75. package/{solid → src/solid}/index.js +3 -0
  76. package/src/solid/index.js.map +10 -0
  77. package/src/testing/index.d.ts +90 -0
  78. package/src/testing/index.js +276 -0
  79. package/src/testing/index.js.map +10 -0
  80. package/src/types.d.ts +194 -126
  81. package/addons/index.js +0 -5240
  82. package/addons/opentui/index.js +0 -5632
  83. package/html.js +0 -5042
  84. package/index.js +0 -4411
  85. package/opentui.js +0 -4887
  86. package/src/services/primitives/binding-inputs.d.ts +0 -4
@@ -0,0 +1,1130 @@
1
+ // @bun
2
+ // src/addons/universal/default-parser.ts
3
+ var namedSingleStrokeKeys = new Set([
4
+ "up",
5
+ "down",
6
+ "left",
7
+ "right",
8
+ "clear",
9
+ "escape",
10
+ "return",
11
+ "linefeed",
12
+ "enter",
13
+ "tab",
14
+ "backspace",
15
+ "delete",
16
+ "insert",
17
+ "home",
18
+ "end",
19
+ "pageup",
20
+ "pagedown",
21
+ "space",
22
+ "lt",
23
+ "gt",
24
+ "plus",
25
+ "minus",
26
+ "equal",
27
+ "comma",
28
+ "period",
29
+ "slash",
30
+ "backslash",
31
+ "semicolon",
32
+ "quote",
33
+ "backquote",
34
+ "leftbracket",
35
+ "rightbracket",
36
+ "capslock",
37
+ "numlock",
38
+ "scrolllock",
39
+ "printscreen",
40
+ "pause",
41
+ "menu",
42
+ "apps",
43
+ "kp0",
44
+ "kp1",
45
+ "kp2",
46
+ "kp3",
47
+ "kp4",
48
+ "kp5",
49
+ "kp6",
50
+ "kp7",
51
+ "kp8",
52
+ "kp9",
53
+ "kpdecimal",
54
+ "kpdivide",
55
+ "kpmultiply",
56
+ "kpminus",
57
+ "kpplus",
58
+ "kpenter",
59
+ "kpequal",
60
+ "kpseparator",
61
+ "kpleft",
62
+ "kpright",
63
+ "kpup",
64
+ "kpdown",
65
+ "kppageup",
66
+ "kppagedown",
67
+ "kphome",
68
+ "kpend",
69
+ "kpinsert",
70
+ "kpdelete",
71
+ "mediaplay",
72
+ "mediapause",
73
+ "mediaplaypause",
74
+ "mediareverse",
75
+ "mediastop",
76
+ "mediafastforward",
77
+ "mediarewind",
78
+ "medianext",
79
+ "mediaprev",
80
+ "mediarecord",
81
+ "volumedown",
82
+ "volumeup",
83
+ "mute",
84
+ "leftshift",
85
+ "leftctrl",
86
+ "leftalt",
87
+ "leftsuper",
88
+ "lefthyper",
89
+ "leftmeta",
90
+ "rightshift",
91
+ "rightctrl",
92
+ "rightalt",
93
+ "rightsuper",
94
+ "righthyper",
95
+ "rightmeta",
96
+ "iso_level3_shift",
97
+ "iso_level5_shift",
98
+ "option",
99
+ "alt",
100
+ "meta",
101
+ "super",
102
+ "hyper",
103
+ "control",
104
+ "ctrl",
105
+ "shift"
106
+ ]);
107
+ var modifierKeyNames = new Set(["ctrl", "control", "shift", "meta", "alt", "option", "super", "hyper"]);
108
+ var namedSingleStrokeKeyPrefixes = createPrefixBuckets(namedSingleStrokeKeys);
109
+ var modifierKeyPrefixes = createPrefixBuckets(modifierKeyNames);
110
+ function createPrefixBuckets(values) {
111
+ const buckets = new Map;
112
+ for (const value of values) {
113
+ const first = value.charCodeAt(0);
114
+ if (Number.isNaN(first)) {
115
+ continue;
116
+ }
117
+ let bucket = buckets.get(first);
118
+ if (!bucket) {
119
+ bucket = [];
120
+ buckets.set(first, bucket);
121
+ }
122
+ bucket.push(value);
123
+ }
124
+ for (const bucket of buckets.values()) {
125
+ bucket.sort((left, right) => right.length - left.length);
126
+ }
127
+ return buckets;
128
+ }
129
+ function toLowerAsciiCode(code) {
130
+ return code >= 65 && code <= 90 ? code + 32 : code;
131
+ }
132
+ function isDigitCode(code) {
133
+ return code >= 48 && code <= 57;
134
+ }
135
+ function startsWithAsciiInsensitive(input, prefix, index) {
136
+ if (input.startsWith(prefix, index)) {
137
+ return true;
138
+ }
139
+ if (index + prefix.length > input.length) {
140
+ return false;
141
+ }
142
+ for (let offset = 0;offset < prefix.length; offset += 1) {
143
+ if (toLowerAsciiCode(input.charCodeAt(index + offset)) !== prefix.charCodeAt(offset)) {
144
+ return false;
145
+ }
146
+ }
147
+ return true;
148
+ }
149
+ function findBucketedPrefixMatch(buckets, input, index) {
150
+ const first = input.charCodeAt(index);
151
+ if (Number.isNaN(first)) {
152
+ return;
153
+ }
154
+ const candidates = buckets.get(toLowerAsciiCode(first));
155
+ if (!candidates) {
156
+ return;
157
+ }
158
+ for (const candidate of candidates) {
159
+ if (startsWithAsciiInsensitive(input, candidate, index)) {
160
+ return candidate;
161
+ }
162
+ }
163
+ return;
164
+ }
165
+ function parseObjectKeyInput(ctx, key, display, match, tokenName) {
166
+ return ctx.parseObjectKey(key, {
167
+ display,
168
+ match,
169
+ tokenName
170
+ });
171
+ }
172
+ function parseStringKeyPart(input, ctx) {
173
+ if (input === " ") {
174
+ return ctx.parseObjectKey({ name: "space" }, { display: "space" });
175
+ }
176
+ if (input === "+") {
177
+ return ctx.parseObjectKey({ name: "+" }, { display: "+" });
178
+ }
179
+ const parts = input.split("+");
180
+ let name = "";
181
+ let displayName = "";
182
+ let ctrl = false;
183
+ let shift = false;
184
+ let meta = false;
185
+ let superKey = false;
186
+ let hyper = false;
187
+ for (const rawPart of parts) {
188
+ const part = rawPart.trim();
189
+ if (!part) {
190
+ continue;
191
+ }
192
+ const lowered = part.toLowerCase();
193
+ if (lowered === "ctrl" || lowered === "control") {
194
+ ctrl = true;
195
+ continue;
196
+ }
197
+ if (lowered === "shift") {
198
+ shift = true;
199
+ continue;
200
+ }
201
+ if (lowered === "meta" || lowered === "alt" || lowered === "option") {
202
+ meta = true;
203
+ continue;
204
+ }
205
+ if (lowered === "super") {
206
+ superKey = true;
207
+ continue;
208
+ }
209
+ if (lowered === "hyper") {
210
+ hyper = true;
211
+ continue;
212
+ }
213
+ if (name) {
214
+ throw new Error(`Invalid key "${input}": multiple key names are not supported`);
215
+ }
216
+ name = part;
217
+ displayName = lowered;
218
+ }
219
+ if (!name) {
220
+ throw new Error(`Invalid key "${input}": missing key name`);
221
+ }
222
+ const displayParts = [];
223
+ if (ctrl)
224
+ displayParts.push("ctrl");
225
+ if (shift)
226
+ displayParts.push("shift");
227
+ if (meta)
228
+ displayParts.push("meta");
229
+ if (superKey)
230
+ displayParts.push("super");
231
+ if (hyper)
232
+ displayParts.push("hyper");
233
+ displayParts.push(displayName);
234
+ return ctx.parseObjectKey({
235
+ name,
236
+ ctrl,
237
+ shift,
238
+ meta,
239
+ super: superKey,
240
+ hyper: hyper || undefined
241
+ }, {
242
+ display: displayParts.join("+")
243
+ });
244
+ }
245
+ function parseNamedKeyPart(name, ctx) {
246
+ const normalized = name.trim().toLowerCase();
247
+ return ctx.parseObjectKey({ name: normalized }, { display: normalized });
248
+ }
249
+ function findNamedSingleStrokeKey(input, index) {
250
+ const namedKey = findBucketedPrefixMatch(namedSingleStrokeKeyPrefixes, input, index);
251
+ if (namedKey) {
252
+ return namedKey;
253
+ }
254
+ if (toLowerAsciiCode(input.charCodeAt(index)) !== 102 || !isDigitCode(input.charCodeAt(index + 1))) {
255
+ return;
256
+ }
257
+ const end = isDigitCode(input.charCodeAt(index + 2)) ? index + 3 : index + 2;
258
+ return input.slice(index, end).toLowerCase();
259
+ }
260
+ function findModifierKey(input, index) {
261
+ return findBucketedPrefixMatch(modifierKeyPrefixes, input, index);
262
+ }
263
+ function parseModifiedKeyPart(input, index, ctx) {
264
+ let cursor = index;
265
+ let ctrl = false;
266
+ let shift = false;
267
+ let meta = false;
268
+ let superKey = false;
269
+ let hyper = false;
270
+ while (cursor < input.length) {
271
+ const modifier = findModifierKey(input, cursor);
272
+ if (!modifier) {
273
+ break;
274
+ }
275
+ const plusIndex = cursor + modifier.length;
276
+ if (input[plusIndex] !== "+") {
277
+ break;
278
+ }
279
+ if (modifier === "ctrl" || modifier === "control") {
280
+ ctrl = true;
281
+ } else if (modifier === "shift") {
282
+ shift = true;
283
+ } else if (modifier === "meta" || modifier === "alt" || modifier === "option") {
284
+ meta = true;
285
+ } else if (modifier === "super") {
286
+ superKey = true;
287
+ } else if (modifier === "hyper") {
288
+ hyper = true;
289
+ }
290
+ cursor = plusIndex + 1;
291
+ }
292
+ if (cursor === index) {
293
+ return;
294
+ }
295
+ const char = input[cursor];
296
+ if (char === undefined) {
297
+ throw new Error(`Invalid key "${input.slice(index)}": missing key name`);
298
+ }
299
+ const name = findNamedSingleStrokeKey(input, cursor) ?? char;
300
+ const displayName = name === " " ? "space" : name === "+" ? "+" : name.toLowerCase();
301
+ const displayParts = [];
302
+ if (ctrl)
303
+ displayParts.push("ctrl");
304
+ if (shift)
305
+ displayParts.push("shift");
306
+ if (meta)
307
+ displayParts.push("meta");
308
+ if (superKey)
309
+ displayParts.push("super");
310
+ if (hyper)
311
+ displayParts.push("hyper");
312
+ displayParts.push(displayName);
313
+ return {
314
+ part: ctx.parseObjectKey({
315
+ name: name === " " ? "space" : name,
316
+ ctrl,
317
+ shift,
318
+ meta,
319
+ super: superKey,
320
+ hyper: hyper || undefined
321
+ }, {
322
+ display: displayParts.join("+")
323
+ }),
324
+ nextIndex: cursor + name.length
325
+ };
326
+ }
327
+ var defaultBindingParser = (ctx) => {
328
+ const { input, index, tokens, normalizeTokenName } = ctx;
329
+ if (index === 0 && input.includes("+") && /\s/.test(input)) {
330
+ return {
331
+ parts: [parseStringKeyPart(input, ctx)],
332
+ nextIndex: input.length
333
+ };
334
+ }
335
+ const char = input[index];
336
+ if (char === undefined) {
337
+ return;
338
+ }
339
+ if (char === "<") {
340
+ const end = input.indexOf(">", index);
341
+ if (end === -1) {
342
+ return {
343
+ parts: [parseStringKeyPart(char, ctx)],
344
+ nextIndex: index + 1
345
+ };
346
+ }
347
+ const tokenName = normalizeTokenName(input.slice(index + 1, end));
348
+ const token = tokens.get(tokenName);
349
+ if (!token) {
350
+ return {
351
+ parts: [],
352
+ nextIndex: end + 1,
353
+ unknownTokens: [tokenName]
354
+ };
355
+ }
356
+ return {
357
+ parts: [parseObjectKeyInput(ctx, token.stroke, `<${tokenName}>`, token.match, tokenName)],
358
+ nextIndex: end + 1,
359
+ usedTokens: [tokenName]
360
+ };
361
+ }
362
+ if (char === "{") {
363
+ const end = input.indexOf("}", index);
364
+ if (end === -1) {
365
+ return {
366
+ parts: [parseStringKeyPart(char, ctx)],
367
+ nextIndex: index + 1
368
+ };
369
+ }
370
+ const patternName = normalizeTokenName(input.slice(index + 1, end));
371
+ const pattern = ctx.patterns.get(patternName);
372
+ if (!pattern) {
373
+ return {
374
+ parts: [],
375
+ nextIndex: end + 1,
376
+ unknownTokens: [patternName]
377
+ };
378
+ }
379
+ const part = ctx.parseObjectKey({ name: patternName, ctrl: false, shift: false, meta: false, super: false }, { display: pattern.display ?? `{${patternName}}`, match: pattern.match });
380
+ return {
381
+ parts: [{ ...part, patternName: pattern.name, payloadKey: pattern.payloadKey }],
382
+ nextIndex: end + 1,
383
+ usedTokens: [patternName]
384
+ };
385
+ }
386
+ const modified = parseModifiedKeyPart(input, index, ctx);
387
+ if (modified) {
388
+ return {
389
+ parts: [modified.part],
390
+ nextIndex: modified.nextIndex
391
+ };
392
+ }
393
+ const namedKey = findNamedSingleStrokeKey(input, index);
394
+ if (namedKey) {
395
+ return {
396
+ parts: [parseNamedKeyPart(namedKey, ctx)],
397
+ nextIndex: index + namedKey.length
398
+ };
399
+ }
400
+ return {
401
+ parts: [parseStringKeyPart(char, ctx)],
402
+ nextIndex: index + 1
403
+ };
404
+ };
405
+ var defaultEventMatchResolver = (event, ctx) => {
406
+ return [
407
+ ctx.resolveKey({
408
+ name: event.name,
409
+ ctrl: event.ctrl,
410
+ shift: event.shift,
411
+ meta: event.meta,
412
+ super: event.super ?? false,
413
+ hyper: event.hyper || undefined
414
+ })
415
+ ];
416
+ };
417
+ function registerDefaultBindingParser(keymap) {
418
+ return keymap.appendBindingParser((ctx) => defaultBindingParser(ctx));
419
+ }
420
+ function registerDefaultEventMatchResolver(keymap) {
421
+ return keymap.appendEventMatchResolver((event, ctx) => defaultEventMatchResolver(event, ctx));
422
+ }
423
+ function registerDefaultKeys(keymap) {
424
+ const offParser = registerDefaultBindingParser(keymap);
425
+ const offResolver = registerDefaultEventMatchResolver(keymap);
426
+ return () => {
427
+ offResolver();
428
+ offParser();
429
+ };
430
+ }
431
+ // src/addons/universal/binding-overrides.ts
432
+ var BINDING_OVERRIDES_RESOURCE = Symbol("keymap:binding-overrides");
433
+ function normalizeBindingOverrides(value) {
434
+ if (!Array.isArray(value)) {
435
+ throw new Error('Keymap layer field "bindingOverrides" must be an array of binding objects');
436
+ }
437
+ return value;
438
+ }
439
+ function getBindingOverrides(layer) {
440
+ const overrides = layer.bindingOverrides;
441
+ if (!overrides || !Array.isArray(overrides)) {
442
+ return;
443
+ }
444
+ return normalizeBindingOverrides(overrides);
445
+ }
446
+ function registerBindingOverrides(keymap) {
447
+ return keymap.acquireResource(BINDING_OVERRIDES_RESOURCE, () => {
448
+ const offLayerField = keymap.registerLayerFields({
449
+ bindingOverrides(value) {
450
+ normalizeBindingOverrides(value);
451
+ }
452
+ });
453
+ const offTransformer = keymap.appendLayerBindingsTransformer((bindings, ctx) => {
454
+ const overrides = getBindingOverrides(ctx.layer);
455
+ if (!overrides) {
456
+ return;
457
+ }
458
+ const validation = ctx.validateBindings(overrides);
459
+ if (!validation.ok) {
460
+ throw new Error(validation.reason);
461
+ }
462
+ const overrideCommands = new Set(overrides.flatMap((binding) => typeof binding.cmd === "string" ? [binding.cmd.trim()] : []));
463
+ return [
464
+ ...overrides,
465
+ ...bindings.filter((binding) => {
466
+ return typeof binding.cmd !== "string" || !overrideCommands.has(binding.cmd.trim());
467
+ })
468
+ ];
469
+ });
470
+ return () => {
471
+ offTransformer();
472
+ offLayerField();
473
+ };
474
+ });
475
+ }
476
+ // src/addons/universal/aliases.ts
477
+ var ALIASES_FIELD_RESOURCE = Symbol("keymap:aliases-field");
478
+ function normalizeAliases(value) {
479
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
480
+ throw new Error('Keymap aliases field "aliases" must be an object of key-name mappings');
481
+ }
482
+ const aliases = {};
483
+ for (const [name, key] of Object.entries(value)) {
484
+ const trimmedName = name.trim();
485
+ if (!trimmedName) {
486
+ throw new Error('Keymap aliases field "aliases" cannot contain empty alias names');
487
+ }
488
+ if (typeof key !== "string") {
489
+ throw new Error(`Keymap alias "${trimmedName}" must map to a string key name`);
490
+ }
491
+ const trimmedKey = key.trim();
492
+ if (!trimmedKey) {
493
+ throw new Error(`Keymap alias "${trimmedName}" cannot map to an empty key name`);
494
+ }
495
+ aliases[trimmedName.toLowerCase()] = trimmedKey.toLowerCase();
496
+ }
497
+ return aliases;
498
+ }
499
+ function getAliases(layer) {
500
+ const aliases = layer.aliases;
501
+ if (!aliases || typeof aliases !== "object" || Array.isArray(aliases)) {
502
+ return;
503
+ }
504
+ return normalizeAliases(aliases);
505
+ }
506
+ function registerAliasesField(keymap) {
507
+ return keymap.acquireResource(ALIASES_FIELD_RESOURCE, () => {
508
+ const offLayerField = keymap.registerLayerFields({
509
+ aliases(value, ctx) {
510
+ normalizeAliases(value);
511
+ }
512
+ });
513
+ const offBindingTransformer = keymap.appendBindingTransformer((binding, ctx) => {
514
+ const aliases = getAliases(ctx.layer);
515
+ if (!aliases) {
516
+ return;
517
+ }
518
+ if (binding.sequence.length !== 1) {
519
+ return;
520
+ }
521
+ const [part] = binding.sequence;
522
+ if (!part) {
523
+ return;
524
+ }
525
+ const aliasedName = aliases[part.stroke.name];
526
+ if (!aliasedName) {
527
+ return;
528
+ }
529
+ ctx.add({
530
+ ...binding,
531
+ sequence: [
532
+ ctx.parseKey({
533
+ ...part.stroke,
534
+ name: aliasedName
535
+ })
536
+ ]
537
+ });
538
+ });
539
+ return () => {
540
+ offBindingTransformer();
541
+ offLayerField();
542
+ };
543
+ });
544
+ }
545
+ // src/addons/universal/backspace-pops-pending-sequence.ts
546
+ function registerBackspacePopsPendingSequence(keymap, options) {
547
+ const shouldPreventDefault = options?.preventDefault ?? true;
548
+ return keymap.intercept("key", ({ event, consume }) => {
549
+ if (event.name !== "backspace") {
550
+ return;
551
+ }
552
+ if (!keymap.popPendingSequence()) {
553
+ return;
554
+ }
555
+ if (shouldPreventDefault) {
556
+ consume();
557
+ }
558
+ }, { priority: options?.priority ?? 0 });
559
+ }
560
+ // src/addons/universal/comma-bindings.ts
561
+ var COMMA_BINDINGS_RESOURCE = Symbol("keymap:comma-bindings");
562
+ var commaBindingExpander = ({ input, displays }) => {
563
+ if (!input.includes(",")) {
564
+ return;
565
+ }
566
+ const parts = input.split(",").map((part) => part.trim());
567
+ if (parts.some((part) => part.length === 0)) {
568
+ throw new Error(`Invalid key sequence "${input}": comma-separated bindings cannot contain empty entries`);
569
+ }
570
+ if (!displays) {
571
+ return parts.map((key) => ({ key }));
572
+ }
573
+ const displayParts = displays.length === 1 ? displays[0].split(",").map((part) => part.trim()) : displays;
574
+ if (displayParts.length !== parts.length || displayParts.some((part) => part.length === 0)) {
575
+ return parts.map((key) => ({ key }));
576
+ }
577
+ return parts.map((key, index) => ({
578
+ key,
579
+ displays: displayParts[index].trim().split(/\s+/).filter(Boolean)
580
+ }));
581
+ };
582
+ function registerCommaBindings(keymap) {
583
+ return keymap.acquireResource(COMMA_BINDINGS_RESOURCE, () => {
584
+ return keymap.appendBindingExpander(commaBindingExpander);
585
+ });
586
+ }
587
+ // src/addons/universal/dead-bindings.ts
588
+ import { stringifyKeySequence, stringifyKeyStroke } from "@opentui/keymap";
589
+ var DEAD_BINDING_WARNINGS_RESOURCE = Symbol("keymap:dead-binding-warnings");
590
+ function isDeadMetadataOnlyBinding(binding) {
591
+ if (binding.command !== undefined) {
592
+ return false;
593
+ }
594
+ if (binding.event === "release") {
595
+ return true;
596
+ }
597
+ return !binding.hasContinuations && !binding.hasCommandAtSequence;
598
+ }
599
+ function warnDeadMetadataOnlyBinding(ctx, binding) {
600
+ const sequence = stringifyKeySequence(binding.parsedBinding.sequence, { preferDisplay: true });
601
+ const sourceKey = typeof binding.parsedBinding.key === "string" ? binding.parsedBinding.key : stringifyKeyStroke(binding.parsedBinding.key);
602
+ const warningKey = `dead-binding:${binding.sourceLayerOrder}:${binding.bindingIndex}:${sourceKey}`;
603
+ ctx.warnOnce(warningKey, "dead-binding", {
604
+ binding: binding.parsedBinding,
605
+ target: binding.sourceTarget
606
+ }, `[Keymap] Binding "${sequence}" has no command and no reachable continuations; it will never trigger`);
607
+ }
608
+ function registerDeadBindingWarnings(keymap) {
609
+ return keymap.acquireResource(DEAD_BINDING_WARNINGS_RESOURCE, () => {
610
+ return keymap.appendLayerAnalyzer((ctx) => {
611
+ for (const binding of ctx.bindings) {
612
+ if (!isDeadMetadataOnlyBinding(binding)) {
613
+ continue;
614
+ }
615
+ warnDeadMetadataOnlyBinding(ctx, binding);
616
+ }
617
+ });
618
+ });
619
+ }
620
+ // src/addons/universal/escape-clears-pending-sequence.ts
621
+ function registerEscapeClearsPendingSequence(keymap, options) {
622
+ const shouldPreventDefault = options?.preventDefault ?? true;
623
+ return keymap.intercept("key", ({ event, consume }) => {
624
+ if (event.name !== "escape") {
625
+ return;
626
+ }
627
+ if (!keymap.hasPendingSequence()) {
628
+ return;
629
+ }
630
+ keymap.clearPendingSequence();
631
+ if (shouldPreventDefault) {
632
+ consume();
633
+ }
634
+ }, { priority: options?.priority ?? 0 });
635
+ }
636
+ // src/addons/universal/enabled.ts
637
+ var ENABLED_FIELDS_RESOURCE = Symbol("keymap:enabled-fields");
638
+ function isReactiveMatcher(value) {
639
+ if (!value || typeof value !== "object") {
640
+ return false;
641
+ }
642
+ const candidate = value;
643
+ return typeof candidate.get === "function" && typeof candidate.subscribe === "function";
644
+ }
645
+ function normalizeEnabledValue(fieldName, value) {
646
+ if (typeof value === "boolean") {
647
+ return value;
648
+ }
649
+ if (typeof value === "function") {
650
+ return value;
651
+ }
652
+ if (isReactiveMatcher(value)) {
653
+ return value;
654
+ }
655
+ throw new Error(`Keymap enabled field "${fieldName}" must be a boolean, a function, or a reactive matcher`);
656
+ }
657
+ function registerEnabledFields(keymap) {
658
+ return keymap.acquireResource(ENABLED_FIELDS_RESOURCE, () => {
659
+ const offLayerFields = keymap.registerLayerFields({
660
+ enabled(value, ctx) {
661
+ const normalized = normalizeEnabledValue("enabled", value);
662
+ if (normalized === true) {
663
+ return;
664
+ }
665
+ if (normalized === false) {
666
+ ctx.activeWhen(() => false);
667
+ return;
668
+ }
669
+ ctx.activeWhen(normalized);
670
+ }
671
+ });
672
+ const offCommandFields = keymap.registerCommandFields({
673
+ enabled(value, ctx) {
674
+ const normalized = normalizeEnabledValue("enabled", value);
675
+ if (normalized === true) {
676
+ return;
677
+ }
678
+ if (normalized === false) {
679
+ ctx.activeWhen(() => false);
680
+ return;
681
+ }
682
+ ctx.activeWhen(normalized);
683
+ }
684
+ });
685
+ return () => {
686
+ offCommandFields();
687
+ offLayerFields();
688
+ };
689
+ });
690
+ }
691
+ // src/addons/universal/emacs-bindings.ts
692
+ var EMACS_BINDINGS_RESOURCE = Symbol("keymap:emacs-bindings");
693
+ function parseEmacsStroke(input, sequence, parseObjectKey) {
694
+ const parts = input.split("+");
695
+ let name = "";
696
+ let ctrl = false;
697
+ let shift = false;
698
+ let meta = false;
699
+ let superKey = false;
700
+ let hyper = false;
701
+ for (const rawPart of parts) {
702
+ const part = rawPart.trim();
703
+ if (!part) {
704
+ continue;
705
+ }
706
+ const lowered = part.toLowerCase();
707
+ if (lowered === "ctrl" || lowered === "control") {
708
+ ctrl = true;
709
+ continue;
710
+ }
711
+ if (lowered === "shift") {
712
+ shift = true;
713
+ continue;
714
+ }
715
+ if (lowered === "meta" || lowered === "alt" || lowered === "option") {
716
+ meta = true;
717
+ continue;
718
+ }
719
+ if (lowered === "super") {
720
+ superKey = true;
721
+ continue;
722
+ }
723
+ if (lowered === "hyper") {
724
+ hyper = true;
725
+ continue;
726
+ }
727
+ if (name) {
728
+ throw new Error(`Invalid emacs key sequence "${sequence}": stroke "${input}" contains multiple key names`);
729
+ }
730
+ name = part;
731
+ }
732
+ if (!name) {
733
+ throw new Error(`Invalid emacs key sequence "${sequence}": stroke "${input}" is missing a key name`);
734
+ }
735
+ return parseObjectKey({
736
+ name,
737
+ ctrl,
738
+ shift,
739
+ meta,
740
+ super: superKey,
741
+ hyper: hyper || undefined
742
+ });
743
+ }
744
+ function parseEmacsSequence(input, parseObjectKey) {
745
+ const strokes = input.trim().split(/\s+/).filter(Boolean);
746
+ if (strokes.length <= 1) {
747
+ return;
748
+ }
749
+ if (!strokes.some((stroke) => stroke.includes("+"))) {
750
+ return;
751
+ }
752
+ return strokes.map((stroke) => parseEmacsStroke(stroke, input, parseObjectKey));
753
+ }
754
+ function registerEmacsBindings(keymap) {
755
+ return keymap.acquireResource(EMACS_BINDINGS_RESOURCE, () => {
756
+ const parseEmacsBinding = ({ input, index, parseObjectKey }) => {
757
+ const parsed = parseEmacsSequence(input, parseObjectKey);
758
+ if (!parsed || index !== 0) {
759
+ return;
760
+ }
761
+ return {
762
+ parts: parsed,
763
+ nextIndex: input.length
764
+ };
765
+ };
766
+ return keymap.prependBindingParser(parseEmacsBinding);
767
+ });
768
+ }
769
+ // src/addons/universal/ex-commands.ts
770
+ var EX_COMMANDS_RESOURCE = Symbol("keymap:ex-commands");
771
+ function isExCommandPayload(value) {
772
+ if (!value || typeof value !== "object") {
773
+ return false;
774
+ }
775
+ const candidate = value;
776
+ return typeof candidate.raw === "string" && Array.isArray(candidate.args);
777
+ }
778
+ function getExCommandPayload(input, payload) {
779
+ if (isExCommandPayload(payload)) {
780
+ return payload;
781
+ }
782
+ return { raw: input, args: [], payload };
783
+ }
784
+ function normalizeExCommandName(name) {
785
+ const normalized = name.trim();
786
+ if (!normalized) {
787
+ throw new Error("Invalid keymap command name: name cannot be empty");
788
+ }
789
+ if (/\s/.test(normalized)) {
790
+ throw new Error(`Invalid keymap command name "${name}": command names cannot contain whitespace`);
791
+ }
792
+ if (normalized.startsWith(":")) {
793
+ return normalized;
794
+ }
795
+ return `:${normalized}`;
796
+ }
797
+ function normalizeExCommandAliases(value) {
798
+ if (!Array.isArray(value)) {
799
+ throw new Error('Keymap ex-command field "aliases" must be an array of command names');
800
+ }
801
+ const aliases = [];
802
+ for (const alias of value) {
803
+ if (typeof alias !== "string") {
804
+ throw new Error('Keymap ex-command field "aliases" must only contain command names');
805
+ }
806
+ aliases.push(alias);
807
+ }
808
+ return aliases;
809
+ }
810
+ function normalizeExCommandNargs(value) {
811
+ if (value === "0" || value === "1" || value === "?" || value === "*" || value === "+") {
812
+ return value;
813
+ }
814
+ throw new Error('Keymap ex-command field "nargs" must be "0", "1", "?", "*", or "+"');
815
+ }
816
+ function getExCommandAliases(command) {
817
+ const aliases = command.aliases;
818
+ if (aliases === undefined) {
819
+ return [];
820
+ }
821
+ return normalizeExCommandAliases(aliases);
822
+ }
823
+ function isExCommand(command) {
824
+ return command.name.trim().startsWith(":") || command.namespace === "excommands";
825
+ }
826
+ function parseCommandInput(input) {
827
+ const trimmed = input.trim();
828
+ if (!trimmed) {
829
+ throw new Error("Invalid keymap command: command cannot be empty");
830
+ }
831
+ const parts = trimmed.split(/\s+/);
832
+ const [name, ...args] = parts;
833
+ if (!name) {
834
+ throw new Error(`Invalid keymap command "${input}"`);
835
+ }
836
+ return {
837
+ input: trimmed,
838
+ name,
839
+ args
840
+ };
841
+ }
842
+ function validateCommandArgs(command, args) {
843
+ if (command.nargs === undefined) {
844
+ return true;
845
+ }
846
+ const nargs = normalizeExCommandNargs(command.nargs);
847
+ const count = args.length;
848
+ if (nargs === "0") {
849
+ return count === 0;
850
+ }
851
+ if (nargs === "1") {
852
+ return count === 1;
853
+ }
854
+ if (nargs === "?") {
855
+ return count <= 1;
856
+ }
857
+ if (nargs === "*") {
858
+ return true;
859
+ }
860
+ if (nargs === "+") {
861
+ return count >= 1;
862
+ }
863
+ return true;
864
+ }
865
+ function createExCommandRegistration(command, name) {
866
+ const run = command.run;
867
+ return {
868
+ ...command,
869
+ name,
870
+ namespace: "excommands",
871
+ run(ctx) {
872
+ const payload = getExCommandPayload(ctx.input, ctx.payload);
873
+ if (!validateCommandArgs(command, payload.args)) {
874
+ return { ok: false, reason: "invalid-args" };
875
+ }
876
+ return run({
877
+ ...ctx,
878
+ command: ctx.command,
879
+ payload
880
+ });
881
+ }
882
+ };
883
+ }
884
+ function registerExCommands(keymap) {
885
+ return keymap.acquireResource(EX_COMMANDS_RESOURCE, () => {
886
+ const offFields = keymap.registerCommandFields({
887
+ aliases(value) {
888
+ normalizeExCommandAliases(value);
889
+ },
890
+ nargs(value) {
891
+ normalizeExCommandNargs(value);
892
+ }
893
+ });
894
+ const offTransformer = keymap.appendCommandTransformer((command, ctx) => {
895
+ if (!isExCommand(command)) {
896
+ return;
897
+ }
898
+ ctx.skipOriginal();
899
+ if (command.nargs !== undefined) {
900
+ normalizeExCommandNargs(command.nargs);
901
+ }
902
+ const names = [command.name, ...getExCommandAliases(command)].map((name) => normalizeExCommandName(name));
903
+ for (const name of names) {
904
+ ctx.add(createExCommandRegistration(command, name));
905
+ }
906
+ });
907
+ const offResolver = keymap.appendCommandResolver((input, ctx) => {
908
+ if (!input.startsWith(":")) {
909
+ return;
910
+ }
911
+ const parsed = parseCommandInput(input);
912
+ const normalizedName = normalizeExCommandName(parsed.name);
913
+ const command = ctx.getCommand(normalizedName);
914
+ if (!command) {
915
+ return;
916
+ }
917
+ ctx.setInput(parsed.input);
918
+ ctx.setPayload({ raw: parsed.input, args: parsed.args, payload: ctx.payload });
919
+ return command;
920
+ });
921
+ return () => {
922
+ offResolver();
923
+ offTransformer();
924
+ offFields();
925
+ };
926
+ });
927
+ }
928
+ // src/addons/universal/leader.ts
929
+ function registerLeader(keymap, options) {
930
+ return keymap.registerToken({
931
+ name: options.name ?? "leader",
932
+ key: options.trigger
933
+ });
934
+ }
935
+ // src/addons/universal/metadata.ts
936
+ var METADATA_FIELDS_RESOURCE = Symbol("keymap:metadata-fields");
937
+ function normalizeMetadataText(fieldName, value) {
938
+ if (typeof value !== "string") {
939
+ throw new Error(`Keymap metadata field "${fieldName}" must be a string`);
940
+ }
941
+ const trimmed = value.trim();
942
+ if (!trimmed) {
943
+ throw new Error(`Keymap metadata field "${fieldName}" cannot be empty`);
944
+ }
945
+ return trimmed;
946
+ }
947
+ function registerMetadataFields(keymap) {
948
+ return keymap.acquireResource(METADATA_FIELDS_RESOURCE, () => {
949
+ const offBindingFields = keymap.registerBindingFields({
950
+ desc(value, ctx) {
951
+ ctx.attr("desc", normalizeMetadataText("desc", value));
952
+ },
953
+ group(value, ctx) {
954
+ ctx.attr("group", normalizeMetadataText("group", value));
955
+ }
956
+ });
957
+ const offCommandFields = keymap.registerCommandFields({
958
+ desc(value, ctx) {
959
+ ctx.attr("desc", normalizeMetadataText("desc", value));
960
+ },
961
+ title(value, ctx) {
962
+ ctx.attr("title", normalizeMetadataText("title", value));
963
+ },
964
+ category(value, ctx) {
965
+ ctx.attr("category", normalizeMetadataText("category", value));
966
+ }
967
+ });
968
+ return () => {
969
+ offCommandFields();
970
+ offBindingFields();
971
+ };
972
+ });
973
+ }
974
+ // src/addons/universal/mod-bindings.ts
975
+ var MOD_BINDINGS_RESOURCE = Symbol("keymap:mod-bindings");
976
+ var MOD_MODIFIER_PATTERN = /(^|[+,\s])mod(?=\s*\+)/i;
977
+ var MOD_MODIFIER_REPLACE_PATTERN = /(^|[+,\s])mod(?=\s*\+)/gi;
978
+ function resolveModModifier(metadata) {
979
+ const primary = metadata.primaryModifier;
980
+ if ((primary === "ctrl" || primary === "super") && metadata.modifiers[primary] !== "unsupported") {
981
+ return primary;
982
+ }
983
+ if (metadata.modifiers.ctrl !== "unsupported") {
984
+ return "ctrl";
985
+ }
986
+ return "super";
987
+ }
988
+ function createDisplays(input) {
989
+ if (input.includes(",")) {
990
+ return [input];
991
+ }
992
+ const trimmed = input.trim();
993
+ return trimmed.includes(" ") ? trimmed.split(/\s+/) : [input];
994
+ }
995
+ function registerModBindings(keymap) {
996
+ return keymap.acquireResource(MOD_BINDINGS_RESOURCE, () => {
997
+ const expandModBinding = ({ input, displays }) => {
998
+ if (!MOD_MODIFIER_PATTERN.test(input)) {
999
+ return;
1000
+ }
1001
+ const modifier = resolveModModifier(keymap.getHostMetadata());
1002
+ return [
1003
+ {
1004
+ key: input.replace(MOD_MODIFIER_REPLACE_PATTERN, (_match, prefix) => `${prefix}${modifier}`),
1005
+ displays: displays ?? createDisplays(input)
1006
+ }
1007
+ ];
1008
+ };
1009
+ return keymap.appendBindingExpander(expandModBinding);
1010
+ });
1011
+ }
1012
+ // src/addons/universal/neovim-disambiguation.ts
1013
+ function registerNeovimDisambiguation(keymap, options) {
1014
+ const timeoutMs = options?.timeoutMs ?? 300;
1015
+ return keymap.appendDisambiguationResolver((ctx) => {
1016
+ return ctx.defer(async (deferred) => {
1017
+ const elapsed = await deferred.sleep(timeoutMs);
1018
+ if (!elapsed) {
1019
+ return;
1020
+ }
1021
+ return deferred.runExact();
1022
+ });
1023
+ });
1024
+ }
1025
+ // src/addons/universal/timed-leader.ts
1026
+ function registerTimedLeader(keymap, options) {
1027
+ const matchesTrigger = keymap.createKeyMatcher(options.trigger);
1028
+ const timeoutMs = options.timeoutMs ?? 1500;
1029
+ let armed = false;
1030
+ let timeout;
1031
+ const clearTimer = () => {
1032
+ if (!timeout) {
1033
+ return;
1034
+ }
1035
+ clearTimeout(timeout);
1036
+ timeout = undefined;
1037
+ };
1038
+ const scheduleTimeout = () => {
1039
+ clearTimer();
1040
+ timeout = setTimeout(() => {
1041
+ keymap.clearPendingSequence();
1042
+ }, timeoutMs);
1043
+ };
1044
+ const syncArmedState = (sequence) => {
1045
+ const nextArmed = matchesTrigger(sequence[0]);
1046
+ if (nextArmed) {
1047
+ scheduleTimeout();
1048
+ } else {
1049
+ clearTimer();
1050
+ }
1051
+ if (nextArmed === armed) {
1052
+ return;
1053
+ }
1054
+ armed = nextArmed;
1055
+ if (armed) {
1056
+ options.onArm?.();
1057
+ return;
1058
+ }
1059
+ options.onDisarm?.();
1060
+ };
1061
+ const offLeader = registerLeader(keymap, options);
1062
+ const offPendingSequenceChange = keymap.on("pendingSequence", (sequence) => {
1063
+ syncArmedState(sequence);
1064
+ });
1065
+ syncArmedState(keymap.getPendingSequence());
1066
+ const dispose = () => {
1067
+ clearTimer();
1068
+ offPendingSequenceChange();
1069
+ offLeader();
1070
+ if (!armed) {
1071
+ return;
1072
+ }
1073
+ armed = false;
1074
+ options.onDisarm?.();
1075
+ };
1076
+ return dispose;
1077
+ }
1078
+ // src/addons/universal/unresolved-commands.ts
1079
+ import { stringifyKeySequence as stringifyKeySequence2, stringifyKeyStroke as stringifyKeyStroke2 } from "@opentui/keymap";
1080
+ var UNRESOLVED_COMMAND_WARNINGS_RESOURCE = Symbol("keymap:unresolved-command-warnings");
1081
+ function warnUnresolvedCommand(ctx, binding) {
1082
+ if (typeof binding.command !== "string") {
1083
+ return;
1084
+ }
1085
+ if (ctx.checkCommandResolution(binding.command) !== "unresolved") {
1086
+ return;
1087
+ }
1088
+ const sequence = stringifyKeySequence2(binding.parsedBinding.sequence, { preferDisplay: true });
1089
+ const sourceKey = typeof binding.parsedBinding.key === "string" ? binding.parsedBinding.key : stringifyKeyStroke2(binding.parsedBinding.key);
1090
+ const warning = {
1091
+ command: binding.command,
1092
+ binding: binding.parsedBinding,
1093
+ target: binding.sourceTarget
1094
+ };
1095
+ ctx.warnOnce(`unresolved:${binding.sourceLayerOrder}:${binding.bindingIndex}:${binding.command}:${sourceKey}`, "unresolved-command", warning, `[Keymap] Unresolved command "${binding.command}" for binding "${sequence}"`);
1096
+ }
1097
+ function registerUnresolvedCommandWarnings(keymap) {
1098
+ return keymap.acquireResource(UNRESOLVED_COMMAND_WARNINGS_RESOURCE, () => {
1099
+ return keymap.appendLayerAnalyzer((ctx) => {
1100
+ for (const binding of ctx.bindings) {
1101
+ warnUnresolvedCommand(ctx, binding);
1102
+ }
1103
+ });
1104
+ });
1105
+ }
1106
+ export {
1107
+ registerUnresolvedCommandWarnings,
1108
+ registerTimedLeader,
1109
+ registerNeovimDisambiguation,
1110
+ registerModBindings,
1111
+ registerMetadataFields,
1112
+ registerLeader,
1113
+ registerExCommands,
1114
+ registerEscapeClearsPendingSequence,
1115
+ registerEnabledFields,
1116
+ registerEmacsBindings,
1117
+ registerDefaultKeys,
1118
+ registerDefaultEventMatchResolver,
1119
+ registerDefaultBindingParser,
1120
+ registerDeadBindingWarnings,
1121
+ registerCommaBindings,
1122
+ registerBindingOverrides,
1123
+ registerBackspacePopsPendingSequence,
1124
+ registerAliasesField,
1125
+ defaultEventMatchResolver,
1126
+ defaultBindingParser
1127
+ };
1128
+
1129
+ //# debugId=BC9676433CEF75A964756E2164756E21
1130
+ //# sourceMappingURL=index.js.map