@echojs-ecosystem/form 0.1.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 +86 -0
- package/dist/index.d.ts +416 -0
- package/dist/index.js +867 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# @echojs-ecosystem/form
|
|
4
|
+
|
|
5
|
+
**Signal-native forms — fields, validation, and Hyperdom bindings.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@echojs-ecosystem/form)
|
|
8
|
+
[](https://echojs.dev/docs/guides/forms)
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Framework-agnostic form primitives for EchoJS. **Fields are reactive objects** with `.value()`, `.meta()`, validation via [Standard Schema](https://github.com/standard-schema/standard-schema), and optional Hyperdom bindings.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **`createField`** — single value with validation & meta (touched, errors, dirty)
|
|
19
|
+
- **`createFieldArray`** — dynamic lists of fields
|
|
20
|
+
- **`createForm`** — grouped fields with submit & schema validation
|
|
21
|
+
- **`bindField`** — Hyperdom binding for `@echojs-ecosystem/ui` inputs
|
|
22
|
+
- **Persistence-ready** — extend fields with `@echojs-ecosystem/persist`
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @echojs-ecosystem/form @echojs-ecosystem/reactivity
|
|
28
|
+
# optional UI bindings
|
|
29
|
+
npm install @echojs-ecosystem/hyperdom @echojs-ecosystem/ui
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { createField, createForm } from "@echojs-ecosystem/form";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
|
|
38
|
+
const schema = z.object({
|
|
39
|
+
email: z.string().email(),
|
|
40
|
+
remember: z.boolean(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const loginForm = createForm(
|
|
44
|
+
{
|
|
45
|
+
email: createField(""),
|
|
46
|
+
remember: createField(false),
|
|
47
|
+
},
|
|
48
|
+
{ name: "LoginForm", validationSchema: schema },
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// In a view
|
|
52
|
+
loginForm.fields.email.value();
|
|
53
|
+
loginForm.fields.email.meta().errors;
|
|
54
|
+
await loginForm.submit();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Hyperdom + UI
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { bindField } from "@echojs-ecosystem/form";
|
|
61
|
+
|
|
62
|
+
bindField(loginForm.fields.email, { variant: "email" });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## API
|
|
66
|
+
|
|
67
|
+
| Export | Description |
|
|
68
|
+
|--------|-------------|
|
|
69
|
+
| `createField` | Single reactive field |
|
|
70
|
+
| `createFieldArray` | Array of fields |
|
|
71
|
+
| `createFieldSet` | Field grouping |
|
|
72
|
+
| `createForm` | Form with validation |
|
|
73
|
+
| `bindField` | Hyperdom control binding |
|
|
74
|
+
| `createFieldKit` | Reusable field factories |
|
|
75
|
+
|
|
76
|
+
## Related packages
|
|
77
|
+
|
|
78
|
+
| Package | Role |
|
|
79
|
+
|---------|------|
|
|
80
|
+
| [`@echojs-ecosystem/persist`](https://www.npmjs.com/package/@echojs-ecosystem/persist) | Save field/form state to storage |
|
|
81
|
+
| [`@echojs-ecosystem/ui`](https://www.npmjs.com/package/@echojs-ecosystem/ui) | Accessible inputs via `bindField` |
|
|
82
|
+
| [`@echojs-ecosystem/hyperdom`](https://www.npmjs.com/package/@echojs-ecosystem/hyperdom) | View layer |
|
|
83
|
+
|
|
84
|
+
## Documentation
|
|
85
|
+
|
|
86
|
+
[echojs.dev/docs/guides/forms](https://echojs.dev/docs/guides/forms)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { Signal, ReadValue } from '@echojs-ecosystem/reactivity';
|
|
2
|
+
import { PersistExtension, PersistExtensionResult } from '@echojs-ecosystem/persist';
|
|
3
|
+
import { ZodEnum } from 'zod';
|
|
4
|
+
|
|
5
|
+
/** Persist API поверх `createField`: `value` / `subscribe`; запись — через `field.set()`. */
|
|
6
|
+
type FieldPersistMethods<T> = {
|
|
7
|
+
value(): T;
|
|
8
|
+
subscribe(listener: (value: T, prevValue: T) => void): () => void;
|
|
9
|
+
extend<Snapshot = T>(extension: PersistExtension<T, Snapshot>): Field<T> & PersistExtensionResult<T, Snapshot>;
|
|
10
|
+
};
|
|
11
|
+
type FieldArrayPersistMethods<T> = {
|
|
12
|
+
value(): T[];
|
|
13
|
+
set(next: T[]): void;
|
|
14
|
+
subscribe(listener: (value: T[], prevValue: T[]) => void): () => void;
|
|
15
|
+
extend<Snapshot = T[]>(extension: PersistExtension<T[], Snapshot>): FieldArray<T> & PersistExtensionResult<T[], Snapshot>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type FieldValidator<T> = (value: ReadValue<T>) => string | null;
|
|
19
|
+
type StandardSchemaLike<T = unknown> = {
|
|
20
|
+
"~standard": {
|
|
21
|
+
readonly version: 1;
|
|
22
|
+
readonly vendor: string;
|
|
23
|
+
readonly validate: (value: unknown) => {
|
|
24
|
+
readonly value: T;
|
|
25
|
+
readonly issues?: undefined;
|
|
26
|
+
} | {
|
|
27
|
+
readonly issues: ReadonlyArray<{
|
|
28
|
+
readonly message: string;
|
|
29
|
+
readonly path?: ReadonlyArray<unknown>;
|
|
30
|
+
}>;
|
|
31
|
+
} | Promise<{
|
|
32
|
+
readonly value: T;
|
|
33
|
+
readonly issues?: undefined;
|
|
34
|
+
} | {
|
|
35
|
+
readonly issues: ReadonlyArray<{
|
|
36
|
+
readonly message: string;
|
|
37
|
+
readonly path?: ReadonlyArray<unknown>;
|
|
38
|
+
}>;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
type StandardSchemaIssue = {
|
|
43
|
+
message: string;
|
|
44
|
+
path?: readonly (string | number | symbol)[];
|
|
45
|
+
};
|
|
46
|
+
type FieldMeta = {
|
|
47
|
+
dirty: boolean;
|
|
48
|
+
touched: boolean;
|
|
49
|
+
focused: boolean;
|
|
50
|
+
errors: readonly string[];
|
|
51
|
+
};
|
|
52
|
+
type FieldBinding = {
|
|
53
|
+
/** Text-like inputs: update value on input/change without forcing controlled `value:` on every render. */
|
|
54
|
+
onInputText: (e: {
|
|
55
|
+
currentTarget: HTMLInputElement | HTMLTextAreaElement;
|
|
56
|
+
}) => void;
|
|
57
|
+
onChangeText: (e: {
|
|
58
|
+
currentTarget: HTMLInputElement | HTMLTextAreaElement;
|
|
59
|
+
}) => void;
|
|
60
|
+
onFocus: () => void;
|
|
61
|
+
onBlur: () => void;
|
|
62
|
+
};
|
|
63
|
+
type FieldValidationMode = "manual" | "onChange" | "onBlur";
|
|
64
|
+
type FormValidationMode = "manual" | "onChange" | "onBlur" | "onFocus" | "all";
|
|
65
|
+
type FieldCore<T> = {
|
|
66
|
+
$value: Signal<T>;
|
|
67
|
+
$meta: Signal<FieldMeta>;
|
|
68
|
+
/** Shortcut for `$value.value()` — use in views and `bindField`. */
|
|
69
|
+
value: () => ReadValue<T>;
|
|
70
|
+
/** Shortcut for `$meta.value()`. */
|
|
71
|
+
meta: () => FieldMeta;
|
|
72
|
+
/** Same handlers as `bind()` — for Hyperdom `bindField` and custom inputs. */
|
|
73
|
+
handlers: FieldBinding;
|
|
74
|
+
set: (next: T) => void;
|
|
75
|
+
reset: (next?: T) => void;
|
|
76
|
+
focus: () => void;
|
|
77
|
+
blur: () => void;
|
|
78
|
+
touch: () => void;
|
|
79
|
+
/** @deprecated Prefer `.handlers` — kept for backward compatibility. */
|
|
80
|
+
bind: () => FieldBinding;
|
|
81
|
+
validate: () => string[];
|
|
82
|
+
validateAsync: () => Promise<string[]>;
|
|
83
|
+
clearErrors: () => void;
|
|
84
|
+
};
|
|
85
|
+
type Field<T> = FieldCore<T> & FieldPersistMethods<T>;
|
|
86
|
+
type FieldArrayCore<Item> = {
|
|
87
|
+
$items: Signal<Item[]>;
|
|
88
|
+
append: (item: Item) => void;
|
|
89
|
+
prepend: (item: Item) => void;
|
|
90
|
+
removeAt: (index: number) => void;
|
|
91
|
+
move: (from: number, to: number) => void;
|
|
92
|
+
updateAt: (index: number, fn: (prev: Item) => Item) => void;
|
|
93
|
+
replace: (next: Item[]) => void;
|
|
94
|
+
reset: () => void;
|
|
95
|
+
};
|
|
96
|
+
type FieldArray<Item> = FieldArrayCore<Item> & FieldArrayPersistMethods<Item>;
|
|
97
|
+
/** Операции над вложенными `FieldArray`, см. `defineNestedFieldArrayOps`. */
|
|
98
|
+
type NestedFieldArrayOps<Row extends Record<string, unknown> = Record<string, unknown>> = {
|
|
99
|
+
append: (item?: Row) => void;
|
|
100
|
+
removeAt: (index: number) => void;
|
|
101
|
+
remove: (index: number) => void;
|
|
102
|
+
at: (index: number) => Record<string, NestedFieldArrayOps> | undefined;
|
|
103
|
+
};
|
|
104
|
+
type FieldSet<Shape extends Record<string, any>> = {
|
|
105
|
+
fields: Shape;
|
|
106
|
+
validate: () => Record<string, string[]>;
|
|
107
|
+
reset: () => void;
|
|
108
|
+
};
|
|
109
|
+
type FormSubmitResult<T> = {
|
|
110
|
+
ok: true;
|
|
111
|
+
value: T;
|
|
112
|
+
} | {
|
|
113
|
+
ok: false;
|
|
114
|
+
errors: Record<string, unknown>;
|
|
115
|
+
};
|
|
116
|
+
type Form<TValue, TFields extends Record<string, any>, TArrayActions extends Record<string, unknown> = {}> = {
|
|
117
|
+
/** Имя формы из `createForm(fields, { name, ... })`. */
|
|
118
|
+
displayName: string;
|
|
119
|
+
fields: TFields;
|
|
120
|
+
$submitting: Signal<boolean>;
|
|
121
|
+
$submitCount: Signal<number>;
|
|
122
|
+
$errors: Signal<Record<string, unknown> | undefined>;
|
|
123
|
+
$schemaErrors: Signal<Record<string, string[]> | undefined>;
|
|
124
|
+
/** Фабрики строк и append/remove, объявленные в `createForm({ arrayActions })`. */
|
|
125
|
+
arrayActions: TArrayActions;
|
|
126
|
+
validate: () => Record<string, unknown>;
|
|
127
|
+
validateAsync: () => Promise<Record<string, unknown>>;
|
|
128
|
+
reset: () => void;
|
|
129
|
+
/** Подмешать значения после создания формы (тот же формат, что у `defaultAsyncValues`). */
|
|
130
|
+
hydrate: (partial: Partial<TValue>) => void;
|
|
131
|
+
submit: (handler: (value: TValue) => void | Promise<void>) => Promise<FormSubmitResult<TValue>>;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Одно поле формы: значение и мета в сигналах, биндинг к DOM без встроенной схемы.
|
|
136
|
+
* Валидацию на submit задавайте через `createForm(..., { schema | validate })`.
|
|
137
|
+
*/
|
|
138
|
+
declare const createField: <T>(initial: T) => Field<T>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Creates a dynamic array helper for form state (append/remove/move/update).
|
|
142
|
+
*
|
|
143
|
+
* This primitive only manages the array itself; compose it with fields/fieldSets for rich models.
|
|
144
|
+
*/
|
|
145
|
+
declare const createFieldArray: <Item>(initial?: Item[]) => FieldArray<Item>;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Конфиг вложенных `createFieldArray`: на каждом уровне — фабрика строки и при необходимости дочерние массивы по ключам строки.
|
|
149
|
+
*/
|
|
150
|
+
type FieldArrayBranchConfig<Row extends Record<string, unknown>> = {
|
|
151
|
+
newRow: () => Row;
|
|
152
|
+
children?: Partial<{
|
|
153
|
+
[K in keyof Row]: Row[K] extends FieldArray<infer Child> ? Child extends Record<string, unknown> ? FieldArrayBranchConfig<Child> : FieldArrayBranchConfig<Record<string, never>> : never;
|
|
154
|
+
}>;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Строит цепочку операций над вложенными `FieldArray` по конфигу (фабрики строк + дерево дочерних ключей).
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* const d = form.nestedArrays!.departments;
|
|
162
|
+
* d.append();
|
|
163
|
+
* d.at(0)?.employees.append();
|
|
164
|
+
* d.at(0)?.employees.at(0)?.tickets.removeAt(1);
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
declare const defineNestedFieldArrayOps: <Row extends Record<string, unknown>>(fieldArray: FieldArray<Row>, cfg: FieldArrayBranchConfig<Row>) => NestedFieldArrayOps<Row>;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Groups fields (or nested fieldSets) into a single unit.
|
|
171
|
+
*
|
|
172
|
+
* This version is intentionally simple: it calls `validate()`/`reset()` on children when present.
|
|
173
|
+
*/
|
|
174
|
+
declare const createFieldSet: <Shape extends Record<string, any>>(fields: Shape) => FieldSet<Shape>;
|
|
175
|
+
|
|
176
|
+
type CreateFormOptions<TValue, TFields extends Record<string, any>, TArrayActions extends Record<string, unknown> = {}> = {
|
|
177
|
+
/** Имя формы (отладка, логирование). */
|
|
178
|
+
name: string;
|
|
179
|
+
/**
|
|
180
|
+
* Снимок значения для `submit` / `validationSchema`.
|
|
181
|
+
*
|
|
182
|
+
* Если не указан, используется {@link collectFormValueFromFields}: структура полей должна
|
|
183
|
+
* совпадать с `TValue` (см. JSDoc у `collectFormValueFromFields`). Иначе передайте свою функцию.
|
|
184
|
+
*/
|
|
185
|
+
validate?: (fields: TFields) => Record<string, unknown>;
|
|
186
|
+
validateAsync?: (fields: TFields) => Promise<Record<string, unknown>>;
|
|
187
|
+
/** Back-compat alias for `validationSchema`. */
|
|
188
|
+
schema?: StandardSchemaLike<TValue>;
|
|
189
|
+
/** Общая Standard Schema (Zod и др.) при submit: ошибки попадают в `errors.$schema` (плоская карта путей). */
|
|
190
|
+
validationSchema?: StandardSchemaLike<TValue>;
|
|
191
|
+
/**
|
|
192
|
+
* When to auto-run validation (incl. `validationSchema`) and keep `$errors/$schemaErrors` updated.
|
|
193
|
+
*
|
|
194
|
+
* Default: "manual" (only `submit()` triggers validation).
|
|
195
|
+
*/
|
|
196
|
+
validationOn?: FormValidationMode;
|
|
197
|
+
/**
|
|
198
|
+
* Override how a submit "value snapshot" is built from the field tree.
|
|
199
|
+
*
|
|
200
|
+
* By default we use `collectFormValueFromFields(...)`.
|
|
201
|
+
*/
|
|
202
|
+
getValue?: () => TValue;
|
|
203
|
+
/**
|
|
204
|
+
* Начальные значения: записываются в дерево полей через `hydrateFormFields` сразу после создания формы.
|
|
205
|
+
*/
|
|
206
|
+
defaultValues?: Partial<TValue>;
|
|
207
|
+
/**
|
|
208
|
+
* Асинхронная подгрузка значений (частичный объект). Применяется через `hydrateFormFields`.
|
|
209
|
+
*/
|
|
210
|
+
defaultAsyncValues?: () => Promise<Partial<TValue>>;
|
|
211
|
+
/**
|
|
212
|
+
* Фабрики строк для `FieldArray` при гидратации `defaultValues` / `defaultAsyncValues`.
|
|
213
|
+
*/
|
|
214
|
+
fieldArrayFactories?: Record<string, () => unknown>;
|
|
215
|
+
/**
|
|
216
|
+
* Фабрики строк (`createTicket`, …) и append/remove через `arrayGenerator` из `@echojs-ecosystem/form`.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```ts
|
|
220
|
+
* arrayActions: (form) => {
|
|
221
|
+
* const createRow = () => ({ title: createField("") });
|
|
222
|
+
* return {
|
|
223
|
+
* createRow,
|
|
224
|
+
* appendRow: arrayGenerator.append(form, createRow, "items"),
|
|
225
|
+
* removeRow: arrayGenerator.remove(form, "items"),
|
|
226
|
+
* };
|
|
227
|
+
* },
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
arrayActions?: (form: Form<TValue, TFields, TArrayActions>) => TArrayActions;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Обёртка над деревом полей: submit, валидация полей (`createField` / `createFieldArray`) и опционально схема на submit.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* const form = createForm(
|
|
238
|
+
* { title: createField(""), items: createFieldArray([]) },
|
|
239
|
+
* { name: "MyForm", validationSchema: z.object({ ... }), defaultValues: { title: "a" } },
|
|
240
|
+
* );
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
declare function createForm<TValue, TFields extends Record<string, any>, TArrayActions extends Record<string, unknown> = {}>(fields: TFields, opts: CreateFormOptions<TValue, TFields, TArrayActions>): Form<TValue, TFields, TArrayActions>;
|
|
244
|
+
|
|
245
|
+
type StrKey<T> = Extract<keyof T, string>;
|
|
246
|
+
type UnwrapFieldSet<T> = T extends FieldSet<infer Shape> ? Shape : T extends {
|
|
247
|
+
fields: infer F;
|
|
248
|
+
} ? F : T;
|
|
249
|
+
type RowShape<T> = T extends FieldArray<infer Row> ? UnwrapFieldSet<Row> : never;
|
|
250
|
+
type NextRecord<T> = T extends Record<string, any> ? T : never;
|
|
251
|
+
type Join<P extends string, K extends string> = P extends "" ? K : `${P}.${K}`;
|
|
252
|
+
type ArrayPathsFromRecord<TRecord, Prefix extends string = ""> = TRecord extends Record<string, any> ? {
|
|
253
|
+
[K in StrKey<TRecord>]: TRecord[K] extends FieldArray<any> ? Join<Prefix, K> | ArrayPathsFromRecord<NextRecord<RowShape<TRecord[K]>>, Join<Prefix, K>> : never;
|
|
254
|
+
}[StrKey<TRecord>] : never;
|
|
255
|
+
/**
|
|
256
|
+
* Union of dot-separated paths that resolve to a `FieldArray` inside a field tree.
|
|
257
|
+
*
|
|
258
|
+
* Example: `"departments" | "departments.employees" | "departments.employees.tickets"`.
|
|
259
|
+
*/
|
|
260
|
+
type ArrayPath<TFields extends Record<string, any>> = ArrayPathsFromRecord<TFields>;
|
|
261
|
+
|
|
262
|
+
/** Утилиты для append/remove по вложенным `FieldArray` (пути — `ArrayPath`). */
|
|
263
|
+
declare const arrayGenerator: {
|
|
264
|
+
append<TValue, TFields extends Record<string, any>, Row>(form: Form<TValue, TFields, any>, makeRow: () => Row, path: ArrayPath<TFields>): (...indices: number[]) => void;
|
|
265
|
+
remove<TValue, TFields extends Record<string, any>>(form: Form<TValue, TFields, any>, path: ArrayPath<TFields>): (...indices: number[]) => void;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Generates an append function for a nested FieldArray path.
|
|
270
|
+
*
|
|
271
|
+
* `path` is a dot-separated chain of array names, where the last segment is the target array.
|
|
272
|
+
* Example: "departments.employees.tickets" => appendTicket(deptIndex, employeeIndex)
|
|
273
|
+
*
|
|
274
|
+
* The returned function expects N indices where N = segments.length - 1.
|
|
275
|
+
*/
|
|
276
|
+
declare const generateAppendToArray: <TValue, TFields extends Record<string, any>, Row>(form: Form<TValue, TFields, any>, makeRow: () => Row, path: ArrayPath<TFields>) => (...indices: number[]) => void;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Generates a remove function for a nested FieldArray path.
|
|
280
|
+
*
|
|
281
|
+
* `path` is a dot-separated chain of array names, where the last segment is the target array.
|
|
282
|
+
* Example: "departments.employees.tickets" => removeTicket(deptIndex, employeeIndex, ticketIndex)
|
|
283
|
+
*
|
|
284
|
+
* The returned function expects N indices where N = segments.length.
|
|
285
|
+
*/
|
|
286
|
+
declare const generateRemoveFromArray: <TValue, TFields extends Record<string, any>>(form: Form<TValue, TFields, any>, path: ArrayPath<TFields>) => (...indices: number[]) => void;
|
|
287
|
+
|
|
288
|
+
type FieldKitFieldOpts<T> = {
|
|
289
|
+
validationMode?: FieldValidationMode;
|
|
290
|
+
parseDOMValue?: (raw: string) => T;
|
|
291
|
+
parseDOMChecked?: (checked: boolean) => T;
|
|
292
|
+
validate?: FieldValidator<T>[];
|
|
293
|
+
schemaIssuePrefix?: string;
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Helpers to build fields from `defaultValues` / `defaultAsyncValues` without repeating
|
|
297
|
+
* `seed.foo ?? ""` for every key. Pass as the second argument to the `fields` factory.
|
|
298
|
+
*/
|
|
299
|
+
type FieldKit<TSeed extends Record<string, unknown>> = {
|
|
300
|
+
str: (key: string, schema: StandardSchemaLike<string>, parent?: object, extra?: FieldKitFieldOpts<string>) => Field<string>;
|
|
301
|
+
num: (key: string, schema: StandardSchemaLike<number>, parent?: object, extra?: FieldKitFieldOpts<number>) => Field<number>;
|
|
302
|
+
bool: (key: string, schema: StandardSchemaLike<boolean>, parent?: object, extra?: FieldKitFieldOpts<boolean>) => Field<boolean>;
|
|
303
|
+
/**
|
|
304
|
+
* `z.enum` / string union fields: reads `parent[key]` when it matches an enum option, else `fallback`.
|
|
305
|
+
*/
|
|
306
|
+
pick: <T extends string>(key: string, schema: ZodEnum<[T, ...T[]]>, parent: object | undefined, fallback: T, extra?: FieldKitFieldOpts<T>) => Field<T>;
|
|
307
|
+
list: <Row>(key: string, rowFactory: (rowSeed: Record<string, unknown>, index: number) => Row, parent?: object) => FieldArray<Row>;
|
|
308
|
+
};
|
|
309
|
+
declare const createFieldKit: <TSeed extends Record<string, unknown>>(seed: TSeed) => FieldKit<TSeed>;
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Собирает «сырое» значение формы из дерева примитивов (`Field` / `FieldSet` / `FieldArray`).
|
|
313
|
+
*
|
|
314
|
+
* Соглашение по форме:
|
|
315
|
+
* - ключи объекта `fields` совпадают с ключами итогового JSON;
|
|
316
|
+
* - `FieldSet` сериализуется как **один уровень** его `fields` (без ключа `fields` в выходе);
|
|
317
|
+
* - `FieldArray` → массив, каждый элемент — объект строки (рекурсивно).
|
|
318
|
+
*
|
|
319
|
+
* Если дерево полей **не совпадает** со схемой (например, `credentials.email` в UI, а в Zod плоский `email`),
|
|
320
|
+
* задайте свой `getValue` во втором аргументе `createForm(fields, { getValue })`.
|
|
321
|
+
*/
|
|
322
|
+
declare const collectFormValueFromFields: (node: unknown) => unknown;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Рекурсивно записывает `partial` в дерево полей (Field / FieldSet / FieldArray).
|
|
326
|
+
*
|
|
327
|
+
* Для расширения массива нужен `rowFactory` из второго аргумента `createForm(fields, { fieldArrayFactories })`.
|
|
328
|
+
*/
|
|
329
|
+
declare const hydrateFormFields: (fields: Record<string, unknown>, partial: unknown, fieldArrayFactories?: Record<string, () => unknown>) => void;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Схлопывает дерево результатов `deepValidateSync/Async` в плоскую карту `путь → сообщения`.
|
|
333
|
+
*
|
|
334
|
+
* Пути строятся через точку; индексы массивов — как числовые сегменты (`tags.0.tag`).
|
|
335
|
+
*/
|
|
336
|
+
declare const flattenValidationErrors: (node: unknown, prefix?: string) => Record<string, string[]>;
|
|
337
|
+
/**
|
|
338
|
+
* Оставляет только те ошибки общей `validationSchema`, которые **не перекрыты** ошибками полей.
|
|
339
|
+
*
|
|
340
|
+
* Правило: если у поля есть ошибка по пути `fk`, то отбрасываем любую ошибку схемы по пути `sk`, если
|
|
341
|
+
* `fk === sk`, либо один путь является префиксом другого (вложенность / массивы).
|
|
342
|
+
*/
|
|
343
|
+
declare const filterRootSchemaErrorsDeferredToFieldErrors: (fieldFlat: Record<string, string[]>, schemaFlat: Record<string, string[]>) => Record<string, string[]>;
|
|
344
|
+
|
|
345
|
+
declare const normalizeStandardSchemaPathSegments: (path?: ReadonlyArray<unknown> | undefined) => string[];
|
|
346
|
+
/**
|
|
347
|
+
* Normalize common Standard Schema validation result shapes into a flat `{ issues[] }`.
|
|
348
|
+
*
|
|
349
|
+
* Covers:
|
|
350
|
+
* - `{ issues: [...] }`
|
|
351
|
+
* - `{ errors: [...] }` (some wrappers)
|
|
352
|
+
* - `{ value }` → no issues (success-ish)
|
|
353
|
+
*/
|
|
354
|
+
declare const normalizeStandardSchemaIssues: (result: unknown) => StandardSchemaIssue[];
|
|
355
|
+
/**
|
|
356
|
+
* Runs `StandardSchema`'s `.validate()` and normalizes failures into `{ issues }`.
|
|
357
|
+
*
|
|
358
|
+
* Handles Promise results automatically.
|
|
359
|
+
*/
|
|
360
|
+
declare const standardSchemaIssuesForUnknown: (schema: StandardSchemaLike<unknown>, value: unknown) => Promise<{
|
|
361
|
+
ok: boolean;
|
|
362
|
+
issues: StandardSchemaIssue[];
|
|
363
|
+
}>;
|
|
364
|
+
/**
|
|
365
|
+
* Same as `standardSchemaIssuesForUnknown()`, but resolves immediately when the vendor returns sync results.
|
|
366
|
+
*
|
|
367
|
+
* Throws if validation returns a Promise (use `standardSchemaIssuesForUnknown()` / `validateAsync()` instead).
|
|
368
|
+
*/
|
|
369
|
+
declare const standardSchemaIssuesForUnknownSync: (schema: StandardSchemaLike<unknown>, value: unknown) => {
|
|
370
|
+
ok: boolean;
|
|
371
|
+
issues: StandardSchemaIssue[];
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Turns Standard Schema-ish issues into a flat map keyed by dotted paths.
|
|
376
|
+
*
|
|
377
|
+
* Issues without paths are aggregated under `"$root"` (useful for form-level errors).
|
|
378
|
+
*/
|
|
379
|
+
declare const flattenFieldErrors: (issues: readonly StandardSchemaIssue[]) => Record<string, string[]>;
|
|
380
|
+
|
|
381
|
+
/** Поле формы для Hyperdom (`createField` / `form.fields.*`). */
|
|
382
|
+
type HyperdomFormFieldRef<T> = Field<T>;
|
|
383
|
+
type bindFieldOptions = {
|
|
384
|
+
variant: "text" | "email" | "password" | "search" | "url";
|
|
385
|
+
controlledValue?: boolean;
|
|
386
|
+
} | {
|
|
387
|
+
variant: "textarea";
|
|
388
|
+
controlledValue?: boolean;
|
|
389
|
+
} | {
|
|
390
|
+
variant: "checkbox";
|
|
391
|
+
} | {
|
|
392
|
+
variant: "select";
|
|
393
|
+
/** По умолчанию `true`: селект почти всегда должен быть controlled. */
|
|
394
|
+
controlledValue?: boolean;
|
|
395
|
+
} | {
|
|
396
|
+
variant: "number";
|
|
397
|
+
controlledValue?: boolean;
|
|
398
|
+
} | {
|
|
399
|
+
variant: "range";
|
|
400
|
+
min?: number;
|
|
401
|
+
max?: number;
|
|
402
|
+
step?: number;
|
|
403
|
+
controlledValue?: boolean;
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* Унифицированный биндинг поля `@echojs-ecosystem/form` к пропсам Hyperdom (`input`, `textarea`, `select`, …).
|
|
407
|
+
*
|
|
408
|
+
* Для списков внутри `List` рекомендуется `controlledValue: true` у текстовых вариантов, чтобы значения
|
|
409
|
+
* не терялись при пересоздании DOM.
|
|
410
|
+
*
|
|
411
|
+
* Возвращаемый тип намеренно широкий: Hyperdom различает обработчики `onChange` для `input` / `select` / `textarea`,
|
|
412
|
+
* а контроллер выбирается по `variant` в рантайме.
|
|
413
|
+
*/
|
|
414
|
+
declare const bindField: <T>(field: HyperdomFormFieldRef<T>, opts: bindFieldOptions) => any;
|
|
415
|
+
|
|
416
|
+
export { type ArrayPath, type CreateFormOptions, type Field, type FieldArray, type FieldArrayBranchConfig, type FieldBinding, type FieldKit, type FieldKitFieldOpts, type FieldMeta, type FieldSet, type FieldValidationMode, type FieldValidator, type Form, type FormSubmitResult, type HyperdomFormFieldRef, type NestedFieldArrayOps, type StandardSchemaLike, arrayGenerator, bindField, type bindFieldOptions, collectFormValueFromFields, createField, createFieldArray, createFieldKit, createFieldSet, createForm, defineNestedFieldArrayOps, filterRootSchemaErrorsDeferredToFieldErrors, flattenFieldErrors, flattenValidationErrors, generateAppendToArray, generateRemoveFromArray, hydrateFormFields, normalizeStandardSchemaIssues, normalizeStandardSchemaPathSegments, standardSchemaIssuesForUnknown, standardSchemaIssuesForUnknownSync };
|