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,163 @@
|
|
|
1
|
+
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { FormContext } from './context.js';
|
|
3
|
+
import { useKeyboard } from '../../keyboard/hook.js';
|
|
4
|
+
/**
|
|
5
|
+
* Form container — manages field values, validation, and submission.
|
|
6
|
+
*
|
|
7
|
+
* Integrates with ink-cartridge's keyboard and focus system:
|
|
8
|
+
* - **Ctrl+Enter** triggers `submitForm()` (avoids conflicting with TextInput's Enter)
|
|
9
|
+
* - On validation failure, calls `focusSet()` on the first error field
|
|
10
|
+
*
|
|
11
|
+
* Must be used inside a `<KeyboardProvider>` for keyboard bindings to work.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Form
|
|
16
|
+
* initialValues={{ email: '', password: '' }}
|
|
17
|
+
* onSubmit={(values) => handleLogin(values)}
|
|
18
|
+
* onError={(errors) => console.log('Validation failed', errors)}
|
|
19
|
+
* >
|
|
20
|
+
* <Field name="email" rules={[required, isEmail]}>
|
|
21
|
+
* {({ value, onChange, error, focusId }) => (
|
|
22
|
+
* <TextInput focusId={focusId} value={value} onChange={onChange} />
|
|
23
|
+
* )}
|
|
24
|
+
* </Field>
|
|
25
|
+
* <Field name="password" rules={[required]}>
|
|
26
|
+
* {({ value, onChange, error, focusId }) => (
|
|
27
|
+
* <TextInput focusId={focusId} value={value} onChange={onChange} mask="*" />
|
|
28
|
+
* )}
|
|
29
|
+
* </Field>
|
|
30
|
+
* </Form>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function Form({ children, onSubmit, onError, initialValues = {}, submitRef }) {
|
|
34
|
+
const [values, setValues] = useState(initialValues);
|
|
35
|
+
const [errors, setErrors] = useState({});
|
|
36
|
+
// Holds the focusId of the first error field; consumed by useEffect to
|
|
37
|
+
// call focusSet() outside of render.
|
|
38
|
+
const [focusedErrorField, setFocusedErrorField] = useState(null);
|
|
39
|
+
// Refs to avoid stale closures in callbacks
|
|
40
|
+
const valuesRef = useRef(values);
|
|
41
|
+
valuesRef.current = values;
|
|
42
|
+
const rulesRef = useRef({});
|
|
43
|
+
const focusIdRef = useRef({});
|
|
44
|
+
const { focusSet, boundKeyboard } = useKeyboard();
|
|
45
|
+
/**
|
|
46
|
+
* Register a field's validation rules and default value.
|
|
47
|
+
*
|
|
48
|
+
* Does NOT set the default if the field already has a value
|
|
49
|
+
* (e.g. set via `initialValues`).
|
|
50
|
+
*/
|
|
51
|
+
const registerField = useCallback((name, defaultValue, rules) => {
|
|
52
|
+
if (rules)
|
|
53
|
+
rulesRef.current[name] = rules;
|
|
54
|
+
if (defaultValue !== undefined && !(name in valuesRef.current)) {
|
|
55
|
+
setValues((prev) => {
|
|
56
|
+
if (name in prev)
|
|
57
|
+
return prev;
|
|
58
|
+
return { ...prev, [name]: defaultValue };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
/** Unregister a field's rules and focusId mapping on unmount. */
|
|
63
|
+
const unregisterField = useCallback((name) => {
|
|
64
|
+
delete rulesRef.current[name];
|
|
65
|
+
delete focusIdRef.current[name];
|
|
66
|
+
}, []);
|
|
67
|
+
/**
|
|
68
|
+
* Update a single field's value and clear its validation error.
|
|
69
|
+
*
|
|
70
|
+
* Errors are cleared optimistically so the user sees immediate
|
|
71
|
+
* feedback that the error is being addressed.
|
|
72
|
+
*/
|
|
73
|
+
const setFieldValue = useCallback((name, value) => {
|
|
74
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
75
|
+
setErrors((prev) => {
|
|
76
|
+
if (prev[name] === undefined)
|
|
77
|
+
return prev;
|
|
78
|
+
const next = { ...prev };
|
|
79
|
+
delete next[name];
|
|
80
|
+
return next;
|
|
81
|
+
});
|
|
82
|
+
}, []);
|
|
83
|
+
/**
|
|
84
|
+
* Run all registered validation rules and return the errors record.
|
|
85
|
+
* For each field, rules are evaluated in order — the first error wins.
|
|
86
|
+
*/
|
|
87
|
+
const validate = useCallback(() => {
|
|
88
|
+
const currentValues = valuesRef.current;
|
|
89
|
+
const newErrors = {};
|
|
90
|
+
for (const [name, validators] of Object.entries(rulesRef.current)) {
|
|
91
|
+
for (const fn of validators) {
|
|
92
|
+
const error = fn(currentValues[name], currentValues);
|
|
93
|
+
if (error) {
|
|
94
|
+
newErrors[name] = error;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return newErrors;
|
|
100
|
+
}, []);
|
|
101
|
+
/**
|
|
102
|
+
* Validate all fields, then either:
|
|
103
|
+
* 1. On error — persist errors, call `onError`, focus first error field
|
|
104
|
+
* 2. On success — call `onSubmit` with current values
|
|
105
|
+
*/
|
|
106
|
+
const submitForm = useCallback(() => {
|
|
107
|
+
const validationErrors = validate();
|
|
108
|
+
setErrors(validationErrors);
|
|
109
|
+
const hasErrors = Object.values(validationErrors).some(Boolean);
|
|
110
|
+
if (hasErrors) {
|
|
111
|
+
onError?.(validationErrors);
|
|
112
|
+
const firstError = Object.keys(validationErrors)[0];
|
|
113
|
+
const targetFocusId = focusIdRef.current[firstError];
|
|
114
|
+
if (targetFocusId) {
|
|
115
|
+
setFocusedErrorField(targetFocusId);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
onSubmit(valuesRef.current);
|
|
120
|
+
}, [validate, onSubmit, onError]);
|
|
121
|
+
// Stable ref so the boundKeyboard effect always calls the latest submitForm
|
|
122
|
+
const submitFormRef = useRef(submitForm);
|
|
123
|
+
submitFormRef.current = submitForm;
|
|
124
|
+
// Defer focusSet to avoid calling it inside render
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (focusedErrorField) {
|
|
127
|
+
focusSet(focusedErrorField);
|
|
128
|
+
setFocusedErrorField(null);
|
|
129
|
+
}
|
|
130
|
+
}, [focusedErrorField, focusSet]);
|
|
131
|
+
// Expose submitForm via submitRef prop
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (submitRef)
|
|
134
|
+
submitRef.current = submitForm;
|
|
135
|
+
}, [submitForm, submitRef]);
|
|
136
|
+
// Bind Ctrl+S to submit via screen-level binding. Focus-target bindings
|
|
137
|
+
// (TextInput's '*' wildcard) do NOT match Ctrl+S (isNormalCharacter
|
|
138
|
+
// returns false when ctrl is set), so the event safely passes through
|
|
139
|
+
// to the screen level without needing globalKeys.
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const unBind = boundKeyboard(['ctrl+s'], () => submitFormRef.current());
|
|
142
|
+
return () => unBind();
|
|
143
|
+
}, [boundKeyboard]);
|
|
144
|
+
/**
|
|
145
|
+
* Wrapper around registerField that also stores the field's focusId
|
|
146
|
+
* for error focus navigation.
|
|
147
|
+
*/
|
|
148
|
+
const registerFieldWithFocus = useCallback((name, defaultValue, rules, focusId) => {
|
|
149
|
+
registerField(name, defaultValue, rules);
|
|
150
|
+
if (focusId) {
|
|
151
|
+
focusIdRef.current[name] = focusId;
|
|
152
|
+
}
|
|
153
|
+
}, [registerField]);
|
|
154
|
+
const contextValue = {
|
|
155
|
+
values,
|
|
156
|
+
errors,
|
|
157
|
+
setFieldValue,
|
|
158
|
+
registerField: registerFieldWithFocus,
|
|
159
|
+
unregisterField,
|
|
160
|
+
submitForm,
|
|
161
|
+
};
|
|
162
|
+
return (React.createElement(FormContext.Provider, { value: contextValue }, children));
|
|
163
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FormContextValue } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* React Context that carries form state ({@link FormContextValue}).
|
|
4
|
+
* Provided by {@link Form} and consumed by {@link Field}.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare const FormContext: import("react").Context<FormContextValue | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Access the form context from within a {@link Field} component.
|
|
11
|
+
*
|
|
12
|
+
* @throws If called outside of a {@link Form} provider.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useFormContext(): FormContextValue;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* React Context that carries form state ({@link FormContextValue}).
|
|
4
|
+
* Provided by {@link Form} and consumed by {@link Field}.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export const FormContext = createContext(null);
|
|
9
|
+
/**
|
|
10
|
+
* Access the form context from within a {@link Field} component.
|
|
11
|
+
*
|
|
12
|
+
* @throws If called outside of a {@link Form} provider.
|
|
13
|
+
*/
|
|
14
|
+
export function useFormContext() {
|
|
15
|
+
const ctx = useContext(FormContext);
|
|
16
|
+
if (!ctx) {
|
|
17
|
+
throw new Error('[Ink-Cartridge] useFormContext() must be used inside a <Form> component.');
|
|
18
|
+
}
|
|
19
|
+
return ctx;
|
|
20
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation function.
|
|
3
|
+
* Takes the field's current value and all form values, returns an error
|
|
4
|
+
* message string if invalid, or `undefined` if valid.
|
|
5
|
+
*
|
|
6
|
+
* @param value The current value of the field being validated.
|
|
7
|
+
* @param values All form values (useful for cross-field validation).
|
|
8
|
+
* @returns Error message, or undefined if valid.
|
|
9
|
+
*/
|
|
10
|
+
export type Validator = (value: any, values: Record<string, any>) => string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Value provided by {@link Form} via {@link FormContext}.
|
|
13
|
+
*/
|
|
14
|
+
export interface FormContextValue {
|
|
15
|
+
/** Current values of all registered fields. */
|
|
16
|
+
values: Record<string, any>;
|
|
17
|
+
/** Current validation errors. `undefined` means the field is valid. */
|
|
18
|
+
errors: Record<string, string | undefined>;
|
|
19
|
+
/**
|
|
20
|
+
* Update a single field's value and clear its error.
|
|
21
|
+
* Called by {@link Field} when the user changes input.
|
|
22
|
+
*/
|
|
23
|
+
setFieldValue: (name: string, value: any) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Register a field with the form.
|
|
26
|
+
* Called by {@link Field} on mount.
|
|
27
|
+
*
|
|
28
|
+
* @param name Field name (unique within the form).
|
|
29
|
+
* @param defaultValue Initial value if the form has no value for this name yet.
|
|
30
|
+
* @param rules Validation rules applied on submit.
|
|
31
|
+
* @param focusId The focus target ID for this field's input, used by Form
|
|
32
|
+
* to focus the first error field on submit failure.
|
|
33
|
+
*/
|
|
34
|
+
registerField: (name: string, defaultValue?: any, rules?: Validator[], focusId?: string) => void;
|
|
35
|
+
/** Unregister a field on unmount. */
|
|
36
|
+
unregisterField: (name: string) => void;
|
|
37
|
+
/** Trigger validation and submit. Bound to Enter key by Form. */
|
|
38
|
+
submitForm: () => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Props for the {@link Form} component.
|
|
42
|
+
*/
|
|
43
|
+
export interface FormProps {
|
|
44
|
+
/** Form body containing {@link Field} components. */
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* Called when validation passes and the form is submitted.
|
|
48
|
+
* Receives all field values as a record.
|
|
49
|
+
*/
|
|
50
|
+
onSubmit: (values: Record<string, any>) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Called when validation fails on submit.
|
|
53
|
+
* Receives the errors record (field name → error message).
|
|
54
|
+
*/
|
|
55
|
+
onError?: (errors: Record<string, string | undefined>) => void;
|
|
56
|
+
/** Initial values for fields. Overrides Field-level `defaultValue`. */
|
|
57
|
+
initialValues?: Record<string, any>;
|
|
58
|
+
/**
|
|
59
|
+
* Mutable ref that receives the `submitForm` function after mount.
|
|
60
|
+
* Useful for programmatic submission in tests or non-keyboard flows.
|
|
61
|
+
*/
|
|
62
|
+
submitRef?: React.MutableRefObject<(() => void) | undefined>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Props passed to the render child of {@link Field}.
|
|
66
|
+
*/
|
|
67
|
+
export interface FieldRenderProps {
|
|
68
|
+
/** Current value of this field (from Form state). */
|
|
69
|
+
value: any;
|
|
70
|
+
/** Current validation error for this field, or undefined. */
|
|
71
|
+
error: string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Call this when the user changes input.
|
|
74
|
+
* Handles calling Form's `setFieldValue` and clearing errors.
|
|
75
|
+
*/
|
|
76
|
+
onChange: (value: any) => void;
|
|
77
|
+
/**
|
|
78
|
+
* The focus target ID for this field's input component.
|
|
79
|
+
* Pass to the child component's `focusId` prop.
|
|
80
|
+
*/
|
|
81
|
+
focusId: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Props for the {@link Field} component.
|
|
85
|
+
*/
|
|
86
|
+
export interface FieldProps {
|
|
87
|
+
/** Unique field name. Used as the key in Form's `values` and `errors`. */
|
|
88
|
+
name: string;
|
|
89
|
+
/**
|
|
90
|
+
* Render function receiving {@link FieldRenderProps}.
|
|
91
|
+
* Returns the actual input component (TextInput, NumberInput, etc.).
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```tsx
|
|
95
|
+
* <Field name="email" rules={[required]}>
|
|
96
|
+
* {({ value, onChange, error, focusId }) => (
|
|
97
|
+
* <TextInput focusId={focusId} value={value} onChange={onChange} />
|
|
98
|
+
* )}
|
|
99
|
+
* </Field>
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
children: (fieldProps: FieldRenderProps) => React.ReactNode;
|
|
103
|
+
/** Validation rules evaluated on submit, in order. First match wins. */
|
|
104
|
+
rules?: Validator[];
|
|
105
|
+
/**
|
|
106
|
+
* Default value for this field, used only on initial mount if not
|
|
107
|
+
* already set via Form's `initialValues`.
|
|
108
|
+
*/
|
|
109
|
+
defaultValue?: any;
|
|
110
|
+
/**
|
|
111
|
+
* Override the auto-generated focusId (`${name}-field`).
|
|
112
|
+
* Useful when the same input name is used on different screens.
|
|
113
|
+
*/
|
|
114
|
+
focusId?: string;
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
export function KeyHint({ keys = [] }) {
|
|
4
|
+
return (React.createElement(Box, { gap: 2 }, keys.map(({ key, desc }) => (React.createElement(Box, { key: key },
|
|
5
|
+
React.createElement(Text, { color: "yellow" },
|
|
6
|
+
"[",
|
|
7
|
+
key,
|
|
8
|
+
"]"),
|
|
9
|
+
React.createElement(Text, { dimColor: true },
|
|
10
|
+
" ",
|
|
11
|
+
desc))))));
|
|
12
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { MultiSelectInputProps, Item } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Multi-select list component integrated with the ink-cartridge keyboard and
|
|
5
|
+
* focus system.
|
|
6
|
+
*
|
|
7
|
+
* Users navigate with arrow keys (or vim-style j/k), toggle items with
|
|
8
|
+
* Space, and confirm with Enter. Number keys 1-9 toggle the corresponding
|
|
9
|
+
* visible item directly.
|
|
10
|
+
*
|
|
11
|
+
* When the component is not the active focus target, its highlight is
|
|
12
|
+
* hidden but selection checkboxes remain visible.
|
|
13
|
+
*
|
|
14
|
+
* Supports both **controlled** (`selected` prop) and **uncontrolled**
|
|
15
|
+
* (`defaultSelected` prop) usage.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - The type of the value associated with each item.
|
|
18
|
+
* @typeParam I - The extended item type, must extend Item<T>.
|
|
19
|
+
* Defaults to Item<T>.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // Uncontrolled
|
|
24
|
+
* <MultiSelectInput
|
|
25
|
+
* focusId="colors"
|
|
26
|
+
* items={[
|
|
27
|
+
* { label: 'Red', value: 'red' },
|
|
28
|
+
* { label: 'Blue', value: 'blue' },
|
|
29
|
+
* ]}
|
|
30
|
+
* defaultSelected={['red']}
|
|
31
|
+
* onSubmit={(vals) => console.log('Confirmed:', vals)}
|
|
32
|
+
* />
|
|
33
|
+
*
|
|
34
|
+
* // Controlled
|
|
35
|
+
* const [sel, setSel] = useState<string[]>([]);
|
|
36
|
+
* <MultiSelectInput
|
|
37
|
+
* focusId="colors"
|
|
38
|
+
* items={items}
|
|
39
|
+
* selected={sel}
|
|
40
|
+
* onChange={setSel}
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
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.FunctionComponentElement<{
|
|
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>>;
|