ink-cartridge 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -221
- package/dist/binary-storage/StreamingReader.js +3 -0
- package/dist/components/dialog/ConfirmDialog.js +1 -1
- package/dist/components/fold/Fold.js +4 -4
- package/dist/components/form/Field.js +4 -3
- package/dist/components/multi-select/MultiSelectInput.js +1 -1
- package/dist/components/number-input/NumberInput.js +2 -2
- package/dist/components/search-input/SearchInput.js +1 -1
- package/dist/components/select/SelectInput.d.ts +1 -82
- package/dist/components/select/SelectInput.js +20 -100
- package/dist/components/select-row/SelectRow.d.ts +22 -0
- package/dist/components/select-row/SelectRow.js +69 -0
- package/dist/components/select-row/types.d.ts +30 -0
- package/dist/components/select-row/types.js +1 -0
- package/dist/components/text/TextInput.js +1 -1
- package/dist/components/tools/select/useSelectNavigation.d.ts +48 -0
- package/dist/components/tools/select/useSelectNavigation.js +107 -0
- package/dist/components/tools/select/utils.d.ts +8 -0
- package/dist/components/tools/select/utils.js +10 -0
- package/dist/dev/dev-screen.js +153 -7
- package/dist/dev/globalKey-display.js +1 -3
- package/dist/dev/globalSeq-display.d.ts +3 -0
- package/dist/dev/globalSeq-display.js +160 -0
- package/dist/dev/layerKey-display.d.ts +9 -0
- package/dist/dev/layerKey-display.js +279 -0
- package/dist/event/EventBus.d.ts +67 -0
- package/dist/event/EventBus.js +102 -0
- package/dist/event/EventProvider.d.ts +38 -0
- package/dist/event/EventProvider.js +46 -0
- package/dist/event/context.d.ts +2 -0
- package/dist/event/context.js +2 -0
- package/dist/event/hook.d.ts +48 -0
- package/dist/event/hook.js +70 -0
- package/dist/event/index.d.ts +5 -0
- package/dist/event/index.js +4 -0
- package/dist/event/types.d.ts +15 -0
- package/dist/event/types.js +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -0
- package/dist/keyboard/check-global-key.js +1 -0
- package/dist/keyboard/context.d.ts +65 -1
- package/dist/keyboard/global-sequence-processor/index.js +99 -26
- package/dist/keyboard/hook.js +11 -6
- package/dist/keyboard/index.d.ts +1 -1
- package/dist/keyboard/layer-handler.js +77 -22
- package/dist/keyboard/modal-processor/index.js +22 -0
- package/dist/keyboard/provider.js +261 -190
- package/dist/keyboard/types.d.ts +48 -0
- package/dist/language/provider.js +9 -5
- package/dist/screen/provider.js +1 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -1,249 +1,164 @@
|
|
|
1
1
|
# ink-cartridge
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> A component kit for rapidly building complex, multi-page, interaction-heavy terminal applications — filling the critical gaps Ink leaves open.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/BAIGAOa/ink-trc/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/ink-cartridge)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
+
## Table of Contents
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npx ink-cartridge init my-tui
|
|
18
|
-
cd my-tui
|
|
19
|
-
npm start
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### Install in existing project
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
npm install ink-cartridge
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Requirements
|
|
29
|
-
|
|
30
|
-
| Dependency | Minimum Version |
|
|
31
|
-
| ---------- | --------------- |
|
|
32
|
-
| Node.js | 22 |
|
|
33
|
-
| ink | 5 |
|
|
34
|
-
| react | 18 |
|
|
35
|
-
|
|
36
|
-
---
|
|
11
|
+
- [Design Philosophy](#design-philosophy)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Scaffold](#scaffold)
|
|
14
|
+
- [Documentation](#documentation)
|
|
15
|
+
- [Other](#other)
|
|
16
|
+
- [License](#license)
|
|
37
17
|
|
|
38
18
|
## Design Philosophy
|
|
39
19
|
|
|
40
|
-
ink-cartridge
|
|
41
|
-
|
|
42
|
-
### Screen as Component
|
|
43
|
-
|
|
44
|
-
In ink-cartridge, **every React component is a "screen"**. Register them into a **screen tree** via `registerComponent`, then navigate the tree with `skip` / `back` / `gotoScreen`. This design makes screen navigation predictable and eliminates the chaos of hand-written conditional rendering (`if-else` / `switch`).
|
|
45
|
-
|
|
46
|
-
### Layered Keyboard Events
|
|
47
|
-
|
|
48
|
-
No more global `useInput` cluttered with `if-else` chains. ink-cartridge's keyboard system maintains **per-screen-layer** key bindings. Events bubble from **top to bottom** through the stack, with four key mechanisms:
|
|
49
|
-
|
|
50
|
-
- **Sequence (`boundSequence`)** — Multi-key chords (e.g. `gg`, `dd`, `cw`) with timeout and exclusive/non-exclusive modes. Sequences take priority over ordinary bindings.
|
|
51
|
-
- **Blocked Key (`blockedKey`)** — Let a key pass through the current layer to be handled below
|
|
52
|
-
- **Stop (`stop`)** — Prevent a key from propagating to lower layers. Supports `stopAction` mode to block by shortcut action ID instead of literal key name
|
|
53
|
-
- **Global Key (`globalKeys`)** — Shortcuts independent of the screen stack
|
|
54
|
-
|
|
55
|
-
#### Finer-grained partitioning
|
|
56
|
-
|
|
57
|
-
Within the same level, identical keys are also in competition. To address this, we have a complete **focus system**.
|
|
58
|
-
Each level maintains a set of focus targets, and only one focus is active at any given time within a level. Each focus target has its own bound keyboard operations. Only the activated focus target is eligible to execute them during event dispatching in **useInput**.
|
|
59
|
-
|
|
60
|
-
**For more details, please refer to the API documentation.**
|
|
61
|
-
|
|
62
|
-
### Shortcut Actions
|
|
20
|
+
Ink gives you `useInput` and `render`. Everything else — screen navigation, layered keyboard events, focus management, cross-component communication — you build yourself. ink-cartridge provides all of that, designed for **multi-page, interaction-dense terminal apps** where a single global `useInput` with `if-else` chains breaks down.
|
|
63
21
|
|
|
64
|
-
|
|
22
|
+
Three pillars:
|
|
65
23
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
]);
|
|
70
|
-
boundKeyboard(['q'], 'quit');
|
|
71
|
-
globalKeys([{ key: 'escape', operate: 'quit' }]);
|
|
72
|
-
stop(['quit'], { stopAction: true });
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Sequence Actions
|
|
76
|
-
|
|
77
|
-
Decouple sequence operation definition from key binding with `defineSequenceAction`. Register named sequence operations once, then reference them by string ID in `globalSequence` and `boundSequence`:
|
|
24
|
+
- **Screen as component** — Every React component is a screen. Register them into a tree, navigate with `skip` / `back` / `gotoScreen`. No hand-written conditional rendering.
|
|
25
|
+
- **Layered keyboard engine** — Each screen owns its key bindings. A 7-stage pipeline resolves conflicts between modals, overlays, global keys, and the screen stack. Focus system partitions keys within the same layer.
|
|
26
|
+
- **Event bus** — Decoupled cross-component communication. Global keys emit events; any component subscribes. Zero prop drilling.
|
|
78
27
|
|
|
79
|
-
|
|
80
|
-
defineSequenceAction([
|
|
81
|
-
{ sequenceActionId: 'save', action: () => saveFile(), keys: ['ctrl+s'] },
|
|
82
|
-
]);
|
|
28
|
+
## Installation
|
|
83
29
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// Screen-level sequence using the action's predefined keys
|
|
88
|
-
boundSequence('save');
|
|
89
|
-
|
|
90
|
-
// Modify an existing action's keys dynamically
|
|
91
|
-
modifySequenceAction('save', ['ctrl+shift+s']);
|
|
30
|
+
```bash
|
|
31
|
+
npm install ink-cartridge
|
|
92
32
|
```
|
|
93
33
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`openOverlay()` and `closeOverlay()` provide floating dialogs on top of the screen stack. Combined with the keyboard system, overlays intercept keys before they reach the underlying screen — ideal for confirmation dialogs, modals, and pop-up menus.
|
|
97
|
-
|
|
98
|
-
### Module-Level Functions
|
|
99
|
-
|
|
100
|
-
Navigation functions (`skip`, `back`, `gotoScreen`, `openOverlay`, `closeOverlay`) work both inside React components (via hooks) and as **module-level imports** in any `.ts` / `.tsx` file. This allows non-UI layers — game engines, state managers, etc. — to trigger screen transitions.
|
|
101
|
-
|
|
102
|
-
### Type Safety
|
|
103
|
-
|
|
104
|
-
Every API provides full TypeScript type inference. Functions like `skip`, `gotoScreen`, and `openOverlay` automatically infer parameter types from your component's props, catching errors at compile time.
|
|
34
|
+
## Scaffold
|
|
105
35
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
## ⚠️ Important: Component Nesting Order
|
|
109
|
-
|
|
110
|
-
`KeyboardProvider` **must** be nested inside `ScenarioManagementProvider`, because it depends on the screen context to obtain the current screen stack.
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
{/* ❌ Wrong: KeyboardProvider outside screen context */}
|
|
114
|
-
<KeyboardProvider>
|
|
115
|
-
<ScenarioManagementProvider defaultScreen={Menu}>
|
|
116
|
-
...
|
|
117
|
-
</ScenarioManagementProvider>
|
|
118
|
-
</KeyboardProvider>
|
|
119
|
-
|
|
120
|
-
{/* ✅ Correct: KeyboardProvider inside screen context */}
|
|
121
|
-
<ScenarioManagementProvider defaultScreen={Menu}>
|
|
122
|
-
<KeyboardProvider>
|
|
123
|
-
...
|
|
124
|
-
</KeyboardProvider>
|
|
125
|
-
</ScenarioManagementProvider>
|
|
36
|
+
```bash
|
|
37
|
+
npx ink-cartridge init my-tui
|
|
126
38
|
```
|
|
127
39
|
|
|
128
|
-
> The screen system can be used independently without `KeyboardProvider`; but the keyboard system requires the screen context.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
40
|
## Documentation
|
|
133
41
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
42
|
+
<details>
|
|
43
|
+
<summary><b>keyboard/</b> — Architecture & API index</summary>
|
|
44
|
+
|
|
45
|
+
- [README](docs/keyboard/README.md) — Architecture & API index
|
|
46
|
+
- [KeyboardProvider](docs/keyboard/KeyboardProvider-API.md)
|
|
47
|
+
- [useKeyboard](docs/keyboard/useKeyboard-API.md)
|
|
48
|
+
- [boundKeyboard](docs/keyboard/boundKeyboard-API.md)
|
|
49
|
+
- [boundSequence](docs/keyboard/boundSequence-API.md)
|
|
50
|
+
- [blockedKey](docs/keyboard/blockedKey-API.md)
|
|
51
|
+
- [stop](docs/keyboard/stop-API.md)
|
|
52
|
+
- [globalKeys](docs/keyboard/globalKeys-API.md)
|
|
53
|
+
- [globalSequence](docs/keyboard/globalSequence-API.md)
|
|
54
|
+
- [focus system](docs/keyboard/focus-system-API.md)
|
|
55
|
+
- [shortcut actions](docs/keyboard/shortcut-actions-API.md)
|
|
56
|
+
- [sequence actions](docs/keyboard/sequence-actions-API.md)
|
|
57
|
+
- [allowModal](docs/keyboard/allowModal-API.md)
|
|
58
|
+
- [useModalMissListener](docs/keyboard/useModalMissListener-API.md)
|
|
59
|
+
- [enableWildcardPriority](docs/keyboard/enableWildcardPriority-API.md)
|
|
60
|
+
- [advanced](docs/keyboard/advanced.md)
|
|
61
|
+
</details>
|
|
62
|
+
|
|
63
|
+
<details>
|
|
64
|
+
<summary><b>screen/</b> — Architecture & API index</summary>
|
|
65
|
+
|
|
66
|
+
- [README](docs/screen/README.md) — Architecture & API index
|
|
67
|
+
- [registerComponent](docs/screen/registerComponent-API.md)
|
|
68
|
+
- [ScenarioManagementProvider](docs/screen/ScenarioManagementProvider-API.md)
|
|
69
|
+
- [CurrentScreen](docs/screen/CurrentScreen-API.md)
|
|
70
|
+
- [useScreenSystem](docs/screen/useScreenSystem-API.md)
|
|
71
|
+
- [skip](docs/screen/skip-API.md)
|
|
72
|
+
- [back](docs/screen/back-API.md)
|
|
73
|
+
- [gotoScreen](docs/screen/gotoScreen-API.md)
|
|
74
|
+
- [overlay](docs/screen/overlay-API.md)
|
|
75
|
+
- [modal](docs/screen/modal-API.md)
|
|
76
|
+
- [ModalContext](docs/screen/ModalContext-API.md)
|
|
77
|
+
- [advanced](docs/screen/advanced.md)
|
|
78
|
+
</details>
|
|
79
|
+
|
|
80
|
+
<details>
|
|
81
|
+
<summary><b>event/</b> — Architecture & API index</summary>
|
|
82
|
+
|
|
83
|
+
- [README](docs/event/README.md) — Architecture & API index
|
|
84
|
+
- [createEventBus](docs/event/createEventBus-API.md)
|
|
85
|
+
- [EventProvider](docs/event/EventProvider-API.md)
|
|
86
|
+
- [useEmitter](docs/event/useEmitter-API.md)
|
|
87
|
+
- [useSubscribe](docs/event/useSubscribe-API.md)
|
|
88
|
+
- [useEventBus](docs/event/useEventBus-API.md)
|
|
89
|
+
- [EventBus](docs/event/EventBus-API.md)
|
|
90
|
+
- [advanced](docs/event/advanced.md)
|
|
91
|
+
</details>
|
|
92
|
+
|
|
93
|
+
<details>
|
|
94
|
+
<summary><b>components/</b> — Component index</summary>
|
|
95
|
+
|
|
96
|
+
- [README](docs/components/README.md) — Component index
|
|
97
|
+
- [SelectInput](docs/components/SelectInput/SelectInput-API.md)
|
|
98
|
+
- [SelectRow](docs/components/SelectRow/SelectRow-API.md)
|
|
99
|
+
- [MultiSelectInput](docs/components/MultiSelectInput/MultiSelectInput-API.md)
|
|
100
|
+
- [TextInput](docs/components/TextInput/TextInput-API.md)
|
|
101
|
+
- [UncontrolledTextInput](docs/components/TextInput/UncontrolledTextInput-API.md)
|
|
102
|
+
- [NumberInput](docs/components/NumberInput/NumberInput-API.md)
|
|
103
|
+
- [SearchInput](docs/components/SearchInput/SearchInput-API.md)
|
|
104
|
+
- [ConfirmDialog](docs/components/ConfirmDialog/ConfirmDialog-API.md)
|
|
105
|
+
- [Spinner](docs/components/Spinner/Spinner-API.md)
|
|
106
|
+
- [ProgressBar](docs/components/ProgressBar/ProgressBar-API.md)
|
|
107
|
+
- [Divider](docs/components/Divider/Divider-API.md)
|
|
108
|
+
- [Badge](docs/components/Badge/Badge-API.md)
|
|
109
|
+
- [KeyHint](docs/components/KeyHint/KeyHint-API.md)
|
|
110
|
+
- [Tabs](docs/components/Tabs/Tabs-API.md)
|
|
111
|
+
- [Fold](docs/components/Fold/Fold-API.md)
|
|
112
|
+
- [Form](docs/components/Form/Form-API.md)
|
|
113
|
+
- [Field](docs/components/Form/Field-API.md)
|
|
114
|
+
- [useFormContext](docs/components/Form/useFormContext-API.md)
|
|
115
|
+
</details>
|
|
116
|
+
|
|
117
|
+
<details>
|
|
118
|
+
<summary><b>theme/</b></summary>
|
|
119
|
+
|
|
120
|
+
- [README](docs/theme/README.md)
|
|
121
|
+
- [ThemeProvider](docs/theme/ThemeProvider-API.md)
|
|
122
|
+
- [useTheme](docs/theme/useTheme-API.md)
|
|
123
|
+
- [advanced](docs/theme/advanced.md)
|
|
124
|
+
</details>
|
|
125
|
+
|
|
126
|
+
<details>
|
|
127
|
+
<summary><b>language/</b></summary>
|
|
128
|
+
|
|
129
|
+
- [README](docs/language/README.md)
|
|
130
|
+
- [LanguageProvider](docs/language/LanguageProvider-API.md)
|
|
131
|
+
- [useI18n](docs/language/useI18n-API.md)
|
|
132
|
+
- [advanced](docs/language/advanced.md)
|
|
133
|
+
</details>
|
|
134
|
+
|
|
135
|
+
<details>
|
|
136
|
+
<summary><b>storage/</b></summary>
|
|
137
|
+
|
|
138
|
+
- [README](docs/storage/README.md)
|
|
139
|
+
- [createStorage](docs/storage/createStorage-API.md)
|
|
140
|
+
</details>
|
|
141
|
+
|
|
142
|
+
<details>
|
|
143
|
+
<summary><b>binary-storage/</b></summary>
|
|
144
|
+
|
|
145
|
+
- [README](docs/binary-storage/README.md)
|
|
146
|
+
- [createBinaryStorage](docs/binary-storage/createBinaryStorage-API.md)
|
|
147
|
+
- [createStreamingReader](docs/binary-storage/createStreamingReader-API.md)
|
|
148
|
+
</details>
|
|
149
|
+
|
|
150
|
+
<details>
|
|
151
|
+
<summary><b>dev-tool/</b></summary>
|
|
152
|
+
|
|
153
|
+
- [README](docs/dev-tool/README.md)
|
|
154
|
+
- [openDevTool](docs/dev-tool/openDevTool-API.md)
|
|
155
|
+
- [closeDevTool](docs/dev-tool/closeDevTool-API.md)
|
|
156
|
+
</details>
|
|
240
157
|
|
|
241
158
|
## Other
|
|
242
159
|
|
|
243
|
-
|
|
244
|
-
For example, the method of **blockedKeys**. Why is it called that? I don't know. Maybe I didn't think about it at that time.
|
|
245
|
-
Actually, it should be called **penetration**, but I don't want to change it.
|
|
160
|
+
The method `blockedKey` is poorly named — it means *pass-through*, not "block." The internal name is `penetration`. Too late to rename now.
|
|
246
161
|
|
|
247
162
|
## License
|
|
248
163
|
|
|
249
|
-
[MIT](LICENSE)
|
|
164
|
+
[MIT](LICENSE)
|
|
@@ -182,6 +182,9 @@ class StreamingReader {
|
|
|
182
182
|
return waiter.deferred.promise;
|
|
183
183
|
}
|
|
184
184
|
[Symbol.asyncIterator]() {
|
|
185
|
+
// Aliasing `this` for use inside the returned iterator object's methods
|
|
186
|
+
// is a standard pattern for async iterators; the iterator is a plain
|
|
187
|
+
// object, not an arrow function, so `this` would be lost otherwise.
|
|
185
188
|
const reader = this;
|
|
186
189
|
return {
|
|
187
190
|
async next() {
|
|
@@ -43,7 +43,7 @@ export function ConfirmDialog({ title, message, confirmLabel = '确认', cancelL
|
|
|
43
43
|
focusUnregister('dialog-confirm');
|
|
44
44
|
focusUnregister('dialog-cancel');
|
|
45
45
|
};
|
|
46
|
-
}, []);
|
|
46
|
+
}, [boundKeyboard, focusSet, focusUnregister]);
|
|
47
47
|
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 2, paddingY: 1 },
|
|
48
48
|
React.createElement(Box, { marginBottom: 1 },
|
|
49
49
|
React.createElement(Text, { bold: true, color: "yellow" }, '⚠ ' + title)),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { useKeyboard, useFocusState } from '../../keyboard/index.js';
|
|
4
4
|
export function Fold({ focusId, label, preview, children, expanded, onToggle, defaultExpanded, storage, storageKey, }) {
|
|
@@ -18,7 +18,7 @@ export function Fold({ focusId, label, preview, children, expanded, onToggle, de
|
|
|
18
18
|
});
|
|
19
19
|
return () => { cancelled = true; };
|
|
20
20
|
}, [storage, persistKey, defaultExpanded]);
|
|
21
|
-
const toggle = () => {
|
|
21
|
+
const toggle = useCallback(() => {
|
|
22
22
|
if (isControlled) {
|
|
23
23
|
onToggle?.();
|
|
24
24
|
}
|
|
@@ -29,11 +29,11 @@ export function Fold({ focusId, label, preview, children, expanded, onToggle, de
|
|
|
29
29
|
return next;
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
};
|
|
32
|
+
}, [isControlled, onToggle, storage, persistKey]);
|
|
33
33
|
useEffect(() => {
|
|
34
34
|
const unSpace = boundKeyboard([' '], () => toggle(), { focusId });
|
|
35
35
|
return () => { unSpace(); focusUnregister(focusId); };
|
|
36
|
-
}, [focusId]);
|
|
36
|
+
}, [focusId, boundKeyboard, focusUnregister, toggle]);
|
|
37
37
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
38
38
|
React.createElement(Box, null,
|
|
39
39
|
React.createElement(Text, { color: isFocused ? 'cyan' : 'grey' }, isExpanded ? '▼ ' : '▶ '),
|
|
@@ -35,7 +35,7 @@ export function Field({ name, children, rules, defaultValue, focusId }) {
|
|
|
35
35
|
useEffect(() => {
|
|
36
36
|
registerField(name, defaultValue, rules, effectiveFocusId);
|
|
37
37
|
return () => unregisterField(name);
|
|
38
|
-
}, [name, registerField, unregisterField]);
|
|
38
|
+
}, [name, defaultValue, rules, effectiveFocusId, registerField, unregisterField]);
|
|
39
39
|
/**
|
|
40
40
|
* Stable onChange handler — calls Form's setFieldValue,
|
|
41
41
|
* which updates the value and clears any validation error.
|
|
@@ -48,12 +48,13 @@ export function Field({ name, children, rules, defaultValue, focusId }) {
|
|
|
48
48
|
* components on the initial render.
|
|
49
49
|
*/
|
|
50
50
|
const resolvedValue = values[name] !== undefined ? values[name] : (defaultValue ?? '');
|
|
51
|
+
const error = errors[name];
|
|
51
52
|
/** Memoize the render props to avoid unnecessary re-renders. */
|
|
52
53
|
const fieldProps = useMemo(() => ({
|
|
53
54
|
value: resolvedValue,
|
|
54
|
-
error
|
|
55
|
+
error,
|
|
55
56
|
onChange,
|
|
56
57
|
focusId: effectiveFocusId,
|
|
57
|
-
}), [resolvedValue,
|
|
58
|
+
}), [resolvedValue, error, onChange, effectiveFocusId]);
|
|
58
59
|
return React.createElement(React.Fragment, null, children(fieldProps));
|
|
59
60
|
}
|
|
@@ -203,7 +203,7 @@ export function MultiSelectInput({ items = [], selected: controlledSelected, def
|
|
|
203
203
|
// Unregister old focus target when focusId changes
|
|
204
204
|
useEffect(() => {
|
|
205
205
|
return () => focusUnregister(focusId);
|
|
206
|
-
}, [focusId]);
|
|
206
|
+
}, [focusId, focusUnregister]);
|
|
207
207
|
// 注册键盘绑定
|
|
208
208
|
useEffect(() => {
|
|
209
209
|
const fid = focusId;
|
|
@@ -7,7 +7,7 @@ export function NumberInput({ focusId, value, onChange, min = -Infinity, max = I
|
|
|
7
7
|
// Unregister old focus target when focusId changes
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
return () => focusUnregister(focusId);
|
|
10
|
-
}, [focusId]);
|
|
10
|
+
}, [focusId, focusUnregister]);
|
|
11
11
|
useEffect(() => {
|
|
12
12
|
const up = boundKeyboard(['up', 'right'], () => {
|
|
13
13
|
const next = Math.min(value + step, max);
|
|
@@ -38,7 +38,7 @@ export function NumberInput({ focusId, value, onChange, min = -Infinity, max = I
|
|
|
38
38
|
down();
|
|
39
39
|
wildcard();
|
|
40
40
|
};
|
|
41
|
-
}, [focusId, value, min, max, step, onChange]);
|
|
41
|
+
}, [focusId, value, min, max, step, onChange, boundKeyboard]);
|
|
42
42
|
const text = isFocused ? `${value}█` : String(value);
|
|
43
43
|
return React.createElement(Text, null, text);
|
|
44
44
|
}
|
|
@@ -7,7 +7,7 @@ export function SearchInput({ focusId, value, onChange, placeholder, onSubmit, }
|
|
|
7
7
|
useEffect(() => {
|
|
8
8
|
const unEsc = boundKeyboard(['escape'], () => onChange(''), { focusId });
|
|
9
9
|
return () => { unEsc(); };
|
|
10
|
-
}, [focusId, onChange]);
|
|
10
|
+
}, [focusId, onChange, boundKeyboard]);
|
|
11
11
|
return (React.createElement(Box, null,
|
|
12
12
|
React.createElement(Text, { color: "blue" }, "Search "),
|
|
13
13
|
React.createElement(TextInput, { focusId: focusId, value: value, onChange: onChange, placeholder: placeholder, onSubmit: onSubmit }),
|
|
@@ -16,85 +16,4 @@ import type { SelectInputProps, Item } from './types.js';
|
|
|
16
16
|
* @typeParam T - The type of the value associated with each item.
|
|
17
17
|
* @typeParam I - The extended item type, must extend Item<T>. Defaults to Item<T>.
|
|
18
18
|
*/
|
|
19
|
-
export declare function SelectInput<T, I extends Item<T> = Item<T>>({ items, onSelect, itemComponent, indicatorComponent, focusId, limit: limitProp, storage, storageKey, }: SelectInputProps<T, I>): React.
|
|
20
|
-
readonly position?: "absolute" | "relative" | "static" | undefined;
|
|
21
|
-
readonly top?: number | string | undefined;
|
|
22
|
-
readonly right?: number | string | undefined;
|
|
23
|
-
readonly bottom?: number | string | undefined;
|
|
24
|
-
readonly left?: number | string | undefined;
|
|
25
|
-
readonly columnGap?: number | undefined;
|
|
26
|
-
readonly rowGap?: number | undefined;
|
|
27
|
-
readonly gap?: number | undefined;
|
|
28
|
-
readonly margin?: number | undefined;
|
|
29
|
-
readonly marginX?: number | undefined;
|
|
30
|
-
readonly marginY?: number | undefined;
|
|
31
|
-
readonly marginTop?: number | undefined;
|
|
32
|
-
readonly marginBottom?: number | undefined;
|
|
33
|
-
readonly marginLeft?: number | undefined;
|
|
34
|
-
readonly marginRight?: number | undefined;
|
|
35
|
-
readonly padding?: number | undefined;
|
|
36
|
-
readonly paddingX?: number | undefined;
|
|
37
|
-
readonly paddingY?: number | undefined;
|
|
38
|
-
readonly paddingTop?: number | undefined;
|
|
39
|
-
readonly paddingBottom?: number | undefined;
|
|
40
|
-
readonly paddingLeft?: number | undefined;
|
|
41
|
-
readonly paddingRight?: number | undefined;
|
|
42
|
-
readonly flexGrow?: number | undefined;
|
|
43
|
-
readonly flexShrink?: number | undefined;
|
|
44
|
-
readonly flexDirection?: "row" | "column" | "row-reverse" | "column-reverse" | undefined;
|
|
45
|
-
readonly flexBasis?: number | string | undefined;
|
|
46
|
-
readonly flexWrap?: "nowrap" | "wrap" | "wrap-reverse" | undefined;
|
|
47
|
-
readonly alignItems?: "flex-start" | "center" | "flex-end" | "stretch" | "baseline" | undefined;
|
|
48
|
-
readonly alignSelf?: "flex-start" | "center" | "flex-end" | "auto" | "stretch" | "baseline" | undefined;
|
|
49
|
-
readonly alignContent?: "flex-start" | "flex-end" | "center" | "stretch" | "space-between" | "space-around" | "space-evenly" | undefined;
|
|
50
|
-
readonly justifyContent?: "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly" | "center" | undefined;
|
|
51
|
-
readonly width?: number | string | undefined;
|
|
52
|
-
readonly height?: number | string | undefined;
|
|
53
|
-
readonly minWidth?: number | string | undefined;
|
|
54
|
-
readonly minHeight?: number | string | undefined;
|
|
55
|
-
readonly maxWidth?: number | string | undefined;
|
|
56
|
-
readonly maxHeight?: number | string | undefined;
|
|
57
|
-
readonly aspectRatio?: number | undefined;
|
|
58
|
-
readonly display?: "flex" | "none" | undefined;
|
|
59
|
-
readonly borderStyle?: (keyof import("cli-boxes").Boxes | import("cli-boxes").BoxStyle) | undefined;
|
|
60
|
-
readonly borderTop?: boolean | undefined;
|
|
61
|
-
readonly borderBottom?: boolean | undefined;
|
|
62
|
-
readonly borderLeft?: boolean | undefined;
|
|
63
|
-
readonly borderRight?: boolean | undefined;
|
|
64
|
-
readonly borderColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
65
|
-
readonly borderTopColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
66
|
-
readonly borderBottomColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
67
|
-
readonly borderLeftColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
68
|
-
readonly borderRightColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
69
|
-
readonly borderDimColor?: boolean | undefined;
|
|
70
|
-
readonly borderTopDimColor?: boolean | undefined;
|
|
71
|
-
readonly borderBottomDimColor?: boolean | undefined;
|
|
72
|
-
readonly borderLeftDimColor?: boolean | undefined;
|
|
73
|
-
readonly borderRightDimColor?: boolean | undefined;
|
|
74
|
-
readonly borderBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
75
|
-
readonly borderTopBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
76
|
-
readonly borderBottomBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
77
|
-
readonly borderLeftBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
78
|
-
readonly borderRightBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
79
|
-
readonly overflow?: "visible" | "hidden" | undefined;
|
|
80
|
-
readonly overflowX?: "visible" | "hidden" | undefined;
|
|
81
|
-
readonly overflowY?: "visible" | "hidden" | undefined;
|
|
82
|
-
readonly backgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
83
|
-
} & {
|
|
84
|
-
readonly 'aria-label'?: string;
|
|
85
|
-
readonly 'aria-hidden'?: boolean;
|
|
86
|
-
readonly 'aria-role'?: "button" | "checkbox" | "combobox" | "list" | "listbox" | "listitem" | "menu" | "menuitem" | "option" | "progressbar" | "radio" | "radiogroup" | "tab" | "tablist" | "table" | "textbox" | "timer" | "toolbar";
|
|
87
|
-
readonly 'aria-state'?: {
|
|
88
|
-
readonly busy?: boolean;
|
|
89
|
-
readonly checked?: boolean;
|
|
90
|
-
readonly disabled?: boolean;
|
|
91
|
-
readonly expanded?: boolean;
|
|
92
|
-
readonly multiline?: boolean;
|
|
93
|
-
readonly multiselectable?: boolean;
|
|
94
|
-
readonly readonly?: boolean;
|
|
95
|
-
readonly required?: boolean;
|
|
96
|
-
readonly selected?: boolean;
|
|
97
|
-
};
|
|
98
|
-
} & {
|
|
99
|
-
children?: React.ReactNode | undefined;
|
|
100
|
-
} & React.RefAttributes<import("ink").DOMElement>>;
|
|
19
|
+
export declare function SelectInput<T, I extends Item<T> = Item<T>>({ items, onSelect, itemComponent, indicatorComponent, focusId, limit: limitProp, storage, storageKey, }: SelectInputProps<T, I>): React.JSX.Element;
|