ink-cartridge 3.6.1

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 (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -0
  3. package/dist/binary-storage/BinaryStorage.d.ts +308 -0
  4. package/dist/binary-storage/BinaryStorage.js +548 -0
  5. package/dist/binary-storage/StreamingReader.d.ts +53 -0
  6. package/dist/binary-storage/StreamingReader.js +530 -0
  7. package/dist/binary-storage/index.d.ts +91 -0
  8. package/dist/binary-storage/index.js +91 -0
  9. package/dist/binary-storage/types.d.ts +400 -0
  10. package/dist/binary-storage/types.js +15 -0
  11. package/dist/cli/index.d.ts +2 -0
  12. package/dist/cli/index.js +214 -0
  13. package/dist/cli/initTheme.d.ts +14 -0
  14. package/dist/cli/initTheme.js +124 -0
  15. package/dist/cli/makeLanguageType.d.ts +46 -0
  16. package/dist/cli/makeLanguageType.js +264 -0
  17. package/dist/cli/makeThemeType.d.ts +43 -0
  18. package/dist/cli/makeThemeType.js +222 -0
  19. package/dist/components/badge/Badge.d.ts +7 -0
  20. package/dist/components/badge/Badge.js +9 -0
  21. package/dist/components/dialog/ConfirmDialog.d.ts +23 -0
  22. package/dist/components/dialog/ConfirmDialog.js +57 -0
  23. package/dist/components/dialog/types.d.ts +20 -0
  24. package/dist/components/dialog/types.js +1 -0
  25. package/dist/components/divider/Divider.d.ts +8 -0
  26. package/dist/components/divider/Divider.js +10 -0
  27. package/dist/components/fold/Fold.d.ts +3 -0
  28. package/dist/components/fold/Fold.js +44 -0
  29. package/dist/components/fold/types.d.ts +28 -0
  30. package/dist/components/fold/types.js +1 -0
  31. package/dist/components/form/Field.d.ts +30 -0
  32. package/dist/components/form/Field.js +59 -0
  33. package/dist/components/form/Form.d.ts +32 -0
  34. package/dist/components/form/Form.js +163 -0
  35. package/dist/components/form/context.d.ts +14 -0
  36. package/dist/components/form/context.js +20 -0
  37. package/dist/components/form/types.d.ts +115 -0
  38. package/dist/components/form/types.js +1 -0
  39. package/dist/components/key-hint/KeyHint.d.ts +10 -0
  40. package/dist/components/key-hint/KeyHint.js +12 -0
  41. package/dist/components/multi-select/MultiSelectInput.d.ts +125 -0
  42. package/dist/components/multi-select/MultiSelectInput.js +287 -0
  43. package/dist/components/multi-select/types.d.ts +100 -0
  44. package/dist/components/multi-select/types.js +1 -0
  45. package/dist/components/number-input/NumberInput.d.ts +11 -0
  46. package/dist/components/number-input/NumberInput.js +44 -0
  47. package/dist/components/progress-bar/ProgressBar.d.ts +11 -0
  48. package/dist/components/progress-bar/ProgressBar.js +9 -0
  49. package/dist/components/search-input/SearchInput.d.ts +10 -0
  50. package/dist/components/search-input/SearchInput.js +15 -0
  51. package/dist/components/select/SelectInput.d.ts +100 -0
  52. package/dist/components/select/SelectInput.js +147 -0
  53. package/dist/components/select/types.d.ts +63 -0
  54. package/dist/components/select/types.js +1 -0
  55. package/dist/components/spinner/Spinner.d.ts +12 -0
  56. package/dist/components/spinner/Spinner.js +24 -0
  57. package/dist/components/tabs/Tabs.d.ts +3 -0
  58. package/dist/components/tabs/Tabs.js +57 -0
  59. package/dist/components/tabs/types.d.ts +32 -0
  60. package/dist/components/tabs/types.js +1 -0
  61. package/dist/components/text/TextInput.d.ts +39 -0
  62. package/dist/components/text/TextInput.js +233 -0
  63. package/dist/components/text/types.d.ts +62 -0
  64. package/dist/components/text/types.js +1 -0
  65. package/dist/dev/dev-screen.d.ts +36 -0
  66. package/dist/dev/dev-screen.js +142 -0
  67. package/dist/dev/entrance.d.ts +51 -0
  68. package/dist/dev/entrance.js +67 -0
  69. package/dist/dev/globalKey-display.d.ts +2 -0
  70. package/dist/dev/globalKey-display.js +8 -0
  71. package/dist/dev/types.d.ts +9 -0
  72. package/dist/dev/types.js +1 -0
  73. package/dist/index.d.ts +46 -0
  74. package/dist/index.js +50 -0
  75. package/dist/keyboard/check-global-key.d.ts +18 -0
  76. package/dist/keyboard/check-global-key.js +52 -0
  77. package/dist/keyboard/context.d.ts +398 -0
  78. package/dist/keyboard/context.js +9 -0
  79. package/dist/keyboard/global-key-processor/index.d.ts +16 -0
  80. package/dist/keyboard/global-key-processor/index.js +75 -0
  81. package/dist/keyboard/global-sequence-processor/index.d.ts +14 -0
  82. package/dist/keyboard/global-sequence-processor/index.js +165 -0
  83. package/dist/keyboard/hook.d.ts +51 -0
  84. package/dist/keyboard/hook.js +105 -0
  85. package/dist/keyboard/index.d.ts +5 -0
  86. package/dist/keyboard/index.js +3 -0
  87. package/dist/keyboard/keyNormalizer.d.ts +37 -0
  88. package/dist/keyboard/keyNormalizer.js +119 -0
  89. package/dist/keyboard/layer-handler.d.ts +57 -0
  90. package/dist/keyboard/layer-handler.js +274 -0
  91. package/dist/keyboard/modal-processor/index.d.ts +16 -0
  92. package/dist/keyboard/modal-processor/index.js +97 -0
  93. package/dist/keyboard/overlay-processor/index.d.ts +16 -0
  94. package/dist/keyboard/overlay-processor/index.js +30 -0
  95. package/dist/keyboard/pipeline/chain.d.ts +10 -0
  96. package/dist/keyboard/pipeline/chain.js +45 -0
  97. package/dist/keyboard/pipeline/context.d.ts +34 -0
  98. package/dist/keyboard/pipeline/context.js +42 -0
  99. package/dist/keyboard/pipeline/index.d.ts +3 -0
  100. package/dist/keyboard/pipeline/index.js +2 -0
  101. package/dist/keyboard/provider.d.ts +32 -0
  102. package/dist/keyboard/provider.js +985 -0
  103. package/dist/keyboard/screen-stack-processor/index.d.ts +14 -0
  104. package/dist/keyboard/screen-stack-processor/index.js +32 -0
  105. package/dist/keyboard/types.d.ts +632 -0
  106. package/dist/keyboard/types.js +1 -0
  107. package/dist/language/context.d.ts +7 -0
  108. package/dist/language/context.js +7 -0
  109. package/dist/language/hook.d.ts +19 -0
  110. package/dist/language/hook.js +26 -0
  111. package/dist/language/index.d.ts +3 -0
  112. package/dist/language/index.js +2 -0
  113. package/dist/language/provider.d.ts +30 -0
  114. package/dist/language/provider.js +155 -0
  115. package/dist/language/types.d.ts +124 -0
  116. package/dist/language/types.js +1 -0
  117. package/dist/screen/ModalContext.d.ts +11 -0
  118. package/dist/screen/ModalContext.js +12 -0
  119. package/dist/screen/OverlayContext.d.ts +9 -0
  120. package/dist/screen/OverlayContext.js +10 -0
  121. package/dist/screen/context.d.ts +61 -0
  122. package/dist/screen/context.js +8 -0
  123. package/dist/screen/current-screen.d.ts +16 -0
  124. package/dist/screen/current-screen.js +38 -0
  125. package/dist/screen/hook.d.ts +13 -0
  126. package/dist/screen/hook.js +20 -0
  127. package/dist/screen/index.d.ts +7 -0
  128. package/dist/screen/index.js +5 -0
  129. package/dist/screen/provider.d.ts +112 -0
  130. package/dist/screen/provider.js +577 -0
  131. package/dist/screen/registry.d.ts +27 -0
  132. package/dist/screen/registry.js +71 -0
  133. package/dist/screen/types.d.ts +227 -0
  134. package/dist/screen/types.js +1 -0
  135. package/dist/storage/index.d.ts +45 -0
  136. package/dist/storage/index.js +46 -0
  137. package/dist/storage/storage.d.ts +139 -0
  138. package/dist/storage/storage.js +236 -0
  139. package/dist/storage/types.d.ts +145 -0
  140. package/dist/storage/types.js +1 -0
  141. package/dist/theme/context.d.ts +7 -0
  142. package/dist/theme/context.js +7 -0
  143. package/dist/theme/hook.d.ts +11 -0
  144. package/dist/theme/hook.js +18 -0
  145. package/dist/theme/index.d.ts +4 -0
  146. package/dist/theme/index.js +2 -0
  147. package/dist/theme/provider.d.ts +18 -0
  148. package/dist/theme/provider.js +308 -0
  149. package/dist/theme/types.d.ts +53 -0
  150. package/dist/theme/types.js +1 -0
  151. package/package.json +71 -0
@@ -0,0 +1,398 @@
1
+ import type { KeyHandler, BoundKeyboardOptions, BlockedKeyOptions, StopOptions, GlobalKeyEntry, GlobalSequenceEntry, ShortcutOperationEntry, SequenceOperationEntry, SequenceOptions, ModalMissCallback, ModalMissOptions, ResolvedGlobalKeyEntry } from "./types.js";
2
+ /**
3
+ * Type for the owner stack used to track overlay context.
4
+ * Can be a component type (for screens) or a string (for overlay IDs).
5
+ */
6
+ export type LayerOwner = React.ComponentType<any> | string;
7
+ /**
8
+ * Value provided by {@link KeyboardProvider} via React context.
9
+ */
10
+ export interface KeyboardContextValue {
11
+ /**
12
+ * Bind one or more keys to a handler on the current screen layer.
13
+ *
14
+ * When a `focusId` is provided, the binding is stored on a named focus
15
+ * target instead of the screen-level bucket. Only the currently active
16
+ * focus target receives events.
17
+ *
18
+ * Overloads:
19
+ * 1. `(keys: string | string[], handler: KeyHandler, options?: BoundKeyboardOptions)` —
20
+ * explicit keys and handler. A single string is normalized to `[string]`.
21
+ * 2. `(actionId: string, options: BoundKeyboardOptions)` —
22
+ * uses a registered shortcut action's predefined keys and callback.
23
+ *
24
+ * @param keys Key name(s) to bind (e.g. `"s"`, `["s", "ctrl+q", "return"]`).
25
+ * @param handler Callback receiving the raw `input` and `key` from Ink.
26
+ * @param options Optional binding behavior (`onlyThis`, `focusId`, `once`, `times`, `when`).
27
+ * @returns An unbind function that removes this binding when called.
28
+ */
29
+ boundKeyboard: {
30
+ (keys: string | string[], handler: KeyHandler, options?: BoundKeyboardOptions): () => void;
31
+ (actionId: string, options: BoundKeyboardOptions): () => void;
32
+ };
33
+ /**
34
+ * Mark one or more keys as "transparent" on the current layer.
35
+ *
36
+ * When a transparent key reaches this layer (or the named focus target),
37
+ * the layer's own bindings are skipped and the key continues to propagate
38
+ * to layers below.
39
+ *
40
+ * @param keys Key names to make transparent.
41
+ * @param options If `focusId` is provided, marks transparent only
42
+ * within that focus target.
43
+ */
44
+ blockedKey: (keys: string[], options?: BlockedKeyOptions) => () => void;
45
+ /**
46
+ * Prevent one or more keys from propagating to layers below.
47
+ *
48
+ * Stopped keys are consumed at this layer: local bindings are evaluated
49
+ * first, and if no binding matches, the key is blocked from reaching
50
+ * lower layers.
51
+ *
52
+ * @param keys Key names to stop from propagating.
53
+ * @param options If `focusId` is provided, stops only within that
54
+ * focus target.
55
+ * @returns An unstop function that removes the keys from the
56
+ * stop list.
57
+ */
58
+ stop: (keys: string[], options?: StopOptions) => () => void;
59
+ /**
60
+ * Register global key bindings.
61
+ *
62
+ * Global keys fire independently of the screen stack (subject to
63
+ * `category` whitelist and `affectOverlay` placement).
64
+ *
65
+ * By default (or with `{ mode: 'replace' }`), replaces all previously
66
+ * registered global keys. Pass `{ mode: 'add' }` to append without
67
+ * removing existing entries.
68
+ *
69
+ * @param entries Array of global key definitions.
70
+ * @param options Optional: `{ mode: 'replace' | 'add' }`. Default `'replace'`.
71
+ */
72
+ globalKeys: (entries: GlobalKeyEntry[], options?: {
73
+ mode?: "replace" | "add";
74
+ }) => void;
75
+ /**
76
+ * Return a snapshot of all currently registered global key entries.
77
+ *
78
+ * Each entry includes the resolved `operate` callback (actions resolved
79
+ * from action IDs), plus metadata: `key`, `cover`, `affectOverlay`,
80
+ * `category`, `times`, `pressCount`, `observer`, `executeWhenNoOverlay`,
81
+ * and `when`.
82
+ *
83
+ * @returns A shallow copy of the current global keys array.
84
+ */
85
+ getGlobalKeys: () => ResolvedGlobalKeyEntry[];
86
+ /**
87
+ * Register global sequence key bindings.
88
+ *
89
+ * Global sequences fire independently of the screen stack with higher
90
+ * priority than {@link globalKeys}. They match multi-key sequences
91
+ * instead of single key presses.
92
+ *
93
+ * By default (or with `{ mode: 'replace' }`), replaces all previously
94
+ * registered global sequences. Pass `{ mode: 'add' }` to append without
95
+ * removing existing entries.
96
+ *
97
+ * **Priority chain**: global sequences are evaluated before global keys
98
+ * in both the `affectOverlay: true` and `affectOverlay: false` stages:
99
+ * 1. globalSequence(affectOverlay:true)
100
+ * 2. globalKeys(affectOverlay:true)
101
+ * 3. overlay layer
102
+ * 4. globalSequence(affectOverlay:false)
103
+ * 5. globalKeys(affectOverlay:false)
104
+ * 6. screen stack
105
+ *
106
+ * **Cover**: Only `boundSequence` can override a global sequence (not
107
+ * `boundKeyboard`). When `cover: false`, `boundSequence` with the same
108
+ * first key throws.
109
+ *
110
+ * **No `times` support**: Unlike `globalKeys`, global sequences do not
111
+ * support the `times` option.
112
+ *
113
+ * @param entries Array of global sequence definitions.
114
+ * @param options Optional: `{ mode: 'replace' | 'add' }`. Default `'replace'`.
115
+ * @throws If any `keys` array has length < 2.
116
+ */
117
+ globalSequence: (entries: GlobalSequenceEntry[], options?: {
118
+ mode?: "replace" | "add";
119
+ }) => void;
120
+ /**
121
+ * Remove a focus target from the current screen layer.
122
+ *
123
+ * If the removed target was the currently active one, the next target
124
+ * (in registration order) is activated automatically. If no targets
125
+ * remain, `currentFocusId` becomes `null`.
126
+ *
127
+ * Components should call this in their `useEffect` cleanup alongside
128
+ * unbinding their focus-level key bindings.
129
+ *
130
+ * @param focusId The focus target id to remove.
131
+ */
132
+ focusUnregister: (focusId: string) => void;
133
+ /**
134
+ * Activate a specific focus target by its id.
135
+ *
136
+ * Throws a runtime error if the current screen has no keyboard layer
137
+ * or no focus target with the given id is registered.
138
+ *
139
+ * @param focusId The focus target id to activate.
140
+ * @throws If the current screen has no keyboard layer or the focus
141
+ * target does not exist.
142
+ */
143
+ focusSet: (focusId: string) => void;
144
+ /**
145
+ * Activate the next focus target in registration order.
146
+ *
147
+ * Equivalent to pressing Tab. Wraps around to the first target if
148
+ * the last target is currently active.
149
+ */
150
+ focusNext: () => void;
151
+ /**
152
+ * Activate the previous focus target in registration order.
153
+ *
154
+ * Equivalent to pressing Shift+Tab. Wraps around to the last target
155
+ * if the first target is currently active.
156
+ */
157
+ focusPrev: () => void;
158
+ /**
159
+ * Return the currently active focus target id on the current screen.
160
+ *
161
+ * @returns The active focus id, or `null` if no focus targets exist.
162
+ */
163
+ focusCurrent: () => string | null;
164
+ /**
165
+ * Subscribe to focus changes on the current screen layer.
166
+ *
167
+ * The listener is called whenever the active focus id changes (via
168
+ * Tab, `focusSet`, `focusNext`, `focusPrev`, or `focusUnregister`).
169
+ *
170
+ * @param listener Callback invoked on focus change.
171
+ * @returns An unsubscribe function.
172
+ */
173
+ subscribeFocus: (listener: () => void) => () => void;
174
+ /**
175
+ * Register named shortcut actions that can be referenced by key bindings
176
+ * using a string identifier instead of an inline callback.
177
+ *
178
+ * Decouples operation definition from key binding.
179
+ *
180
+ * @param entries - Array of shortcut operation definitions.
181
+ * Each entry must have a unique `actionId`.
182
+ *
183
+ * @throws {Error} If an `actionId` is duplicated.
184
+ */
185
+ defineShortcutAction: (entries: ShortcutOperationEntry[]) => void;
186
+ /**
187
+ * Dynamically register a single shortcut action.
188
+ *
189
+ * @param entry - The shortcut operation definition to add.
190
+ * @throws {Error} If an action with the same `actionId` already exists.
191
+ */
192
+ addAction: (entry: ShortcutOperationEntry) => void;
193
+ /**
194
+ * Check whether a shortcut action with the given id exists.
195
+ *
196
+ * @param actionId - The action id to look up.
197
+ * @returns `true` if the action is registered, `false` otherwise.
198
+ */
199
+ hasAction: (actionId: string) => boolean;
200
+ /**
201
+ * Remove a registered shortcut action.
202
+ *
203
+ * @param actionId - The action id to remove.
204
+ * @throws {Error} If no action with the given id exists.
205
+ */
206
+ removeAction: (actionId: string) => void;
207
+ /**
208
+ * Modify the default keys of an existing shortcut action.
209
+ *
210
+ * @param actionId - The unique identifier of the action.
211
+ * @param keys - New key names to replace the previous default keys.
212
+ * @throws If the action does not exist or was not registered with a `keys` field.
213
+ */
214
+ modifyAction: (actionId: string, keys: string[]) => void;
215
+ /**
216
+ * Clear all registered shortcut operations.
217
+ * Primarily used for testing or full keyboard reset scenarios.
218
+ */
219
+ clearShortcutOperations: () => void;
220
+ /**
221
+ * Register named sequence actions that can be referenced by sequence
222
+ * bindings using a string identifier instead of an inline callback.
223
+ *
224
+ * @param entries - Array of sequence operation definitions.
225
+ * Each entry must have a unique `sequenceActionId`.
226
+ * @throws {Error} If a `sequenceActionId` is duplicated.
227
+ */
228
+ defineSequenceAction: (entries: SequenceOperationEntry[]) => void;
229
+ /**
230
+ * Dynamically register a single sequence action.
231
+ *
232
+ * @param entry - The sequence operation definition to add.
233
+ * @throws {Error} If an action with the same `sequenceActionId` already exists.
234
+ */
235
+ addSequenceAction: (entry: SequenceOperationEntry) => void;
236
+ /**
237
+ * Check whether a sequence action with the given id exists.
238
+ *
239
+ * @param sequenceActionId - The action id to look up.
240
+ * @returns `true` if the action is registered, `false` otherwise.
241
+ */
242
+ hasSequenceAction: (sequenceActionId: string) => boolean;
243
+ /**
244
+ * Remove a registered sequence action.
245
+ *
246
+ * @param sequenceActionId - The action id to remove.
247
+ * @throws {Error} If no action with the given id exists.
248
+ */
249
+ removeSequenceAction: (sequenceActionId: string) => void;
250
+ /**
251
+ * Modify the default keys (and optionally timeout) of an existing sequence action.
252
+ *
253
+ * @param sequenceActionId - The unique identifier of the action.
254
+ * @param keys - New key names to replace the previous default keys.
255
+ * @param timeout - Optional new timeout value.
256
+ * @throws If the action does not exist or was not registered with a `keys` (or `timeout`) field.
257
+ */
258
+ modifySequenceAction: (sequenceActionId: string, keys: string[], timeout?: number) => void;
259
+ /**
260
+ * Clear all registered sequence operations.
261
+ * Primarily used for testing or full keyboard reset scenarios.
262
+ */
263
+ clearSequenceOperations: () => void;
264
+ /**
265
+ * Internal: Push an owner onto the owner stack.
266
+ * Used by useKeyboard() when rendering inside an overlay.
267
+ */
268
+ _pushOwner: (owner: LayerOwner) => void;
269
+ /**
270
+ * Internal: Pop an owner from the owner stack.
271
+ * Used by useKeyboard() cleanup when leaving an overlay context.
272
+ */
273
+ _popOwner: (owner: LayerOwner) => void;
274
+ /**
275
+ * Register a multi-key sequence binding on the current screen layer.
276
+ *
277
+ * When the first key of a sequence is pressed, the layer enters a pending
278
+ * state waiting for subsequent keys within `timeout` milliseconds (default
279
+ * 500). If the full sequence is entered in order before the timeout, the
280
+ * handler fires. Otherwise the pending state is cancelled.
281
+ *
282
+ * **Sequence priority**: Sequences are evaluated before ordinary
283
+ * `boundKeyboard` bindings. When a sequence's first key is pressed, it
284
+ * is consumed by the sequence system and will not trigger any normal
285
+ * binding for that key.
286
+ *
287
+ * **Exclusive vs non-exclusive (default)**: In non-exclusive mode, a
288
+ * key that does NOT match the next expected key in the sequence
289
+ * immediately cancels the pending sequence and falls through to normal
290
+ * bindings. In exclusive mode (`exclusive: true`), mismatched keys are
291
+ * silently consumed — the sequence keeps waiting within its timeout.
292
+ *
293
+ * **Layer isolation**: Each screen / overlay maintains its own pending
294
+ * sequence state. Navigating away, switching focus, or closing an
295
+ * overlay automatically clears any pending sequence on that layer.
296
+ *
297
+ * @param keys Ordered key names that make up the sequence
298
+ * (e.g. `['g', 'g']`, `['c', 'w']`). Length must be ≥ 2.
299
+ * @param handler Callback invoked when the full sequence is matched.
300
+ * Receives the Ink `input` and `key` of the final key
301
+ * press that completed the sequence.
302
+ * @param options Optional configuration:
303
+ * - `timeout` (ms, default 500): how long to wait between
304
+ * key presses before cancelling the sequence.
305
+ * - `exclusive` (default false): if true, mismatched keys
306
+ * are consumed silently; if false, they cancel the
307
+ * sequence and fall through.
308
+ * - `onlyThis` / `focusId`: same behaviour as
309
+ * `boundKeyboard`.
310
+ * @returns An unbind function that removes the sequence binding
311
+ * when called.
312
+ *
313
+ * @example
314
+ * ```tsx
315
+ * // Vim-like 'gg' to jump to the top
316
+ * useEffect(() => {
317
+ * boundSequence(['g', 'g'], () => scrollToTop());
318
+ * }, []);
319
+ *
320
+ * // Exclusive mode: only 'ctrl+w' 'q' triggers, no other key interrupts
321
+ * useEffect(() => {
322
+ * boundSequence(['ctrl+w', 'q'], closeTab, { exclusive: true });
323
+ * }, []);
324
+ *
325
+ * // Sequence restricted to a specific focus target
326
+ * useEffect(() => {
327
+ * boundSequence(['d', 'd'], deleteLine, { focusId: 'editor' });
328
+ * }, []);
329
+ * ```
330
+ */
331
+ /**
332
+ * Register a multi-key sequence binding on the current screen layer.
333
+ *
334
+ * Overloads:
335
+ * 1. `(keys: string | string[], handler: KeyHandler, options?: SequenceOptions)` —
336
+ * explicit keys and handler. A single string is normalized to `[string]`
337
+ * (but the sequence still requires at least 2 keys).
338
+ * 2. `(actionId: string, options?: SequenceOptions)` —
339
+ * uses a registered sequence action's predefined keys and callback.
340
+ */
341
+ boundSequence: {
342
+ (keys: string | string[], handler: KeyHandler, options?: SequenceOptions): () => void;
343
+ (actionId: string, options?: SequenceOptions): () => void;
344
+ };
345
+ /**
346
+ * Enable wildcard priority mode.
347
+ *
348
+ * When enabled, wildcard `*` bindings take absolute priority over ALL
349
+ * other key handling — sequences, exact key matches, everything. Only
350
+ * normal character input (as determined by `isNormalCharacter`) is
351
+ * affected — special keys (Tab, Return, Escape, arrow keys, modifiers,
352
+ * etc.) are never matched by wildcard and always fall through to normal
353
+ * processing.
354
+ *
355
+ * Uses reference counting: multiple callers can enable independently.
356
+ * Each returned disable function decrements the count; the mode is
357
+ * disabled when the count reaches zero.
358
+ *
359
+ * @returns A function that, when called, disables wildcard priority
360
+ * for this caller. When all callers have disabled, original
361
+ * priority is restored.
362
+ *
363
+ * @example
364
+ * ```tsx
365
+ * useEffect(() => {
366
+ * const disable = enableWildcardPriority();
367
+ * const unbind = boundKeyboard(['*'], handleInput, { focusId: 'input' });
368
+ * return () => { unbind(); disable(); };
369
+ * }, []);
370
+ * ```
371
+ */
372
+ enableWildcardPriority: () => (() => void);
373
+ /**
374
+ * Subscribe to unhandled key presses inside a modal.
375
+ *
376
+ * When the active modal receives a key that was not consumed by any
377
+ * binding (according to the specified {@link ModalMissOptions}), the
378
+ * callback is invoked with either `{ miss: true, key, input, eventNames }`
379
+ * or `{ miss: false }`.
380
+ *
381
+ * Only functions inside a modal component (where {@link ModalContext} is
382
+ * set). Returns an unsubscribe function. Calling outside a modal is a
383
+ * silent no-op.
384
+ *
385
+ * @param cb - Callback invoked on every key press in the modal.
386
+ * @param options - Controls which mechanics count as "handled".
387
+ * @returns An unsubscribe function.
388
+ */
389
+ useModalMissListener: (cb: ModalMissCallback, options?: ModalMissOptions) => () => void;
390
+ }
391
+ /**
392
+ * React context for the keyboard system.
393
+ *
394
+ * Accessed via {@link useKeyboard}. Must be provided by a
395
+ * {@link KeyboardProvider} nested inside a
396
+ * {@link ScenarioManagementProvider}.
397
+ */
398
+ export declare const KeyboardContext: import("react").Context<KeyboardContextValue | null>;
@@ -0,0 +1,9 @@
1
+ import { createContext } from "react";
2
+ /**
3
+ * React context for the keyboard system.
4
+ *
5
+ * Accessed via {@link useKeyboard}. Must be provided by a
6
+ * {@link KeyboardProvider} nested inside a
7
+ * {@link ScenarioManagementProvider}.
8
+ */
9
+ export const KeyboardContext = createContext(null);
@@ -0,0 +1,16 @@
1
+ import type { PipelineProcessor } from '../types.js';
2
+ /**
3
+ * Create a processor for global single-key bindings.
4
+ *
5
+ * Iterates registered global keys, filters by the given affectOverlay flag,
6
+ * applies executeWhenNoOverlay / override / category / times constraints,
7
+ * and fires the first matching entry.
8
+ *
9
+ * @param config.affectOverlay - Which priority group this processor serves.
10
+ * @returns A PipelineProcessor for the global key stage.
11
+ *
12
+ * @2026-06-14 v3.4.0
13
+ */
14
+ export declare function createGlobalKeyProcessor(config: {
15
+ affectOverlay: boolean;
16
+ }): PipelineProcessor;
@@ -0,0 +1,75 @@
1
+ import { checkGlobalKey } from '../check-global-key.js';
2
+ /**
3
+ * Create a processor for global single-key bindings.
4
+ *
5
+ * Iterates registered global keys, filters by the given affectOverlay flag,
6
+ * applies executeWhenNoOverlay / override / category / times constraints,
7
+ * and fires the first matching entry.
8
+ *
9
+ * @param config.affectOverlay - Which priority group this processor serves.
10
+ * @returns A PipelineProcessor for the global key stage.
11
+ *
12
+ * @2026-06-14 v3.4.0
13
+ */
14
+ export function createGlobalKeyProcessor(config) {
15
+ const { affectOverlay } = config;
16
+ return {
17
+ process(ctx) {
18
+ for (const entry of ctx.globalKeys) {
19
+ if ((entry.affectOverlay ?? false) !== affectOverlay)
20
+ continue;
21
+ // when=false → entry treated as if not registered
22
+ if (entry.when?.() === false)
23
+ continue;
24
+ if (affectOverlay) {
25
+ if (ctx.activeCount === 0 && !entry.executeWhenNoOverlay)
26
+ continue;
27
+ let anyOverlayHasOverride = false;
28
+ if (entry.cover !== false) {
29
+ const keyNames = Array.isArray(entry.key) ? entry.key : [entry.key];
30
+ for (const overlay of ctx.activeOverlays) {
31
+ const overlayLayer = ctx.layersRef.current.get(overlay.id);
32
+ if (overlayLayer && keyNames.some(k => overlayLayer.globalKeyOverrides.has(k))) {
33
+ anyOverlayHasOverride = true;
34
+ break;
35
+ }
36
+ }
37
+ }
38
+ if (anyOverlayHasOverride)
39
+ continue;
40
+ }
41
+ else {
42
+ let screenHasOverride = false;
43
+ if (entry.cover !== false && ctx.topComponent) {
44
+ const keyNames = Array.isArray(entry.key) ? entry.key : [entry.key];
45
+ const topLayer = ctx.layersRef.current.get(ctx.topComponent);
46
+ if (topLayer && keyNames.some(k => topLayer.globalKeyOverrides.has(k))) {
47
+ screenHasOverride = true;
48
+ }
49
+ }
50
+ if (screenHasOverride)
51
+ continue;
52
+ }
53
+ if (checkGlobalKey(entry, ctx.eventNames, ctx.topComponent, ctx.layersRef)) {
54
+ if (entry.times !== undefined && entry.times >= 1) {
55
+ entry.pressCount += 1;
56
+ // For times=3:
57
+ // 1st press: pressCount=1, observer(2), returns true (swallowed)
58
+ // 2nd press: pressCount=2, observer(1), returns true (swallowed)
59
+ // 3rd press: pressCount=3, observer(0), pressCount→0, handler fires
60
+ // Next press starts a new cycle: pressCount=1, observer(2)…
61
+ // @2026-06-19 v3.5.2
62
+ entry.observer?.(entry.times - entry.pressCount);
63
+ if (entry.pressCount < entry.times) {
64
+ return true;
65
+ }
66
+ entry.pressCount = 0;
67
+ }
68
+ entry.operate();
69
+ return true;
70
+ }
71
+ }
72
+ return false;
73
+ },
74
+ };
75
+ }
@@ -0,0 +1,14 @@
1
+ import type { PipelineProcessor } from '../types.js';
2
+ /**
3
+ * Create a processor for global multi-key sequences.
4
+ *
5
+ * Handles two sub-steps in order:
6
+ * 1. Drain any active global pending sequence.
7
+ * 2. Try to start a new sequence from registered entries.
8
+ *
9
+ * @param config.affectOverlay - Which priority group this processor serves.
10
+ * @returns A PipelineProcessor for the global sequence stage.
11
+ */
12
+ export declare function createGlobalSequenceProcessor(config: {
13
+ affectOverlay: boolean;
14
+ }): PipelineProcessor;
@@ -0,0 +1,165 @@
1
+ const DEFAULT_SEQUENCE_TIMEOUT = 500;
2
+ /**
3
+ * Try to start a global pending sequence from a specific affectOverlay group.
4
+ *
5
+ * Iterates the candidate entries, filters by affectOverlay, category,
6
+ * and cover/override constraints, and creates a pending sequence when
7
+ * the first key matches.
8
+ *
9
+ * @param entries Candidate global sequence entries.
10
+ * @param affectOverlay Which group to filter (true = overlay-phase, false = screen-phase).
11
+ * @param ctx Full pipeline context.
12
+ * @returns true when a new pending sequence was started (event consumed).
13
+ *
14
+ * @2026-06-14 v3.4.0
15
+ */
16
+ function tryStartGlobalSequence(entries, affectOverlay, ctx) {
17
+ for (const entry of entries) {
18
+ if ((entry.affectOverlay ?? false) !== affectOverlay)
19
+ continue;
20
+ if (entry.when?.() === false)
21
+ continue;
22
+ if (affectOverlay && ctx.activeCount === 0 && !entry.executeWhenNoOverlay)
23
+ continue;
24
+ if (!ctx.topComponent)
25
+ continue;
26
+ const cat = entry.category;
27
+ if (cat !== undefined && cat !== '*') {
28
+ if (Array.isArray(cat) && cat.length === 0)
29
+ continue;
30
+ if (Array.isArray(cat) && !cat.includes(ctx.topComponent))
31
+ continue;
32
+ }
33
+ // Cover check: only boundSequence can override a global sequence.
34
+ // boundKeyboard is never checked — its keys are single-key bindings
35
+ // that the sequence system always consumes first.
36
+ if (entry.cover !== false) {
37
+ const firstKey = entry.keys[0];
38
+ if (affectOverlay) {
39
+ let anyOverlayHasOverride = false;
40
+ for (const overlay of ctx.activeOverlays) {
41
+ const overlayLayer = ctx.layersRef.current.get(overlay.id);
42
+ if (overlayLayer?.sequences.has(firstKey)) {
43
+ anyOverlayHasOverride = true;
44
+ break;
45
+ }
46
+ }
47
+ if (anyOverlayHasOverride)
48
+ continue;
49
+ }
50
+ else {
51
+ if (ctx.topComponent) {
52
+ const topLayer = ctx.layersRef.current.get(ctx.topComponent);
53
+ if (topLayer?.sequences.has(firstKey))
54
+ continue;
55
+ }
56
+ }
57
+ }
58
+ if (ctx.eventNames.includes(entry.keys[0])) {
59
+ const timeout = entry.timeout ?? DEFAULT_SEQUENCE_TIMEOUT;
60
+ const pending = {
61
+ sequences: entry.keys,
62
+ nextIndex: 1,
63
+ handler: entry.operate,
64
+ timer: undefined,
65
+ timeout,
66
+ exclusive: entry.exclusive ?? false,
67
+ affectOverlay,
68
+ cover: entry.cover ?? true,
69
+ category: entry.category,
70
+ executeWhenNoOverlay: entry.executeWhenNoOverlay,
71
+ when: entry.when,
72
+ };
73
+ const timer = setTimeout(() => {
74
+ if (ctx.pendingSeqRef.current === pending) {
75
+ ctx.pendingSeqRef.current = null;
76
+ }
77
+ }, timeout);
78
+ pending.timer = timer;
79
+ ctx.pendingSeqRef.current = pending;
80
+ return true;
81
+ }
82
+ }
83
+ return false;
84
+ }
85
+ /**
86
+ * Process the currently active global pending sequence.
87
+ *
88
+ * Matches the next expected key, handles exclusive vs non-exclusive
89
+ * mismatch behaviour, and fires the handler when the full sequence
90
+ * is completed.
91
+ *
92
+ * @param ctx Full pipeline context.
93
+ * @returns true when the event was consumed by the pending sequence.
94
+ *
95
+ * @2026-06-14 v3.4.0
96
+ */
97
+ function processGlobalPending(ctx, affectOverlay) {
98
+ const pending = ctx.pendingSeqRef.current;
99
+ if (pending === null)
100
+ return false;
101
+ // Only process the pending sequence in the stage that matches its
102
+ // affectOverlay group — otherwise a pending sequence started in
103
+ // stage ④ (affectOverlay: false) would have its continuation
104
+ // consumed by stage ① (affectOverlay: true), bypassing the
105
+ // overlay layer.
106
+ if (pending.affectOverlay !== affectOverlay)
107
+ return false;
108
+ if (pending.affectOverlay && ctx.activeCount === 0 && !pending.executeWhenNoOverlay) {
109
+ clearTimeout(pending.timer);
110
+ ctx.pendingSeqRef.current = null;
111
+ return false;
112
+ }
113
+ if (pending.when?.() === false) {
114
+ clearTimeout(pending.timer);
115
+ ctx.pendingSeqRef.current = null;
116
+ return false;
117
+ }
118
+ const expectedKey = pending.sequences[pending.nextIndex];
119
+ if (ctx.eventNames.includes(expectedKey)) {
120
+ clearTimeout(pending.timer);
121
+ pending.nextIndex++;
122
+ if (pending.nextIndex === pending.sequences.length) {
123
+ pending.handler();
124
+ ctx.pendingSeqRef.current = null;
125
+ }
126
+ else {
127
+ pending.timer = setTimeout(() => {
128
+ if (ctx.pendingSeqRef.current === pending) {
129
+ ctx.pendingSeqRef.current = null;
130
+ }
131
+ }, pending.timeout);
132
+ }
133
+ return true;
134
+ }
135
+ if (pending.exclusive) {
136
+ // Exclusive mode: silently consume the mismatched key, keep waiting.
137
+ return true;
138
+ }
139
+ // Non-exclusive (default): cancel the sequence, key falls through.
140
+ clearTimeout(pending.timer);
141
+ ctx.pendingSeqRef.current = null;
142
+ return false;
143
+ }
144
+ /**
145
+ * Create a processor for global multi-key sequences.
146
+ *
147
+ * Handles two sub-steps in order:
148
+ * 1. Drain any active global pending sequence.
149
+ * 2. Try to start a new sequence from registered entries.
150
+ *
151
+ * @param config.affectOverlay - Which priority group this processor serves.
152
+ * @returns A PipelineProcessor for the global sequence stage.
153
+ */
154
+ export function createGlobalSequenceProcessor(config) {
155
+ const { affectOverlay } = config;
156
+ return {
157
+ process(ctx) {
158
+ if (processGlobalPending(ctx, affectOverlay))
159
+ return true;
160
+ if (tryStartGlobalSequence(ctx.globalSequences, affectOverlay, ctx))
161
+ return true;
162
+ return false;
163
+ },
164
+ };
165
+ }