ink-cartridge 3.7.1 → 3.8.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/README.md +145 -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.d.ts +1 -82
- 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.d.ts +1 -1
- package/dist/dev/dev-screen.js +159 -9
- package/dist/dev/entrance.d.ts +27 -22
- package/dist/dev/entrance.js +32 -32
- package/dist/dev/globalKey-display.js +2 -1
- package/dist/dev/globalSeq-display.d.ts +3 -0
- package/dist/dev/globalSeq-display.js +161 -0
- package/dist/dev/layerKey-display.d.ts +9 -0
- package/dist/dev/layerKey-display.js +280 -0
- package/dist/dev/types.d.ts +4 -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 +72 -4
- package/dist/keyboard/global-sequence-processor/index.js +99 -26
- package/dist/keyboard/hook.js +61 -17
- 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 +236 -189
- package/dist/keyboard/types.d.ts +48 -0
- package/dist/language/provider.js +9 -5
- package/dist/screen/ModalContext.d.ts +12 -3
- package/dist/screen/ModalContext.js +6 -2
- package/dist/screen/OverlayContext.d.ts +14 -3
- package/dist/screen/OverlayContext.js +8 -2
- package/dist/screen/current-screen.js +6 -3
- package/dist/screen/provider.d.ts +13 -4
- package/dist/screen/provider.js +88 -26
- package/dist/screen/types.d.ts +26 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -1,248 +1,172 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<br>
|
|
3
|
+
<br>
|
|
4
|
+
<img width="440" alt="cartridge" src="static/cartridge.png">
|
|
5
|
+
<br>
|
|
6
|
+
<br>
|
|
7
|
+
<br>
|
|
8
|
+
</div>
|
|
2
9
|
|
|
3
|
-
>
|
|
10
|
+
<h1 align="center">Cartridge</h1>
|
|
11
|
+
|
|
12
|
+
>A component kit for rapidly building complex, multi-page, interaction-heavy terminal applications — filling the critical gaps Ink leaves open.
|
|
4
13
|
|
|
5
14
|
[](https://github.com/BAIGAOa/ink-trc/actions/workflows/ci.yml)
|
|
6
15
|
[](https://www.npmjs.com/package/ink-cartridge)
|
|
7
16
|
[](https://opensource.org/licenses/MIT)
|
|
8
17
|
|
|
18
|
+
## Table of Contents
|
|
9
19
|
|
|
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
|
-
---
|
|
20
|
+
- [Design Philosophy](#design-philosophy)
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Scaffold](#scaffold)
|
|
23
|
+
- [Documentation](#documentation)
|
|
24
|
+
- [Other](#other)
|
|
25
|
+
- [License](#license)
|
|
37
26
|
|
|
38
27
|
## Design Philosophy
|
|
39
28
|
|
|
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
|
|
29
|
+
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.
|
|
47
30
|
|
|
48
|
-
|
|
31
|
+
Three pillars:
|
|
49
32
|
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **Global Key (`globalKeys`)** — Shortcuts independent of the screen stack
|
|
33
|
+
- **Screen as component** — Every React component is a screen. Register them into a tree, navigate with `skip` / `back` / `gotoScreen`. No hand-written conditional rendering.
|
|
34
|
+
- **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.
|
|
35
|
+
- **Event bus** — Decoupled cross-component communication. Global keys emit events; any component subscribes. Zero prop drilling.
|
|
54
36
|
|
|
55
|
-
|
|
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
|
|
63
|
-
|
|
64
|
-
Decouple operation definition from key binding with `defineShortcutAction`. Register named operations once, then reference them by string ID in `boundKeyboard`, `globalKeys`, and `stop`:
|
|
65
|
-
|
|
66
|
-
```tsx
|
|
67
|
-
defineShortcutAction([
|
|
68
|
-
{ actionId: 'quit', action: () => process.exit() },
|
|
69
|
-
]);
|
|
70
|
-
boundKeyboard(['q'], 'quit');
|
|
71
|
-
globalKeys([{ key: 'escape', operate: 'quit' }]);
|
|
72
|
-
stop(['quit'], { stopAction: true });
|
|
73
|
-
```
|
|
37
|
+
## Installation
|
|
74
38
|
|
|
75
|
-
|
|
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`:
|
|
78
|
-
|
|
79
|
-
```tsx
|
|
80
|
-
defineSequenceAction([
|
|
81
|
-
{ sequenceActionId: 'save', action: () => saveFile(), keys: ['ctrl+s'] },
|
|
82
|
-
]);
|
|
83
|
-
|
|
84
|
-
// Global sequence referencing the action
|
|
85
|
-
globalSequence([{ keys: ['ctrl+s'], operate: 'save' }]);
|
|
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']);
|
|
39
|
+
```bash
|
|
40
|
+
npm install ink-cartridge
|
|
92
41
|
```
|
|
93
42
|
|
|
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.
|
|
43
|
+
## Scaffold
|
|
105
44
|
|
|
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>
|
|
45
|
+
```bash
|
|
46
|
+
npx ink-cartridge init my-tui
|
|
126
47
|
```
|
|
127
48
|
|
|
128
|
-
> The screen system can be used independently without `KeyboardProvider`; but the keyboard system requires the screen context.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
49
|
## Documentation
|
|
133
50
|
|
|
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
|
-
|
|
51
|
+
<details>
|
|
52
|
+
<summary><b>keyboard/</b> — Architecture & API index</summary>
|
|
53
|
+
|
|
54
|
+
- [README](docs/keyboard/README.md) — Architecture & API index
|
|
55
|
+
- [KeyboardProvider](docs/keyboard/KeyboardProvider-API.md)
|
|
56
|
+
- [useKeyboard](docs/keyboard/useKeyboard-API.md)
|
|
57
|
+
- [boundKeyboard](docs/keyboard/boundKeyboard-API.md)
|
|
58
|
+
- [boundSequence](docs/keyboard/boundSequence-API.md)
|
|
59
|
+
- [blockedKey](docs/keyboard/blockedKey-API.md)
|
|
60
|
+
- [stop](docs/keyboard/stop-API.md)
|
|
61
|
+
- [globalKeys](docs/keyboard/globalKeys-API.md)
|
|
62
|
+
- [globalSequence](docs/keyboard/globalSequence-API.md)
|
|
63
|
+
- [focus system](docs/keyboard/focus-system-API.md)
|
|
64
|
+
- [shortcut actions](docs/keyboard/shortcut-actions-API.md)
|
|
65
|
+
- [sequence actions](docs/keyboard/sequence-actions-API.md)
|
|
66
|
+
- [allowModal](docs/keyboard/allowModal-API.md)
|
|
67
|
+
- [useModalMissListener](docs/keyboard/useModalMissListener-API.md)
|
|
68
|
+
- [enableWildcardPriority](docs/keyboard/enableWildcardPriority-API.md)
|
|
69
|
+
- [advanced](docs/keyboard/advanced.md)
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
<summary><b>screen/</b> — Architecture & API index</summary>
|
|
74
|
+
|
|
75
|
+
- [README](docs/screen/README.md) — Architecture & API index
|
|
76
|
+
- [registerComponent](docs/screen/registerComponent-API.md)
|
|
77
|
+
- [ScenarioManagementProvider](docs/screen/ScenarioManagementProvider-API.md)
|
|
78
|
+
- [CurrentScreen](docs/screen/CurrentScreen-API.md)
|
|
79
|
+
- [useScreenSystem](docs/screen/useScreenSystem-API.md)
|
|
80
|
+
- [skip](docs/screen/skip-API.md)
|
|
81
|
+
- [back](docs/screen/back-API.md)
|
|
82
|
+
- [gotoScreen](docs/screen/gotoScreen-API.md)
|
|
83
|
+
- [overlay](docs/screen/overlay-API.md)
|
|
84
|
+
- [modal](docs/screen/modal-API.md)
|
|
85
|
+
- [ModalContext](docs/screen/ModalContext-API.md)
|
|
86
|
+
- [advanced](docs/screen/advanced.md)
|
|
87
|
+
</details>
|
|
88
|
+
|
|
89
|
+
<details>
|
|
90
|
+
<summary><b>event/</b> — Architecture & API index</summary>
|
|
91
|
+
|
|
92
|
+
- [README](docs/event/README.md) — Architecture & API index
|
|
93
|
+
- [createEventBus](docs/event/createEventBus-API.md)
|
|
94
|
+
- [EventProvider](docs/event/EventProvider-API.md)
|
|
95
|
+
- [useEmitter](docs/event/useEmitter-API.md)
|
|
96
|
+
- [useSubscribe](docs/event/useSubscribe-API.md)
|
|
97
|
+
- [useEventBus](docs/event/useEventBus-API.md)
|
|
98
|
+
- [EventBus](docs/event/EventBus-API.md)
|
|
99
|
+
- [advanced](docs/event/advanced.md)
|
|
100
|
+
</details>
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary><b>components/</b> — Component index</summary>
|
|
104
|
+
|
|
105
|
+
- [README](docs/components/README.md) — Component index
|
|
106
|
+
- [SelectInput](docs/components/SelectInput/SelectInput-API.md)
|
|
107
|
+
- [SelectRow](docs/components/SelectRow/SelectRow-API.md)
|
|
108
|
+
- [MultiSelectInput](docs/components/MultiSelectInput/MultiSelectInput-API.md)
|
|
109
|
+
- [TextInput](docs/components/TextInput/TextInput-API.md)
|
|
110
|
+
- [UncontrolledTextInput](docs/components/TextInput/UncontrolledTextInput-API.md)
|
|
111
|
+
- [NumberInput](docs/components/NumberInput/NumberInput-API.md)
|
|
112
|
+
- [SearchInput](docs/components/SearchInput/SearchInput-API.md)
|
|
113
|
+
- [ConfirmDialog](docs/components/ConfirmDialog/ConfirmDialog-API.md)
|
|
114
|
+
- [Spinner](docs/components/Spinner/Spinner-API.md)
|
|
115
|
+
- [ProgressBar](docs/components/ProgressBar/ProgressBar-API.md)
|
|
116
|
+
- [Divider](docs/components/Divider/Divider-API.md)
|
|
117
|
+
- [Badge](docs/components/Badge/Badge-API.md)
|
|
118
|
+
- [KeyHint](docs/components/KeyHint/KeyHint-API.md)
|
|
119
|
+
- [Tabs](docs/components/Tabs/Tabs-API.md)
|
|
120
|
+
- [Fold](docs/components/Fold/Fold-API.md)
|
|
121
|
+
- [Form](docs/components/Form/Form-API.md)
|
|
122
|
+
- [Field](docs/components/Form/Field-API.md)
|
|
123
|
+
- [useFormContext](docs/components/Form/useFormContext-API.md)
|
|
124
|
+
</details>
|
|
125
|
+
|
|
126
|
+
<details>
|
|
127
|
+
<summary><b>theme/</b></summary>
|
|
128
|
+
|
|
129
|
+
- [README](docs/theme/README.md)
|
|
130
|
+
- [ThemeProvider](docs/theme/ThemeProvider-API.md)
|
|
131
|
+
- [useTheme](docs/theme/useTheme-API.md)
|
|
132
|
+
- [advanced](docs/theme/advanced.md)
|
|
133
|
+
</details>
|
|
134
|
+
|
|
135
|
+
<details>
|
|
136
|
+
<summary><b>language/</b></summary>
|
|
137
|
+
|
|
138
|
+
- [README](docs/language/README.md)
|
|
139
|
+
- [LanguageProvider](docs/language/LanguageProvider-API.md)
|
|
140
|
+
- [useI18n](docs/language/useI18n-API.md)
|
|
141
|
+
- [advanced](docs/language/advanced.md)
|
|
142
|
+
</details>
|
|
143
|
+
|
|
144
|
+
<details>
|
|
145
|
+
<summary><b>storage/</b></summary>
|
|
146
|
+
|
|
147
|
+
- [README](docs/storage/README.md)
|
|
148
|
+
- [createStorage](docs/storage/createStorage-API.md)
|
|
149
|
+
</details>
|
|
150
|
+
|
|
151
|
+
<details>
|
|
152
|
+
<summary><b>binary-storage/</b></summary>
|
|
153
|
+
|
|
154
|
+
- [README](docs/binary-storage/README.md)
|
|
155
|
+
- [createBinaryStorage](docs/binary-storage/createBinaryStorage-API.md)
|
|
156
|
+
- [createStreamingReader](docs/binary-storage/createStreamingReader-API.md)
|
|
157
|
+
</details>
|
|
158
|
+
|
|
159
|
+
<details>
|
|
160
|
+
<summary><b>dev-tool/</b></summary>
|
|
161
|
+
|
|
162
|
+
- [README](docs/dev-tool/README.md)
|
|
163
|
+
- [openDevTool](docs/dev-tool/openDevTool-API.md)
|
|
164
|
+
- [closeDevTool](docs/dev-tool/closeDevTool-API.md)
|
|
165
|
+
</details>
|
|
240
166
|
|
|
241
167
|
## Other
|
|
242
168
|
|
|
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.
|
|
169
|
+
The method `blockedKey` is poorly named — it means *pass-through*, not "block." The internal name is `penetration`. Too late to rename now.
|
|
246
170
|
|
|
247
171
|
## License
|
|
248
172
|
|
|
@@ -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
|
}
|
|
@@ -41,85 +41,4 @@ import type { MultiSelectInputProps, Item } from './types.js';
|
|
|
41
41
|
* />
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
|
-
export declare function MultiSelectInput<T, I extends Item<T> = Item<T>>({ items, selected: controlledSelected, defaultSelected, onChange, onSubmit, onSelect, onUnselect, onHighlight, indicatorComponent, checkboxComponent, itemComponent, focusId, limit: limitProp, initialIndex, storage, storageKey, }: MultiSelectInputProps<T, I>): React.
|
|
45
|
-
readonly position?: "absolute" | "relative" | "static" | undefined;
|
|
46
|
-
readonly top?: number | string | undefined;
|
|
47
|
-
readonly right?: number | string | undefined;
|
|
48
|
-
readonly bottom?: number | string | undefined;
|
|
49
|
-
readonly left?: number | string | undefined;
|
|
50
|
-
readonly columnGap?: number | undefined;
|
|
51
|
-
readonly rowGap?: number | undefined;
|
|
52
|
-
readonly gap?: number | undefined;
|
|
53
|
-
readonly margin?: number | undefined;
|
|
54
|
-
readonly marginX?: number | undefined;
|
|
55
|
-
readonly marginY?: number | undefined;
|
|
56
|
-
readonly marginTop?: number | undefined;
|
|
57
|
-
readonly marginBottom?: number | undefined;
|
|
58
|
-
readonly marginLeft?: number | undefined;
|
|
59
|
-
readonly marginRight?: number | undefined;
|
|
60
|
-
readonly padding?: number | undefined;
|
|
61
|
-
readonly paddingX?: number | undefined;
|
|
62
|
-
readonly paddingY?: number | undefined;
|
|
63
|
-
readonly paddingTop?: number | undefined;
|
|
64
|
-
readonly paddingBottom?: number | undefined;
|
|
65
|
-
readonly paddingLeft?: number | undefined;
|
|
66
|
-
readonly paddingRight?: number | undefined;
|
|
67
|
-
readonly flexGrow?: number | undefined;
|
|
68
|
-
readonly flexShrink?: number | undefined;
|
|
69
|
-
readonly flexDirection?: "row" | "column" | "row-reverse" | "column-reverse" | undefined;
|
|
70
|
-
readonly flexBasis?: number | string | undefined;
|
|
71
|
-
readonly flexWrap?: "nowrap" | "wrap" | "wrap-reverse" | undefined;
|
|
72
|
-
readonly alignItems?: "flex-start" | "center" | "flex-end" | "stretch" | "baseline" | undefined;
|
|
73
|
-
readonly alignSelf?: "flex-start" | "center" | "flex-end" | "auto" | "stretch" | "baseline" | undefined;
|
|
74
|
-
readonly alignContent?: "flex-start" | "flex-end" | "center" | "stretch" | "space-between" | "space-around" | "space-evenly" | undefined;
|
|
75
|
-
readonly justifyContent?: "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly" | "center" | undefined;
|
|
76
|
-
readonly width?: number | string | undefined;
|
|
77
|
-
readonly height?: number | string | undefined;
|
|
78
|
-
readonly minWidth?: number | string | undefined;
|
|
79
|
-
readonly minHeight?: number | string | undefined;
|
|
80
|
-
readonly maxWidth?: number | string | undefined;
|
|
81
|
-
readonly maxHeight?: number | string | undefined;
|
|
82
|
-
readonly aspectRatio?: number | undefined;
|
|
83
|
-
readonly display?: "flex" | "none" | undefined;
|
|
84
|
-
readonly borderStyle?: (keyof import("cli-boxes").Boxes | import("cli-boxes").BoxStyle) | undefined;
|
|
85
|
-
readonly borderTop?: boolean | undefined;
|
|
86
|
-
readonly borderBottom?: boolean | undefined;
|
|
87
|
-
readonly borderLeft?: boolean | undefined;
|
|
88
|
-
readonly borderRight?: boolean | undefined;
|
|
89
|
-
readonly borderColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
90
|
-
readonly borderTopColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
91
|
-
readonly borderBottomColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
92
|
-
readonly borderLeftColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
93
|
-
readonly borderRightColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
94
|
-
readonly borderDimColor?: boolean | undefined;
|
|
95
|
-
readonly borderTopDimColor?: boolean | undefined;
|
|
96
|
-
readonly borderBottomDimColor?: boolean | undefined;
|
|
97
|
-
readonly borderLeftDimColor?: boolean | undefined;
|
|
98
|
-
readonly borderRightDimColor?: boolean | undefined;
|
|
99
|
-
readonly borderBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
100
|
-
readonly borderTopBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
101
|
-
readonly borderBottomBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
102
|
-
readonly borderLeftBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
103
|
-
readonly borderRightBackgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
104
|
-
readonly overflow?: "visible" | "hidden" | undefined;
|
|
105
|
-
readonly overflowX?: "visible" | "hidden" | undefined;
|
|
106
|
-
readonly overflowY?: "visible" | "hidden" | undefined;
|
|
107
|
-
readonly backgroundColor?: import("type-fest").LiteralUnion<import("ansi-styles").ForegroundColorName, string> | undefined;
|
|
108
|
-
} & {
|
|
109
|
-
readonly 'aria-label'?: string;
|
|
110
|
-
readonly 'aria-hidden'?: boolean;
|
|
111
|
-
readonly 'aria-role'?: "button" | "checkbox" | "combobox" | "list" | "listbox" | "listitem" | "menu" | "menuitem" | "option" | "progressbar" | "radio" | "radiogroup" | "tab" | "tablist" | "table" | "textbox" | "timer" | "toolbar";
|
|
112
|
-
readonly 'aria-state'?: {
|
|
113
|
-
readonly busy?: boolean;
|
|
114
|
-
readonly checked?: boolean;
|
|
115
|
-
readonly disabled?: boolean;
|
|
116
|
-
readonly expanded?: boolean;
|
|
117
|
-
readonly multiline?: boolean;
|
|
118
|
-
readonly multiselectable?: boolean;
|
|
119
|
-
readonly readonly?: boolean;
|
|
120
|
-
readonly required?: boolean;
|
|
121
|
-
readonly selected?: boolean;
|
|
122
|
-
};
|
|
123
|
-
} & {
|
|
124
|
-
children?: React.ReactNode | undefined;
|
|
125
|
-
} & React.RefAttributes<import("ink").DOMElement>>;
|
|
44
|
+
export declare function MultiSelectInput<T, I extends Item<T> = Item<T>>({ items, selected: controlledSelected, defaultSelected, onChange, onSubmit, onSelect, onUnselect, onHighlight, indicatorComponent, checkboxComponent, itemComponent, focusId, limit: limitProp, initialIndex, storage, storageKey, }: MultiSelectInputProps<T, I>): React.JSX.Element;
|
|
@@ -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 }),
|