@reformer/core 11.0.0-beta.7 → 11.0.0-beta.8
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 +7 -3
- package/dist/behaviors.js +211 -174
- package/dist/create-model-BBRHhHwK.js +30 -0
- package/dist/form/behaviors/collections.d.ts +14 -1
- package/dist/form/behaviors/context.d.ts +18 -2
- package/dist/form/behaviors/index.d.ts +3 -1
- package/dist/form/behaviors/internals.d.ts +0 -1
- package/dist/form/behaviors/schema-operators.d.ts +76 -0
- package/dist/form/behaviors/types.d.ts +15 -3
- package/dist/form/create-core-form.d.ts +5 -3
- package/dist/form/create-form.d.ts +28 -53
- package/dist/form/form-bundle.d.ts +136 -0
- package/dist/form/index.d.ts +6 -1
- package/dist/form/nodes/field-node.d.ts +2 -2
- package/dist/form/nodes/group-node.d.ts +1 -1
- package/dist/form/nodes/model-array-node.d.ts +10 -5
- package/dist/form/schema-controller.d.ts +137 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +22 -16
- package/dist/form/types/deep-schema.d.ts +2 -2
- package/dist/form/types/form-proxy.d.ts +1 -1
- package/dist/form/types/schema-node.d.ts +37 -12
- package/dist/form/validation/context.d.ts +6 -1
- package/dist/form/validation/index.d.ts +1 -1
- package/dist/form/validation/operators.d.ts +70 -8
- package/dist/form/validation/types.d.ts +2 -1
- package/dist/index.js +345 -254
- package/dist/model/index.d.ts +5 -0
- package/dist/model/model-nodes.d.ts +67 -4
- package/dist/model/model-signals-proxy.d.ts +8 -1
- package/dist/model/model-value-proxy.d.ts +76 -0
- package/dist/model/types.d.ts +7 -4
- package/dist/model-value-proxy-caTmAiUd.js +419 -0
- package/dist/model.js +19 -17
- package/dist/node-BeV_lV5i.js +100 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/platforms/react/index.d.ts +1 -0
- package/dist/strategy-DgzELWno.js +204 -0
- package/dist/validation.js +7 -6
- package/docs/llms/03-api-signatures.md +4 -4
- package/docs/llms/09-formschema.md +5 -5
- package/docs/llms/10-arrays.md +23 -4
- package/docs/llms/16-ui-components.md +2 -2
- package/docs/llms/18-conditional-fields.md +1 -1
- package/docs/llms/21-array-operations.md +26 -0
- package/docs/llms/28-submit-and-reset.md +3 -3
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/33-validation-strategy.md +2 -2
- package/docs/llms/35-unified-contract.md +246 -0
- package/llms-index.json +1 -1
- package/llms.txt +1148 -122
- package/package.json +1 -1
- package/dist/create-model-B8U-COv-.js +0 -103
- package/dist/model-signals-proxy-2vqS243J.js +0 -250
- package/dist/node-DGEFMTQu.js +0 -18
- package/dist/strategy-BXS_gBfz.js +0 -170
package/llms.txt
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
- 32-async-options-loading.md — async-options-loading
|
|
40
40
|
- 33-validation-strategy.md — useFormValidation — Единый выбор стратегии валидации
|
|
41
41
|
- 34-i18n.md — i18n — локаль, загрузка словарей и провайдер
|
|
42
|
+
- 35-unified-contract.md — Единый контракт формы — одна схема, одна сборка, одно поведение
|
|
42
43
|
- API Reference (auto-generated from JSDoc)
|
|
43
44
|
|
|
44
45
|
## 1. 1. API Reference
|
|
@@ -275,7 +276,7 @@ createModel<T extends object>(initial: T): FormModel<T>
|
|
|
275
276
|
// model.arrayField → ModelArray<Item> (push/removeAt/insertAt/move/swap/clear/at/map/length)
|
|
276
277
|
|
|
277
278
|
// Форма (ноды поверх сигналов модели)
|
|
278
|
-
|
|
279
|
+
createFormFromModel<T>({ model, schema, behavior? }): FormProxy<T>
|
|
279
280
|
// form.<field> → FieldNode / GroupNode / FormArrayProxy
|
|
280
281
|
// form.<field>.setValue(v) / .value.value / .errors.value / .disabled.value
|
|
281
282
|
// form.<field>.enable() / .disable() / .reset() / .markAsTouched() / .setErrors([...])
|
|
@@ -331,7 +332,7 @@ maxAge(years: number, options?: { message?: string })
|
|
|
331
332
|
`ValidationSchema` и гоняются раннером `validateModel` по требованию.
|
|
332
333
|
|
|
333
334
|
```typescript
|
|
334
|
-
import { createModel, createForm } from '@reformer/core';
|
|
335
|
+
import { createModel, createForm, createFormFromModel } from '@reformer/core';
|
|
335
336
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
336
337
|
import { required, min, max, email } from '@reformer/core/validators';
|
|
337
338
|
|
|
@@ -339,7 +340,7 @@ type Loan = { email: string; age: number | null; amount: number | null };
|
|
|
339
340
|
const model = createModel<Loan>({ email: '', age: null, amount: null });
|
|
340
341
|
|
|
341
342
|
// layout-схема createForm НЕ несёт validators — только привязка поля к сигналу + компонент
|
|
342
|
-
const form =
|
|
343
|
+
const form = createFormFromModel({
|
|
343
344
|
model,
|
|
344
345
|
schema: {
|
|
345
346
|
email: { value: model.$.email, component: Input },
|
|
@@ -526,7 +527,7 @@ const behavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
|
526
527
|
});
|
|
527
528
|
});
|
|
528
529
|
|
|
529
|
-
const form =
|
|
530
|
+
const form = createFormFromModel({ model, schema, behavior });
|
|
530
531
|
```
|
|
531
532
|
|
|
532
533
|
DSL-операторы: `compute` (auto-tracking, без явного списка источников), `computeFrom`, `copyFrom`,
|
|
@@ -1112,7 +1113,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
1112
1113
|
### Primitive Fields
|
|
1113
1114
|
|
|
1114
1115
|
```typescript
|
|
1115
|
-
import { createModel,
|
|
1116
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
1116
1117
|
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
1117
1118
|
import { required } from '@reformer/core/validators';
|
|
1118
1119
|
import { Input, InputNumber, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
@@ -1153,7 +1154,7 @@ const schema = {
|
|
|
1153
1154
|
},
|
|
1154
1155
|
};
|
|
1155
1156
|
|
|
1156
|
-
const form =
|
|
1157
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
1157
1158
|
```
|
|
1158
1159
|
|
|
1159
1160
|
### Nested Objects
|
|
@@ -1198,7 +1199,7 @@ const schema = {
|
|
|
1198
1199
|
|
|
1199
1200
|
```typescript
|
|
1200
1201
|
// M1: данные из модели + схема (+ опциональный декларативный behavior)
|
|
1201
|
-
const form =
|
|
1202
|
+
const form = createFormFromModel<MyForm>({
|
|
1202
1203
|
model, // FormModel<MyForm> — обязателен
|
|
1203
1204
|
schema, // дерево узлов, привязанных к сигналам
|
|
1204
1205
|
behavior: myBehavior, // опционально: defineFormBehavior(...) из @reformer/core/behaviors
|
|
@@ -1213,12 +1214,12 @@ model.items.push({ id: '1', name: 'Item' }); // операции над масс
|
|
|
1213
1214
|
> **Тип `schema` в M1 — `unknown`, не `FormSchema<T>`.** Это осознанно: интерпретатор обходит
|
|
1214
1215
|
> произвольную структуру и собирает листья `{ value: signal, component?, ... }`, поэтому обёртки
|
|
1215
1216
|
> (`{ children: [...] }`, wizard/steps) компилируются. Строгий `FormSchema<T>` (keyed-map по полям
|
|
1216
|
-
> `T`) применяется только на legacy-пути `
|
|
1217
|
+
> `T`) применяется только на legacy-пути `createLegacyForm(schema)` без модели.
|
|
1217
1218
|
|
|
1218
1219
|
### createForm Returns a Proxy
|
|
1219
1220
|
|
|
1220
1221
|
```typescript
|
|
1221
|
-
const form =
|
|
1222
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
1222
1223
|
|
|
1223
1224
|
form.email; // FieldNode<string> — TypeScript знает тип
|
|
1224
1225
|
form.address.city; // FieldNode<string> — вложенный доступ
|
|
@@ -1239,7 +1240,7 @@ if (isFieldNode(node)) { /* ... */ }
|
|
|
1239
1240
|
элемента из его под-модели (`FormModel<Item>`).
|
|
1240
1241
|
|
|
1241
1242
|
```typescript
|
|
1242
|
-
import { createModel,
|
|
1243
|
+
import { createModel, createFormFromModel, type FormModel } from '@reformer/core';
|
|
1243
1244
|
import { Input, InputNumber } from '@reformer/ui-kit';
|
|
1244
1245
|
|
|
1245
1246
|
type Item = { id: string; name: string; price: number };
|
|
@@ -1258,7 +1259,7 @@ const schema = {
|
|
|
1258
1259
|
items: { array: model.items, item: itemSchema },
|
|
1259
1260
|
};
|
|
1260
1261
|
|
|
1261
|
-
const form =
|
|
1262
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
1262
1263
|
```
|
|
1263
1264
|
|
|
1264
1265
|
> **Type constraint:** тип элемента `Item` объявляй через `type`-alias (не `interface`) — иначе
|
|
@@ -1280,7 +1281,7 @@ const model = createModel<MyForm>({ tags: [] });
|
|
|
1280
1281
|
const schema = {
|
|
1281
1282
|
tags: { value: model.$.tags, component: SelectMulti }, // поле над массивом целиком
|
|
1282
1283
|
};
|
|
1283
|
-
const form =
|
|
1284
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
1284
1285
|
|
|
1285
1286
|
form.tags.setValue(['a', 'b']); // FieldNode<string[]>
|
|
1286
1287
|
model.$.tags.value = ['c']; // ручка значения: заменяет массив целиком
|
|
@@ -1295,6 +1296,25 @@ validate(model.$.tags, [required(), maxLength(3)]); // правило получ
|
|
|
1295
1296
|
- Запись в узел-ГРУППУ (`model.$.<группа>.value = …`) — ошибка: группа пишется по полям или через
|
|
1296
1297
|
`model.patch(...)`.
|
|
1297
1298
|
|
|
1299
|
+
### Массив в группе и в строке другого массива
|
|
1300
|
+
|
|
1301
|
+
Узел `{ array, item }` стоит на любой глубине: массив в группе получает ноду `form.<группа>.<массив>`,
|
|
1302
|
+
массив в строке — `form.<массив>.at(i).<массив>`. Под-схема строки объявляет свои массивы так же.
|
|
1303
|
+
|
|
1304
|
+
```typescript
|
|
1305
|
+
const contactItem = (contact: FormModel<Contact>) => ({
|
|
1306
|
+
name: { value: contact.$.name, component: Input },
|
|
1307
|
+
phones: { array: contact.phones, item: phoneItem }, // массив в строке массива
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
const schema = {
|
|
1311
|
+
hotlines: { array: model.details.hotlines, item: phoneItem }, // массив в группе
|
|
1312
|
+
contacts: { array: model.contacts, item: contactItem },
|
|
1313
|
+
};
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
Поведение строк вложенного массива — `applyEach` внутри схемы строки, см. `21-array-operations.md`.
|
|
1317
|
+
|
|
1298
1318
|
### Один массив — три слоя (три разных движка)
|
|
1299
1319
|
|
|
1300
1320
|
Одна и та же коллекция описывается **тремя разными формами** — по одной на движок. Их легко
|
|
@@ -1306,7 +1326,7 @@ validate(model.$.tags, [required(), maxLength(3)]); // правило получ
|
|
|
1306
1326
|
`model.$.properties`.
|
|
1307
1327
|
|
|
1308
1328
|
```typescript
|
|
1309
|
-
// узел схемы для
|
|
1329
|
+
// узел схемы для createFormFromModel({ model, schema })
|
|
1310
1330
|
properties: { array: model.properties, item: propertyItem },
|
|
1311
1331
|
```
|
|
1312
1332
|
|
|
@@ -1866,7 +1886,7 @@ the guide below.
|
|
|
1866
1886
|
|
|
1867
1887
|
```tsx
|
|
1868
1888
|
import { useMemo } from 'react';
|
|
1869
|
-
import { createModel,
|
|
1889
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
1870
1890
|
import { FormField, Input, SelectAsync, CheckboxWithLabel, Button } from '@reformer/ui-kit';
|
|
1871
1891
|
|
|
1872
1892
|
type RegistrationForm = {
|
|
@@ -1901,7 +1921,7 @@ function RegistrationPage() {
|
|
|
1901
1921
|
componentProps: { label: 'I agree to terms' },
|
|
1902
1922
|
},
|
|
1903
1923
|
};
|
|
1904
|
-
return
|
|
1924
|
+
return createFormFromModel<RegistrationForm>({ model, schema });
|
|
1905
1925
|
}, []);
|
|
1906
1926
|
|
|
1907
1927
|
return (
|
|
@@ -2230,7 +2250,7 @@ export const creditBehavior = defineFormBehavior<CreditForm>(({ model }) => {
|
|
|
2230
2250
|
- **Все поля должны быть материализованы в модели** (`createModel`), даже те, что показываются
|
|
2231
2251
|
только под условием. `enableWhen`/`compute`/`copyFrom` принимают сигналы `model.$.field`, а не
|
|
2232
2252
|
строковые пути; сигнала для несуществующего поля нет.
|
|
2233
|
-
- Поведение подключается к форме через `
|
|
2253
|
+
- Поведение подключается к форме через `createFormFromModel({ model, schema, behavior })` — форма владеет
|
|
2234
2254
|
жизненным циклом, DSL-операторы регистрируют свой cleanup сами (ручной массив cleanup'ов не нужен).
|
|
2235
2255
|
- Таргетом может быть и **группа**: `enableWhen(model.$.residenceAddress, () => model.sameAsRegistration === false)`
|
|
2236
2256
|
проходит по поддереву; без `resetOnDisable` значение группы сохраняется (удобно, когда оно копируется
|
|
@@ -2677,6 +2697,32 @@ const behavior = defineFormBehavior<MyForm>(({ model }) => {
|
|
|
2677
2697
|
});
|
|
2678
2698
|
```
|
|
2679
2699
|
|
|
2700
|
+
### Вложенные области — массив в группе и в строке массива
|
|
2701
|
+
|
|
2702
|
+
Узел `{ array, item }` и операторы `applyEach` / `apply` / `enableWhen` работают на любой глубине:
|
|
2703
|
+
цель оператора — **ручка** `model.$.…`, под-модель и нода находятся по её идентичности, а не по
|
|
2704
|
+
пути.
|
|
2705
|
+
|
|
2706
|
+
```typescript
|
|
2707
|
+
const phoneBehavior = defineFormBehavior<Phone>(({ model: phone }) => {
|
|
2708
|
+
enableWhen(phone.$.number, () => phone.primary);
|
|
2709
|
+
});
|
|
2710
|
+
|
|
2711
|
+
const contactBehavior = defineFormBehavior<Contact>(({ model: contact }) => {
|
|
2712
|
+
applyEach(contact.$.phones, phoneBehavior); // массив внутри строки
|
|
2713
|
+
enableWhen(contact.$.address, () => contact.quantity > 0); // группа внутри строки
|
|
2714
|
+
apply(contact.$.address, addressBehavior); // под-схема получает настоящую под-модель
|
|
2715
|
+
});
|
|
2716
|
+
|
|
2717
|
+
const behavior = defineFormBehavior<MyForm>(({ model }) => {
|
|
2718
|
+
applyEach(model.$.contacts, contactBehavior);
|
|
2719
|
+
});
|
|
2720
|
+
```
|
|
2721
|
+
|
|
2722
|
+
Ручка → value-фасад: `modelOf(model.$.address)` — под-модель (`get` / `set` / `patch`),
|
|
2723
|
+
`modelOf(model.$.phones)` — фасад массива (`push` / `removeAt` / `at`). Нода формы по ручке —
|
|
2724
|
+
`getNodeForSignal(model.$.address)`.
|
|
2725
|
+
|
|
2680
2726
|
### Aggregate write — aggregateInto
|
|
2681
2727
|
|
|
2682
2728
|
Агрегатная запись в строки (например, «последняя строка = 100 − Σ остальных»):
|
|
@@ -3493,7 +3539,7 @@ form.<field>.clearErrors();
|
|
|
3493
3539
|
|
|
3494
3540
|
```tsx
|
|
3495
3541
|
import { useMemo } from 'react';
|
|
3496
|
-
import { createModel, createForm } from '@reformer/core';
|
|
3542
|
+
import { createModel, createForm, createFormFromModel } from '@reformer/core';
|
|
3497
3543
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
3498
3544
|
import { required, email, minLength } from '@reformer/core/validators';
|
|
3499
3545
|
|
|
@@ -3511,7 +3557,7 @@ function RegistrationForm() {
|
|
|
3511
3557
|
const { model, form } = useMemo(() => {
|
|
3512
3558
|
const m = createModel<RegistrationFormData>({ username: '', email: '', password: '' });
|
|
3513
3559
|
// schema здесь — layout-дерево нод (компоненты/раскладка), без валидаторов.
|
|
3514
|
-
return { model: m, form:
|
|
3560
|
+
return { model: m, form: createFormFromModel({ model: m, schema: buildLayout(m) }) };
|
|
3515
3561
|
}, []);
|
|
3516
3562
|
|
|
3517
3563
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
@@ -3643,7 +3689,7 @@ const handleSubmit = async (e) => {
|
|
|
3643
3689
|
|
|
3644
3690
|
```typescript
|
|
3645
3691
|
// ❌ Валидаторы в layout-дереве, отдаваемом в createForm — layout не несёт правил
|
|
3646
|
-
|
|
3692
|
+
createFormFromModel({ model, schema: { username: { value: '', validators: [required()] } } });
|
|
3647
3693
|
|
|
3648
3694
|
// ✅ Валидация — отдельная схема, прогоняется раннером
|
|
3649
3695
|
const schema = defineValidationSchema<T>(({ model }) => {
|
|
@@ -3735,7 +3781,7 @@ Initial values задаются в `createModel(initial)`; `reset()` возвр
|
|
|
3735
3781
|
### Initial values в модели
|
|
3736
3782
|
|
|
3737
3783
|
```typescript
|
|
3738
|
-
import { createModel,
|
|
3784
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
3739
3785
|
import { Input, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
3740
3786
|
|
|
3741
3787
|
type ProfileForm = { username: string; language: 'ru' | 'en'; marketing: boolean };
|
|
@@ -3756,7 +3802,7 @@ const schema = {
|
|
|
3756
3802
|
},
|
|
3757
3803
|
marketing: { value: model.$.marketing, component: CheckboxWithLabel, componentProps: { label: 'Рассылка' } },
|
|
3758
3804
|
};
|
|
3759
|
-
const form =
|
|
3805
|
+
const form = createFormFromModel({ model, schema });
|
|
3760
3806
|
// model.get() === { username: '', language: 'ru', marketing: true }
|
|
3761
3807
|
// после правок: model.reset() возвращает к этим значениям
|
|
3762
3808
|
```
|
|
@@ -3843,14 +3889,14 @@ export const addressBehavior = defineFormBehavior<AddressForm>(({ model, form })
|
|
|
3843
3889
|
// ❌ model/form пересоздаются на каждый рендер → preload запускается каждый раз
|
|
3844
3890
|
function MyForm() {
|
|
3845
3891
|
const model = createModel<T>(initial); // КАЖДЫЙ рендер!
|
|
3846
|
-
const form =
|
|
3892
|
+
const form = createFormFromModel({ model, schema });
|
|
3847
3893
|
}
|
|
3848
3894
|
|
|
3849
3895
|
// ✅ Стабильные ссылки через useMemo
|
|
3850
3896
|
function MyForm() {
|
|
3851
3897
|
const { model, form } = useMemo(() => {
|
|
3852
3898
|
const m = createModel<T>(initial);
|
|
3853
|
-
return { model: m, form:
|
|
3899
|
+
return { model: m, form: createFormFromModel({ model: m, schema: buildSchema(m) }) };
|
|
3854
3900
|
}, []);
|
|
3855
3901
|
}
|
|
3856
3902
|
```
|
|
@@ -4382,7 +4428,7 @@ function createFormValidation<T>(
|
|
|
4382
4428
|
`markAsTouched` не нужен.
|
|
4383
4429
|
|
|
4384
4430
|
```tsx
|
|
4385
|
-
import { createModel,
|
|
4431
|
+
import { createModel, createFormFromModel, useFormValidation } from '@reformer/core';
|
|
4386
4432
|
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
4387
4433
|
import { required, email, minLength } from '@reformer/core/validators';
|
|
4388
4434
|
|
|
@@ -4397,7 +4443,7 @@ const registrationValidation = defineValidationSchema<RegistrationData>(({ model
|
|
|
4397
4443
|
|
|
4398
4444
|
function RegistrationForm() {
|
|
4399
4445
|
const model = useMemo(() => createModel<RegistrationData>({ username: '', email: '', password: '' }), []);
|
|
4400
|
-
const form = useMemo(() =>
|
|
4446
|
+
const form = useMemo(() => createFormFromModel({ model }), [model]);
|
|
4401
4447
|
|
|
4402
4448
|
const { submit, isValidating } = useFormValidation({
|
|
4403
4449
|
model,
|
|
@@ -4775,7 +4821,254 @@ const source = () => ({ weekStartsOn: 1, messages: {} });
|
|
|
4775
4821
|
- [28-submit-and-reset.md](./28-submit-and-reset.md) — жизненный цикл формы; смена языка его не затрагивает
|
|
4776
4822
|
- [33-validation-strategy.md](./33-validation-strategy.md) — когда запускается валидация
|
|
4777
4823
|
|
|
4778
|
-
## 94.
|
|
4824
|
+
## 94. Purpose
|
|
4825
|
+
|
|
4826
|
+
**Единый контракт формы — одна схема, одна сборка, одно поведение**
|
|
4827
|
+
|
|
4828
|
+
Форма описывается одинаково для всех способов реализации — разметка в JSX, рендерер по TS-схеме,
|
|
4829
|
+
рендерер по JSON. Схема одна (дерево узлов), привязка одна (ручка `model.$.…`), сборка одна
|
|
4830
|
+
(`createForm`), поведение одно (`defineFormBehavior`). Способы отличаются только видом схемы
|
|
4831
|
+
(билдер или документ) и тем, кто рисует результат.
|
|
4832
|
+
|
|
4833
|
+
Прежние записи — ключи `value` / `array`, оператор `each`, `createCoreForm` — пока принимаются.
|
|
4834
|
+
Новый код пишется так, как описано здесь.
|
|
4835
|
+
|
|
4836
|
+
| Слой | поле | массив под-форм | подформа |
|
|
4837
|
+
| --------- | --------------------------------- | ----------------------------------------- | ------------------------------------- |
|
|
4838
|
+
| схема | `{ model: model.$.x, component }` | `{ model: model.$.items, item }` | `{ model: model.$.group, part }` |
|
|
4839
|
+
| валидация | `validate(model.$.x, rules)` | `applyEach(model.$.items, itemRules)` | `apply(model.$.group, groupRules)` |
|
|
4840
|
+
| поведение | `compute(model.$.x, …)` | `applyEach(model.$.items, itemBehavior)` | `apply(model.$.group, groupBehavior)` |
|
|
4841
|
+
|
|
4842
|
+
Правило привязки: `model.$.…` — привязка (схема, правило, оператор). Без `$` — чтение значения
|
|
4843
|
+
(`model.loanType === 'mortgage'`), мутация (`model.items.push()`) и под-модель как область.
|
|
4844
|
+
|
|
4845
|
+
## 95. Модель — начальные значения и шаблоны элементов
|
|
4846
|
+
|
|
4847
|
+
```typescript
|
|
4848
|
+
import { arrayOf, createModel } from '@reformer/core';
|
|
4849
|
+
|
|
4850
|
+
const blankPhone = (): Phone => ({ number: '' });
|
|
4851
|
+
const blankCoBorrower = (): CoBorrower => ({ name: '', phones: arrayOf(blankPhone) });
|
|
4852
|
+
|
|
4853
|
+
export const createCreditModel = () =>
|
|
4854
|
+
createModel<CreditForm>({
|
|
4855
|
+
loanType: 'consumer',
|
|
4856
|
+
registrationAddress: { city: '', street: '' },
|
|
4857
|
+
coBorrowers: arrayOf(blankCoBorrower), // пустой массив + шаблон нового элемента
|
|
4858
|
+
tags: [], // массив как одно значение поля — шаблон не нужен
|
|
4859
|
+
});
|
|
4860
|
+
```
|
|
4861
|
+
|
|
4862
|
+
`arrayOf(blank, items?)` возвращает обычный массив `U[]` и запоминает фабрику нового элемента.
|
|
4863
|
+
После этого `push()` и `insertAt(i)` без значения кладут элемент по шаблону — в модели, в ноде
|
|
4864
|
+
массива формы и в кнопке «Добавить» секции массива.
|
|
4865
|
+
|
|
4866
|
+
- Шаблон переживает `set` / `patch` / `reset` модели.
|
|
4867
|
+
- Вложенный массив объявляется внутри шаблона элемента; элементы, пришедшие обычными данными
|
|
4868
|
+
(загрузка с сервера), получают его шаблон из шаблона родителя.
|
|
4869
|
+
- У массива без шаблона `push()` без значения бросает ошибку с подсказкой.
|
|
4870
|
+
- Шаблон привязан к самому массиву: до `createModel` его нельзя копировать. `structuredClone`,
|
|
4871
|
+
`JSON.parse(JSON.stringify(…))` и `[...array]` отдают массив без шаблона. Начальные значения с
|
|
4872
|
+
`arrayOf` держат в фабрике, а не в константе, которую клонируют.
|
|
4873
|
+
|
|
4874
|
+
## 96. Схема — одно дерево
|
|
4875
|
+
|
|
4876
|
+
```typescript
|
|
4877
|
+
import type { FormModel } from '@reformer/core';
|
|
4878
|
+
|
|
4879
|
+
// подформа: объявлена один раз, получает под-модель
|
|
4880
|
+
const address = (model: FormModel<Address>) => ({
|
|
4881
|
+
component: Box,
|
|
4882
|
+
children: [
|
|
4883
|
+
{ model: model.$.city, component: Input, componentProps: { label: 'Город' } },
|
|
4884
|
+
{ model: model.$.street, component: Input, componentProps: { label: 'Улица' } },
|
|
4885
|
+
],
|
|
4886
|
+
});
|
|
4887
|
+
|
|
4888
|
+
// строка массива — такая же функция от под-модели
|
|
4889
|
+
const coBorrower = (model: FormModel<CoBorrower>) => ({
|
|
4890
|
+
selector: 'co-borrower',
|
|
4891
|
+
component: Box,
|
|
4892
|
+
children: [{ model: model.$.name, component: Input, componentProps: { label: 'ФИО' } }],
|
|
4893
|
+
});
|
|
4894
|
+
|
|
4895
|
+
export const creditSchema = (model: FormModel<CreditForm>) => ({
|
|
4896
|
+
component: Box,
|
|
4897
|
+
children: [
|
|
4898
|
+
{ model: model.$.loanType, component: SelectAsync, componentProps: { label: 'Тип' } }, // поле
|
|
4899
|
+
{ model: model.$.tags, component: SelectMulti }, // массив как одно значение
|
|
4900
|
+
{
|
|
4901
|
+
selector: 'registration',
|
|
4902
|
+
component: Section,
|
|
4903
|
+
componentProps: { title: 'Адрес регистрации' },
|
|
4904
|
+
children: [{ model: model.$.registrationAddress, part: address }], // подформа
|
|
4905
|
+
},
|
|
4906
|
+
{ model: model.$.coBorrowers, component: FormArray, item: coBorrower }, // массив под-форм
|
|
4907
|
+
],
|
|
4908
|
+
});
|
|
4909
|
+
```
|
|
4910
|
+
|
|
4911
|
+
Чем узел является, решают ручка и соседние ключи:
|
|
4912
|
+
|
|
4913
|
+
| Узел | Как отличить |
|
|
4914
|
+
| --------------- | --------------------------------------------- |
|
|
4915
|
+
| поле | `model` — лист или массив; нет `item`, `part` |
|
|
4916
|
+
| массив под-форм | `model` — массив, `item` — функция |
|
|
4917
|
+
| подформа | `model` — группа, `part` — функция |
|
|
4918
|
+
| контейнер | есть `children`, нет `model` |
|
|
4919
|
+
|
|
4920
|
+
- Узел узнаётся по ЗНАЧЕНИЮ: в записи «имя поля → узел» поле данных может называться `model`,
|
|
4921
|
+
`item`, `part` или `value` — под таким ключом лежит обычный вложенный узел.
|
|
4922
|
+
- Поля подформы принадлежат той же форме: `form.registrationAddress.city`.
|
|
4923
|
+
- Билдер `item` / `part` вызывается один раз на под-модель; сборка и рендерер получают одно и то
|
|
4924
|
+
же поддерево (`schemaSubtree(builder, subModel)`).
|
|
4925
|
+
- `initialValue` узла-массива — запасной шаблон нового элемента для форм, чья модель создаётся из
|
|
4926
|
+
данных без кода. Шаблон модели (`arrayOf`) главнее.
|
|
4927
|
+
|
|
4928
|
+
## 97. Валидация — подформа и массив подключаются привязкой
|
|
4929
|
+
|
|
4930
|
+
```typescript
|
|
4931
|
+
import {
|
|
4932
|
+
apply,
|
|
4933
|
+
applyEach,
|
|
4934
|
+
cross,
|
|
4935
|
+
defineValidationSchema,
|
|
4936
|
+
validate,
|
|
4937
|
+
validateWhen,
|
|
4938
|
+
} from '@reformer/core/validation';
|
|
4939
|
+
|
|
4940
|
+
const addressRules = defineValidationSchema<Address>(({ model }) => {
|
|
4941
|
+
validate(model.$.city, [required()]);
|
|
4942
|
+
cross<Address>(model.$.street, (address) =>
|
|
4943
|
+
address.city !== '' && address.street === '' ? streetRequired : null
|
|
4944
|
+
);
|
|
4945
|
+
});
|
|
4946
|
+
|
|
4947
|
+
const coBorrowerRules = defineValidationSchema<CoBorrower>(({ model }) => {
|
|
4948
|
+
validate(model.$.name, [required()]);
|
|
4949
|
+
});
|
|
4950
|
+
|
|
4951
|
+
export const contactsRules = defineValidationSchema<CreditForm>(({ model }) => {
|
|
4952
|
+
apply(model.$.registrationAddress, addressRules);
|
|
4953
|
+
validateWhen(
|
|
4954
|
+
() => !model.sameAsRegistration,
|
|
4955
|
+
() => apply(model.$.residenceAddress, addressRules)
|
|
4956
|
+
);
|
|
4957
|
+
applyEach(model.$.coBorrowers, coBorrowerRules);
|
|
4958
|
+
});
|
|
4959
|
+
```
|
|
4960
|
+
|
|
4961
|
+
- У схемы, подключённой через `apply(ручка, схема)` / `applyEach`, своя область: `model` — под-модель,
|
|
4962
|
+
`cross` получает её снапшот. Захватывать снапшот в замыкание больше не нужно.
|
|
4963
|
+
- Одна и та же схема подключается и к группе, и к элементам массива.
|
|
4964
|
+
- `apply(model.$.a, rules)` принимает и массив ручек: `apply([model.$.a, model.$.b], rules)`.
|
|
4965
|
+
- `apply(schemaA, schemaB)` — композиция схем над той же моделью — остаётся.
|
|
4966
|
+
- Правило получает `(value, scope, root)`: модель области и корень прогона.
|
|
4967
|
+
- `each(model.items, (item) => …)` — прежняя запись `applyEach`: принимает фасад и ручку, области
|
|
4968
|
+
не создаёт.
|
|
4969
|
+
|
|
4970
|
+
## 98. Поведение — модель, форма и схема
|
|
4971
|
+
|
|
4972
|
+
```typescript
|
|
4973
|
+
import {
|
|
4974
|
+
apply,
|
|
4975
|
+
applyEach,
|
|
4976
|
+
defineFormBehavior,
|
|
4977
|
+
enableWhen,
|
|
4978
|
+
hideWhen,
|
|
4979
|
+
onComponentEvent,
|
|
4980
|
+
onMount,
|
|
4981
|
+
} from '@reformer/core/behaviors';
|
|
4982
|
+
|
|
4983
|
+
const addressBehavior = defineFormBehavior<Address>(({ model, schema }) => {
|
|
4984
|
+
hideWhen(schema.node('street'), () => model.city === ''); // узел ищется внутри части
|
|
4985
|
+
});
|
|
4986
|
+
|
|
4987
|
+
export const creditBehavior = defineFormBehavior<CreditForm>(({ model, form, schema }) => {
|
|
4988
|
+
const isMortgage = () => model.loanType === 'mortgage';
|
|
4989
|
+
|
|
4990
|
+
enableWhen(model.$.propertyValue, isMortgage, { resetOnDisable: true });
|
|
4991
|
+
hideWhen(schema.node('mortgage'), () => !isMortgage());
|
|
4992
|
+
onComponentEvent(schema.node('wizard'), 'onSubmit', () => submitApplication(model.get()));
|
|
4993
|
+
onMount(schema.node('data-boundary'), () => void loadApplication(model));
|
|
4994
|
+
|
|
4995
|
+
apply([model.$.registrationAddress, model.$.residenceAddress], addressBehavior);
|
|
4996
|
+
applyEach(model.$.coBorrowers, coBorrowerBehavior);
|
|
4997
|
+
});
|
|
4998
|
+
```
|
|
4999
|
+
|
|
5000
|
+
Операторы узлов схемы:
|
|
5001
|
+
|
|
5002
|
+
| Оператор | Что записывает |
|
|
5003
|
+
| ----------------------------------------- | ---------------------------------------------------------- |
|
|
5004
|
+
| `hideWhen(node, condition)` | узел скрыт, пока условие истинно |
|
|
5005
|
+
| `onComponentEvent(node, event, handler)` | обработчик пропа-события компонента |
|
|
5006
|
+
| `onMount(node, fn)` / `onUnmount(node, fn)` | хуки монтирования узла |
|
|
5007
|
+
| `onInit(node, fn)` | `fn` вызывается сразу, до первого рендера |
|
|
5008
|
+
| `renderEffect(schema, fn)` | реактивный эффект, пока схема области смонтирована |
|
|
5009
|
+
| `schema.node(sel).setHidden / patchProps` | принудительная видимость и пропсы узла |
|
|
5010
|
+
| `schema.node(sel).getRef()` | ref на компонент узла — объект `{ current }` |
|
|
5011
|
+
|
|
5012
|
+
- **Области изолированы.** `schema.node(selector)` ищет узел только в своей области: корневое
|
|
5013
|
+
поведение — в корневом дереве, поведение подформы и строки массива — в своём поддереве. До узла
|
|
5014
|
+
строки или части корень дотягивается через `applyEach` / `apply`. Два монтирования одной части
|
|
5015
|
+
не конфликтуют.
|
|
5016
|
+
- **Исключение — ref по пути модели**: `schema.node('phones.0.number').getRef()` ищется от корня.
|
|
5017
|
+
- **Операторы узлов только записывают правило — исполняет его рендерер.** При разметке в JSX они
|
|
5018
|
+
ничего не делают: видимость, отправка и загрузка данных остаются в JSX.
|
|
5019
|
+
- Правило на селектор, которого нет в дереве, ничего не делает; в dev — предупреждение.
|
|
5020
|
+
|
|
5021
|
+
## 99. Сборка — `createForm`
|
|
5022
|
+
|
|
5023
|
+
```tsx
|
|
5024
|
+
import { createForm, useFormBundle } from '@reformer/core';
|
|
5025
|
+
|
|
5026
|
+
const credit = useFormBundle(() =>
|
|
5027
|
+
createForm<CreditForm>({
|
|
5028
|
+
model: createCreditModel(), // либо initial: { … }
|
|
5029
|
+
schema: creditSchema, // (model) => узел; для JSON — документ + registry
|
|
5030
|
+
behavior: creditBehavior,
|
|
5031
|
+
validation: { steps: { loan: loanRules, contacts: contactsRules }, extras: crossRules },
|
|
5032
|
+
})
|
|
5033
|
+
);
|
|
5034
|
+
// credit = { model, form, validation, render }
|
|
5035
|
+
|
|
5036
|
+
<FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />; // рендерер
|
|
5037
|
+
<FormField control={credit.form.loanType} />; // разметка в JSX — та же сборка
|
|
5038
|
+
```
|
|
5039
|
+
|
|
5040
|
+
- Порядок: модель → `seed` → дерево схемы → ноды формы → поведение → валидация → `setup`. Дерево
|
|
5041
|
+
строится один раз.
|
|
5042
|
+
- `validation` в бандле типизирована по конфигу: правила переданы — поле есть всегда.
|
|
5043
|
+
- `render` — `{ tree, controller, node(selector) }`: готовое дерево и схема-контроллер сборки.
|
|
5044
|
+
- JSON: `createForm({ model, schema: document, registry })` — дерево собирает
|
|
5045
|
+
`registry.resolveSchema`. Ядро документ не разбирает.
|
|
5046
|
+
- Внутри рендерера бандл доступен компонентам через `useFormBundleContext()` — так визард берёт
|
|
5047
|
+
форму и валидацию сам.
|
|
5048
|
+
|
|
5049
|
+
### Низкоуровневые фабрики
|
|
5050
|
+
|
|
5051
|
+
| Задача | Фабрика |
|
|
5052
|
+
| ---------------------------------------------- | ---------------------------------------- |
|
|
5053
|
+
| форма из модели и готового дерева, без бандла | `createFormFromModel({ model, schema })` |
|
|
5054
|
+
| форма без модели — плоская схема полей | `createLegacyForm(schema)` |
|
|
5055
|
+
|
|
5056
|
+
`createForm` с готовым деревом вместо билдера или без модели бросает ошибку с отсылкой к нужной
|
|
5057
|
+
фабрике.
|
|
5058
|
+
|
|
5059
|
+
## 100. Частые ошибки
|
|
5060
|
+
|
|
5061
|
+
| Ошибка | Как правильно |
|
|
5062
|
+
| -------------------------------------------------- | ------------------------------------------------------- |
|
|
5063
|
+
| `model: model.address` (под-модель у поля) | `model: model.$.address.city` — привязка всегда с `$` |
|
|
5064
|
+
| `{ model: model.$.address, component }` без `part` | группа полем не бывает — нужен `part` |
|
|
5065
|
+
| `createForm({ model, schema: дерево })` | `schema` — билдер `(model) => узел` |
|
|
5066
|
+
| `hideWhen(schema.node('row-узел'), …)` из корня | внутри `applyEach(model.$.items, itemBehavior)` |
|
|
5067
|
+
| `push({})` для новой строки | `arrayOf(blank)` в модели и `push()` без значения |
|
|
5068
|
+
| `createModel(structuredClone(INITIAL))` с `arrayOf` | фабрика начальных значений — клон теряет шаблон массива |
|
|
5069
|
+
| `cross` в подформе читает корень | `apply(model.$.group, rules)` — область подформы |
|
|
5070
|
+
|
|
5071
|
+
## 101. API Reference
|
|
4779
5072
|
|
|
4780
5073
|
_Auto-generated from JSDoc on public exports._
|
|
4781
5074
|
|
|
@@ -4825,14 +5118,22 @@ _Source: src/form/types/index.ts_
|
|
|
4825
5118
|
|
|
4826
5119
|
**Kind:** `function`
|
|
4827
5120
|
|
|
4828
|
-
|
|
5121
|
+
Применить под-схему к одному или нескольким полям-группам (переиспользование).
|
|
5122
|
+
|
|
5123
|
+
Под-схема получает scope группы: `model` — настоящая под-модель (`model.$.field`, чтение и
|
|
5124
|
+
запись значений, `get`/`set`/`patch`), `form` — нода группы. Цель — ручка группы
|
|
5125
|
+
(`model.$.address`); работает и в корневой схеме, и в схеме строки массива.
|
|
4829
5126
|
|
|
4830
5127
|
**Signature:**
|
|
4831
5128
|
```typescript
|
|
4832
|
-
export function apply<
|
|
5129
|
+
export function apply<TField>(targets: object | object[], subSchema: FormBehavior<TField>): void
|
|
4833
5130
|
```
|
|
4834
5131
|
|
|
4835
|
-
|
|
5132
|
+
**Examples:**
|
|
5133
|
+
|
|
5134
|
+
apply([model.$.registrationAddress, model.$.residenceAddress], addressBehavior);
|
|
5135
|
+
|
|
5136
|
+
_Source: src/form/behaviors/collections.ts_
|
|
4836
5137
|
|
|
4837
5138
|
### applyEach
|
|
4838
5139
|
|
|
@@ -4848,6 +5149,9 @@ _Source: src/form/validation/operators.ts_
|
|
|
4848
5149
|
рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
|
|
4849
5150
|
к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
|
|
4850
5151
|
|
|
5152
|
+
Работает на любой глубине: массив в корне, в группе и в строке другого массива — в схеме строки
|
|
5153
|
+
можно снова вызвать `applyEach`.
|
|
5154
|
+
|
|
4851
5155
|
**Signature:**
|
|
4852
5156
|
```typescript
|
|
4853
5157
|
export function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void
|
|
@@ -4858,6 +5162,7 @@ export function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>)
|
|
|
4858
5162
|
applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
|
|
4859
5163
|
compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
|
|
4860
5164
|
enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
|
|
5165
|
+
applyEach(row.$.phones, phoneBehavior); // массив внутри строки
|
|
4861
5166
|
}));
|
|
4862
5167
|
|
|
4863
5168
|
_Source: src/form/behaviors/collections.ts_
|
|
@@ -5074,6 +5379,52 @@ export interface ArrayNodeLike {
|
|
|
5074
5379
|
|
|
5075
5380
|
_Source: src/form/types/index.ts_
|
|
5076
5381
|
|
|
5382
|
+
### arrayOf
|
|
5383
|
+
|
|
5384
|
+
**Kind:** `function`
|
|
5385
|
+
|
|
5386
|
+
Массив модели с шаблоном нового элемента.
|
|
5387
|
+
|
|
5388
|
+
Шаблон — фабрика значения, которое получает `push()` / `insertAt(i)` без аргумента: кнопка
|
|
5389
|
+
«Добавить» секции массива, `model.items.push()` в поведении. Объявляется там же, где остальные
|
|
5390
|
+
начальные значения, — в модели; схеме и JSX знать его не нужно.
|
|
5391
|
+
|
|
5392
|
+
Возвращает обычный массив: тип поля остаётся `U[]`. Шаблон переживает `set` / `patch` / `reset`
|
|
5393
|
+
модели. Вложенные массивы объявляются внутри шаблона элемента — тогда и элементы, пришедшие
|
|
5394
|
+
обычными данными (загрузка с сервера), получают их шаблоны.
|
|
5395
|
+
|
|
5396
|
+
Шаблон привязан к самому массиву, поэтому до `createModel` его нельзя копировать:
|
|
5397
|
+
`structuredClone`, `JSON.parse(JSON.stringify(…))` и `[...array]` отдают массив без шаблона, и
|
|
5398
|
+
`push()` без значения бросит ошибку. Начальные значения с `arrayOf` держат в фабрике
|
|
5399
|
+
(`const initial = () => ({ items: arrayOf(blank) })`), а не в константе, которую клонируют.
|
|
5400
|
+
|
|
5401
|
+
**Signature:**
|
|
5402
|
+
```typescript
|
|
5403
|
+
export function arrayOf<U>(blank: () => U, items: readonly U[] = []): U[]
|
|
5404
|
+
```
|
|
5405
|
+
|
|
5406
|
+
**Parameters:**
|
|
5407
|
+
- `blank` — - Фабрика нового элемента. Вызывается на каждое добавление — значение не делится
|
|
5408
|
+
между элементами.
|
|
5409
|
+
- `items` — - Начальные элементы. По умолчанию массив пуст.
|
|
5410
|
+
|
|
5411
|
+
**Returns:** Массив начальных значений с шаблоном.
|
|
5412
|
+
|
|
5413
|
+
**Examples:**
|
|
5414
|
+
|
|
5415
|
+
```typescript
|
|
5416
|
+
const blankPhone = () => ({ number: '' });
|
|
5417
|
+
const blankCoBorrower = () => ({ name: '', phones: arrayOf(blankPhone) });
|
|
5418
|
+
|
|
5419
|
+
const model = createModel({ coBorrowers: arrayOf(blankCoBorrower) });
|
|
5420
|
+
|
|
5421
|
+
model.coBorrowers.push(); // { name: '', phones: [] }
|
|
5422
|
+
model.coBorrowers.at(0).phones.push(); // { number: '' }
|
|
5423
|
+
model.coBorrowers.push({ name: 'Анна', phones: [] }); // значение целиком — как раньше
|
|
5424
|
+
```
|
|
5425
|
+
|
|
5426
|
+
_Source: src/model/model-nodes.ts_
|
|
5427
|
+
|
|
5077
5428
|
### AsyncRule
|
|
5078
5429
|
|
|
5079
5430
|
**Kind:** `type`
|
|
@@ -5167,11 +5518,19 @@ _Source: src/model/behaviors-value.ts_
|
|
|
5167
5518
|
|
|
5168
5519
|
**Kind:** `type`
|
|
5169
5520
|
|
|
5170
|
-
|
|
5521
|
+
Область схемы поведения: модель (значения и сигналы), форма (ноды) и схема (узлы разметки).
|
|
5522
|
+
|
|
5523
|
+
`schema` адресует узлы СВОЕЙ области: в корневом поведении — корневое дерево, в поведении
|
|
5524
|
+
подформы (`apply`) и строки массива (`applyEach`) — поддерево этой части. Правила узлов
|
|
5525
|
+
(`hideWhen`, `onComponentEvent`, `onMount`) исполняет рендерер; без него они ничего не делают.
|
|
5171
5526
|
|
|
5172
5527
|
**Signature:**
|
|
5173
5528
|
```typescript
|
|
5174
|
-
export type BehaviorScope<T> = {
|
|
5529
|
+
export type BehaviorScope<T> = {
|
|
5530
|
+
model: FormModel<T>;
|
|
5531
|
+
form: FormProxy<T>;
|
|
5532
|
+
schema: SchemaScope;
|
|
5533
|
+
};
|
|
5175
5534
|
```
|
|
5176
5535
|
|
|
5177
5536
|
_Source: src/form/behaviors/types.ts_
|
|
@@ -5483,6 +5842,8 @@ const credit = useFormBundle(() =>
|
|
|
5483
5842
|
return <FormWizard form={credit.form} config={credit.validation} steps={STEPS} />;
|
|
5484
5843
|
```
|
|
5485
5844
|
|
|
5845
|
+
**Deprecated:** Пишите `createForm` — та же сборка, в бандле вдобавок дерево для рендерера.
|
|
5846
|
+
|
|
5486
5847
|
_Source: src/form/create-core-form.ts_
|
|
5487
5848
|
|
|
5488
5849
|
### CreateCoreFormConfig
|
|
@@ -5495,8 +5856,8 @@ _Source: src/form/create-core-form.ts_
|
|
|
5495
5856
|
```typescript
|
|
5496
5857
|
export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {
|
|
5497
5858
|
/**
|
|
5498
|
-
* Билдер схемы формы. Именно функция, а не готовое дерево:
|
|
5499
|
-
*
|
|
5859
|
+
* Билдер схемы формы. Именно функция, а не готовое дерево: узлы схемы держат ручки модели
|
|
5860
|
+
* (`model: model.$.email`), поэтому построить дерево до модели нечем.
|
|
5500
5861
|
*/
|
|
5501
5862
|
schema?: (model: FormModel<T>) => FormSchemaNode;
|
|
5502
5863
|
}
|
|
@@ -5508,53 +5869,89 @@ _Source: src/form/create-core-form.ts_
|
|
|
5508
5869
|
|
|
5509
5870
|
**Kind:** `function`
|
|
5510
5871
|
|
|
5511
|
-
|
|
5872
|
+
Собрать модель, форму, валидацию и дерево для рендера за один проход.
|
|
5512
5873
|
|
|
5513
5874
|
**Signature:**
|
|
5514
5875
|
```typescript
|
|
5515
|
-
export function createForm<T>(
|
|
5876
|
+
export function createForm<T extends object>(
|
|
5877
|
+
config: CreateFormConfig<T> & { validation: FormValidation<T> | ValidationSchema<T> }
|
|
5878
|
+
): ValidatedFormBundle<T>;
|
|
5516
5879
|
```
|
|
5517
5880
|
|
|
5518
5881
|
**Parameters:**
|
|
5519
|
-
- `
|
|
5882
|
+
- `config` — - {@link CreateFormConfig}: (`initial` | `model`) + опц. `schema`, `registry`,
|
|
5883
|
+
`behavior`, `validation`, `seed`, `setup`.
|
|
5520
5884
|
|
|
5521
|
-
**Returns:**
|
|
5885
|
+
**Returns:**
|
|
5522
5886
|
|
|
5523
5887
|
**Examples:**
|
|
5524
5888
|
|
|
5525
|
-
|
|
5526
|
-
```
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5889
|
+
Рендерер по TS-схеме
|
|
5890
|
+
```tsx
|
|
5891
|
+
const credit = useFormBundle(() =>
|
|
5892
|
+
createForm<CreditForm>({
|
|
5893
|
+
model: createCreditModel(),
|
|
5894
|
+
schema: creditSchema, // (model) => узел
|
|
5895
|
+
behavior: creditBehavior,
|
|
5896
|
+
validation: { steps: { loan: loanRules }, extras: crossRules },
|
|
5897
|
+
})
|
|
5898
|
+
);
|
|
5899
|
+
return <FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />;
|
|
5900
|
+
```
|
|
5530
5901
|
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
}
|
|
5902
|
+
React-руками — та же сборка, разметка в JSX
|
|
5903
|
+
```tsx
|
|
5904
|
+
const credit = useFormBundle(() => createForm<CreditForm>({ initial, schema: creditSchema }));
|
|
5905
|
+
return <FormField control={credit.form.loanType} />;
|
|
5906
|
+
```
|
|
5535
5907
|
|
|
5536
|
-
|
|
5908
|
+
JSON — документ схемы и реестр
|
|
5909
|
+
```tsx
|
|
5910
|
+
const credit = useFormBundle(() =>
|
|
5911
|
+
createForm<CreditForm>({ model: createCreditModel(), schema: creditJson, registry })
|
|
5912
|
+
);
|
|
5913
|
+
return <FormRenderer form={credit} />;
|
|
5914
|
+
```
|
|
5537
5915
|
|
|
5538
|
-
|
|
5539
|
-
const schema = {
|
|
5540
|
-
children: [
|
|
5541
|
-
{ value: model.$.email, component: Input },
|
|
5542
|
-
{ value: model.$.password, component: Input },
|
|
5543
|
-
],
|
|
5544
|
-
};
|
|
5916
|
+
_Source: src/form/form-bundle.ts_
|
|
5545
5917
|
|
|
5546
|
-
|
|
5547
|
-
form.email.setValue('test@mail.com');
|
|
5918
|
+
### CreateFormConfig
|
|
5548
5919
|
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5920
|
+
**Kind:** `interface`
|
|
5921
|
+
|
|
5922
|
+
Конфиг {@link createForm}.
|
|
5923
|
+
|
|
5924
|
+
**Signature:**
|
|
5925
|
+
```typescript
|
|
5926
|
+
export interface CreateFormConfig<T> {
|
|
5927
|
+
/** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
|
|
5928
|
+
initial?: T;
|
|
5929
|
+
/** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
|
|
5930
|
+
model?: FormModel<T>;
|
|
5931
|
+
/**
|
|
5932
|
+
* Схема формы: билдер дерева `(model) => узел` либо документ (JSON) — тогда нужен `registry`.
|
|
5933
|
+
* Билдер, а не готовое дерево: узлы держат ручки модели (`model: model.$.email`), и построить
|
|
5934
|
+
* дерево до модели нечем.
|
|
5935
|
+
*/
|
|
5936
|
+
schema?: ((model: FormModel<T>) => FormSchemaNode) | object;
|
|
5937
|
+
/** Реестр JSON-варианта: превращает документ схемы в дерево. */
|
|
5938
|
+
registry?: SchemaResolver;
|
|
5939
|
+
/** Поведение формы: значения, состояние нод и правила узлов схемы. */
|
|
5940
|
+
behavior?: FormBehavior<T>;
|
|
5941
|
+
/** Правила валидации: готовая схема либо {@link FormValidation} со стратегией и шагами. */
|
|
5942
|
+
validation?: FormValidation<T> | ValidationSchema<T>;
|
|
5943
|
+
/**
|
|
5944
|
+
* Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
|
|
5945
|
+
* построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
|
|
5946
|
+
* инициализации не срабатывают).
|
|
5947
|
+
*/
|
|
5948
|
+
seed?: (model: FormModel<T>) => void;
|
|
5949
|
+
/** Донастройка ПОСЛЕ сборки: получает готовый бандл. */
|
|
5950
|
+
setup?: (bundle: FormBundle<T>) => void;
|
|
5951
|
+
}
|
|
5555
5952
|
```
|
|
5556
5953
|
|
|
5557
|
-
_Source: src/form/
|
|
5954
|
+
_Source: src/form/form-bundle.ts_
|
|
5558
5955
|
|
|
5559
5956
|
### CreateFormConfigBase
|
|
5560
5957
|
|
|
@@ -5581,7 +5978,7 @@ export interface CreateFormConfigBase<T, B> {
|
|
|
5581
5978
|
seed?: (model: FormModel<T>) => void;
|
|
5582
5979
|
/**
|
|
5583
5980
|
* Донастройка ПОСЛЕ сборки. Единственное место для правок, которые обязаны идти следом за
|
|
5584
|
-
*
|
|
5981
|
+
* сборкой формы: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),
|
|
5585
5982
|
* поэтому такой префилл выполняется здесь.
|
|
5586
5983
|
*/
|
|
5587
5984
|
setup?: (bundle: B) => void;
|
|
@@ -5598,12 +5995,14 @@ _Source: src/form/create-core-form.ts_
|
|
|
5598
5995
|
|
|
5599
5996
|
Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
|
|
5600
5997
|
ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
|
|
5601
|
-
модели, привязывает конфиг поля (component/componentProps) из схемы, материализует
|
|
5602
|
-
|
|
5603
|
-
роутинга ошибок) и, при наличии, запускает декларативное
|
|
5998
|
+
модели, привязывает конфиг поля (component/componentProps) из схемы, материализует массивы
|
|
5999
|
+
под-форм как {@link ModelArrayNode} (на любой глубине), заполняет реестр ручка→нода (для
|
|
6000
|
+
`enableWhen`, `apply`/`applyEach` и роутинга ошибок) и, при наличии, запускает декларативное
|
|
6001
|
+
поведение (cleanup живёт на форме).
|
|
5604
6002
|
|
|
5605
|
-
Обычно вызывается
|
|
5606
|
-
нужен
|
|
6003
|
+
Обычно вызывается сборкой одним вызовом (`createForm`): она строит дерево схемы, зовёт эту
|
|
6004
|
+
фабрику и собирает валидацию. Прямой вызов нужен там, где бандл не нужен, — форма элемента
|
|
6005
|
+
массива, тесты, свой слой сборки.
|
|
5607
6006
|
|
|
5608
6007
|
**Signature:**
|
|
5609
6008
|
```typescript
|
|
@@ -5617,7 +6016,7 @@ export function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormPr
|
|
|
5617
6016
|
|
|
5618
6017
|
**Examples:**
|
|
5619
6018
|
|
|
5620
|
-
Форма из модели + схемы (эквивалент `
|
|
6019
|
+
Форма из модели + схемы (эквивалент `createFormFromModel({ model, schema })`)
|
|
5621
6020
|
```typescript
|
|
5622
6021
|
import { createModel, createFormFromModel } from '@reformer/core';
|
|
5623
6022
|
|
|
@@ -5645,7 +6044,7 @@ console.log(model.email); // 'user@mail.com'
|
|
|
5645
6044
|
```
|
|
5646
6045
|
|
|
5647
6046
|
**See also:**
|
|
5648
|
-
-
|
|
6047
|
+
- `createForm` — сборка одним вызовом: модель, форма, валидация и дерево для рендера
|
|
5649
6048
|
|
|
5650
6049
|
_Source: src/form/create-form.ts_
|
|
5651
6050
|
|
|
@@ -5671,6 +6070,12 @@ export interface CreateFormFromModelArgs<T> {
|
|
|
5671
6070
|
* заполнения реестра сигнал→нода; cleanup живёт на форме и вызывается в `form.dispose()`.
|
|
5672
6071
|
*/
|
|
5673
6072
|
behavior?: FormBehavior<T>;
|
|
6073
|
+
/**
|
|
6074
|
+
* Схема-контроллер сборки: через него поведение получает `schema` своей области. Его передаёт
|
|
6075
|
+
* сборка одним вызовом, чтобы правила узлов (`hideWhen`, `onComponentEvent`) дошли до рендерера.
|
|
6076
|
+
* Без него поведение пишет правила в собственный контроллер, который никто не читает.
|
|
6077
|
+
*/
|
|
6078
|
+
schemaController?: SchemaController;
|
|
5674
6079
|
}
|
|
5675
6080
|
```
|
|
5676
6081
|
|
|
@@ -5736,6 +6141,37 @@ i18n.fileSize(1536); // '1,5 КБ'
|
|
|
5736
6141
|
|
|
5737
6142
|
_Source: src/i18n/translator.ts_
|
|
5738
6143
|
|
|
6144
|
+
### createLegacyForm
|
|
6145
|
+
|
|
6146
|
+
**Kind:** `function`
|
|
6147
|
+
|
|
6148
|
+
Создать форму из плоской схемы полей с инлайн-значениями (`value: ''`) — путь ДО архитектуры M1.
|
|
6149
|
+
|
|
6150
|
+
Значения живут в нодах, модели нет: поведение (`defineFormBehavior`) и внешняя валидация
|
|
6151
|
+
(`validateModel`) к такой форме не подключаются. Для нового кода — {@link createFormFromModel}
|
|
6152
|
+
либо сборка одним вызовом.
|
|
6153
|
+
|
|
6154
|
+
**Signature:**
|
|
6155
|
+
```typescript
|
|
6156
|
+
export function createLegacyForm<T>(schema: FormSchema<T>): FormProxy<T>;
|
|
6157
|
+
```
|
|
6158
|
+
|
|
6159
|
+
**Parameters:**
|
|
6160
|
+
- `schema` — - Схема полей формы
|
|
6161
|
+
|
|
6162
|
+
**Returns:** Типизированная форма с Proxy-доступом к полям
|
|
6163
|
+
|
|
6164
|
+
**Examples:**
|
|
6165
|
+
|
|
6166
|
+
```typescript
|
|
6167
|
+
const form = createLegacyForm<UserForm>({
|
|
6168
|
+
email: { value: '', component: Input },
|
|
6169
|
+
password: { value: '', component: Input },
|
|
6170
|
+
});
|
|
6171
|
+
```
|
|
6172
|
+
|
|
6173
|
+
_Source: src/form/create-form.ts_
|
|
6174
|
+
|
|
5739
6175
|
### createLocaleLoader
|
|
5740
6176
|
|
|
5741
6177
|
**Kind:** `function`
|
|
@@ -5863,13 +6299,70 @@ model.get(); // { email: 'a@b.c', profile: { name: 'Ada' }, tag
|
|
|
5863
6299
|
|
|
5864
6300
|
_Source: src/model/create-model.ts_
|
|
5865
6301
|
|
|
6302
|
+
### createSchemaController
|
|
6303
|
+
|
|
6304
|
+
**Kind:** `function`
|
|
6305
|
+
|
|
6306
|
+
Схема-контроллер одной сборки формы.
|
|
6307
|
+
|
|
6308
|
+
**Signature:**
|
|
6309
|
+
```typescript
|
|
6310
|
+
export function createSchemaController(): SchemaController
|
|
6311
|
+
```
|
|
6312
|
+
|
|
6313
|
+
**Examples:**
|
|
6314
|
+
|
|
6315
|
+
```typescript
|
|
6316
|
+
const controller = createSchemaController();
|
|
6317
|
+
const root = controller.scopeOf(model);
|
|
6318
|
+
root.node('mortgage').setHidden(true);
|
|
6319
|
+
|
|
6320
|
+
controller.scopeOf(model.items.at(0)); // область строки — свои селекторы
|
|
6321
|
+
```
|
|
6322
|
+
|
|
6323
|
+
_Source: src/form/schema-controller.ts_
|
|
6324
|
+
|
|
6325
|
+
### createSchemaOverrideMaps
|
|
6326
|
+
|
|
6327
|
+
**Kind:** `function`
|
|
6328
|
+
|
|
6329
|
+
Пустое хранилище переопределений области.
|
|
6330
|
+
|
|
6331
|
+
**Signature:**
|
|
6332
|
+
```typescript
|
|
6333
|
+
export function createSchemaOverrideMaps(): SchemaOverrideMaps
|
|
6334
|
+
```
|
|
6335
|
+
|
|
6336
|
+
_Source: src/form/schema-controller.ts_
|
|
6337
|
+
|
|
6338
|
+
### createSchemaScope
|
|
6339
|
+
|
|
6340
|
+
**Kind:** `function`
|
|
6341
|
+
|
|
6342
|
+
Схема области поверх хранилища: `node(selector)` → управление узлом.
|
|
6343
|
+
|
|
6344
|
+
**Signature:**
|
|
6345
|
+
```typescript
|
|
6346
|
+
export function createSchemaScope(
|
|
6347
|
+
maps: SchemaOverrideMaps = createSchemaOverrideMaps()
|
|
6348
|
+
): SchemaScope
|
|
6349
|
+
```
|
|
6350
|
+
|
|
6351
|
+
**Parameters:**
|
|
6352
|
+
- `maps` — - Хранилище области; по умолчанию создаётся новое.
|
|
6353
|
+
|
|
6354
|
+
_Source: src/form/schema-controller.ts_
|
|
6355
|
+
|
|
5866
6356
|
### cross
|
|
5867
6357
|
|
|
5868
6358
|
**Kind:** `function`
|
|
5869
6359
|
|
|
5870
|
-
Cross-field правило: `fn` получает СНАПШОТ модели
|
|
5871
|
-
|
|
5872
|
-
|
|
6360
|
+
Cross-field правило: `fn` получает СНАПШОТ модели текущей области (`model.get()`) и вешает
|
|
6361
|
+
ошибку на `sig`.
|
|
6362
|
+
|
|
6363
|
+
Область — та модель, с которой запущена схема: в корне прогона это вся форма, а в схеме,
|
|
6364
|
+
подключённой через {@link apply} / {@link applyEach}, — под-модель группы или строки массива.
|
|
6365
|
+
Поэтому правила подформы пишутся один раз и не знают, где она стоит.
|
|
5873
6366
|
|
|
5874
6367
|
**Signature:**
|
|
5875
6368
|
```typescript
|
|
@@ -5922,10 +6415,11 @@ export function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void):
|
|
|
5922
6415
|
**Examples:**
|
|
5923
6416
|
|
|
5924
6417
|
```ts
|
|
5925
|
-
export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
6418
|
+
export const myBehavior = defineFormBehavior<MyForm>(({ model, form, schema }) => {
|
|
5926
6419
|
compute(model.$.total, () => model.price * model.qty);
|
|
5927
6420
|
enableWhen([model.$.city], () => Boolean(model.country));
|
|
5928
6421
|
onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
|
|
6422
|
+
hideWhen(schema.node('delivery'), () => model.country === '');
|
|
5929
6423
|
});
|
|
5930
6424
|
```
|
|
5931
6425
|
|
|
@@ -6019,18 +6513,21 @@ _Source: src/form/behaviors/node.ts_
|
|
|
6019
6513
|
|
|
6020
6514
|
**Kind:** `function`
|
|
6021
6515
|
|
|
6022
|
-
Применить под-правила к КАЖДОМУ элементу
|
|
6023
|
-
|
|
6024
|
-
|
|
6516
|
+
Применить под-правила к КАЖДОМУ элементу массива — прежняя запись {@link applyEach}.
|
|
6517
|
+
|
|
6518
|
+
Колбэк получает под-модель элемента, но области не создаёт: `cross` внутри него видит снапшот
|
|
6519
|
+
корня прогона.
|
|
6025
6520
|
|
|
6026
6521
|
**Signature:**
|
|
6027
6522
|
```typescript
|
|
6028
6523
|
export function each<U extends object>(
|
|
6029
|
-
arr: ModelArray<U>,
|
|
6524
|
+
arr: ArrayBinding<U> | ModelArray<U>,
|
|
6030
6525
|
itemFn: (item: FormModel<U>) => void
|
|
6031
6526
|
): void
|
|
6032
6527
|
```
|
|
6033
6528
|
|
|
6529
|
+
**Deprecated:** Пишите `applyEach(model.$.<массив>, schema)`.
|
|
6530
|
+
|
|
6034
6531
|
_Source: src/form/validation/operators.ts_
|
|
6035
6532
|
|
|
6036
6533
|
### eachLeafSignal
|
|
@@ -6892,13 +7389,57 @@ _Source: src/i18n/message-format.ts_
|
|
|
6892
7389
|
**Signature:**
|
|
6893
7390
|
```typescript
|
|
6894
7391
|
export interface FormBehavior<T> {
|
|
6895
|
-
/**
|
|
6896
|
-
|
|
6897
|
-
|
|
7392
|
+
/**
|
|
7393
|
+
* @internal Запускается сборкой формы после построения нод и заполнения реестра сигнал→нода.
|
|
7394
|
+
* `controller` — схема-контроллер сборки; без него поведение получает собственный, который
|
|
7395
|
+
* никто не читает (модель без рендерера).
|
|
7396
|
+
*/
|
|
7397
|
+
__run(model: FormModel<T>, form: FormProxy<T>, controller?: SchemaController): BehaviorCleanup;
|
|
7398
|
+
}
|
|
6898
7399
|
```
|
|
6899
7400
|
|
|
6900
7401
|
_Source: src/form/behaviors/types.ts_
|
|
6901
7402
|
|
|
7403
|
+
### FormBundle
|
|
7404
|
+
|
|
7405
|
+
**Kind:** `interface`
|
|
7406
|
+
|
|
7407
|
+
Результат {@link createForm}.
|
|
7408
|
+
|
|
7409
|
+
**Signature:**
|
|
7410
|
+
```typescript
|
|
7411
|
+
export interface FormBundle<T> {
|
|
7412
|
+
model: FormModel<T>;
|
|
7413
|
+
form: FormProxy<T>;
|
|
7414
|
+
/** Собранная валидация; есть, если в конфиге заданы правила. */
|
|
7415
|
+
validation?: FormValidationBundle<T>;
|
|
7416
|
+
render: FormRender;
|
|
7417
|
+
}
|
|
7418
|
+
```
|
|
7419
|
+
|
|
7420
|
+
_Source: src/form/form-bundle.ts_
|
|
7421
|
+
|
|
7422
|
+
### FormBundleContext
|
|
7423
|
+
|
|
7424
|
+
**Kind:** `const`
|
|
7425
|
+
|
|
7426
|
+
Контекст сборки. Значение — бандл `createForm`; вне рендерера — `null`.
|
|
7427
|
+
|
|
7428
|
+
**Signature:**
|
|
7429
|
+
```typescript
|
|
7430
|
+
export const FormBundleContext
|
|
7431
|
+
```
|
|
7432
|
+
|
|
7433
|
+
**Examples:**
|
|
7434
|
+
|
|
7435
|
+
```tsx
|
|
7436
|
+
<FormBundleContext.Provider value={bundle}>
|
|
7437
|
+
<MyWizard />
|
|
7438
|
+
</FormBundleContext.Provider>
|
|
7439
|
+
```
|
|
7440
|
+
|
|
7441
|
+
_Source: src/platforms/react/form-bundle-context.ts_
|
|
7442
|
+
|
|
6902
7443
|
### FormBundleLike
|
|
6903
7444
|
|
|
6904
7445
|
**Kind:** `interface`
|
|
@@ -7165,7 +7706,7 @@ interface UserForm {
|
|
|
7165
7706
|
};
|
|
7166
7707
|
}
|
|
7167
7708
|
|
|
7168
|
-
const form =
|
|
7709
|
+
const form = createLegacyForm<UserForm>(schema);
|
|
7169
7710
|
|
|
7170
7711
|
// Доступ к методам GroupNode
|
|
7171
7712
|
await form.validate();
|
|
@@ -7179,6 +7720,30 @@ form.profile.name.setValue('John');
|
|
|
7179
7720
|
|
|
7180
7721
|
_Source: src/form/types/form-proxy.ts_
|
|
7181
7722
|
|
|
7723
|
+
### FormRender
|
|
7724
|
+
|
|
7725
|
+
**Kind:** `interface`
|
|
7726
|
+
|
|
7727
|
+
Часть бандла для рендерера: готовое дерево и схема-контроллер сборки.
|
|
7728
|
+
|
|
7729
|
+
**Signature:**
|
|
7730
|
+
```typescript
|
|
7731
|
+
export interface FormRender {
|
|
7732
|
+
/** Корневой узел дерева схемы; `undefined`, если схема не задана. */
|
|
7733
|
+
readonly tree: FormSchemaNode | undefined;
|
|
7734
|
+
/** Схема-контроллер сборки: области корня, строк массивов и подформ. */
|
|
7735
|
+
readonly controller: SchemaController;
|
|
7736
|
+
/** Управление узлом КОРНЕВОЙ области по его `selector`. */
|
|
7737
|
+
node(selector: string): SchemaNodeControl;
|
|
7738
|
+
/** Обёртка поля из реестра (JSON-вариант). */
|
|
7739
|
+
readonly fieldWrapper?: unknown;
|
|
7740
|
+
/** Граница ошибок из реестра (JSON-вариант). */
|
|
7741
|
+
readonly errorBoundary?: unknown;
|
|
7742
|
+
}
|
|
7743
|
+
```
|
|
7744
|
+
|
|
7745
|
+
_Source: src/form/form-bundle.ts_
|
|
7746
|
+
|
|
7182
7747
|
### FormSchema
|
|
7183
7748
|
|
|
7184
7749
|
**Kind:** `type`
|
|
@@ -7192,7 +7757,7 @@ _Source: src/form/types/form-proxy.ts_
|
|
|
7192
7757
|
Использует NonNullable для корректной обработки опциональных полей.
|
|
7193
7758
|
|
|
7194
7759
|
⚠️ Не путать с {@link FormSchemaNode} — тот описывает **узел дерева** M1-схемы (лист/массив/
|
|
7195
|
-
контейнер), передаваемой в `
|
|
7760
|
+
контейнер), передаваемой в `createFormFromModel({ model, schema })`. `FormSchema` — это data-shaped конфиг
|
|
7196
7761
|
(ключи = поля данных), а не узел дерева.
|
|
7197
7762
|
|
|
7198
7763
|
**Signature:**
|
|
@@ -7248,12 +7813,13 @@ _Source: src/form/types/deep-schema.ts_
|
|
|
7248
7813
|
|
|
7249
7814
|
**Kind:** `interface`
|
|
7250
7815
|
|
|
7251
|
-
Узел единой схемы M1 — layout-дерево, обходимое `
|
|
7816
|
+
Узел единой схемы M1 — layout-дерево, обходимое `createFormFromModel({ model, schema })`
|
|
7252
7817
|
и рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).
|
|
7253
7818
|
|
|
7254
7819
|
Узел совмещает несколько ролей (различаются рантаймом по форме):
|
|
7255
|
-
- **поле** —
|
|
7256
|
-
-
|
|
7820
|
+
- **поле** — `{ model: model.$.x, component, componentProps }`;
|
|
7821
|
+
- **массив под-форм** — `{ model: model.$.items, item: (model) => узел }`;
|
|
7822
|
+
- **подформа** — `{ model: model.$.group, part: (model) => узел }`;
|
|
7257
7823
|
- **контейнер/ветка** — вложенные узлы (`children`), опц. условие `when`;
|
|
7258
7824
|
- **record-of-fields** — под-узлы под произвольными именованными ключами (индексная сигнатура).
|
|
7259
7825
|
|
|
@@ -7264,9 +7830,20 @@ _Source: src/form/types/deep-schema.ts_
|
|
|
7264
7830
|
```typescript
|
|
7265
7831
|
export interface FormSchemaNode {
|
|
7266
7832
|
/**
|
|
7267
|
-
*
|
|
7268
|
-
*
|
|
7269
|
-
*
|
|
7833
|
+
* Привязка узла к части модели — ручка из дерева `model.$`. Чем узел является, решают ручка и
|
|
7834
|
+
* соседние ключи:
|
|
7835
|
+
* - лист или массив (`model.$.email`, `model.$.tags`) — **поле**;
|
|
7836
|
+
* - массив вместе с {@link FormSchemaNode.item} — **массив под-форм**;
|
|
7837
|
+
* - группа вместе с {@link FormSchemaNode.part} — **подформа**.
|
|
7838
|
+
*
|
|
7839
|
+
* Узел распознаётся по значению, а не по имени ключа: в записи «имя поля → узел» поле данных
|
|
7840
|
+
* может называться `model`, и тогда под этим ключом лежит обычный вложенный узел.
|
|
7841
|
+
*/
|
|
7842
|
+
model?: unknown;
|
|
7843
|
+
/**
|
|
7844
|
+
* Прежняя запись привязки поля — то же, что {@link FormSchemaNode.model}.
|
|
7845
|
+
*
|
|
7846
|
+
* @deprecated Пишите `model: model.$.<path>`.
|
|
7270
7847
|
*/
|
|
7271
7848
|
value?: unknown;
|
|
7272
7849
|
/**
|
|
@@ -7296,14 +7873,27 @@ export interface FormSchemaNode {
|
|
|
7296
7873
|
* выводит на своём месте в порядке следования.
|
|
7297
7874
|
*/
|
|
7298
7875
|
children?: readonly (FormSchemaNode | string | number | Signal<any>)[];
|
|
7299
|
-
/**
|
|
7876
|
+
/**
|
|
7877
|
+
* Прежняя запись привязки массива под-форм — фасад `model.<path>` (вместе с `item`).
|
|
7878
|
+
*
|
|
7879
|
+
* @deprecated Пишите `model: model.$.<path>`.
|
|
7880
|
+
*/
|
|
7300
7881
|
array?: SchemaArrayControl;
|
|
7301
7882
|
/** Схема элемента массива: под-модель элемента → узел поддерева. */
|
|
7302
|
-
item?: (
|
|
7883
|
+
item?: (model: any) => FormSchemaNode;
|
|
7303
7884
|
/**
|
|
7304
|
-
*
|
|
7305
|
-
*
|
|
7306
|
-
*
|
|
7885
|
+
* Подформа: под-модель группы → узел поддерева. Часть объявляется один раз и подключается к
|
|
7886
|
+
* любой группе той же формы данных: `{ model: model.$.registrationAddress, part: address }`.
|
|
7887
|
+
* Привязки внутри части идут через `$` полученной под-модели.
|
|
7888
|
+
*/
|
|
7889
|
+
part?: (model: any) => FormSchemaNode;
|
|
7890
|
+
/**
|
|
7891
|
+
* Шаблон нового элемента массива для кнопки «Добавить»: либо готовое значение, либо фабрика
|
|
7892
|
+
* `() => value`. Запасной путь — для форм, чья модель создаётся из данных без кода: шаблон,
|
|
7893
|
+
* объявленный в модели (`arrayOf(blank)`), главнее.
|
|
7894
|
+
*
|
|
7895
|
+
* Тип не различает варианты (union `unknown | (() => unknown)` схлопывается в `unknown`) —
|
|
7896
|
+
* рантайм различает по `typeof initialValue === 'function'`.
|
|
7307
7897
|
*/
|
|
7308
7898
|
initialValue?: unknown;
|
|
7309
7899
|
/** Свободная вложенность: record-of-fields и произвольные под-узлы. */
|
|
@@ -7522,21 +8112,22 @@ _Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
|
7522
8112
|
|
|
7523
8113
|
**Kind:** `function`
|
|
7524
8114
|
|
|
7525
|
-
Найти ноду формы по
|
|
8115
|
+
Найти ноду формы по ручке модели.
|
|
7526
8116
|
|
|
7527
|
-
Используется state-операциями behavior (`enableWhen`/`disableWhen`)
|
|
7528
|
-
валидации в ноды. Возвращает `undefined`, если форма
|
|
7529
|
-
|
|
8117
|
+
Используется state-операциями behavior (`enableWhen`/`disableWhen`), подключением под-схем
|
|
8118
|
+
(`apply`/`applyEach`) и роутингом ошибок валидации в ноды. Возвращает `undefined`, если форма
|
|
8119
|
+
ещё не построена или эта часть модели в форме не материализована (например, массив без
|
|
8120
|
+
item-схемы).
|
|
7530
8121
|
|
|
7531
8122
|
**Signature:**
|
|
7532
8123
|
```typescript
|
|
7533
|
-
export function getNodeForSignal(signal:
|
|
8124
|
+
export function getNodeForSignal(signal: object): FormNode<any> | undefined
|
|
7534
8125
|
```
|
|
7535
8126
|
|
|
7536
8127
|
**Parameters:**
|
|
7537
|
-
- `signal` — -
|
|
8128
|
+
- `signal` — - Ручка из {@link FormModel}: `model.$.<поле>`, `model.$.<группа>`, `model.$.<массив>`
|
|
7538
8129
|
|
|
7539
|
-
**Returns:** Нода формы
|
|
8130
|
+
**Returns:** Нода формы — поля, группы или массива — либо `undefined`
|
|
7540
8131
|
|
|
7541
8132
|
**Examples:**
|
|
7542
8133
|
|
|
@@ -7548,8 +8139,14 @@ const sig = model.$.email;
|
|
|
7548
8139
|
getNodeForSignal(sig)?.setErrors([{ code: 'required', message: 'Обязательно' }]);
|
|
7549
8140
|
```
|
|
7550
8141
|
|
|
8142
|
+
Нода группы и строки массива
|
|
8143
|
+
```typescript
|
|
8144
|
+
getNodeForSignal(model.$.address)?.disable(); // группа целиком
|
|
8145
|
+
getNodeForSignal(model.$.phones[0])?.markAsTouched(); // форма строки массива
|
|
8146
|
+
```
|
|
8147
|
+
|
|
7551
8148
|
**See also:**
|
|
7552
|
-
- {@link registerSignalNode} - регистрация связи
|
|
8149
|
+
- {@link registerSignalNode} - регистрация связи ручка→нода
|
|
7553
8150
|
|
|
7554
8151
|
_Source: src/form/signal-node-registry.ts_
|
|
7555
8152
|
|
|
@@ -7583,7 +8180,7 @@ _Source: src/form/type-guards.ts_
|
|
|
7583
8180
|
|
|
7584
8181
|
**Kind:** `function`
|
|
7585
8182
|
|
|
7586
|
-
|
|
8183
|
+
Текущая область ({ model, form, schema }) активной схемы (escape hatch для кросс-операторов).
|
|
7587
8184
|
|
|
7588
8185
|
**Signature:**
|
|
7589
8186
|
```typescript
|
|
@@ -7599,7 +8196,7 @@ _Source: src/form/behaviors/context.ts_
|
|
|
7599
8196
|
GroupNode - узел для группы полей
|
|
7600
8197
|
|
|
7601
8198
|
Создаётся из {@link FormSchema} (дерево field-конфигов). Обычно строится через `createForm`
|
|
7602
|
-
(M1: `
|
|
8199
|
+
(M1: `createFormFromModel({ model, schema })`); schema-валидация/behavior живут на слое модели
|
|
7603
8200
|
(`validateModel` из `@reformer/core/validation`, `computeFrom`/`enableWhen`/…), а не на ноде.
|
|
7604
8201
|
|
|
7605
8202
|
**Signature:**
|
|
@@ -7646,6 +8243,28 @@ export interface GroupNodeConfig<T> {
|
|
|
7646
8243
|
|
|
7647
8244
|
_Source: src/form/types/index.ts_
|
|
7648
8245
|
|
|
8246
|
+
### hideWhen
|
|
8247
|
+
|
|
8248
|
+
**Kind:** `function`
|
|
8249
|
+
|
|
8250
|
+
Скрывать узел, пока условие истинно.
|
|
8251
|
+
|
|
8252
|
+
Условие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.
|
|
8253
|
+
Принудительное `schema.node(selector).setHidden(...)` главнее условия.
|
|
8254
|
+
|
|
8255
|
+
**Signature:**
|
|
8256
|
+
```typescript
|
|
8257
|
+
export function hideWhen(node: SchemaNodeControl, condition: () => boolean): void
|
|
8258
|
+
```
|
|
8259
|
+
|
|
8260
|
+
**Examples:**
|
|
8261
|
+
|
|
8262
|
+
```typescript
|
|
8263
|
+
hideWhen(schema.node('mortgage'), () => model.loanType !== 'mortgage');
|
|
8264
|
+
```
|
|
8265
|
+
|
|
8266
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
8267
|
+
|
|
7649
8268
|
### I18N_CONTEXT_MARKER
|
|
7650
8269
|
|
|
7651
8270
|
**Kind:** `const`
|
|
@@ -9137,10 +9756,13 @@ export interface ModelArray<U> {
|
|
|
9137
9756
|
readonly __path: string;
|
|
9138
9757
|
/** Реактивная длина. */
|
|
9139
9758
|
readonly length: number;
|
|
9140
|
-
/**
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9759
|
+
/**
|
|
9760
|
+
* Добавить элемент в конец (значение элемента целиком). Без значения — элемент по шаблону
|
|
9761
|
+
* массива (`arrayOf(blank)` в модели); шаблона нет — ошибка.
|
|
9762
|
+
*/
|
|
9763
|
+
push(item?: U): void;
|
|
9764
|
+
/** Вставить элемент по индексу. Без значения — элемент по шаблону, как у {@link push}. */
|
|
9765
|
+
insertAt(index: number, item?: U): void;
|
|
9144
9766
|
/** Удалить элемент по индексу. */
|
|
9145
9767
|
removeAt(index: number): void;
|
|
9146
9768
|
/** Переместить элемент. */
|
|
@@ -9177,8 +9799,8 @@ _Source: src/model/types.ts_
|
|
|
9177
9799
|
export interface ModelArrayControl<TItem extends object> {
|
|
9178
9800
|
readonly length: number;
|
|
9179
9801
|
at(index: number): FormModel<TItem> | undefined;
|
|
9180
|
-
push(item
|
|
9181
|
-
insertAt(index: number, item
|
|
9802
|
+
push(item?: TItem): void;
|
|
9803
|
+
insertAt(index: number, item?: TItem): void;
|
|
9182
9804
|
removeAt(index: number): void;
|
|
9183
9805
|
move(from: number, to: number): void;
|
|
9184
9806
|
swap(a: number, b: number): void;
|
|
@@ -9202,7 +9824,7 @@ _Source: src/form/nodes/model-array-node.ts_
|
|
|
9202
9824
|
reorder/повторном рендере не пересоздаются (состояние и валидация сохраняются). Реализует тот же
|
|
9203
9825
|
контракт, что ждут секции массива и `useFormControl` (`length`/`value`/`valid`/`errors`/`at`/`push`/…).
|
|
9204
9826
|
|
|
9205
|
-
Обычно создаётся не напрямую, а `
|
|
9827
|
+
Обычно создаётся не напрямую, а `createFormFromModel({ model, schema })`: когда в схеме встречается узел
|
|
9206
9828
|
массива `{ array: model.<field>, item: (item) => itemSchema }`, форма материализует его как
|
|
9207
9829
|
`ModelArrayNode` и кладёт под `form.<field>` (совместим с `FormArraySection`).
|
|
9208
9830
|
|
|
@@ -9222,7 +9844,7 @@ name: { value: item.$.name, component: Input },
|
|
|
9222
9844
|
qty: { value: item.$.qty, component: Input },
|
|
9223
9845
|
});
|
|
9224
9846
|
|
|
9225
|
-
const form =
|
|
9847
|
+
const form = createFormFromModel({
|
|
9226
9848
|
model,
|
|
9227
9849
|
schema: {
|
|
9228
9850
|
children: [{ array: model.rows, item: rowItem }],
|
|
@@ -9300,6 +9922,57 @@ export type ModelObject<T> = {
|
|
|
9300
9922
|
|
|
9301
9923
|
_Source: src/model/types.ts_
|
|
9302
9924
|
|
|
9925
|
+
### modelOf
|
|
9926
|
+
|
|
9927
|
+
**Kind:** `function`
|
|
9928
|
+
|
|
9929
|
+
Value-фасад по ручке дерева `model.$`: группа → под-модель {@link FormModel}, массив →
|
|
9930
|
+
{@link ModelArray}. Обратная операция к `model.$`: `modelOf(model.$.address) === model.address`.
|
|
9931
|
+
|
|
9932
|
+
Ручка находит фасад по ИДЕНТИЧНОСТИ, а не по пути. Путь для этого не годится: он абсолютный
|
|
9933
|
+
(`items.0.phones`) и меняется при перестановке строк, а области поведения и формы строк
|
|
9934
|
+
вложенные — поиск по пути от под-модели строки ничего не находит.
|
|
9935
|
+
|
|
9936
|
+
Лист ручкой контейнера не является — функция бросает. В частности, группа или массив,
|
|
9937
|
+
созданные из начального `null`, в модели — лист: вид узла фиксируется при создании.
|
|
9938
|
+
|
|
9939
|
+
**Signature:**
|
|
9940
|
+
```typescript
|
|
9941
|
+
export function modelOf<V extends object | null | undefined>(handle: { peek(): V }): ModelOf<V>
|
|
9942
|
+
```
|
|
9943
|
+
|
|
9944
|
+
**Parameters:**
|
|
9945
|
+
- `handle` — - Узел-группа или узел-массив дерева `model.$`.
|
|
9946
|
+
|
|
9947
|
+
**Returns:** Под-модель группы либо фасад массива.
|
|
9948
|
+
|
|
9949
|
+
**Examples:**
|
|
9950
|
+
|
|
9951
|
+
```typescript
|
|
9952
|
+
const model = createModel({ address: { city: '' }, phones: [{ number: '' }] });
|
|
9953
|
+
|
|
9954
|
+
modelOf(model.$.address).city = 'Казань'; // под-модель: чтение, запись, get/set/patch
|
|
9955
|
+
modelOf(model.$.phones).push({ number: '+7' }); // фасад массива: мутации и обход
|
|
9956
|
+
modelOf(model.$.phones[0]).number; // строка массива — тоже группа
|
|
9957
|
+
```
|
|
9958
|
+
|
|
9959
|
+
_Source: src/model/model-value-proxy.ts_
|
|
9960
|
+
|
|
9961
|
+
### ModelOf
|
|
9962
|
+
|
|
9963
|
+
**Kind:** `type`
|
|
9964
|
+
|
|
9965
|
+
Value-фасад для ручки {@link modelOf}: массив → {@link ModelArray}, объект → под-модель
|
|
9966
|
+
{@link FormModel}.
|
|
9967
|
+
|
|
9968
|
+
**Signature:**
|
|
9969
|
+
```typescript
|
|
9970
|
+
export type ModelOf<V> =
|
|
9971
|
+
NonNullable<V> extends ReadonlyArray<infer U> ? ModelArray<U> : FormModel<NonNullable<V>>;
|
|
9972
|
+
```
|
|
9973
|
+
|
|
9974
|
+
_Source: src/model/model-value-proxy.ts_
|
|
9975
|
+
|
|
9303
9976
|
### ModelSignals
|
|
9304
9977
|
|
|
9305
9978
|
**Kind:** `type`
|
|
@@ -9478,6 +10151,25 @@ const node = factory.createNode({ value: '', component: Input }); // → FieldNo
|
|
|
9478
10151
|
|
|
9479
10152
|
_Source: src/form/factories/node-factory.ts_
|
|
9480
10153
|
|
|
10154
|
+
### NodeLifecycleHooks
|
|
10155
|
+
|
|
10156
|
+
**Kind:** `interface`
|
|
10157
|
+
|
|
10158
|
+
Хуки жизненного цикла узла схемы. Каждый хук опционален; повторная регистрация перезаписывает
|
|
10159
|
+
предыдущее значение.
|
|
10160
|
+
|
|
10161
|
+
**Signature:**
|
|
10162
|
+
```typescript
|
|
10163
|
+
export interface NodeLifecycleHooks {
|
|
10164
|
+
/** Срабатывает один раз при mount узла. Может вернуть cleanup-функцию. */
|
|
10165
|
+
onMount?: () => void | (() => void);
|
|
10166
|
+
/** Срабатывает один раз при unmount узла. */
|
|
10167
|
+
onUnmount?: () => void;
|
|
10168
|
+
}
|
|
10169
|
+
```
|
|
10170
|
+
|
|
10171
|
+
_Source: src/form/schema-controller.ts_
|
|
10172
|
+
|
|
9481
10173
|
### nonNegative
|
|
9482
10174
|
|
|
9483
10175
|
**Kind:** `function`
|
|
@@ -9572,6 +10264,34 @@ export function onChange<T>(
|
|
|
9572
10264
|
|
|
9573
10265
|
_Source: src/form/behaviors/operators.ts_
|
|
9574
10266
|
|
|
10267
|
+
### onComponentEvent
|
|
10268
|
+
|
|
10269
|
+
**Kind:** `function`
|
|
10270
|
+
|
|
10271
|
+
Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).
|
|
10272
|
+
|
|
10273
|
+
Обработчик получает те же аргументы, что и исходный проп компонента, и перекрывает одноимённый
|
|
10274
|
+
проп из `componentProps`.
|
|
10275
|
+
|
|
10276
|
+
**Signature:**
|
|
10277
|
+
```typescript
|
|
10278
|
+
export function onComponentEvent(
|
|
10279
|
+
node: SchemaNodeControl,
|
|
10280
|
+
event: string,
|
|
10281
|
+
handler: (...args: any[]) => any
|
|
10282
|
+
): void
|
|
10283
|
+
```
|
|
10284
|
+
|
|
10285
|
+
**Examples:**
|
|
10286
|
+
|
|
10287
|
+
```typescript
|
|
10288
|
+
onComponentEvent(schema.node('wizard'), 'onSubmit', async () => {
|
|
10289
|
+
await submitApplication(model.get());
|
|
10290
|
+
});
|
|
10291
|
+
```
|
|
10292
|
+
|
|
10293
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10294
|
+
|
|
9575
10295
|
### onDispose
|
|
9576
10296
|
|
|
9577
10297
|
**Kind:** `function`
|
|
@@ -9585,6 +10305,70 @@ export function onDispose(cleanup: BehaviorCleanup): void
|
|
|
9585
10305
|
|
|
9586
10306
|
_Source: src/form/behaviors/context.ts_
|
|
9587
10307
|
|
|
10308
|
+
### onInit
|
|
10309
|
+
|
|
10310
|
+
**Kind:** `function`
|
|
10311
|
+
|
|
10312
|
+
Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла. Единственный
|
|
10313
|
+
хук, способный повлиять на первый рендер, — внутри можно править пропсы через
|
|
10314
|
+
`schema.node(selector).patchProps({ … })`.
|
|
10315
|
+
|
|
10316
|
+
К монтированию узла не привязан; аргумент `node` принимается ради симметрии с
|
|
10317
|
+
{@link onMount} / {@link onUnmount}.
|
|
10318
|
+
|
|
10319
|
+
**Signature:**
|
|
10320
|
+
```typescript
|
|
10321
|
+
export function onInit(_node: SchemaNodeControl, fn: () => void): void
|
|
10322
|
+
```
|
|
10323
|
+
|
|
10324
|
+
**Examples:**
|
|
10325
|
+
|
|
10326
|
+
```typescript
|
|
10327
|
+
onInit(schema.node('summary'), () => {
|
|
10328
|
+
schema.node('summary').patchProps({ currency: detectCurrency() });
|
|
10329
|
+
});
|
|
10330
|
+
```
|
|
10331
|
+
|
|
10332
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10333
|
+
|
|
10334
|
+
### onMount
|
|
10335
|
+
|
|
10336
|
+
**Kind:** `function`
|
|
10337
|
+
|
|
10338
|
+
Хук после первого монтирования узла. Может вернуть очистку — она выполнится при размонтировании.
|
|
10339
|
+
|
|
10340
|
+
**Signature:**
|
|
10341
|
+
```typescript
|
|
10342
|
+
export function onMount(node: SchemaNodeControl, fn: () => void | (() => void)): void
|
|
10343
|
+
```
|
|
10344
|
+
|
|
10345
|
+
**Examples:**
|
|
10346
|
+
|
|
10347
|
+
```typescript
|
|
10348
|
+
onMount(schema.node('data-boundary'), () => void loadApplication());
|
|
10349
|
+
```
|
|
10350
|
+
|
|
10351
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10352
|
+
|
|
10353
|
+
### onUnmount
|
|
10354
|
+
|
|
10355
|
+
**Kind:** `function`
|
|
10356
|
+
|
|
10357
|
+
Хук перед размонтированием узла.
|
|
10358
|
+
|
|
10359
|
+
**Signature:**
|
|
10360
|
+
```typescript
|
|
10361
|
+
export function onUnmount(node: SchemaNodeControl, fn: () => void): void
|
|
10362
|
+
```
|
|
10363
|
+
|
|
10364
|
+
**Examples:**
|
|
10365
|
+
|
|
10366
|
+
```typescript
|
|
10367
|
+
onUnmount(schema.node('wizard'), () => saveDraft(model.get()));
|
|
10368
|
+
```
|
|
10369
|
+
|
|
10370
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10371
|
+
|
|
9588
10372
|
### parseMessage
|
|
9589
10373
|
|
|
9590
10374
|
**Kind:** `function`
|
|
@@ -9780,19 +10564,19 @@ _Source: src/form/validators/phone.ts_
|
|
|
9780
10564
|
|
|
9781
10565
|
**Kind:** `function`
|
|
9782
10566
|
|
|
9783
|
-
Связать
|
|
10567
|
+
Связать ручку модели с её нодой формы.
|
|
9784
10568
|
|
|
9785
|
-
Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого
|
|
9786
|
-
|
|
10569
|
+
Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого поля,
|
|
10570
|
+
группы и массива под-форм. Прикладной код обычно этот реестр не заполняет напрямую.
|
|
9787
10571
|
|
|
9788
10572
|
**Signature:**
|
|
9789
10573
|
```typescript
|
|
9790
|
-
export function registerSignalNode(signal:
|
|
10574
|
+
export function registerSignalNode(signal: object, node: FormNode<any>): void
|
|
9791
10575
|
```
|
|
9792
10576
|
|
|
9793
10577
|
**Parameters:**
|
|
9794
|
-
- `signal` — -
|
|
9795
|
-
- `node` — - Нода формы, отвечающая за
|
|
10578
|
+
- `signal` — - Ручка из {@link FormModel}: сигнал листа, узел-массив или узел-группа дерева `model.$`
|
|
10579
|
+
- `node` — - Нода формы, отвечающая за эту часть модели
|
|
9796
10580
|
|
|
9797
10581
|
**Examples:**
|
|
9798
10582
|
|
|
@@ -9806,10 +10590,35 @@ if (sig && node) registerSignalNode(sig, node);
|
|
|
9806
10590
|
```
|
|
9807
10591
|
|
|
9808
10592
|
**See also:**
|
|
9809
|
-
- {@link getNodeForSignal} - обратный поиск ноды по
|
|
10593
|
+
- {@link getNodeForSignal} - обратный поиск ноды по ручке
|
|
9810
10594
|
|
|
9811
10595
|
_Source: src/form/signal-node-registry.ts_
|
|
9812
10596
|
|
|
10597
|
+
### renderEffect
|
|
10598
|
+
|
|
10599
|
+
**Kind:** `function`
|
|
10600
|
+
|
|
10601
|
+
Реактивный эффект, живущий, пока схема области смонтирована рендерером.
|
|
10602
|
+
|
|
10603
|
+
В отличие от `effect` поведения, запускается после монтирования — внутри доступны ref узлов
|
|
10604
|
+
(`schema.node(selector).getRef().current`). Может вернуть функцию очистки.
|
|
10605
|
+
|
|
10606
|
+
**Signature:**
|
|
10607
|
+
```typescript
|
|
10608
|
+
export function renderEffect(schema: SchemaScope, effectFn: () => void | (() => void)): void
|
|
10609
|
+
```
|
|
10610
|
+
|
|
10611
|
+
**Examples:**
|
|
10612
|
+
|
|
10613
|
+
```typescript
|
|
10614
|
+
const wizard = schema.node('wizard').getRef<FormWizardHandle>();
|
|
10615
|
+
renderEffect(schema, () => {
|
|
10616
|
+
if (model.loanType === 'mortgage') wizard.current?.goToStep(1);
|
|
10617
|
+
});
|
|
10618
|
+
```
|
|
10619
|
+
|
|
10620
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10621
|
+
|
|
9813
10622
|
### required
|
|
9814
10623
|
|
|
9815
10624
|
**Kind:** `function`
|
|
@@ -9874,6 +10683,27 @@ resetWhen(model.$.cardNumber, () => model.paymentType !== 'card', { resetValue:
|
|
|
9874
10683
|
|
|
9875
10684
|
_Source: src/model/behaviors-value.ts_
|
|
9876
10685
|
|
|
10686
|
+
### ResolvedSchema
|
|
10687
|
+
|
|
10688
|
+
**Kind:** `interface`
|
|
10689
|
+
|
|
10690
|
+
Дерево, собранное реестром из документа схемы (JSON), и то, что реестр хочет донести до
|
|
10691
|
+
рендерера вместе с ним.
|
|
10692
|
+
|
|
10693
|
+
**Signature:**
|
|
10694
|
+
```typescript
|
|
10695
|
+
export interface ResolvedSchema {
|
|
10696
|
+
/** Корневой узел дерева. */
|
|
10697
|
+
tree: FormSchemaNode;
|
|
10698
|
+
/** Обёртка поля — компонент, которым рендерер оборачивает каждое поле. */
|
|
10699
|
+
fieldWrapper?: unknown;
|
|
10700
|
+
/** Граница ошибок — компонент, которым рендерер оборачивает дерево. */
|
|
10701
|
+
errorBoundary?: unknown;
|
|
10702
|
+
}
|
|
10703
|
+
```
|
|
10704
|
+
|
|
10705
|
+
_Source: src/form/form-bundle.ts_
|
|
10706
|
+
|
|
9877
10707
|
### resolveLocalized
|
|
9878
10708
|
|
|
9879
10709
|
**Kind:** `function`
|
|
@@ -9961,7 +10791,8 @@ _Source: src/model/behaviors-value.ts_
|
|
|
9961
10791
|
(`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`
|
|
9962
10792
|
ОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля
|
|
9963
10793
|
(`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).
|
|
9964
|
-
На вызове раннер приводит правило к callable и передаёт `(value,
|
|
10794
|
+
На вызове раннер приводит правило к callable и передаёт `(value, scope, root)`: модель текущей
|
|
10795
|
+
области (в корне прогона — вся модель) и корень прогона.
|
|
9965
10796
|
|
|
9966
10797
|
**Signature:**
|
|
9967
10798
|
```typescript
|
|
@@ -10097,13 +10928,155 @@ export interface SchemaArrayControl {
|
|
|
10097
10928
|
/** Реактивная длина. */
|
|
10098
10929
|
readonly length: number;
|
|
10099
10930
|
at(index: number): unknown;
|
|
10100
|
-
push(item
|
|
10931
|
+
push(item?: unknown): void;
|
|
10101
10932
|
removeAt(index: number): void;
|
|
10102
10933
|
}
|
|
10103
10934
|
```
|
|
10104
10935
|
|
|
10105
10936
|
_Source: src/form/types/schema-node.ts_
|
|
10106
10937
|
|
|
10938
|
+
### SchemaController
|
|
10939
|
+
|
|
10940
|
+
**Kind:** `interface`
|
|
10941
|
+
|
|
10942
|
+
Схема-контроллер сборки: области по под-моделям.
|
|
10943
|
+
|
|
10944
|
+
**Signature:**
|
|
10945
|
+
```typescript
|
|
10946
|
+
export interface SchemaController {
|
|
10947
|
+
/**
|
|
10948
|
+
* Область под-модели: корня сборки, строки массива или группы подформы. Создаётся при первом
|
|
10949
|
+
* обращении; повторное обращение отдаёт ту же область.
|
|
10950
|
+
*/
|
|
10951
|
+
scopeOf(subModel: object): SchemaScope;
|
|
10952
|
+
}
|
|
10953
|
+
```
|
|
10954
|
+
|
|
10955
|
+
_Source: src/form/schema-controller.ts_
|
|
10956
|
+
|
|
10957
|
+
### SchemaNodeControl
|
|
10958
|
+
|
|
10959
|
+
**Kind:** `interface`
|
|
10960
|
+
|
|
10961
|
+
Управление одним узлом схемы. Получается через `schema.node(selector)`.
|
|
10962
|
+
|
|
10963
|
+
**Signature:**
|
|
10964
|
+
```typescript
|
|
10965
|
+
export interface SchemaNodeControl {
|
|
10966
|
+
/** Принудительно скрыть или показать узел, минуя условие `hideWhen`. */
|
|
10967
|
+
setHidden(value: boolean): this;
|
|
10968
|
+
/** Убрать принудительное значение — снова действует условие `hideWhen`. */
|
|
10969
|
+
resetHidden(): this;
|
|
10970
|
+
/** Подмешать объект в `componentProps` узла. */
|
|
10971
|
+
patchProps(partial: Record<string, unknown>): this;
|
|
10972
|
+
/** Убрать подмешанные пропсы. */
|
|
10973
|
+
resetProps(): this;
|
|
10974
|
+
/**
|
|
10975
|
+
* Ref на компонент узла. Создаётся один раз на селектор; рендерер вешает его на компонент.
|
|
10976
|
+
* Компонент обязан принимать `ref`.
|
|
10977
|
+
*/
|
|
10978
|
+
getRef<H>(): { current: H | null };
|
|
10979
|
+
/** @internal Селектор узла. */
|
|
10980
|
+
__selector: string;
|
|
10981
|
+
/** @internal Хранилище области. */
|
|
10982
|
+
__overrideMaps: SchemaOverrideMaps;
|
|
10983
|
+
}
|
|
10984
|
+
```
|
|
10985
|
+
|
|
10986
|
+
_Source: src/form/schema-controller.ts_
|
|
10987
|
+
|
|
10988
|
+
### SchemaOverrideMaps
|
|
10989
|
+
|
|
10990
|
+
**Kind:** `interface`
|
|
10991
|
+
|
|
10992
|
+
Хранилище переопределений одной области схемы. Ключ каждой карты — `selector` узла.
|
|
10993
|
+
|
|
10994
|
+
**Signature:**
|
|
10995
|
+
```typescript
|
|
10996
|
+
export interface SchemaOverrideMaps {
|
|
10997
|
+
hiddenOverrides: Map<string, boolean | null>;
|
|
10998
|
+
propsOverrides: Map<string, Record<string, unknown> | null>;
|
|
10999
|
+
refRegistry: Map<string, { current: any }>;
|
|
11000
|
+
/** Условия скрытия узлов: selector → реактивное условие. */
|
|
11001
|
+
conditionRegistry: Map<string, () => boolean>;
|
|
11002
|
+
/** Реактивные эффекты, живущие, пока область смонтирована (`renderEffect`). */
|
|
11003
|
+
effectRegistry: Array<() => void | (() => void)>;
|
|
11004
|
+
/** Обработчики событий компонентов: selector → { имя пропа → обработчик }. */
|
|
11005
|
+
callbackRegistry: Map<string, Map<string, (...args: any[]) => any>>;
|
|
11006
|
+
/** Хуки жизненного цикла узла: selector → { onMount, onUnmount }. */
|
|
11007
|
+
lifecycleRegistry: Map<string, NodeLifecycleHooks>;
|
|
11008
|
+
/** Общий счётчик изменений области. Узлы на него не подписываются — см. `versionFor`. */
|
|
11009
|
+
version: Signal<number>;
|
|
11010
|
+
/**
|
|
11011
|
+
* Версия-сигнал одного селектора (создаётся лениво). Узел подписывается только на сигнал СВОЕГО
|
|
11012
|
+
* селектора, поэтому `setHidden` / `patchProps` уведомляют один узел, а не всё дерево.
|
|
11013
|
+
*/
|
|
11014
|
+
versionFor(selector: string): Signal<number>;
|
|
11015
|
+
}
|
|
11016
|
+
```
|
|
11017
|
+
|
|
11018
|
+
_Source: src/form/schema-controller.ts_
|
|
11019
|
+
|
|
11020
|
+
### SchemaResolver
|
|
11021
|
+
|
|
11022
|
+
**Kind:** `interface`
|
|
11023
|
+
|
|
11024
|
+
Реестр, умеющий превратить документ схемы в дерево. Метод необязателен: у интерфейса реестра
|
|
11025
|
+
есть сторонние реализации, и ядро документ само не разбирает.
|
|
11026
|
+
|
|
11027
|
+
**Signature:**
|
|
11028
|
+
```typescript
|
|
11029
|
+
export interface SchemaResolver {
|
|
11030
|
+
resolveSchema?(document: unknown, model: FormModel<never>): ResolvedSchema;
|
|
11031
|
+
}
|
|
11032
|
+
```
|
|
11033
|
+
|
|
11034
|
+
_Source: src/form/form-bundle.ts_
|
|
11035
|
+
|
|
11036
|
+
### SchemaScope
|
|
11037
|
+
|
|
11038
|
+
**Kind:** `interface`
|
|
11039
|
+
|
|
11040
|
+
Схема одной области: адресует узлы по `selector`.
|
|
11041
|
+
|
|
11042
|
+
**Signature:**
|
|
11043
|
+
```typescript
|
|
11044
|
+
export interface SchemaScope {
|
|
11045
|
+
/** Управление узлом области по его `selector`. */
|
|
11046
|
+
node(selector: string): SchemaNodeControl;
|
|
11047
|
+
/** @internal Хранилище области — его читает рендерер. */
|
|
11048
|
+
__overrideMaps: SchemaOverrideMaps;
|
|
11049
|
+
}
|
|
11050
|
+
```
|
|
11051
|
+
|
|
11052
|
+
_Source: src/form/schema-controller.ts_
|
|
11053
|
+
|
|
11054
|
+
### schemaSubtree
|
|
11055
|
+
|
|
11056
|
+
**Kind:** `function`
|
|
11057
|
+
|
|
11058
|
+
Поддерево схемы для под-модели — результат `builder(subModel)`, один на пару.
|
|
11059
|
+
|
|
11060
|
+
**Signature:**
|
|
11061
|
+
```typescript
|
|
11062
|
+
export function schemaSubtree<N>(builder: (subModel: any) => N, subModel: object): N
|
|
11063
|
+
```
|
|
11064
|
+
|
|
11065
|
+
**Parameters:**
|
|
11066
|
+
- `builder` — - Билдер поддерева: `item` узла-массива или `part` узла-подформы.
|
|
11067
|
+
- `subModel` — - Под-модель строки массива или группы.
|
|
11068
|
+
|
|
11069
|
+
**Returns:** Узел поддерева; повторный вызов с той же парой отдаёт тот же объект.
|
|
11070
|
+
|
|
11071
|
+
**Examples:**
|
|
11072
|
+
|
|
11073
|
+
```typescript
|
|
11074
|
+
const row = model.items.at(0);
|
|
11075
|
+
schemaSubtree(itemSchema, row) === schemaSubtree(itemSchema, row); // true
|
|
11076
|
+
```
|
|
11077
|
+
|
|
11078
|
+
_Source: src/form/schema-subtree.ts_
|
|
11079
|
+
|
|
10107
11080
|
### SetValueOptions
|
|
10108
11081
|
|
|
10109
11082
|
**Kind:** `interface`
|
|
@@ -10332,6 +11305,36 @@ export type UnknownRecord = Record<string, unknown>;
|
|
|
10332
11305
|
|
|
10333
11306
|
_Source: src/form/types/index.ts_
|
|
10334
11307
|
|
|
11308
|
+
### unknownSchemaSelectors
|
|
11309
|
+
|
|
11310
|
+
**Kind:** `function`
|
|
11311
|
+
|
|
11312
|
+
Селекторы, на которые в области записаны правила, но которых нет в её дереве. Такое правило
|
|
11313
|
+
ничего не делает — сборка и рендерер в dev предупреждают о нём.
|
|
11314
|
+
|
|
11315
|
+
Ref в проверку не входит: его разрешено брать по абсолютному пути модели
|
|
11316
|
+
(`phones.0.number`), а такой строки при сборке может ещё не быть.
|
|
11317
|
+
|
|
11318
|
+
**Signature:**
|
|
11319
|
+
```typescript
|
|
11320
|
+
export function unknownSchemaSelectors(maps: SchemaOverrideMaps, tree: unknown): string[]
|
|
11321
|
+
```
|
|
11322
|
+
|
|
11323
|
+
**Parameters:**
|
|
11324
|
+
- `maps` — - Хранилище области.
|
|
11325
|
+
- `tree` — - Дерево (поддерево) области.
|
|
11326
|
+
|
|
11327
|
+
**Returns:** Селекторы с правилами, не найденные в дереве.
|
|
11328
|
+
|
|
11329
|
+
**Examples:**
|
|
11330
|
+
|
|
11331
|
+
```typescript
|
|
11332
|
+
const scope = controller.scopeOf(model);
|
|
11333
|
+
unknownSchemaSelectors(scope.__overrideMaps, tree); // ['mortgage'] — узла с таким selector нет
|
|
11334
|
+
```
|
|
11335
|
+
|
|
11336
|
+
_Source: src/form/schema-controller.ts_
|
|
11337
|
+
|
|
10335
11338
|
### unmarkDerived
|
|
10336
11339
|
|
|
10337
11340
|
**Kind:** `function`
|
|
@@ -10519,6 +11522,29 @@ const credit = useFormBundle(() => createCoreForm<CreditForm>({ model: createCre
|
|
|
10519
11522
|
|
|
10520
11523
|
_Source: src/platforms/react/hooks/use-form-bundle.ts_
|
|
10521
11524
|
|
|
11525
|
+
### useFormBundleContext
|
|
11526
|
+
|
|
11527
|
+
**Kind:** `function`
|
|
11528
|
+
|
|
11529
|
+
Бандл сборки, внутри которой отрисован компонент; `null` — компонент стоит вне рендерера
|
|
11530
|
+
(например, визард в разметке React-руками получает форму и валидацию пропсами).
|
|
11531
|
+
|
|
11532
|
+
**Signature:**
|
|
11533
|
+
```typescript
|
|
11534
|
+
export function useFormBundleContext<T = unknown>(): FormBundle<T> | null
|
|
11535
|
+
```
|
|
11536
|
+
|
|
11537
|
+
**Examples:**
|
|
11538
|
+
|
|
11539
|
+
```tsx
|
|
11540
|
+
function StepFooter() {
|
|
11541
|
+
const bundle = useFormBundleContext<CreditForm>();
|
|
11542
|
+
return <button onClick={() => bundle?.validation?.validateAll()}>Проверить</button>;
|
|
11543
|
+
}
|
|
11544
|
+
```
|
|
11545
|
+
|
|
11546
|
+
_Source: src/platforms/react/form-bundle-context.ts_
|
|
11547
|
+
|
|
10522
11548
|
### useFormControl
|
|
10523
11549
|
|
|
10524
11550
|
**Kind:** `function`
|