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.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/dist/binary-storage/BinaryStorage.d.ts +308 -0
- package/dist/binary-storage/BinaryStorage.js +548 -0
- package/dist/binary-storage/StreamingReader.d.ts +53 -0
- package/dist/binary-storage/StreamingReader.js +530 -0
- package/dist/binary-storage/index.d.ts +91 -0
- package/dist/binary-storage/index.js +91 -0
- package/dist/binary-storage/types.d.ts +400 -0
- package/dist/binary-storage/types.js +15 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +214 -0
- package/dist/cli/initTheme.d.ts +14 -0
- package/dist/cli/initTheme.js +124 -0
- package/dist/cli/makeLanguageType.d.ts +46 -0
- package/dist/cli/makeLanguageType.js +264 -0
- package/dist/cli/makeThemeType.d.ts +43 -0
- package/dist/cli/makeThemeType.js +222 -0
- package/dist/components/badge/Badge.d.ts +7 -0
- package/dist/components/badge/Badge.js +9 -0
- package/dist/components/dialog/ConfirmDialog.d.ts +23 -0
- package/dist/components/dialog/ConfirmDialog.js +57 -0
- package/dist/components/dialog/types.d.ts +20 -0
- package/dist/components/dialog/types.js +1 -0
- package/dist/components/divider/Divider.d.ts +8 -0
- package/dist/components/divider/Divider.js +10 -0
- package/dist/components/fold/Fold.d.ts +3 -0
- package/dist/components/fold/Fold.js +44 -0
- package/dist/components/fold/types.d.ts +28 -0
- package/dist/components/fold/types.js +1 -0
- package/dist/components/form/Field.d.ts +30 -0
- package/dist/components/form/Field.js +59 -0
- package/dist/components/form/Form.d.ts +32 -0
- package/dist/components/form/Form.js +163 -0
- package/dist/components/form/context.d.ts +14 -0
- package/dist/components/form/context.js +20 -0
- package/dist/components/form/types.d.ts +115 -0
- package/dist/components/form/types.js +1 -0
- package/dist/components/key-hint/KeyHint.d.ts +10 -0
- package/dist/components/key-hint/KeyHint.js +12 -0
- package/dist/components/multi-select/MultiSelectInput.d.ts +125 -0
- package/dist/components/multi-select/MultiSelectInput.js +287 -0
- package/dist/components/multi-select/types.d.ts +100 -0
- package/dist/components/multi-select/types.js +1 -0
- package/dist/components/number-input/NumberInput.d.ts +11 -0
- package/dist/components/number-input/NumberInput.js +44 -0
- package/dist/components/progress-bar/ProgressBar.d.ts +11 -0
- package/dist/components/progress-bar/ProgressBar.js +9 -0
- package/dist/components/search-input/SearchInput.d.ts +10 -0
- package/dist/components/search-input/SearchInput.js +15 -0
- package/dist/components/select/SelectInput.d.ts +100 -0
- package/dist/components/select/SelectInput.js +147 -0
- package/dist/components/select/types.d.ts +63 -0
- package/dist/components/select/types.js +1 -0
- package/dist/components/spinner/Spinner.d.ts +12 -0
- package/dist/components/spinner/Spinner.js +24 -0
- package/dist/components/tabs/Tabs.d.ts +3 -0
- package/dist/components/tabs/Tabs.js +57 -0
- package/dist/components/tabs/types.d.ts +32 -0
- package/dist/components/tabs/types.js +1 -0
- package/dist/components/text/TextInput.d.ts +39 -0
- package/dist/components/text/TextInput.js +233 -0
- package/dist/components/text/types.d.ts +62 -0
- package/dist/components/text/types.js +1 -0
- package/dist/dev/dev-screen.d.ts +36 -0
- package/dist/dev/dev-screen.js +142 -0
- package/dist/dev/entrance.d.ts +51 -0
- package/dist/dev/entrance.js +67 -0
- package/dist/dev/globalKey-display.d.ts +2 -0
- package/dist/dev/globalKey-display.js +8 -0
- package/dist/dev/types.d.ts +9 -0
- package/dist/dev/types.js +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +50 -0
- package/dist/keyboard/check-global-key.d.ts +18 -0
- package/dist/keyboard/check-global-key.js +52 -0
- package/dist/keyboard/context.d.ts +398 -0
- package/dist/keyboard/context.js +9 -0
- package/dist/keyboard/global-key-processor/index.d.ts +16 -0
- package/dist/keyboard/global-key-processor/index.js +75 -0
- package/dist/keyboard/global-sequence-processor/index.d.ts +14 -0
- package/dist/keyboard/global-sequence-processor/index.js +165 -0
- package/dist/keyboard/hook.d.ts +51 -0
- package/dist/keyboard/hook.js +105 -0
- package/dist/keyboard/index.d.ts +5 -0
- package/dist/keyboard/index.js +3 -0
- package/dist/keyboard/keyNormalizer.d.ts +37 -0
- package/dist/keyboard/keyNormalizer.js +119 -0
- package/dist/keyboard/layer-handler.d.ts +57 -0
- package/dist/keyboard/layer-handler.js +274 -0
- package/dist/keyboard/modal-processor/index.d.ts +16 -0
- package/dist/keyboard/modal-processor/index.js +97 -0
- package/dist/keyboard/overlay-processor/index.d.ts +16 -0
- package/dist/keyboard/overlay-processor/index.js +30 -0
- package/dist/keyboard/pipeline/chain.d.ts +10 -0
- package/dist/keyboard/pipeline/chain.js +45 -0
- package/dist/keyboard/pipeline/context.d.ts +34 -0
- package/dist/keyboard/pipeline/context.js +42 -0
- package/dist/keyboard/pipeline/index.d.ts +3 -0
- package/dist/keyboard/pipeline/index.js +2 -0
- package/dist/keyboard/provider.d.ts +32 -0
- package/dist/keyboard/provider.js +985 -0
- package/dist/keyboard/screen-stack-processor/index.d.ts +14 -0
- package/dist/keyboard/screen-stack-processor/index.js +32 -0
- package/dist/keyboard/types.d.ts +632 -0
- package/dist/keyboard/types.js +1 -0
- package/dist/language/context.d.ts +7 -0
- package/dist/language/context.js +7 -0
- package/dist/language/hook.d.ts +19 -0
- package/dist/language/hook.js +26 -0
- package/dist/language/index.d.ts +3 -0
- package/dist/language/index.js +2 -0
- package/dist/language/provider.d.ts +30 -0
- package/dist/language/provider.js +155 -0
- package/dist/language/types.d.ts +124 -0
- package/dist/language/types.js +1 -0
- package/dist/screen/ModalContext.d.ts +11 -0
- package/dist/screen/ModalContext.js +12 -0
- package/dist/screen/OverlayContext.d.ts +9 -0
- package/dist/screen/OverlayContext.js +10 -0
- package/dist/screen/context.d.ts +61 -0
- package/dist/screen/context.js +8 -0
- package/dist/screen/current-screen.d.ts +16 -0
- package/dist/screen/current-screen.js +38 -0
- package/dist/screen/hook.d.ts +13 -0
- package/dist/screen/hook.js +20 -0
- package/dist/screen/index.d.ts +7 -0
- package/dist/screen/index.js +5 -0
- package/dist/screen/provider.d.ts +112 -0
- package/dist/screen/provider.js +577 -0
- package/dist/screen/registry.d.ts +27 -0
- package/dist/screen/registry.js +71 -0
- package/dist/screen/types.d.ts +227 -0
- package/dist/screen/types.js +1 -0
- package/dist/storage/index.d.ts +45 -0
- package/dist/storage/index.js +46 -0
- package/dist/storage/storage.d.ts +139 -0
- package/dist/storage/storage.js +236 -0
- package/dist/storage/types.d.ts +145 -0
- package/dist/storage/types.js +1 -0
- package/dist/theme/context.d.ts +7 -0
- package/dist/theme/context.js +7 -0
- package/dist/theme/hook.d.ts +11 -0
- package/dist/theme/hook.js +18 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/provider.d.ts +18 -0
- package/dist/theme/provider.js +308 -0
- package/dist/theme/types.d.ts +53 -0
- package/dist/theme/types.js +1 -0
- 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
|
+
}
|