ink-cartridge 3.7.1 → 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.
Files changed (50) hide show
  1. package/README.md +136 -221
  2. package/dist/binary-storage/StreamingReader.js +3 -0
  3. package/dist/components/dialog/ConfirmDialog.js +1 -1
  4. package/dist/components/fold/Fold.js +4 -4
  5. package/dist/components/form/Field.js +4 -3
  6. package/dist/components/multi-select/MultiSelectInput.js +1 -1
  7. package/dist/components/number-input/NumberInput.js +2 -2
  8. package/dist/components/search-input/SearchInput.js +1 -1
  9. package/dist/components/select/SelectInput.d.ts +1 -82
  10. package/dist/components/select/SelectInput.js +20 -100
  11. package/dist/components/select-row/SelectRow.d.ts +22 -0
  12. package/dist/components/select-row/SelectRow.js +69 -0
  13. package/dist/components/select-row/types.d.ts +30 -0
  14. package/dist/components/select-row/types.js +1 -0
  15. package/dist/components/text/TextInput.js +1 -1
  16. package/dist/components/tools/select/useSelectNavigation.d.ts +48 -0
  17. package/dist/components/tools/select/useSelectNavigation.js +107 -0
  18. package/dist/components/tools/select/utils.d.ts +8 -0
  19. package/dist/components/tools/select/utils.js +10 -0
  20. package/dist/dev/dev-screen.js +153 -7
  21. package/dist/dev/globalSeq-display.d.ts +3 -0
  22. package/dist/dev/globalSeq-display.js +160 -0
  23. package/dist/dev/layerKey-display.d.ts +9 -0
  24. package/dist/dev/layerKey-display.js +279 -0
  25. package/dist/event/EventBus.d.ts +67 -0
  26. package/dist/event/EventBus.js +102 -0
  27. package/dist/event/EventProvider.d.ts +38 -0
  28. package/dist/event/EventProvider.js +46 -0
  29. package/dist/event/context.d.ts +2 -0
  30. package/dist/event/context.js +2 -0
  31. package/dist/event/hook.d.ts +48 -0
  32. package/dist/event/hook.js +70 -0
  33. package/dist/event/index.d.ts +5 -0
  34. package/dist/event/index.js +4 -0
  35. package/dist/event/types.d.ts +15 -0
  36. package/dist/event/types.js +1 -0
  37. package/dist/index.d.ts +6 -1
  38. package/dist/index.js +5 -0
  39. package/dist/keyboard/check-global-key.js +1 -0
  40. package/dist/keyboard/context.d.ts +65 -1
  41. package/dist/keyboard/global-sequence-processor/index.js +99 -26
  42. package/dist/keyboard/hook.js +11 -6
  43. package/dist/keyboard/index.d.ts +1 -1
  44. package/dist/keyboard/layer-handler.js +77 -22
  45. package/dist/keyboard/modal-processor/index.js +22 -0
  46. package/dist/keyboard/provider.js +236 -189
  47. package/dist/keyboard/types.d.ts +48 -0
  48. package/dist/language/provider.js +9 -5
  49. package/dist/screen/provider.js +1 -0
  50. package/package.json +11 -1
package/README.md CHANGED
@@ -1,249 +1,164 @@
1
1
  # ink-cartridge
2
2
 
3
- > Ready-to-use Ink components and tools for building terminal UI applications.
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
  [![CI](https://github.com/BAIGAOa/ink-cartridge/actions/workflows/ci.yml/badge.svg)](https://github.com/BAIGAOa/ink-trc/actions/workflows/ci.yml)
6
6
  [![npm version](https://img.shields.io/npm/v/ink-cartridge.svg)](https://www.npmjs.com/package/ink-cartridge)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
+ ## Table of Contents
9
10
 
10
- ---
11
-
12
- ## Getting Started
13
-
14
- ### Quick Start (scaffold a new project)
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 aims to make **screen management** and **keyboard event handling** in Ink applications composable, maintainable, and type-safe.
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
- Decouple operation definition from key binding with `defineShortcutAction`. Register named operations once, then reference them by string ID in `boundKeyboard`, `globalKeys`, and `stop`:
22
+ Three pillars:
65
23
 
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
- ```
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
- ```tsx
80
- defineSequenceAction([
81
- { sequenceActionId: 'save', action: () => saveFile(), keys: ['ctrl+s'] },
82
- ]);
28
+ ## Installation
83
29
 
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']);
30
+ ```bash
31
+ npm install ink-cartridge
92
32
  ```
93
33
 
94
- ### Overlay System
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
- - **[Screen Management System](docs/screen.md)** — `registerComponent`, `ScenarioManagementProvider`, `CurrentScreen`, `useScreenSystem`, `skip` / `back` / `gotoScreen` / `openOverlay` / `closeOverlay`
135
- - **[Keyboard System](docs/keyboard.md)**`KeyboardProvider`, `useKeyboard`, `boundKeyboard`, `boundSequence`, `blockedKey`, `stop`, `globalKeys`, `defineShortcutAction`, focus management
136
- - **[Internationalization](docs/language.md)** — `LanguageProvider`, `useI18n`, `t()` translation with interpolation, language switching, **`ink-cartridge makeLanguageType`** CLI for compile-time type-safe translation keys
137
- - **[Theme System](docs/theme.md)**`ThemeProvider`, `useTheme`, There is also a companion type generator and theme profile generator
138
- - **[Persistence System](docs/storage.md)** — `createStorage`, typed key-value JSON storage with automatic type validation, atomic writes, and zero config
139
- - **[Binary Storage System](docs/binary-storage.md)** — `createBinaryStorage` for sequential typed binary streams with positional cursors, and `createStreamingReader` for memory-efficient streaming of large files (500 MB+) with backpressure support
140
-
141
- ---
142
-
143
- ## Components
144
-
145
- ### Selection & Input
146
- - **[SelectInput](src/components/select/README.md)** — Single-select list with focus-aware keyboard navigation
147
- - **[MultiSelectInput](src/components/multi-select/README.md)** — Multi-select list with checkbox toggling (Space to toggle, Enter to submit)
148
- - **[TextInput](src/components/text/README.md)** — Text input with cursor, mask, and focus system integration
149
- - **[SearchInput](src/components/search-input/README.md)** — Search field with 🔍 icon and Esc-to-clear
150
- - **[NumberInput](src/components/number-input/README.md)** — Numeric stepper with min/max/step and keyboard controls
151
-
152
- ### Display & Feedback
153
- - **[Spinner](src/components/spinner/README.md)** — Animated spinner with multiple preset styles
154
- - **[ProgressBar](src/components/progress-bar/README.md)** — Customizable progress bar with percentage display
155
- - **[Badge](src/components/badge/README.md)** — Colored label/tag component
156
- - **[KeyHint](src/components/key-hint/README.md)** Keyboard shortcut hint bar (`[S] Save`)
157
-
158
- ### Navigation
159
- - **[Tabs](src/components/tabs/README.md)** — Tabbed panel with keyboard navigation and focus system integration
160
- - **[Fold](src/components/fold/README.md)** — Collapsible panel with preview and Space-toggle, integrated with focus system
161
-
162
- ### Layout
163
- - **[Divider](src/components/divider/README.md)** — Horizontal separator with optional centered label
164
-
165
- ### Form
166
- - **[Form & Field](src/components/form/README.md)** — Context‑based form system with validation, error focus, and Ctrl+Enter submit
167
-
168
- ### Dialog
169
- - **[ConfirmDialog](src/components/dialog/README.md)** — Modal confirmation dialog with two buttons, designed for the overlay system
170
-
171
- ---
172
-
173
- ## Quick Overview
174
-
175
- ```tsx
176
- import React, { useEffect } from 'react';
177
- import { Box, Text, render } from 'ink';
178
- import {
179
- registerComponent,
180
- ScenarioManagementProvider,
181
- CurrentScreen,
182
- useScreenSystem,
183
- KeyboardProvider,
184
- useKeyboard,
185
- ConfirmDialog,
186
- } from 'ink-cartridge';
187
-
188
- // ── Register screens ──
189
- function Menu() {
190
- const { skip } = useScreenSystem();
191
- const { boundKeyboard } = useKeyboard();
192
- useEffect(() => {
193
- boundKeyboard(['s'], () => skip(Game, { level: 1 }));
194
- }, []);
195
- return (
196
- <Box>
197
- <Text>Main Menu — Press S to start</Text>
198
- </Box>
199
- );
200
- }
201
- registerComponent(Menu, {});
202
-
203
- function Game({ level }: { level: number }) {
204
- const { back } = useScreenSystem();
205
- const { boundKeyboard } = useKeyboard();
206
- useEffect(() => {
207
- boundKeyboard(['b'], () => back());
208
- }, []);
209
- return (
210
- <Box>
211
- <Text>Level {level} — Press B to go back</Text>
212
- </Box>
213
- );
214
- }
215
- registerComponent(Game, { level: 1 }, { parent: Menu });
216
-
217
- // Register the dialog so it can be used with openOverlay()
218
- registerComponent(ConfirmDialog, {
219
- title: '', message: '', onConfirm: () => {}, onCancel: () => {},
220
- });
221
-
222
- // ── Wire up ──
223
- function App() {
224
- return (
225
- <KeyboardProvider>
226
- <CurrentScreen />
227
- </KeyboardProvider>
228
- );
229
- }
230
-
231
- render(
232
- <ScenarioManagementProvider defaultScreen={Menu}>
233
- <App />
234
- </ScenarioManagementProvider>,
235
- );
236
- ```
237
-
238
- ---
239
-
42
+ <details>
43
+ <summary><b>keyboard/</b>Architecture &amp; API index</summary>
44
+
45
+ - [README](docs/keyboard/README.md) — Architecture &amp; 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 &amp; API index</summary>
65
+
66
+ - [README](docs/screen/README.md) — Architecture &amp; 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 &amp; API index</summary>
82
+
83
+ - [README](docs/event/README.md) — Architecture &amp; 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
- I admit I thought about some things too quickly at the beginning of the project.
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: errors[name],
55
+ error,
55
56
  onChange,
56
57
  focusId: effectiveFocusId,
57
- }), [resolvedValue, errors[name], onChange, effectiveFocusId]);
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.FunctionComponentElement<{
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;