@reformer/core 11.0.0-beta.4 → 11.0.0-beta.5
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/dist/form/create-form.d.ts +11 -9
- package/dist/form/factories/node-factory.d.ts +17 -16
- package/dist/form/nodes/array-node.d.ts +3 -3
- package/dist/form/nodes/field-node.d.ts +6 -3
- package/dist/form/nodes/group-node.d.ts +7 -7
- package/dist/form/nodes/model-array-node.d.ts +2 -2
- package/dist/form/types/deep-schema.d.ts +5 -5
- package/dist/form/validators/email.d.ts +5 -6
- package/dist/form/validators/file-type.d.ts +5 -6
- package/dist/form/validators/future-date.d.ts +5 -6
- package/dist/form/validators/index.d.ts +3 -2
- package/dist/form/validators/integer.d.ts +5 -6
- package/dist/form/validators/is-date.d.ts +5 -6
- package/dist/form/validators/is-number.d.ts +5 -6
- package/dist/form/validators/max-age.d.ts +5 -6
- package/dist/form/validators/max-date.d.ts +5 -6
- package/dist/form/validators/max-file-size.d.ts +5 -6
- package/dist/form/validators/max-files.d.ts +5 -6
- package/dist/form/validators/max-length.d.ts +6 -7
- package/dist/form/validators/max-total-file-size.d.ts +5 -6
- package/dist/form/validators/max.d.ts +6 -7
- package/dist/form/validators/min-age.d.ts +5 -6
- package/dist/form/validators/min-date.d.ts +5 -6
- package/dist/form/validators/min-file-size.d.ts +5 -6
- package/dist/form/validators/min-files.d.ts +5 -6
- package/dist/form/validators/min-length.d.ts +6 -7
- package/dist/form/validators/min.d.ts +6 -7
- package/dist/form/validators/multiple-of.d.ts +5 -6
- package/dist/form/validators/non-negative.d.ts +5 -6
- package/dist/form/validators/non-zero.d.ts +5 -6
- package/dist/form/validators/past-date.d.ts +5 -6
- package/dist/form/validators/pattern.d.ts +6 -14
- package/dist/form/validators/phone.d.ts +5 -6
- package/dist/form/validators/required.d.ts +7 -12
- package/dist/form/validators/url.d.ts +7 -12
- package/dist/index.js +26 -23
- package/docs/llms/02-quick-start.md +6 -6
- package/docs/llms/03-api-signatures.md +3 -3
- package/docs/llms/05-common-mistakes.md +1 -1
- package/docs/llms/09-formschema.md +11 -11
- package/docs/llms/10-arrays.md +4 -4
- package/docs/llms/14-extended-mistakes.md +1 -1
- package/docs/llms/15-project-structure.md +8 -7
- package/docs/llms/16-ui-components.md +6 -6
- package/docs/llms/17-nonexistent-api.md +2 -2
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/30-type-safety-recipes.md +1 -1
- package/llms-index.json +1 -0
- package/llms.txt +2043 -224
- package/package.json +2 -1
package/llms.txt
CHANGED
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
|
|
43
43
|
## 1. 1. API Reference
|
|
44
44
|
|
|
45
|
+
**api-reference**
|
|
46
|
+
|
|
45
47
|
### Imports (CRITICALLY IMPORTANT)
|
|
46
48
|
|
|
47
49
|
Архитектура M1: значения живут в **модели** (`createModel`), а форма (`createForm`) строит ноды поверх сигналов модели. Behaviors работают на сигналах (`model.$.field`), а не на строковых путях.
|
|
@@ -106,6 +108,8 @@ const { value, errors, disabled } = useFormControl(control.loanType);
|
|
|
106
108
|
|
|
107
109
|
## 2. 1.5 QUICK START - Minimal Working Form
|
|
108
110
|
|
|
111
|
+
**quick-start**
|
|
112
|
+
|
|
109
113
|
> **Schema-driven UI rule (read first)**: компонент И его пропсы (label, placeholder,
|
|
110
114
|
> options, type) объявляются в **схеме поля** (`component` + `componentProps`).
|
|
111
115
|
> В JSX рендерится один универсальный `<FormField control={form.x} />` из
|
|
@@ -124,7 +128,7 @@ const { value, errors, disabled } = useFormControl(control.loanType);
|
|
|
124
128
|
import { createCoreForm, createModel, useFormBundle, type FormProxy } from '@reformer/core';
|
|
125
129
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
126
130
|
import { required, email } from '@reformer/core/validators';
|
|
127
|
-
import { FormField,
|
|
131
|
+
import { FormField, InputField, Button } from '@reformer/ui-kit';
|
|
128
132
|
|
|
129
133
|
// 1. Define form type as `type` alias (not `interface` — see Recipe 2)
|
|
130
134
|
type ContactForm = {
|
|
@@ -140,12 +144,12 @@ const model = createModel<ContactForm>({ name: '', email: '' });
|
|
|
140
144
|
const schema = {
|
|
141
145
|
name: {
|
|
142
146
|
value: model.$.name,
|
|
143
|
-
component:
|
|
147
|
+
component: InputField,
|
|
144
148
|
componentProps: { label: 'Name', placeholder: 'Your name' },
|
|
145
149
|
},
|
|
146
150
|
email: {
|
|
147
151
|
value: model.$.email,
|
|
148
|
-
component:
|
|
152
|
+
component: InputField,
|
|
149
153
|
componentProps: { label: 'Email', type: 'email' },
|
|
150
154
|
},
|
|
151
155
|
};
|
|
@@ -219,13 +223,13 @@ const model = createModel<MyForm>({ properties: [] });
|
|
|
219
223
|
const propertyItem = (item: FormModel<PropertyItem>) => ({
|
|
220
224
|
type: {
|
|
221
225
|
value: item.$.type,
|
|
222
|
-
component:
|
|
226
|
+
component: SelectField,
|
|
223
227
|
componentProps: { label: 'Тип', options: [/* ... */] },
|
|
224
228
|
},
|
|
225
|
-
description: { value: item.$.description, component:
|
|
229
|
+
description: { value: item.$.description, component: TextareaField, componentProps: { label: 'Описание' } },
|
|
226
230
|
estimatedValue: {
|
|
227
231
|
value: item.$.estimatedValue,
|
|
228
|
-
component:
|
|
232
|
+
component: InputField,
|
|
229
233
|
componentProps: { label: 'Стоимость', type: 'number' },
|
|
230
234
|
},
|
|
231
235
|
});
|
|
@@ -257,6 +261,8 @@ model.properties.length; // реактивная длина
|
|
|
257
261
|
|
|
258
262
|
## 3. 2. API SIGNATURES
|
|
259
263
|
|
|
264
|
+
**api-signatures**
|
|
265
|
+
|
|
260
266
|
### Model & Form
|
|
261
267
|
|
|
262
268
|
```typescript
|
|
@@ -329,9 +335,9 @@ const model = createModel<Loan>({ email: '', age: 0, amount: 0 });
|
|
|
329
335
|
const form = createForm({
|
|
330
336
|
model,
|
|
331
337
|
schema: {
|
|
332
|
-
email: { value: model.$.email, component:
|
|
333
|
-
age: { value: model.$.age, component:
|
|
334
|
-
amount: { value: model.$.amount, component:
|
|
338
|
+
email: { value: model.$.email, component: InputField },
|
|
339
|
+
age: { value: model.$.age, component: InputField },
|
|
340
|
+
amount: { value: model.$.amount, component: InputField },
|
|
335
341
|
},
|
|
336
342
|
});
|
|
337
343
|
|
|
@@ -531,6 +537,8 @@ revalidateWhen([model.$.dep], () => void validateModel(model, schema));
|
|
|
531
537
|
|
|
532
538
|
## 4. 3. COMMON PATTERNS
|
|
533
539
|
|
|
540
|
+
**common-patterns**
|
|
541
|
+
|
|
534
542
|
Все паттерны — на архитектуре M1: значения в модели (`model.$.field`), поведение (`defineFormBehavior`)
|
|
535
543
|
на живых сигналах, валидация — отдельной ambient-схемой (`defineValidationSchema`, прогон по требованию
|
|
536
544
|
через `validateModel`). Слои раздельны: layout НЕ несёт validators.
|
|
@@ -734,6 +742,8 @@ const schema = defineValidationSchema<MyForm>(({ model }) => {
|
|
|
734
742
|
|
|
735
743
|
## 5. 4. COMMON MISTAKES
|
|
736
744
|
|
|
745
|
+
**common-mistakes**
|
|
746
|
+
|
|
737
747
|
### Imports rule (#1 cause of cascading errors — read first)
|
|
738
748
|
|
|
739
749
|
- Модель/форма/хуки/типы (`FormModel`, `ValidationError`)/**примитивы behaviors** — из `@reformer/core`.
|
|
@@ -827,7 +837,7 @@ computeFrom([model.$.price, model.$.quantity], model.$.total, (price, qty) => pr
|
|
|
827
837
|
```typescript
|
|
828
838
|
// ❌ WRONG - дерево { value, validators } и позиционная строка удалены
|
|
829
839
|
const schema = {
|
|
830
|
-
email: { value: model.$.email, component:
|
|
840
|
+
email: { value: model.$.email, component: InputField, validators: [required(), email()] },
|
|
831
841
|
};
|
|
832
842
|
validate(path.email, required(), 'Email is required'); // старая сигнатура validate() — удалена
|
|
833
843
|
|
|
@@ -902,6 +912,8 @@ if (isFieldNode(node)) { ... }
|
|
|
902
912
|
|
|
903
913
|
## 6. 5. TROUBLESHOOTING
|
|
904
914
|
|
|
915
|
+
**troubleshooting**
|
|
916
|
+
|
|
905
917
|
| Error | Cause | Solution |
|
|
906
918
|
| ------------------------------------------------------ | -------------------------------------------------------- | ------------------------------------------------- |
|
|
907
919
|
| `'string' is not assignable to '{ message?: string }'` | Валидатору передали строку вместо options | Используй `required({ message: 'text' })` |
|
|
@@ -914,6 +926,8 @@ if (isFieldNode(node)) { ... }
|
|
|
914
926
|
|
|
915
927
|
## 7. Import Patterns
|
|
916
928
|
|
|
929
|
+
**complete-import**
|
|
930
|
+
|
|
917
931
|
```typescript
|
|
918
932
|
// Модель, форма, хуки, типы, примитивы behaviors — из @reformer/core
|
|
919
933
|
import {
|
|
@@ -1014,6 +1028,8 @@ export type CoBorrower = {
|
|
|
1014
1028
|
|
|
1015
1029
|
## 8. 7. FORM TYPE DEFINITION
|
|
1016
1030
|
|
|
1031
|
+
**form-types**
|
|
1032
|
+
|
|
1017
1033
|
Form-shape объявляй через `type`-alias (не `interface` — см. `30-type-safety-recipes.md`).
|
|
1018
1034
|
Тип описывает форму ДАННЫХ (то, что кладётся в `createModel`), без узлов/сигналов.
|
|
1019
1035
|
|
|
@@ -1062,6 +1078,8 @@ const model = createModel<MyForm>({
|
|
|
1062
1078
|
|
|
1063
1079
|
## 9. 8. SCHEMA FORMAT (CRITICALLY IMPORTANT)
|
|
1064
1080
|
|
|
1081
|
+
**formschema**
|
|
1082
|
+
|
|
1065
1083
|
Под M1 схема **привязывает поле к сигналу модели** (`value: model.$.field`) и держит
|
|
1066
1084
|
UI-конфиг (`component`/`componentProps`). Значения принадлежат модели. Валидаторов в
|
|
1067
1085
|
layout-схеме НЕТ — правила живут в отдельной схеме `defineValidationSchema` из
|
|
@@ -1072,7 +1090,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
1072
1090
|
```typescript
|
|
1073
1091
|
{
|
|
1074
1092
|
value: model.$.fieldName, // сигнал модели (PathAwareSignal) — обязателен
|
|
1075
|
-
component:
|
|
1093
|
+
component: InputField, // React-компонент
|
|
1076
1094
|
componentProps?: object, // пропсы (label, placeholder, options, type, ...)
|
|
1077
1095
|
disabled?: boolean,
|
|
1078
1096
|
updateOn?: 'change' | 'blur' | 'submit',
|
|
@@ -1090,7 +1108,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
1090
1108
|
import { createModel, createForm } from '@reformer/core';
|
|
1091
1109
|
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
1092
1110
|
import { required } from '@reformer/core/validators';
|
|
1093
|
-
import {
|
|
1111
|
+
import { InputField, SelectField, CheckboxField } from '@reformer/ui-kit';
|
|
1094
1112
|
|
|
1095
1113
|
const model = createModel<MyForm>({ name: '', age: null, agree: false, status: 'active' });
|
|
1096
1114
|
|
|
@@ -1102,22 +1120,22 @@ const myValidation = defineValidationSchema<MyForm>(({ model }) => {
|
|
|
1102
1120
|
const schema = {
|
|
1103
1121
|
name: {
|
|
1104
1122
|
value: model.$.name,
|
|
1105
|
-
component:
|
|
1123
|
+
component: InputField,
|
|
1106
1124
|
componentProps: { label: 'Name', placeholder: 'Enter name' },
|
|
1107
1125
|
},
|
|
1108
1126
|
age: {
|
|
1109
1127
|
value: model.$.age,
|
|
1110
|
-
component:
|
|
1128
|
+
component: InputField,
|
|
1111
1129
|
componentProps: { type: 'number', label: 'Age' },
|
|
1112
1130
|
},
|
|
1113
1131
|
agree: {
|
|
1114
1132
|
value: model.$.agree,
|
|
1115
|
-
component:
|
|
1133
|
+
component: CheckboxField,
|
|
1116
1134
|
componentProps: { label: 'I agree to terms' },
|
|
1117
1135
|
},
|
|
1118
1136
|
status: {
|
|
1119
1137
|
value: model.$.status,
|
|
1120
|
-
component:
|
|
1138
|
+
component: SelectField,
|
|
1121
1139
|
componentProps: {
|
|
1122
1140
|
label: 'Status',
|
|
1123
1141
|
options: [
|
|
@@ -1141,9 +1159,9 @@ const form = createForm<MyForm>({ model, schema });
|
|
|
1141
1159
|
import type { FormModel } from '@reformer/core';
|
|
1142
1160
|
|
|
1143
1161
|
const addressNodes = (m: FormModel<Address>) => ({
|
|
1144
|
-
street: { value: m.$.street, component:
|
|
1145
|
-
city: { value: m.$.city, component:
|
|
1146
|
-
zip: { value: m.$.zip, component:
|
|
1162
|
+
street: { value: m.$.street, component: InputField, componentProps: { label: 'Street' } },
|
|
1163
|
+
city: { value: m.$.city, component: InputField, componentProps: { label: 'City' } },
|
|
1164
|
+
zip: { value: m.$.zip, component: InputField, componentProps: { label: 'ZIP' } },
|
|
1147
1165
|
});
|
|
1148
1166
|
|
|
1149
1167
|
const schema = {
|
|
@@ -1160,8 +1178,8 @@ const schema = {
|
|
|
1160
1178
|
import type { FormModel } from '@reformer/core';
|
|
1161
1179
|
|
|
1162
1180
|
const itemSchema = (item: FormModel<Item>) => ({
|
|
1163
|
-
id: { value: item.$.id, component:
|
|
1164
|
-
name: { value: item.$.name, component:
|
|
1181
|
+
id: { value: item.$.id, component: InputField, componentProps: { label: 'ID' } },
|
|
1182
|
+
name: { value: item.$.name, component: InputField, componentProps: { label: 'Name' } },
|
|
1165
1183
|
});
|
|
1166
1184
|
|
|
1167
1185
|
const schema = {
|
|
@@ -1206,6 +1224,8 @@ if (isFieldNode(node)) { /* ... */ }
|
|
|
1206
1224
|
|
|
1207
1225
|
## 10. 9. ARRAY SCHEMA FORMAT
|
|
1208
1226
|
|
|
1227
|
+
**arrays**
|
|
1228
|
+
|
|
1209
1229
|
Массивы объектов — **model-owned**: данные принадлежат модели (`model.arrayField` — это
|
|
1210
1230
|
`ModelArray<Item>` с реактивными `push`/`removeAt`/`length`). В схеме массив объявляется узлом
|
|
1211
1231
|
`{ array: model.<path>, item: (itemModel) => subSchema }`, где `item` строит под-схему одного
|
|
@@ -1213,7 +1233,7 @@ if (isFieldNode(node)) { /* ... */ }
|
|
|
1213
1233
|
|
|
1214
1234
|
```typescript
|
|
1215
1235
|
import { createModel, createForm, type FormModel } from '@reformer/core';
|
|
1216
|
-
import {
|
|
1236
|
+
import { InputField } from '@reformer/ui-kit';
|
|
1217
1237
|
|
|
1218
1238
|
type Item = { id: string; name: string; price: number };
|
|
1219
1239
|
type MyForm = { items: Item[] };
|
|
@@ -1222,9 +1242,9 @@ const model = createModel<MyForm>({ items: [] });
|
|
|
1222
1242
|
|
|
1223
1243
|
// под-схема одного элемента (item.$.field — сигнал под-модели)
|
|
1224
1244
|
const itemSchema = (item: FormModel<Item>) => ({
|
|
1225
|
-
id: { value: item.$.id, component:
|
|
1226
|
-
name: { value: item.$.name, component:
|
|
1227
|
-
price: { value: item.$.price, component:
|
|
1245
|
+
id: { value: item.$.id, component: InputField },
|
|
1246
|
+
name: { value: item.$.name, component: InputField },
|
|
1247
|
+
price: { value: item.$.price, component: InputField, componentProps: { type: 'number' } },
|
|
1228
1248
|
});
|
|
1229
1249
|
|
|
1230
1250
|
const schema = {
|
|
@@ -1368,6 +1388,8 @@ cross(model.$.hasItems, (f: MyForm) => {
|
|
|
1368
1388
|
|
|
1369
1389
|
## 12. 10. ASYNC REACTION — onChange (CRITICALLY IMPORTANT)
|
|
1370
1390
|
|
|
1391
|
+
**async-watchfield**
|
|
1392
|
+
|
|
1371
1393
|
Для async-реакции на изменение поля (загрузка зависимых опций, справочников) используй
|
|
1372
1394
|
`onChange` из `@reformer/core/behaviors`. Колбэк выполняется ВНЕ effect-контекста (можно
|
|
1373
1395
|
безопасно писать сигналы/ноды), а 2-й аргумент — `{ signal }` (AbortSignal): при следующей
|
|
@@ -1422,6 +1444,8 @@ const stop = watchField(model.$.country, (country) => {
|
|
|
1422
1444
|
|
|
1423
1445
|
## 13. 11. ARRAY CLEANUP PATTERN
|
|
1424
1446
|
|
|
1447
|
+
**array-cleanup**
|
|
1448
|
+
|
|
1425
1449
|
Очистка массива при выключении флага — через `onChange` на сигнале флага + `clear()` на
|
|
1426
1450
|
модели-массиве. Колбит выполняется вне effect-контекста, поэтому мутировать массив безопасно.
|
|
1427
1451
|
|
|
@@ -1454,6 +1478,8 @@ clearWhenOff(model.$.hasProperty, model.properties);
|
|
|
1454
1478
|
|
|
1455
1479
|
## 14. 12. MULTI-STEP FORM VALIDATION
|
|
1456
1480
|
|
|
1481
|
+
**multi-step**
|
|
1482
|
+
|
|
1457
1483
|
Каждый шаг — своя `ValidationSchema<Form>` (обычная функция `({ model }) => void`, обёрнутая
|
|
1458
1484
|
`defineValidationSchema`). Переход к следующему шагу проверяется `validateModel(model, stepSchema)`;
|
|
1459
1485
|
полный submit — по общей схеме (композиция шагов через `apply(...)`). `validateModel` сам разносит
|
|
@@ -1546,6 +1572,8 @@ function MultiStepForm() {
|
|
|
1546
1572
|
|
|
1547
1573
|
## 15. 13. EXTENDED COMMON MISTAKES
|
|
1548
1574
|
|
|
1575
|
+
**extended-mistakes**
|
|
1576
|
+
|
|
1549
1577
|
### Reuse via apply / applyEach — не дублируй код
|
|
1550
1578
|
|
|
1551
1579
|
```typescript
|
|
@@ -1617,7 +1645,7 @@ import { revalidateWhen, type ValidationError } from '@reformer/core';
|
|
|
1617
1645
|
// ❌ старое (УДАЛЕНО): дерево { value, validators }, ModelValidator (value, scope, root), validateFormModel
|
|
1618
1646
|
const legacyRule: ModelValidator<number, unknown, Form> = (_value, _scope, root) =>
|
|
1619
1647
|
root.field1 > root.field2 ? { code: 'error', message: 'Invalid' } : null;
|
|
1620
|
-
const legacySchema = { field1: { value: model.$.field1, component:
|
|
1648
|
+
const legacySchema = { field1: { value: model.$.field1, component: InputField, validators: [legacyRule] } };
|
|
1621
1649
|
validateFormModel(model, legacySchema);
|
|
1622
1650
|
|
|
1623
1651
|
// ✅ новое: cross-правило — обычная функция над снапшотом (model.get()), не читает scope/root
|
|
@@ -1645,6 +1673,8 @@ revalidateWhen([model.$.field2], () => void validateModel(model, schema));
|
|
|
1645
1673
|
|
|
1646
1674
|
## 16. 14. PROJECT STRUCTURE (COLOCATION)
|
|
1647
1675
|
|
|
1676
|
+
**project-structure**
|
|
1677
|
+
|
|
1648
1678
|
One form is one module folder. **Default layout — flat minimalist:** every concern is a single
|
|
1649
1679
|
file at the module root, and the whole form (entry + all wizard steps inline) lives in one
|
|
1650
1680
|
`index.tsx`. Filenames are flat with no prefix — **the dot-prefix (`form.` / `renderer.`) is
|
|
@@ -1692,7 +1722,7 @@ export const createCreditApplicationModel = (): FormModel<CreditApplicationForm>
|
|
|
1692
1722
|
// forms/credit-application/form.schema.ts
|
|
1693
1723
|
import type { FormModel } from '@reformer/core';
|
|
1694
1724
|
export const creditApplicationSchema = (model: FormModel<CreditApplicationForm>) => ({
|
|
1695
|
-
loanType: { value: model.$.loanType, component:
|
|
1725
|
+
loanType: { value: model.$.loanType, component: SelectField, componentProps: { /* ... */ } },
|
|
1696
1726
|
personalData: personalDataNodes(model.$.personalData),
|
|
1697
1727
|
properties: { array: model.properties, item: propertyItem },
|
|
1698
1728
|
});
|
|
@@ -1739,9 +1769,9 @@ forms/
|
|
|
1739
1769
|
│
|
|
1740
1770
|
├── schema/ # The form definition
|
|
1741
1771
|
│ ├── model.ts # createModel factory + initial values + array-element factories
|
|
1742
|
-
│ ├── schema.ts
|
|
1772
|
+
│ ├── form.schema.ts # schema builder (model) => tree ({ value: model.$.x, component })
|
|
1743
1773
|
│ ├── validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
|
|
1744
|
-
│ ├── behavior.ts
|
|
1774
|
+
│ ├── form.behavior.ts # defineFormBehavior(...)
|
|
1745
1775
|
│ └── create-form.ts # Assembly: createCoreForm({ model, schema, behavior, validation })
|
|
1746
1776
|
│
|
|
1747
1777
|
└── components/
|
|
@@ -1752,7 +1782,7 @@ forms/
|
|
|
1752
1782
|
|
|
1753
1783
|
Rule of thumb for this layout: **domain raw material → `lib/`; anything describing the form →
|
|
1754
1784
|
`schema/`; React layout → `components/`; root = entry + `index.ts`.** In very large forms you
|
|
1755
|
-
may co-locate each step's `schema.ts` / `validation.ts` / `behavior.ts` inside its
|
|
1785
|
+
may co-locate each step's `form.schema.ts` / `validation.ts` / `form.behavior.ts` inside its
|
|
1756
1786
|
`steps/[Step]/` folder, keeping only the shared `model` and cross-step rules in `schema/` — see
|
|
1757
1787
|
the guide below.
|
|
1758
1788
|
|
|
@@ -1769,12 +1799,15 @@ the guide below.
|
|
|
1769
1799
|
> is `minimalist`.
|
|
1770
1800
|
|
|
1771
1801
|
> Cross-target variants (renderer-react `renderer.schema.ts` + `renderer.behavior.ts` /
|
|
1772
|
-
> renderer-json `renderer.schema.
|
|
1773
|
-
>
|
|
1774
|
-
> **form-directory-layout** guide (`@reformer/mcp`)
|
|
1802
|
+
> renderer-json `renderer.schema.ts` + `renderer.behavior.ts` + `registry.ts`, plus an optional
|
|
1803
|
+
> `renderer.wizard.tsx` shim), the `.tsx` / `.json` schema variants, the centralized-vs-co-located
|
|
1804
|
+
> choice, and the full reuse map live in the **form-directory-layout** guide (`@reformer/mcp`) —
|
|
1805
|
+
> `find_recipe directory-layout`.
|
|
1775
1806
|
|
|
1776
1807
|
## 17. 14.5 UI COMPONENT PATTERNS
|
|
1777
1808
|
|
|
1809
|
+
**ui-components**
|
|
1810
|
+
|
|
1778
1811
|
> **Default rule (read first)**: для UI используй `FormField` из
|
|
1779
1812
|
> [`@reformer/ui-kit`](../../reformer-ui-kit/) — он покрывает 95% случаев одной
|
|
1780
1813
|
> строкой `<FormField control={form.x} />`. Свои field-обёртки пиши ТОЛЬКО если
|
|
@@ -1782,7 +1815,7 @@ the guide below.
|
|
|
1782
1815
|
>
|
|
1783
1816
|
> Канонический schema-driven подход:
|
|
1784
1817
|
>
|
|
1785
|
-
> - **компонент** объявляется в схеме как `component:
|
|
1818
|
+
> - **компонент** объявляется в схеме как `component: InputField` (или `Select`, `Checkbox`, etc.)
|
|
1786
1819
|
> - **пропсы** компонента — в `componentProps: { label, placeholder, options, type, ... }`
|
|
1787
1820
|
> - **JSX рендерит**: `<FormField control={form.x} />` БЕЗ дополнительных props
|
|
1788
1821
|
>
|
|
@@ -1794,7 +1827,7 @@ the guide below.
|
|
|
1794
1827
|
```tsx
|
|
1795
1828
|
import { useMemo } from 'react';
|
|
1796
1829
|
import { createModel, createForm } from '@reformer/core';
|
|
1797
|
-
import { FormField,
|
|
1830
|
+
import { FormField, InputField, SelectField, CheckboxField, Button } from '@reformer/ui-kit';
|
|
1798
1831
|
|
|
1799
1832
|
type RegistrationForm = {
|
|
1800
1833
|
email: string;
|
|
@@ -1808,12 +1841,12 @@ function RegistrationPage() {
|
|
|
1808
1841
|
const schema = {
|
|
1809
1842
|
email: {
|
|
1810
1843
|
value: model.$.email,
|
|
1811
|
-
component:
|
|
1844
|
+
component: InputField,
|
|
1812
1845
|
componentProps: { label: 'Email', type: 'email', placeholder: 'you@example.com' },
|
|
1813
1846
|
},
|
|
1814
1847
|
country: {
|
|
1815
1848
|
value: model.$.country,
|
|
1816
|
-
component:
|
|
1849
|
+
component: SelectField,
|
|
1817
1850
|
componentProps: {
|
|
1818
1851
|
label: 'Country',
|
|
1819
1852
|
options: [
|
|
@@ -1824,7 +1857,7 @@ function RegistrationPage() {
|
|
|
1824
1857
|
},
|
|
1825
1858
|
agree: {
|
|
1826
1859
|
value: model.$.agree,
|
|
1827
|
-
component:
|
|
1860
|
+
component: CheckboxField,
|
|
1828
1861
|
componentProps: { label: 'I agree to terms' },
|
|
1829
1862
|
},
|
|
1830
1863
|
};
|
|
@@ -1867,7 +1900,7 @@ function RegistrationPage() {
|
|
|
1867
1900
|
✅ Всё это в схеме:
|
|
1868
1901
|
|
|
1869
1902
|
```ts
|
|
1870
|
-
{ email: { component:
|
|
1903
|
+
{ email: { component: InputField, componentProps: { label: 'Email' } } }
|
|
1871
1904
|
```
|
|
1872
1905
|
|
|
1873
1906
|
```tsx
|
|
@@ -1965,6 +1998,8 @@ function ShadcnFormField({ control }: { control: FieldNode<string> }) {
|
|
|
1965
1998
|
|
|
1966
1999
|
## 18. 15. NON-EXISTENT API (DO NOT USE)
|
|
1967
2000
|
|
|
2001
|
+
**nonexistent-api**
|
|
2002
|
+
|
|
1968
2003
|
**Следующего API НЕТ в @reformer/core** (наследие старой path-based архитектуры,
|
|
1969
2004
|
удалено при переходе на M1 и при разделении валидации/поведения):
|
|
1970
2005
|
|
|
@@ -2029,8 +2064,8 @@ const schema = {
|
|
|
2029
2064
|
|
|
2030
2065
|
// CORRECT - layout только связывает поле с сигналом модели...
|
|
2031
2066
|
const layout = {
|
|
2032
|
-
name: { value: model.$.name, component:
|
|
2033
|
-
email: { value: model.$.email, component:
|
|
2067
|
+
name: { value: model.$.name, component: InputField, componentProps: { label: 'Name' } },
|
|
2068
|
+
email: { value: model.$.email, component: InputField, componentProps: { label: 'Email' } },
|
|
2034
2069
|
};
|
|
2035
2070
|
|
|
2036
2071
|
// ...а правила — отдельная ambient-схема (прогоняется раннером validateModel):
|
|
@@ -2059,6 +2094,8 @@ revalidateWhen([model.$.password], () => void validateModel(model, validation));
|
|
|
2059
2094
|
|
|
2060
2095
|
## 19. Purpose
|
|
2061
2096
|
|
|
2097
|
+
**Условные поля — видимость, доступность и валидация**
|
|
2098
|
+
|
|
2062
2099
|
«Показать поле X только если Y» под M1 решается тремя независимыми механизмами — выбирай по
|
|
2063
2100
|
тому, что именно должно быть условным:
|
|
2064
2101
|
|
|
@@ -2241,6 +2278,8 @@ function MortgageFields({ form }: { form: CreditForm }) {
|
|
|
2241
2278
|
|
|
2242
2279
|
## 25. 16. READING FIELD VALUES (CRITICALLY IMPORTANT)
|
|
2243
2280
|
|
|
2281
|
+
**reading-values**
|
|
2282
|
+
|
|
2244
2283
|
Под M1 значения живут в модели. Есть три контекста чтения: value-доступ модели, сигналы, и React-хуки.
|
|
2245
2284
|
|
|
2246
2285
|
### В React-компоненте — хуки
|
|
@@ -2330,6 +2369,8 @@ const behavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
|
2330
2369
|
|
|
2331
2370
|
## 26. 17. COMPUTE vs ONCHANGE
|
|
2332
2371
|
|
|
2372
|
+
**compute-vs-watch**
|
|
2373
|
+
|
|
2333
2374
|
Под M1 behaviors работают на сигналах модели. Есть два способа их писать:
|
|
2334
2375
|
|
|
2335
2376
|
- **примитивы из `@reformer/core`** — принимают сигналы, возвращают cleanup, вызываются
|
|
@@ -2478,6 +2519,8 @@ watchField(model.$, (all) => autosave(all)); // любое поле ф
|
|
|
2478
2519
|
|
|
2479
2520
|
## 27. 18. ARRAY OPERATIONS
|
|
2480
2521
|
|
|
2522
|
+
**array-operations**
|
|
2523
|
+
|
|
2481
2524
|
Массивы объектов — model-owned. Мутации делаются через `ModelArray` (`model.arrayField`);
|
|
2482
2525
|
рендер — через ноду формы (`form.items.map` / `.at`).
|
|
2483
2526
|
|
|
@@ -2592,6 +2635,8 @@ cross(model.$.totalPercent, percentagesSumTo100); // носитель — ска
|
|
|
2592
2635
|
|
|
2593
2636
|
## 28. Как устроено под M1
|
|
2594
2637
|
|
|
2638
|
+
**Cycle Detection — предотвращение «Cycle detected»**
|
|
2639
|
+
|
|
2595
2640
|
Behaviors на сигналах уже защищены от типичных циклов:
|
|
2596
2641
|
|
|
2597
2642
|
- `compute`/`computeFrom` пишут в цель только если значение изменилось (**peek-guard**), а цель
|
|
@@ -2677,6 +2722,8 @@ enableWhen([model.$.propertyValue, model.$.initialPayment], () => model.loanType
|
|
|
2677
2722
|
|
|
2678
2723
|
## 32. Purpose
|
|
2679
2724
|
|
|
2725
|
+
**copyFrom — Копирование значений между полями**
|
|
2726
|
+
|
|
2680
2727
|
`copyFrom` декларативно копирует значение одного поля (или группы) в другое при выполнении
|
|
2681
2728
|
условия `when`. Используется для UX-сценариев «совпадает с …»: «адрес проживания = адрес
|
|
2682
2729
|
регистрации», «email для уведомлений = основной email», «billing = shipping». Оператор сам
|
|
@@ -2817,6 +2864,8 @@ A: `copyFrom` при `when === false` просто не пишет (не сбр
|
|
|
2817
2864
|
|
|
2818
2865
|
## 38. Purpose
|
|
2819
2866
|
|
|
2867
|
+
**syncFields — Двусторонняя синхронизация полей**
|
|
2868
|
+
|
|
2820
2869
|
`syncFields` создаёт двунаправленную связь между двумя полями: изменение любого из них
|
|
2821
2870
|
переписывает второе. Применяется для дублей одного значения в разных частях формы
|
|
2822
2871
|
(тех. поле + видимое представление, mirror-поля). Внутренний флаг + `runOutsideEffect`
|
|
@@ -2930,6 +2979,8 @@ A: У `syncFields` нет `when`. Эмулируй через два `copyFrom(a
|
|
|
2930
2979
|
|
|
2931
2980
|
## 44. Purpose
|
|
2932
2981
|
|
|
2982
|
+
**resetWhen — Условный сброс полей**
|
|
2983
|
+
|
|
2933
2984
|
`resetWhen` сбрасывает значение поля к `resetValue`, когда `condition` истинно. Это
|
|
2934
2985
|
альтернатива `enableWhen({ resetOnDisable: true })`, когда поле остаётся **enabled**, но
|
|
2935
2986
|
содержимое нужно очистить (например, переключение способа оплаты обнуляет «номер карты», но
|
|
@@ -3044,6 +3095,8 @@ A: `resetWhen` рассчитан на скалярные сигналы. Для
|
|
|
3044
3095
|
|
|
3045
3096
|
## 50. Purpose
|
|
3046
3097
|
|
|
3098
|
+
**transformValue — Автоматическая трансформация значений**
|
|
3099
|
+
|
|
3047
3100
|
`transformValue` подписывается на изменение поля и переписывает его трансформированной
|
|
3048
3101
|
версией: uppercase для кодов, trim+toLowerCase для email, округление для чисел. Применяется
|
|
3049
3102
|
единообразно ко всем источникам изменения (пользователь, `model.field = …`, `model.set/patch`,
|
|
@@ -3179,6 +3232,8 @@ A: Симптом частых записей. Форматирование с
|
|
|
3179
3232
|
|
|
3180
3233
|
## 56. Purpose
|
|
3181
3234
|
|
|
3235
|
+
**revalidateWhen — Перевалидация по триггерам**
|
|
3236
|
+
|
|
3182
3237
|
`revalidateWhen` вызывает переданный колбэк ревалидации, когда изменяется любая из
|
|
3183
3238
|
зависимостей. Применяется, когда правило зависит от значений других полей
|
|
3184
3239
|
(`amount <= maxAmount`, `confirmPassword === password`, `initialPayment >= propertyValue * 0.2`).
|
|
@@ -3310,6 +3365,8 @@ A: Схема — обычная функция, поэтому опиши ст
|
|
|
3310
3365
|
|
|
3311
3366
|
## 62. Purpose
|
|
3312
3367
|
|
|
3368
|
+
**Submit и Reset — Жизненный цикл отправки формы**
|
|
3369
|
+
|
|
3313
3370
|
Канонический submit-флоу под M1: «запустить полную валидацию данных (`validateModel`) →
|
|
3314
3371
|
проверить `boolean`-результат → достать снимок (`model.get()`) → сделать запрос → `model.reset()`».
|
|
3315
3372
|
Валидация — **отдельный слой** (`@reformer/core/validation`, схема через `defineValidationSchema`), она
|
|
@@ -3568,6 +3625,8 @@ A: `model.get()` возвращает значения всех полей. Фи
|
|
|
3568
3625
|
|
|
3569
3626
|
## 68. Purpose
|
|
3570
3627
|
|
|
3628
|
+
**Async Preload — Загрузка начальных значений и справочников**
|
|
3629
|
+
|
|
3571
3630
|
Загрузка формы данными с сервера под M1: (1) **initial values** в `createModel(...)`; (2)
|
|
3572
3631
|
**`model.set` / `model.patch`** для загрузки/обновления значений; (3) **external React-hook**
|
|
3573
3632
|
для full-blown async preload (параллельный fetch заявки + справочников, обработка ошибок,
|
|
@@ -3597,16 +3656,16 @@ Initial values задаются в `createModel(initial)`; `reset()` возвр
|
|
|
3597
3656
|
|
|
3598
3657
|
```typescript
|
|
3599
3658
|
import { createModel, createForm } from '@reformer/core';
|
|
3600
|
-
import {
|
|
3659
|
+
import { InputField, SelectField } from '@reformer/ui-kit';
|
|
3601
3660
|
|
|
3602
3661
|
type ProfileForm = { username: string; language: 'ru' | 'en'; marketing: boolean };
|
|
3603
3662
|
|
|
3604
3663
|
const model = createModel<ProfileForm>({ username: '', language: 'ru', marketing: true });
|
|
3605
3664
|
const schema = {
|
|
3606
|
-
username: { value: model.$.username, component:
|
|
3665
|
+
username: { value: model.$.username, component: InputField, componentProps: { label: 'Username' } },
|
|
3607
3666
|
language: {
|
|
3608
3667
|
value: model.$.language,
|
|
3609
|
-
component:
|
|
3668
|
+
component: SelectField,
|
|
3610
3669
|
componentProps: {
|
|
3611
3670
|
label: 'Язык',
|
|
3612
3671
|
options: [
|
|
@@ -3615,7 +3674,7 @@ const schema = {
|
|
|
3615
3674
|
],
|
|
3616
3675
|
},
|
|
3617
3676
|
},
|
|
3618
|
-
marketing: { value: model.$.marketing, component:
|
|
3677
|
+
marketing: { value: model.$.marketing, component: InputField, componentProps: { type: 'checkbox' } },
|
|
3619
3678
|
};
|
|
3620
3679
|
const form = createForm({ model, schema });
|
|
3621
3680
|
// model.get() === { username: '', language: 'ru', marketing: true }
|
|
@@ -3760,6 +3819,8 @@ A: `reset()` возвращает к initial-снимку из `createModel`, н
|
|
|
3760
3819
|
|
|
3761
3820
|
## 74. 30. TYPE-SAFETY RECIPES
|
|
3762
3821
|
|
|
3822
|
+
**type-safety-recipes**
|
|
3823
|
+
|
|
3763
3824
|
Идиоматичные паттерны, которые держат сгенерированный код без `any` и `as`-кастов под M1.
|
|
3764
3825
|
|
|
3765
3826
|
### Recipe 1 — Imports (root cause prevention)
|
|
@@ -3819,7 +3880,7 @@ Layout-схема БЕЗ валидаторов — правила живут в
|
|
|
3819
3880
|
const model = createModel<CreditApplicationForm>(initial);
|
|
3820
3881
|
|
|
3821
3882
|
const schema = {
|
|
3822
|
-
loanAmount: { value: model.$.loanAmount, component:
|
|
3883
|
+
loanAmount: { value: model.$.loanAmount, component: InputField },
|
|
3823
3884
|
// вложенная группа — builder, принимающий ModelSignals<Sub>
|
|
3824
3885
|
personalData: personalDataNodes(model.$.personalData),
|
|
3825
3886
|
// массив — { array, item }
|
|
@@ -3920,6 +3981,8 @@ validate(model.$.birthDate, [validateAdultAge]);
|
|
|
3920
3981
|
|
|
3921
3982
|
## 75. 31. ASYNC VALIDATOR
|
|
3922
3983
|
|
|
3984
|
+
**async-validator-debounce**
|
|
3985
|
+
|
|
3923
3986
|
Для проверок типа «уникальность email», «валидация ИНН через API», «проверка адреса» —
|
|
3924
3987
|
async-правило это `AsyncRule<T>` = `(value, { signal }) => Promise<ValidationError | null>`.
|
|
3925
3988
|
Оно регистрируется оператором `validateAsync(sig, [asyncRules])` внутри схемы валидации
|
|
@@ -4056,6 +4119,8 @@ return checking ? <Spinner /> : errors.length ? <Error errors={errors} /> : null
|
|
|
4056
4119
|
|
|
4057
4120
|
## 76. 32. ASYNC OPTIONS LOADING
|
|
4058
4121
|
|
|
4122
|
+
**async-options-loading**
|
|
4123
|
+
|
|
4059
4124
|
Для динамической подгрузки опций dropdown'а по значению другого поля (`region` → `city options`,
|
|
4060
4125
|
`carBrand` → `carModel options`) — используй `onChange` из `@reformer/core/behaviors` +
|
|
4061
4126
|
`form.field.updateComponentProps({ options })`. `onChange` даёт debounce и AbortSignal из коробки.
|
|
@@ -4165,6 +4230,8 @@ loadOptionsOn(model.$.carBrand, form.carModel, fetchCarModels, { resetTarget: tr
|
|
|
4165
4230
|
|
|
4166
4231
|
## 77. Purpose
|
|
4167
4232
|
|
|
4233
|
+
**useFormValidation — Единый выбор стратегии валидации**
|
|
4234
|
+
|
|
4168
4235
|
Одна декларативная точка выбора «когда прогонять схему валидации» вместо ручной разводки
|
|
4169
4236
|
`markAsTouched` + `validateModel` + `revalidateWhen` + `useState(pending)`. Аддитивный слой над
|
|
4170
4237
|
функциональной схемой (`@reformer/core/validation`): переиспользует `validateModel` как
|
|
@@ -4359,6 +4426,34 @@ if (typeof window !== 'undefined') ctrl.start();
|
|
|
4359
4426
|
|
|
4360
4427
|
_Auto-generated from JSDoc on public exports._
|
|
4361
4428
|
|
|
4429
|
+
### aggregateInto
|
|
4430
|
+
|
|
4431
|
+
**Kind:** `function`
|
|
4432
|
+
|
|
4433
|
+
Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список
|
|
4434
|
+
`{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на
|
|
4435
|
+
финальном состоянии — поэтому массовые синхронные мутации (push в цикле) не вызывают каскад на
|
|
4436
|
+
промежуточных состояниях. `derive` должна сходиться (на фикспоинте возвращать те же значения).
|
|
4437
|
+
|
|
4438
|
+
**Signature:**
|
|
4439
|
+
```typescript
|
|
4440
|
+
export function aggregateInto<TItem>(
|
|
4441
|
+
array: object,
|
|
4442
|
+
derive: (rows: TItem[]) => Array<{ index: number; patch: Partial<TItem> }>
|
|
4443
|
+
): void
|
|
4444
|
+
```
|
|
4445
|
+
|
|
4446
|
+
**Examples:**
|
|
4447
|
+
|
|
4448
|
+
// последняя строка = 100 − Σ(остальные)
|
|
4449
|
+
aggregateInto(model.$.rows, (rows) => {
|
|
4450
|
+
const n = rows.length; if (n === 0) return [];
|
|
4451
|
+
const others = rows.slice(0, n - 1).reduce((s, r) => s + r.percent, 0);
|
|
4452
|
+
return [{ index: n - 1, patch: { percent: 100 - others } }];
|
|
4453
|
+
});
|
|
4454
|
+
|
|
4455
|
+
_Source: src/form/behaviors/collections.ts_
|
|
4456
|
+
|
|
4362
4457
|
### AnyFunction
|
|
4363
4458
|
|
|
4364
4459
|
**Kind:** `type`
|
|
@@ -4373,6 +4468,47 @@ export type AnyFunction = (...args: never[]) => unknown;
|
|
|
4373
4468
|
|
|
4374
4469
|
_Source: src/form/types/index.ts_
|
|
4375
4470
|
|
|
4471
|
+
### apply
|
|
4472
|
+
|
|
4473
|
+
**Kind:** `function`
|
|
4474
|
+
|
|
4475
|
+
Композиция под-схем в текущую (над той же моделью scope). Заменяет пошаговую группировку.
|
|
4476
|
+
|
|
4477
|
+
**Signature:**
|
|
4478
|
+
```typescript
|
|
4479
|
+
export function apply<T>(...schemas: ValidationSchema<T>[]): void
|
|
4480
|
+
```
|
|
4481
|
+
|
|
4482
|
+
_Source: src/form/validation/operators.ts_
|
|
4483
|
+
|
|
4484
|
+
### applyEach
|
|
4485
|
+
|
|
4486
|
+
**Kind:** `function`
|
|
4487
|
+
|
|
4488
|
+
Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).
|
|
4489
|
+
Реагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.
|
|
4490
|
+
|
|
4491
|
+
Под-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).
|
|
4492
|
+
- Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.
|
|
4493
|
+
- Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был
|
|
4494
|
+
МАТЕРИАЛИЗОВАН в форме (узел `{ array, item }` в схеме) — тогда `form` строки = та же нода, что
|
|
4495
|
+
рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
|
|
4496
|
+
к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
|
|
4497
|
+
|
|
4498
|
+
**Signature:**
|
|
4499
|
+
```typescript
|
|
4500
|
+
export function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void
|
|
4501
|
+
```
|
|
4502
|
+
|
|
4503
|
+
**Examples:**
|
|
4504
|
+
|
|
4505
|
+
applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
|
|
4506
|
+
compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
|
|
4507
|
+
enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
|
|
4508
|
+
}));
|
|
4509
|
+
|
|
4510
|
+
_Source: src/form/behaviors/collections.ts_
|
|
4511
|
+
|
|
4376
4512
|
### ArrayControlState
|
|
4377
4513
|
|
|
4378
4514
|
**Kind:** `interface`
|
|
@@ -4557,8 +4693,8 @@ export class ArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
4557
4693
|
|
|
4558
4694
|
```typescript
|
|
4559
4695
|
const array = new ArrayNode({
|
|
4560
|
-
title: { value: '', component:
|
|
4561
|
-
price: { value: 0, component:
|
|
4696
|
+
title: { value: '', component: InputField },
|
|
4697
|
+
price: { value: 0, component: InputField },
|
|
4562
4698
|
});
|
|
4563
4699
|
|
|
4564
4700
|
array.push({ title: 'Item 1', price: 100 });
|
|
@@ -4585,6 +4721,23 @@ export interface ArrayNodeLike {
|
|
|
4585
4721
|
|
|
4586
4722
|
_Source: src/form/types/index.ts_
|
|
4587
4723
|
|
|
4724
|
+
### AsyncRule
|
|
4725
|
+
|
|
4726
|
+
**Kind:** `type`
|
|
4727
|
+
|
|
4728
|
+
Асинхронное правило поля. Получает `AbortSignal` для отмены устаревших ответов (быстрый повторный
|
|
4729
|
+
прогон той же схемы отменяет предыдущий). Сетевой сбой не должен блокировать — ловите и возвращайте `null`.
|
|
4730
|
+
|
|
4731
|
+
**Signature:**
|
|
4732
|
+
```typescript
|
|
4733
|
+
export type AsyncRule<TField> = (
|
|
4734
|
+
value: TField,
|
|
4735
|
+
ctx: { signal: AbortSignal }
|
|
4736
|
+
) => Promise<ValidationError | null>;
|
|
4737
|
+
```
|
|
4738
|
+
|
|
4739
|
+
_Source: src/form/validation/types.ts_
|
|
4740
|
+
|
|
4588
4741
|
### AsyncValidatorFn
|
|
4589
4742
|
|
|
4590
4743
|
**Kind:** `type`
|
|
@@ -4657,6 +4810,19 @@ export type BehaviorCleanup = () => void;
|
|
|
4657
4810
|
|
|
4658
4811
|
_Source: src/model/behaviors-value.ts_
|
|
4659
4812
|
|
|
4813
|
+
### BehaviorScope
|
|
4814
|
+
|
|
4815
|
+
**Kind:** `type`
|
|
4816
|
+
|
|
4817
|
+
Контекст схемы поведения: модель (значения/сигналы) + форма (ноды).
|
|
4818
|
+
|
|
4819
|
+
**Signature:**
|
|
4820
|
+
```typescript
|
|
4821
|
+
export type BehaviorScope<T> = { model: FormModel<T>; form: FormProxy<T> };
|
|
4822
|
+
```
|
|
4823
|
+
|
|
4824
|
+
_Source: src/form/behaviors/types.ts_
|
|
4825
|
+
|
|
4660
4826
|
### buildValidation
|
|
4661
4827
|
|
|
4662
4828
|
**Kind:** `function`
|
|
@@ -4694,6 +4860,38 @@ await v!.validateAll(); // все шаги + extras
|
|
|
4694
4860
|
|
|
4695
4861
|
_Source: src/form/validation/config.ts_
|
|
4696
4862
|
|
|
4863
|
+
### ChangeContext
|
|
4864
|
+
|
|
4865
|
+
**Kind:** `interface`
|
|
4866
|
+
|
|
4867
|
+
Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка.
|
|
4868
|
+
|
|
4869
|
+
**Signature:**
|
|
4870
|
+
```typescript
|
|
4871
|
+
export interface ChangeContext {
|
|
4872
|
+
signal: AbortSignal;
|
|
4873
|
+
}
|
|
4874
|
+
```
|
|
4875
|
+
|
|
4876
|
+
_Source: src/form/behaviors/types.ts_
|
|
4877
|
+
|
|
4878
|
+
### compute
|
|
4879
|
+
|
|
4880
|
+
**Kind:** `function`
|
|
4881
|
+
|
|
4882
|
+
Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов.
|
|
4883
|
+
|
|
4884
|
+
**Signature:**
|
|
4885
|
+
```typescript
|
|
4886
|
+
export function compute<R>(
|
|
4887
|
+
target: Signal<R>,
|
|
4888
|
+
read: () => R,
|
|
4889
|
+
options?: { when?: () => boolean }
|
|
4890
|
+
): void
|
|
4891
|
+
```
|
|
4892
|
+
|
|
4893
|
+
_Source: src/form/behaviors/operators.ts_
|
|
4894
|
+
|
|
4697
4895
|
### computeFrom
|
|
4698
4896
|
|
|
4699
4897
|
**Kind:** `function`
|
|
@@ -4916,8 +5114,8 @@ const model = createModel<UserForm>({ email: '', password: '' });
|
|
|
4916
5114
|
// Layout-схема НЕ несёт validators — только component/componentProps.
|
|
4917
5115
|
const schema = {
|
|
4918
5116
|
children: [
|
|
4919
|
-
{ value: model.$.email, component:
|
|
4920
|
-
{ value: model.$.password, component:
|
|
5117
|
+
{ value: model.$.email, component: InputField },
|
|
5118
|
+
{ value: model.$.password, component: InputField },
|
|
4921
5119
|
],
|
|
4922
5120
|
};
|
|
4923
5121
|
|
|
@@ -4976,7 +5174,7 @@ _Source: src/form/create-core-form.ts_
|
|
|
4976
5174
|
|
|
4977
5175
|
Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
|
|
4978
5176
|
ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
|
|
4979
|
-
модели, привязывает конфиг поля (component/componentProps
|
|
5177
|
+
модели, привязывает конфиг поля (component/componentProps) из схемы, материализует
|
|
4980
5178
|
top-level массивы как {@link ModelArrayNode}, заполняет реестр сигнал→нода (для `enableWhen`/
|
|
4981
5179
|
роутинга ошибок) и, при наличии, запускает декларативное поведение (cleanup живёт на форме).
|
|
4982
5180
|
|
|
@@ -5008,9 +5206,10 @@ const model = createModel<Form>({ email: '', profile: { name: '', age: 0 } });
|
|
|
5008
5206
|
const schema = {
|
|
5009
5207
|
component: Section,
|
|
5010
5208
|
children: [
|
|
5011
|
-
|
|
5209
|
+
// Layout несёт только component/componentProps; правила — в отдельной ValidationSchema.
|
|
5210
|
+
{ value: model.$.email, component: InputField },
|
|
5012
5211
|
// вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)
|
|
5013
|
-
{ value: model.$.profile.name, component:
|
|
5212
|
+
{ value: model.$.profile.name, component: InputField },
|
|
5014
5213
|
],
|
|
5015
5214
|
};
|
|
5016
5215
|
|
|
@@ -5031,7 +5230,7 @@ _Source: src/form/create-form.ts_
|
|
|
5031
5230
|
**Kind:** `interface`
|
|
5032
5231
|
|
|
5033
5232
|
Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
|
|
5034
|
-
конфиг полей (component/componentProps
|
|
5233
|
+
конфиг полей (component/componentProps) — из единой схемы.
|
|
5035
5234
|
|
|
5036
5235
|
**Signature:**
|
|
5037
5236
|
```typescript
|
|
@@ -5053,6 +5252,41 @@ export interface CreateFormFromModelArgs<T> {
|
|
|
5053
5252
|
|
|
5054
5253
|
_Source: src/form/create-form.ts_
|
|
5055
5254
|
|
|
5255
|
+
### createFormValidation
|
|
5256
|
+
|
|
5257
|
+
**Kind:** `function`
|
|
5258
|
+
|
|
5259
|
+
Собрать контроллер валидации формы с выбранной стратегией запуска. Переиспользует
|
|
5260
|
+
{@link validateModel} как единственный движок прогона.
|
|
5261
|
+
|
|
5262
|
+
Фабрика ЧИСТАЯ до `start()` (никаких подписок) — безопасна для SSR/headless. Реактивные триггеры
|
|
5263
|
+
арминуются только в `start()` (в React — из `useEffect`).
|
|
5264
|
+
|
|
5265
|
+
**Signature:**
|
|
5266
|
+
```typescript
|
|
5267
|
+
export function createFormValidation<T>(
|
|
5268
|
+
model: FormModel<T>,
|
|
5269
|
+
schema: ValidationSchema<T>,
|
|
5270
|
+
options: ValidationStrategyOptions = {}
|
|
5271
|
+
): FormValidationController
|
|
5272
|
+
```
|
|
5273
|
+
|
|
5274
|
+
**Parameters:**
|
|
5275
|
+
- `model` — - Модель данных.
|
|
5276
|
+
- `schema` — - Схема валидации. **Стабильная ссылка** (иначе `validateModel` не отменит устаревший прогон).
|
|
5277
|
+
- `options` — - {@link ValidationStrategyOptions}.
|
|
5278
|
+
|
|
5279
|
+
**Examples:**
|
|
5280
|
+
|
|
5281
|
+
```ts
|
|
5282
|
+
const ctrl = createFormValidation(model, schema, { strategy: 'afterFirstSubmit', debounce: 300 });
|
|
5283
|
+
const dispose = ctrl.start(); // на клиенте
|
|
5284
|
+
const ok = await ctrl.validate(); // submit → раскрыть все ошибки
|
|
5285
|
+
dispose();
|
|
5286
|
+
```
|
|
5287
|
+
|
|
5288
|
+
_Source: src/form/validation/strategy.ts_
|
|
5289
|
+
|
|
5056
5290
|
### createModel
|
|
5057
5291
|
|
|
5058
5292
|
**Kind:** `function`
|
|
@@ -5089,6 +5323,81 @@ model.get(); // { email: 'a@b.c', profile: { name: 'Ada' }, tag
|
|
|
5089
5323
|
|
|
5090
5324
|
_Source: src/model/create-model.ts_
|
|
5091
5325
|
|
|
5326
|
+
### cross
|
|
5327
|
+
|
|
5328
|
+
**Kind:** `function`
|
|
5329
|
+
|
|
5330
|
+
Cross-field правило: `fn` получает СНАПШОТ модели текущего scope (`model.get()`) и вешает ошибку на `sig`.
|
|
5331
|
+
Для элементов массива / под-моделей захватывайте нужный снапшот в замыкание (`const item = im.get()`),
|
|
5332
|
+
т.к. `fn` всегда получает модель ТЕКУЩЕГО scope (корень прогона), а не под-модель.
|
|
5333
|
+
|
|
5334
|
+
**Signature:**
|
|
5335
|
+
```typescript
|
|
5336
|
+
export function cross<TSnapshot>(
|
|
5337
|
+
sig: PathAwareSignal<unknown>,
|
|
5338
|
+
fn: (form: TSnapshot) => ValidationError | null
|
|
5339
|
+
): void
|
|
5340
|
+
```
|
|
5341
|
+
|
|
5342
|
+
_Source: src/form/validation/operators.ts_
|
|
5343
|
+
|
|
5344
|
+
### defer
|
|
5345
|
+
|
|
5346
|
+
**Kind:** `function`
|
|
5347
|
+
|
|
5348
|
+
Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected».
|
|
5349
|
+
|
|
5350
|
+
**Signature:**
|
|
5351
|
+
```typescript
|
|
5352
|
+
export function defer(fn: () => void): void
|
|
5353
|
+
```
|
|
5354
|
+
|
|
5355
|
+
_Source: src/form/behaviors/context.ts_
|
|
5356
|
+
|
|
5357
|
+
### defineFormBehavior
|
|
5358
|
+
|
|
5359
|
+
**Kind:** `function`
|
|
5360
|
+
|
|
5361
|
+
Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
|
|
5362
|
+
|
|
5363
|
+
**Signature:**
|
|
5364
|
+
```typescript
|
|
5365
|
+
export function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>
|
|
5366
|
+
```
|
|
5367
|
+
|
|
5368
|
+
**Examples:**
|
|
5369
|
+
|
|
5370
|
+
```ts
|
|
5371
|
+
export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
5372
|
+
compute(model.$.total, () => model.price * model.qty);
|
|
5373
|
+
enableWhen([model.$.city], () => Boolean(model.country));
|
|
5374
|
+
onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
|
|
5375
|
+
});
|
|
5376
|
+
```
|
|
5377
|
+
|
|
5378
|
+
_Source: src/form/behaviors/context.ts_
|
|
5379
|
+
|
|
5380
|
+
### defineValidationSchema
|
|
5381
|
+
|
|
5382
|
+
**Kind:** `function`
|
|
5383
|
+
|
|
5384
|
+
Тонкая identity-обёртка для типизации/discoverability (как `defineFormBehavior`). Возвращает схему как есть.
|
|
5385
|
+
|
|
5386
|
+
**Signature:**
|
|
5387
|
+
```typescript
|
|
5388
|
+
export function defineValidationSchema<T>(schema: ValidationSchema<T>): ValidationSchema<T>
|
|
5389
|
+
```
|
|
5390
|
+
|
|
5391
|
+
**Examples:**
|
|
5392
|
+
|
|
5393
|
+
```ts
|
|
5394
|
+
export const step1 = defineValidationSchema<LoanForm>(({ model }) => {
|
|
5395
|
+
validate(model.$.loanAmount, [required(), min(50000)]);
|
|
5396
|
+
});
|
|
5397
|
+
```
|
|
5398
|
+
|
|
5399
|
+
_Source: src/form/validation/run.ts_
|
|
5400
|
+
|
|
5092
5401
|
### disableWhen
|
|
5093
5402
|
|
|
5094
5403
|
**Kind:** `function`
|
|
@@ -5121,6 +5430,23 @@ disableWhen(model.$.discount, () => model.plan !== 'promo', { resetOnDisable: tr
|
|
|
5121
5430
|
|
|
5122
5431
|
_Source: src/form/behaviors/node.ts_
|
|
5123
5432
|
|
|
5433
|
+
### each
|
|
5434
|
+
|
|
5435
|
+
**Kind:** `function`
|
|
5436
|
+
|
|
5437
|
+
Применить под-правила к КАЖДОМУ элементу текущего массива модели.
|
|
5438
|
+
`U extends object` — элементы должны быть под-моделями (объектами); для массива примитивов валидируйте лист напрямую.
|
|
5439
|
+
|
|
5440
|
+
**Signature:**
|
|
5441
|
+
```typescript
|
|
5442
|
+
export function each<U extends object>(
|
|
5443
|
+
arr: ModelArray<U>,
|
|
5444
|
+
itemFn: (item: FormModel<U>) => void
|
|
5445
|
+
): void
|
|
5446
|
+
```
|
|
5447
|
+
|
|
5448
|
+
_Source: src/form/validation/operators.ts_
|
|
5449
|
+
|
|
5124
5450
|
### eachLeafSignal
|
|
5125
5451
|
|
|
5126
5452
|
**Kind:** `function`
|
|
@@ -5142,6 +5468,55 @@ export function eachLeafSignal<T>(
|
|
|
5142
5468
|
|
|
5143
5469
|
_Source: src/model/create-model.ts_
|
|
5144
5470
|
|
|
5471
|
+
### effect
|
|
5472
|
+
|
|
5473
|
+
**Kind:** `function`
|
|
5474
|
+
|
|
5475
|
+
Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup.
|
|
5476
|
+
|
|
5477
|
+
**Signature:**
|
|
5478
|
+
```typescript
|
|
5479
|
+
export function effect(fn: () => void | (() => void)): void
|
|
5480
|
+
```
|
|
5481
|
+
|
|
5482
|
+
_Source: src/form/behaviors/context.ts_
|
|
5483
|
+
|
|
5484
|
+
### email
|
|
5485
|
+
|
|
5486
|
+
**Kind:** `function`
|
|
5487
|
+
|
|
5488
|
+
Фабрика валидатора формата email.
|
|
5489
|
+
|
|
5490
|
+
Проверяет по упрощённому regex `^[^\s@]+@[^\s@]+\.[^\s@]+$`. Пустые значения
|
|
5491
|
+
(`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
|
|
5492
|
+
|
|
5493
|
+
**Signature:**
|
|
5494
|
+
```typescript
|
|
5495
|
+
export function email<TForm = unknown, TField extends string | null | undefined = string>(
|
|
5496
|
+
options?: ValidateOptions
|
|
5497
|
+
): Validator<TForm, TField>
|
|
5498
|
+
```
|
|
5499
|
+
|
|
5500
|
+
**Parameters:**
|
|
5501
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
5502
|
+
|
|
5503
|
+
**Returns:** Чистый валидатор {@link Validator} для строкового поля
|
|
5504
|
+
|
|
5505
|
+
**Examples:**
|
|
5506
|
+
|
|
5507
|
+
Проверка формата email
|
|
5508
|
+
```typescript
|
|
5509
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
5510
|
+
import { required, email } from '@reformer/core/validators';
|
|
5511
|
+
|
|
5512
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
5513
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
5514
|
+
validate(model.$.email, [required(), email({ message: 'Введите корректный email' })]);
|
|
5515
|
+
});
|
|
5516
|
+
```
|
|
5517
|
+
|
|
5518
|
+
_Source: src/form/validators/email.ts_
|
|
5519
|
+
|
|
5145
5520
|
### enableWhen
|
|
5146
5521
|
|
|
5147
5522
|
**Kind:** `function`
|
|
@@ -5244,6 +5619,28 @@ export enum ErrorStrategy {
|
|
|
5244
5619
|
|
|
5245
5620
|
_Source: src/form/validation/error-handler.ts_
|
|
5246
5621
|
|
|
5622
|
+
### exclusiveFlag
|
|
5623
|
+
|
|
5624
|
+
**Kind:** `function`
|
|
5625
|
+
|
|
5626
|
+
Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).
|
|
5627
|
+
Когда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:
|
|
5628
|
+
новые строки с флагом false исключения не запускают.
|
|
5629
|
+
|
|
5630
|
+
**Signature:**
|
|
5631
|
+
```typescript
|
|
5632
|
+
export function exclusiveFlag<TItem>(
|
|
5633
|
+
array: object,
|
|
5634
|
+
getFlag: (row: FormModel<TItem>) => Signal<boolean>
|
|
5635
|
+
): void
|
|
5636
|
+
```
|
|
5637
|
+
|
|
5638
|
+
**Examples:**
|
|
5639
|
+
|
|
5640
|
+
exclusiveFlag(model.$.contacts, (row) => row.$.primary);
|
|
5641
|
+
|
|
5642
|
+
_Source: src/form/behaviors/collections.ts_
|
|
5643
|
+
|
|
5247
5644
|
### FieldConfig
|
|
5248
5645
|
|
|
5249
5646
|
**Kind:** `interface`
|
|
@@ -5522,10 +5919,11 @@ export class FieldNode<T> extends FormNode<T> {
|
|
|
5522
5919
|
|
|
5523
5920
|
**Examples:**
|
|
5524
5921
|
|
|
5922
|
+
Пример node-level: конфиг УЗЛА, а не layout-схема M1 (в ней поля `validators` нет).
|
|
5525
5923
|
```typescript
|
|
5526
5924
|
const field = new FieldNode({
|
|
5527
5925
|
value: '',
|
|
5528
|
-
component:
|
|
5926
|
+
component: InputField,
|
|
5529
5927
|
validators: [required, email],
|
|
5530
5928
|
});
|
|
5531
5929
|
|
|
@@ -5549,29 +5947,92 @@ export type FieldStatus = 'valid' | 'invalid' | 'pending' | 'disabled';
|
|
|
5549
5947
|
|
|
5550
5948
|
_Source: src/form/types/contracts.ts_
|
|
5551
5949
|
|
|
5552
|
-
###
|
|
5553
|
-
|
|
5554
|
-
**Kind:** `type`
|
|
5950
|
+
### FileLike
|
|
5555
5951
|
|
|
5556
|
-
|
|
5952
|
+
**Kind:** `interface`
|
|
5557
5953
|
|
|
5558
|
-
|
|
5954
|
+
Минимальный файлоподобный контракт, с которым работают file-валидаторы:
|
|
5955
|
+
нативный `File`, сериализуемый дескриптор загруженного файла и т.п.
|
|
5559
5956
|
|
|
5560
5957
|
**Signature:**
|
|
5561
5958
|
```typescript
|
|
5562
|
-
export
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5959
|
+
export interface FileLike {
|
|
5960
|
+
name: string;
|
|
5961
|
+
size?: number;
|
|
5962
|
+
type?: string;
|
|
5963
|
+
}
|
|
5964
|
+
```
|
|
5568
5965
|
|
|
5569
|
-
|
|
5570
|
-
* Итерация по элементам массива с типизированными элементами
|
|
5571
|
-
*/
|
|
5572
|
-
forEach(callback: (item: FormProxy<T>, index: number) => void): void;
|
|
5966
|
+
_Source: src/form/validators/file-utils.ts_
|
|
5573
5967
|
|
|
5574
|
-
|
|
5968
|
+
### fileType
|
|
5969
|
+
|
|
5970
|
+
**Kind:** `function`
|
|
5971
|
+
|
|
5972
|
+
Фабрика валидатора типа файла.
|
|
5973
|
+
|
|
5974
|
+
Проверяет каждый файл против accept-строки в синтаксисе нативного атрибута
|
|
5975
|
+
`<input type="file" accept>`: расширения (`.pdf`), точные MIME (`image/png`),
|
|
5976
|
+
wildcard-категории (`image/*`), список через запятую. Матчинг регистронезависимый,
|
|
5977
|
+
по `type` и/или расширению из `name` ({@link matchesFileAccept}).
|
|
5978
|
+
|
|
5979
|
+
Нужен потому, что нативный `accept` — только подсказка пикеру: на drag-and-drop
|
|
5980
|
+
и «All files» он не действует. Пустые значения (`null`/`undefined`/`''`/`[]`)
|
|
5981
|
+
пропускаются (используйте {@link required} для обязательности).
|
|
5982
|
+
|
|
5983
|
+
**Signature:**
|
|
5984
|
+
```typescript
|
|
5985
|
+
export function fileType<TForm = unknown, TField = unknown>(
|
|
5986
|
+
accept: string,
|
|
5987
|
+
options?: ValidateOptions
|
|
5988
|
+
): Validator<TForm, TField>
|
|
5989
|
+
```
|
|
5990
|
+
|
|
5991
|
+
**Parameters:**
|
|
5992
|
+
- `accept` — - Accept-строка допустимых типов, например `'image/*,.pdf'`
|
|
5993
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
5994
|
+
попадают `accept` и `fileName` (первый нарушивший файл).
|
|
5995
|
+
|
|
5996
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
5997
|
+
|
|
5998
|
+
**Examples:**
|
|
5999
|
+
|
|
6000
|
+
Только изображения и PDF
|
|
6001
|
+
```typescript
|
|
6002
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
6003
|
+
import { fileType } from '@reformer/core/validators';
|
|
6004
|
+
|
|
6005
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
6006
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
6007
|
+
validate(model.$.documents, [fileType('image/*,.pdf', { message: 'Только изображения или PDF' })]);
|
|
6008
|
+
});
|
|
6009
|
+
```
|
|
6010
|
+
|
|
6011
|
+
_Source: src/form/validators/file-type.ts_
|
|
6012
|
+
|
|
6013
|
+
### FormArrayProxy
|
|
6014
|
+
|
|
6015
|
+
**Kind:** `type`
|
|
6016
|
+
|
|
6017
|
+
Комбинированный тип для ArrayNode с Proxy доступом к элементам
|
|
6018
|
+
|
|
6019
|
+
Объединяет методы и свойства ArrayNode с типизированным доступом к элементам массива.
|
|
6020
|
+
|
|
6021
|
+
**Signature:**
|
|
6022
|
+
```typescript
|
|
6023
|
+
export type FormArrayProxy<T extends object> = ArrayNode<T> & {
|
|
6024
|
+
/**
|
|
6025
|
+
* Безопасный доступ к элементу массива по индексу
|
|
6026
|
+
* Возвращает GroupNode с типизированными полями или undefined
|
|
6027
|
+
*/
|
|
6028
|
+
at(index: number): FormProxy<T> | undefined;
|
|
6029
|
+
|
|
6030
|
+
/**
|
|
6031
|
+
* Итерация по элементам массива с типизированными элементами
|
|
6032
|
+
*/
|
|
6033
|
+
forEach(callback: (item: FormProxy<T>, index: number) => void): void;
|
|
6034
|
+
|
|
6035
|
+
/**
|
|
5575
6036
|
* Маппинг элементов массива с типизированными элементами
|
|
5576
6037
|
*/
|
|
5577
6038
|
map<R>(callback: (item: FormProxy<T>, index: number) => R): R[];
|
|
@@ -5603,6 +6064,22 @@ todos.forEach((item, i) => {
|
|
|
5603
6064
|
|
|
5604
6065
|
_Source: src/form/types/form-proxy.ts_
|
|
5605
6066
|
|
|
6067
|
+
### FormBehavior
|
|
6068
|
+
|
|
6069
|
+
**Kind:** `interface`
|
|
6070
|
+
|
|
6071
|
+
Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`.
|
|
6072
|
+
|
|
6073
|
+
**Signature:**
|
|
6074
|
+
```typescript
|
|
6075
|
+
export interface FormBehavior<T> {
|
|
6076
|
+
/** @internal Запускается `createForm` после построения нод и заполнения реестра сигнал→нода. */
|
|
6077
|
+
__run(model: FormModel<T>, form: FormProxy<T>): BehaviorCleanup;
|
|
6078
|
+
}
|
|
6079
|
+
```
|
|
6080
|
+
|
|
6081
|
+
_Source: src/form/behaviors/types.ts_
|
|
6082
|
+
|
|
5606
6083
|
### FormBundleLike
|
|
5607
6084
|
|
|
5608
6085
|
**Kind:** `interface`
|
|
@@ -5897,14 +6374,14 @@ interface Form {
|
|
|
5897
6374
|
}
|
|
5898
6375
|
|
|
5899
6376
|
const schema: FormSchema<Form> = {
|
|
5900
|
-
name: { value: '', component:
|
|
6377
|
+
name: { value: '', component: InputField },
|
|
5901
6378
|
address: {
|
|
5902
|
-
city: { value: '', component:
|
|
5903
|
-
street: { value: '', component:
|
|
6379
|
+
city: { value: '', component: InputField },
|
|
6380
|
+
street: { value: '', component: InputField },
|
|
5904
6381
|
},
|
|
5905
6382
|
items: [{
|
|
5906
|
-
title: { value: '', component:
|
|
5907
|
-
price: { value: 0, component:
|
|
6383
|
+
title: { value: '', component: InputField },
|
|
6384
|
+
price: { value: 0, component: InputField },
|
|
5908
6385
|
}],
|
|
5909
6386
|
};
|
|
5910
6387
|
```
|
|
@@ -6074,6 +6551,37 @@ export interface FormValidationBundle<T> {
|
|
|
6074
6551
|
|
|
6075
6552
|
_Source: src/form/validation/config.ts_
|
|
6076
6553
|
|
|
6554
|
+
### FormValidationController
|
|
6555
|
+
|
|
6556
|
+
**Kind:** `interface`
|
|
6557
|
+
|
|
6558
|
+
**Signature:**
|
|
6559
|
+
```typescript
|
|
6560
|
+
export interface FormValidationController {
|
|
6561
|
+
/**
|
|
6562
|
+
* Полный прогон схемы с раскрытием ошибок (`touch: true`) — для submit. Также переводит
|
|
6563
|
+
* `afterFirstSubmit` в live-фазу. Возвращает `false`, если есть блокирующие ошибки.
|
|
6564
|
+
*/
|
|
6565
|
+
validate(): Promise<boolean>;
|
|
6566
|
+
/**
|
|
6567
|
+
* Армировать реактивные подписки стратегии. Идемпотентно. Возвращает `dispose`. НЕ звать при SSR.
|
|
6568
|
+
*
|
|
6569
|
+
* После {@link FormValidationController.dispose} контроллер армируется ЗАНОВО: React монтирует
|
|
6570
|
+
* компоненты повторно (StrictMode в разработке, remount по роуту), и «одноразовый» контроллер
|
|
6571
|
+
* после такого цикла молча оставался бы без живой валидации.
|
|
6572
|
+
*/
|
|
6573
|
+
start(): () => void;
|
|
6574
|
+
/** Снять подписки/таймеры. Идемпотентно; после него `start()` снова армирует стратегию. */
|
|
6575
|
+
dispose(): void;
|
|
6576
|
+
/** Идёт ли прогон (submit или live) — снапшот. */
|
|
6577
|
+
readonly isValidating: boolean;
|
|
6578
|
+
/** Реактивный сигнал состояния прогона (для тонкой подписки в UI). */
|
|
6579
|
+
readonly validating: ReadonlySignal<boolean>;
|
|
6580
|
+
}
|
|
6581
|
+
```
|
|
6582
|
+
|
|
6583
|
+
_Source: src/form/validation/strategy.ts_
|
|
6584
|
+
|
|
6077
6585
|
### FormValue
|
|
6078
6586
|
|
|
6079
6587
|
**Kind:** `type`
|
|
@@ -6097,6 +6605,43 @@ export type FormValue =
|
|
|
6097
6605
|
|
|
6098
6606
|
_Source: src/form/types/contracts.ts_
|
|
6099
6607
|
|
|
6608
|
+
### futureDate
|
|
6609
|
+
|
|
6610
|
+
**Kind:** `function`
|
|
6611
|
+
|
|
6612
|
+
Фабрика валидатора, проверяющего что дата не в прошлом.
|
|
6613
|
+
|
|
6614
|
+
Дата не должна быть раньше сегодняшнего дня (сравнение по нормализованным датам).
|
|
6615
|
+
Пустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).
|
|
6616
|
+
|
|
6617
|
+
**Signature:**
|
|
6618
|
+
```typescript
|
|
6619
|
+
export function futureDate<
|
|
6620
|
+
TForm = unknown,
|
|
6621
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
6622
|
+
>(options?: ValidateOptions): Validator<TForm, TField>
|
|
6623
|
+
```
|
|
6624
|
+
|
|
6625
|
+
**Parameters:**
|
|
6626
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
6627
|
+
|
|
6628
|
+
**Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
|
|
6629
|
+
|
|
6630
|
+
**Examples:**
|
|
6631
|
+
|
|
6632
|
+
Дата не в прошлом
|
|
6633
|
+
```typescript
|
|
6634
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
6635
|
+
import { required, futureDate } from '@reformer/core/validators';
|
|
6636
|
+
|
|
6637
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
6638
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
6639
|
+
validate(model.$.appointmentDate, [required(), futureDate({ message: 'Дата записи должна быть в будущем' })]);
|
|
6640
|
+
});
|
|
6641
|
+
```
|
|
6642
|
+
|
|
6643
|
+
_Source: src/form/validators/future-date.ts_
|
|
6644
|
+
|
|
6100
6645
|
### getNodeForSignal
|
|
6101
6646
|
|
|
6102
6647
|
**Kind:** `function`
|
|
@@ -6158,6 +6703,19 @@ console.log('Node type:', getNodeType(node)); // "FieldNode" | "GroupNode" | "Ar
|
|
|
6158
6703
|
|
|
6159
6704
|
_Source: src/form/type-guards.ts_
|
|
6160
6705
|
|
|
6706
|
+
### getScope
|
|
6707
|
+
|
|
6708
|
+
**Kind:** `function`
|
|
6709
|
+
|
|
6710
|
+
Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов).
|
|
6711
|
+
|
|
6712
|
+
**Signature:**
|
|
6713
|
+
```typescript
|
|
6714
|
+
export function getScope<T>(): BehaviorScope<T>
|
|
6715
|
+
```
|
|
6716
|
+
|
|
6717
|
+
_Source: src/form/behaviors/context.ts_
|
|
6718
|
+
|
|
6161
6719
|
### GroupNode
|
|
6162
6720
|
|
|
6163
6721
|
**Kind:** `class`
|
|
@@ -6180,8 +6738,8 @@ export class GroupNode<T> extends FormNode<T> {
|
|
|
6180
6738
|
|
|
6181
6739
|
```typescript
|
|
6182
6740
|
const form = new GroupNode({
|
|
6183
|
-
email: { valueSignal: model.$.email, component:
|
|
6184
|
-
password: { valueSignal: model.$.password, component:
|
|
6741
|
+
email: { valueSignal: model.$.email, component: InputField },
|
|
6742
|
+
password: { valueSignal: model.$.password, component: InputField },
|
|
6185
6743
|
});
|
|
6186
6744
|
|
|
6187
6745
|
// Прямой доступ к полям через Proxy
|
|
@@ -6212,6 +6770,41 @@ export interface GroupNodeConfig<T> {
|
|
|
6212
6770
|
|
|
6213
6771
|
_Source: src/form/types/index.ts_
|
|
6214
6772
|
|
|
6773
|
+
### integer
|
|
6774
|
+
|
|
6775
|
+
**Kind:** `function`
|
|
6776
|
+
|
|
6777
|
+
Фабрика валидатора, проверяющего что число — целое.
|
|
6778
|
+
|
|
6779
|
+
Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
|
|
6780
|
+
|
|
6781
|
+
**Signature:**
|
|
6782
|
+
```typescript
|
|
6783
|
+
export function integer<TForm = unknown, TField extends number | null | undefined = number>(
|
|
6784
|
+
options?: ValidateOptions
|
|
6785
|
+
): Validator<TForm, TField>
|
|
6786
|
+
```
|
|
6787
|
+
|
|
6788
|
+
**Parameters:**
|
|
6789
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
6790
|
+
|
|
6791
|
+
**Returns:** Чистый валидатор {@link Validator} для числового поля
|
|
6792
|
+
|
|
6793
|
+
**Examples:**
|
|
6794
|
+
|
|
6795
|
+
Проверка целого числа
|
|
6796
|
+
```typescript
|
|
6797
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
6798
|
+
import { required, integer } from '@reformer/core/validators';
|
|
6799
|
+
|
|
6800
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
6801
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
6802
|
+
validate(model.$.count, [required(), integer({ message: 'Должно быть целым числом' })]);
|
|
6803
|
+
});
|
|
6804
|
+
```
|
|
6805
|
+
|
|
6806
|
+
_Source: src/form/validators/integer.ts_
|
|
6807
|
+
|
|
6215
6808
|
### isArrayNode
|
|
6216
6809
|
|
|
6217
6810
|
**Kind:** `function`
|
|
@@ -6243,6 +6836,43 @@ if (isArrayNode(node)) {
|
|
|
6243
6836
|
|
|
6244
6837
|
_Source: src/form/type-guards.ts_
|
|
6245
6838
|
|
|
6839
|
+
### isDate
|
|
6840
|
+
|
|
6841
|
+
**Kind:** `function`
|
|
6842
|
+
|
|
6843
|
+
Фабрика валидатора, проверяющего что значение — валидная дата.
|
|
6844
|
+
|
|
6845
|
+
Принимает `Date` или строку, парсимую в дату. Пустые значения (`''`/`null`/`undefined`)
|
|
6846
|
+
пропускаются (используйте {@link required} для обязательности).
|
|
6847
|
+
|
|
6848
|
+
**Signature:**
|
|
6849
|
+
```typescript
|
|
6850
|
+
export function isDate<
|
|
6851
|
+
TForm = unknown,
|
|
6852
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
6853
|
+
>(options?: ValidateOptions): Validator<TForm, TField>
|
|
6854
|
+
```
|
|
6855
|
+
|
|
6856
|
+
**Parameters:**
|
|
6857
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
6858
|
+
|
|
6859
|
+
**Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
|
|
6860
|
+
|
|
6861
|
+
**Examples:**
|
|
6862
|
+
|
|
6863
|
+
Проверка валидности даты
|
|
6864
|
+
```typescript
|
|
6865
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
6866
|
+
import { required, isDate } from '@reformer/core/validators';
|
|
6867
|
+
|
|
6868
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
6869
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
6870
|
+
validate(model.$.eventDate, [required(), isDate({ message: 'Введите корректную дату' })]);
|
|
6871
|
+
});
|
|
6872
|
+
```
|
|
6873
|
+
|
|
6874
|
+
_Source: src/form/validators/is-date.ts_
|
|
6875
|
+
|
|
6246
6876
|
### isDerived
|
|
6247
6877
|
|
|
6248
6878
|
**Kind:** `function`
|
|
@@ -6308,6 +6938,25 @@ if (isFieldNode(node)) {
|
|
|
6308
6938
|
|
|
6309
6939
|
_Source: src/form/type-guards.ts_
|
|
6310
6940
|
|
|
6941
|
+
### isFileLike
|
|
6942
|
+
|
|
6943
|
+
**Kind:** `function`
|
|
6944
|
+
|
|
6945
|
+
Проверяет, что значение файлоподобно: объект со строковым `name` и опциональными
|
|
6946
|
+
числовым `size` / строковым `type`.
|
|
6947
|
+
|
|
6948
|
+
**Signature:**
|
|
6949
|
+
```typescript
|
|
6950
|
+
export function isFileLike(value: unknown): value is FileLike
|
|
6951
|
+
```
|
|
6952
|
+
|
|
6953
|
+
**Parameters:**
|
|
6954
|
+
- `value` — - Проверяемое значение
|
|
6955
|
+
|
|
6956
|
+
**Returns:** `true`, если значение соответствует {@link FileLike}
|
|
6957
|
+
|
|
6958
|
+
_Source: src/form/validators/file-utils.ts_
|
|
6959
|
+
|
|
6311
6960
|
### isFormNode
|
|
6312
6961
|
|
|
6313
6962
|
**Kind:** `function`
|
|
@@ -6389,6 +7038,43 @@ const isLeaf = (v: unknown) => isSignalLike(v) && !isModelContainerSignal(v);
|
|
|
6389
7038
|
|
|
6390
7039
|
_Source: src/model/model-signals-proxy.ts_
|
|
6391
7040
|
|
|
7041
|
+
### isNumber
|
|
7042
|
+
|
|
7043
|
+
**Kind:** `function`
|
|
7044
|
+
|
|
7045
|
+
Фабрика валидатора, проверяющего что значение — конечное число (не NaN, не строка).
|
|
7046
|
+
|
|
7047
|
+
Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для
|
|
7048
|
+
обязательности). В отличие от других number-валидаторов, **не** пропускает не-числа
|
|
7049
|
+
и `NaN` — это его задача.
|
|
7050
|
+
|
|
7051
|
+
**Signature:**
|
|
7052
|
+
```typescript
|
|
7053
|
+
export function isNumber<TForm = unknown, TField extends number | null | undefined = number>(
|
|
7054
|
+
options?: ValidateOptions
|
|
7055
|
+
): Validator<TForm, TField>
|
|
7056
|
+
```
|
|
7057
|
+
|
|
7058
|
+
**Parameters:**
|
|
7059
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
7060
|
+
|
|
7061
|
+
**Returns:** Чистый валидатор {@link Validator} для числового поля
|
|
7062
|
+
|
|
7063
|
+
**Examples:**
|
|
7064
|
+
|
|
7065
|
+
Проверка, что значение — число
|
|
7066
|
+
```typescript
|
|
7067
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7068
|
+
import { required, isNumber } from '@reformer/core/validators';
|
|
7069
|
+
|
|
7070
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7071
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7072
|
+
validate(model.$.amount, [required(), isNumber({ message: 'Введите число' })]);
|
|
7073
|
+
});
|
|
7074
|
+
```
|
|
7075
|
+
|
|
7076
|
+
_Source: src/form/validators/is-number.ts_
|
|
7077
|
+
|
|
6392
7078
|
### markDerived
|
|
6393
7079
|
|
|
6394
7080
|
**Kind:** `function`
|
|
@@ -6425,94 +7111,638 @@ markDerived(model.$.total);
|
|
|
6425
7111
|
|
|
6426
7112
|
_Source: src/model/derived-registry.ts_
|
|
6427
7113
|
|
|
6428
|
-
###
|
|
7114
|
+
### matchesFileAccept
|
|
6429
7115
|
|
|
6430
|
-
**Kind:** `
|
|
7116
|
+
**Kind:** `function`
|
|
6431
7117
|
|
|
6432
|
-
|
|
7118
|
+
Проверяет файл против accept-паттерна в синтаксисе нативного атрибута
|
|
7119
|
+
`<input type="file" accept>`: список через запятую из расширений (`.pdf`),
|
|
7120
|
+
точных MIME (`image/png`) и wildcard-категорий (`image/*`). Регистронезависимо.
|
|
6433
7121
|
|
|
6434
|
-
|
|
6435
|
-
|
|
7122
|
+
Нужен собственный матчер, потому что `accept` у нативного input — только подсказка
|
|
7123
|
+
пикеру: на drag-and-drop и на программное значение он не действует.
|
|
6436
7124
|
|
|
6437
7125
|
**Signature:**
|
|
6438
7126
|
```typescript
|
|
6439
|
-
export
|
|
6440
|
-
/**
|
|
6441
|
-
* Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`.
|
|
6442
|
-
* Сам узел — {@link ReadonlySignal} модели целиком: `model.$.subscribe(v => …)`.
|
|
6443
|
-
*/
|
|
6444
|
-
readonly $: ModelGroupSignals<T>;
|
|
6445
|
-
/** Снимок значений (без подписки) — для submit. */
|
|
6446
|
-
get(): T;
|
|
6447
|
-
/**
|
|
6448
|
-
* Полная установка значений: принимает объект целиком (все ключи `T`) и записывает их в модель.
|
|
6449
|
-
* Производными полями (цели `compute`) владеет compute — их значения из payload игнорируются.
|
|
6450
|
-
* Не меняет initial-снимок. Для частичного обновления (только переданные ключи) — {@link ModelApi.patch}.
|
|
6451
|
-
*/
|
|
6452
|
-
set(value: T): void;
|
|
6453
|
-
/**
|
|
6454
|
-
* Частичное слияние значений (load/patch с сервера): обновляет только переданные ключи,
|
|
6455
|
-
* отсутствующие ключи НЕ трогаются. Не меняет initial-снимок.
|
|
6456
|
-
*/
|
|
6457
|
-
patch(value: Partial<T>): void;
|
|
6458
|
-
/** Отличаются ли текущие значения от initial-снимка (value-diff). */
|
|
6459
|
-
isDirty(): boolean;
|
|
6460
|
-
/** Сбросить значения к initial-снимку. */
|
|
6461
|
-
reset(): void;
|
|
6462
|
-
/** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */
|
|
6463
|
-
captureInitial(): void;
|
|
6464
|
-
/** Резолв строкового пути в сигнал (для error-routing/мостов). */
|
|
6465
|
-
signalAt(path: string): PathAwareSignal<unknown> | undefined;
|
|
6466
|
-
}
|
|
7127
|
+
export function matchesFileAccept(file: Pick<FileLike, 'name' | 'type'>, accept: string): boolean
|
|
6467
7128
|
```
|
|
6468
7129
|
|
|
6469
|
-
|
|
7130
|
+
**Parameters:**
|
|
7131
|
+
- `file` — - Файлоподобное значение (`name` + опциональный `type`)
|
|
7132
|
+
- `accept` — - Accept-строка; пустая/пробельная строка матчит всё
|
|
6470
7133
|
|
|
6471
|
-
|
|
7134
|
+
**Returns:** `true`, если файл подходит хотя бы под один паттерн
|
|
6472
7135
|
|
|
6473
|
-
|
|
7136
|
+
_Source: src/form/validators/file-utils.ts_
|
|
6474
7137
|
|
|
6475
|
-
|
|
6476
|
-
|
|
7138
|
+
### max
|
|
7139
|
+
|
|
7140
|
+
**Kind:** `function`
|
|
7141
|
+
|
|
7142
|
+
Фабрика валидатора максимального числового значения.
|
|
7143
|
+
|
|
7144
|
+
Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
|
|
6477
7145
|
|
|
6478
7146
|
**Signature:**
|
|
6479
7147
|
```typescript
|
|
6480
|
-
export
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
*/
|
|
6485
|
-
readonly __path: string;
|
|
6486
|
-
/** Реактивная длина. */
|
|
6487
|
-
readonly length: number;
|
|
6488
|
-
/** Добавить элемент в конец (значение элемента целиком). */
|
|
6489
|
-
push(item: U): void;
|
|
6490
|
-
/** Вставить элемент по индексу. */
|
|
6491
|
-
insertAt(index: number, item: U): void;
|
|
6492
|
-
/** Удалить элемент по индексу. */
|
|
6493
|
-
removeAt(index: number): void;
|
|
6494
|
-
/** Переместить элемент. */
|
|
6495
|
-
move(from: number, to: number): void;
|
|
6496
|
-
/** Поменять местами два элемента. */
|
|
6497
|
-
swap(a: number, b: number): void;
|
|
6498
|
-
/** Очистить массив. */
|
|
6499
|
-
clear(): void;
|
|
6500
|
-
/** Элемент по индексу: объект → под-модель, массив → {@link ModelArray}, лист → значение. */
|
|
6501
|
-
at(
|
|
6502
|
-
index: number
|
|
6503
|
-
): NonNullable<U> extends object ? ModelArrayItem<U> : ModelArrayItem<U> | undefined;
|
|
6504
|
-
/** Map по элементам (объект → {@link FormModel}, Opaque/примитив → значение, массив → {@link ModelArray}). */
|
|
6505
|
-
map<R>(fn: (item: ModelArrayItem<U>, index: number) => R): R[];
|
|
6506
|
-
/** Итерация по элементам (см. {@link ModelArrayItem}). */
|
|
6507
|
-
forEach(fn: (item: ModelArrayItem<U>, index: number) => void): void;
|
|
6508
|
-
/** Снимок массива значений (без подписки). */
|
|
6509
|
-
toArray(): U[];
|
|
6510
|
-
/** Индексный value-доступ. */
|
|
6511
|
-
[index: number]: ModelValue<U>;
|
|
6512
|
-
}
|
|
7148
|
+
export function max<TForm = unknown, TField extends number | null | undefined = number>(
|
|
7149
|
+
maxValue: number,
|
|
7150
|
+
options?: ValidateOptions
|
|
7151
|
+
): Validator<TForm, TField>
|
|
6513
7152
|
```
|
|
6514
7153
|
|
|
6515
|
-
|
|
7154
|
+
**Parameters:**
|
|
7155
|
+
- `maxValue` — - Максимально допустимое значение (включительно)
|
|
7156
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7157
|
+
попадают `max` и `actual`.
|
|
7158
|
+
|
|
7159
|
+
**Returns:** Чистый валидатор {@link Validator} для числового поля
|
|
7160
|
+
|
|
7161
|
+
**Examples:**
|
|
7162
|
+
|
|
7163
|
+
Максимальное значение числового поля
|
|
7164
|
+
```typescript
|
|
7165
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7166
|
+
import { required, max } from '@reformer/core/validators';
|
|
7167
|
+
|
|
7168
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7169
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7170
|
+
validate(model.$.quantity, [max(100)]);
|
|
7171
|
+
validate(model.$.discount, [required(), max(50, { message: 'Не более 50%' })]);
|
|
7172
|
+
});
|
|
7173
|
+
```
|
|
7174
|
+
|
|
7175
|
+
_Source: src/form/validators/max.ts_
|
|
7176
|
+
|
|
7177
|
+
### maxAge
|
|
7178
|
+
|
|
7179
|
+
**Kind:** `function`
|
|
7180
|
+
|
|
7181
|
+
Фабрика валидатора максимального возраста (по дате рождения).
|
|
7182
|
+
|
|
7183
|
+
Возраст вычисляется по дате рождения относительно сегодняшнего дня. Пустые и невалидные
|
|
7184
|
+
даты пропускаются (используйте {@link required} и {@link isDate}).
|
|
7185
|
+
|
|
7186
|
+
**Signature:**
|
|
7187
|
+
```typescript
|
|
7188
|
+
export function maxAge<
|
|
7189
|
+
TForm = unknown,
|
|
7190
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
7191
|
+
>(maxAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>
|
|
7192
|
+
```
|
|
7193
|
+
|
|
7194
|
+
**Parameters:**
|
|
7195
|
+
- `maxAgeValue` — - Максимально допустимый возраст (в полных годах)
|
|
7196
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7197
|
+
попадают `maxAge` и `currentAge`.
|
|
7198
|
+
|
|
7199
|
+
**Returns:** Чистый валидатор {@link Validator} для поля даты рождения (`string | Date`)
|
|
7200
|
+
|
|
7201
|
+
**Examples:**
|
|
7202
|
+
|
|
7203
|
+
Максимальный возраст
|
|
7204
|
+
```typescript
|
|
7205
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7206
|
+
import { maxAge } from '@reformer/core/validators';
|
|
7207
|
+
|
|
7208
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7209
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7210
|
+
validate(model.$.birthDate, [maxAge(100, { message: 'Проверьте дату рождения' })]);
|
|
7211
|
+
});
|
|
7212
|
+
```
|
|
7213
|
+
|
|
7214
|
+
_Source: src/form/validators/max-age.ts_
|
|
7215
|
+
|
|
7216
|
+
### maxDate
|
|
7217
|
+
|
|
7218
|
+
**Kind:** `function`
|
|
7219
|
+
|
|
7220
|
+
Фабрика валидатора максимальной даты (включительно).
|
|
7221
|
+
|
|
7222
|
+
Сравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты
|
|
7223
|
+
пропускаются (используйте {@link required} и {@link isDate}).
|
|
7224
|
+
|
|
7225
|
+
**Signature:**
|
|
7226
|
+
```typescript
|
|
7227
|
+
export function maxDate<
|
|
7228
|
+
TForm = unknown,
|
|
7229
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
7230
|
+
>(maxDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>
|
|
7231
|
+
```
|
|
7232
|
+
|
|
7233
|
+
**Parameters:**
|
|
7234
|
+
- `maxDateValue` — - Максимально допустимая дата (включительно)
|
|
7235
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7236
|
+
попадает `maxDate`.
|
|
7237
|
+
|
|
7238
|
+
**Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
|
|
7239
|
+
|
|
7240
|
+
**Examples:**
|
|
7241
|
+
|
|
7242
|
+
Максимальная дата
|
|
7243
|
+
```typescript
|
|
7244
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7245
|
+
import { maxDate } from '@reformer/core/validators';
|
|
7246
|
+
|
|
7247
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7248
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7249
|
+
validate(model.$.birthDate, [maxDate(new Date(), { message: 'Дата не может быть в будущем' })]);
|
|
7250
|
+
});
|
|
7251
|
+
```
|
|
7252
|
+
|
|
7253
|
+
_Source: src/form/validators/max-date.ts_
|
|
7254
|
+
|
|
7255
|
+
### maxFiles
|
|
7256
|
+
|
|
7257
|
+
**Kind:** `function`
|
|
7258
|
+
|
|
7259
|
+
Фабрика валидатора максимального количества файлов.
|
|
7260
|
+
|
|
7261
|
+
Работает с массивом файлов (одиночный файл считается как 1). Пустые значения
|
|
7262
|
+
(`null`/`undefined`/`''`/`[]`) пропускаются (используйте {@link required}
|
|
7263
|
+
для обязательности).
|
|
7264
|
+
|
|
7265
|
+
**Signature:**
|
|
7266
|
+
```typescript
|
|
7267
|
+
export function maxFiles<TForm = unknown, TField = unknown>(
|
|
7268
|
+
max: number,
|
|
7269
|
+
options?: ValidateOptions
|
|
7270
|
+
): Validator<TForm, TField>
|
|
7271
|
+
```
|
|
7272
|
+
|
|
7273
|
+
**Parameters:**
|
|
7274
|
+
- `max` — - Максимально допустимое количество файлов (включительно)
|
|
7275
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7276
|
+
попадают `maxFiles` и `actualCount`.
|
|
7277
|
+
|
|
7278
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
7279
|
+
|
|
7280
|
+
**Examples:**
|
|
7281
|
+
|
|
7282
|
+
Не более трёх вложений
|
|
7283
|
+
```typescript
|
|
7284
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7285
|
+
import { maxFiles } from '@reformer/core/validators';
|
|
7286
|
+
|
|
7287
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7288
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7289
|
+
validate(model.$.documents, [maxFiles(3, { message: 'Максимум 3 файла' })]);
|
|
7290
|
+
});
|
|
7291
|
+
```
|
|
7292
|
+
|
|
7293
|
+
_Source: src/form/validators/max-files.ts_
|
|
7294
|
+
|
|
7295
|
+
### maxFileSize
|
|
7296
|
+
|
|
7297
|
+
**Kind:** `function`
|
|
7298
|
+
|
|
7299
|
+
Фабрика валидатора максимального размера файла.
|
|
7300
|
+
|
|
7301
|
+
Работает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с
|
|
7302
|
+
`name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.
|
|
7303
|
+
Пустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`
|
|
7304
|
+
(например, дескриптор загруженного файла без размера) пропускаются
|
|
7305
|
+
(используйте {@link required} для обязательности).
|
|
7306
|
+
|
|
7307
|
+
**Signature:**
|
|
7308
|
+
```typescript
|
|
7309
|
+
export function maxFileSize<TForm = unknown, TField = unknown>(
|
|
7310
|
+
maxSize: number,
|
|
7311
|
+
options?: ValidateOptions
|
|
7312
|
+
): Validator<TForm, TField>
|
|
7313
|
+
```
|
|
7314
|
+
|
|
7315
|
+
**Parameters:**
|
|
7316
|
+
- `maxSize` — - Максимально допустимый размер файла в байтах (включительно)
|
|
7317
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7318
|
+
попадают `maxFileSize`, `fileName` и `actualSize`.
|
|
7319
|
+
|
|
7320
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
7321
|
+
|
|
7322
|
+
**Examples:**
|
|
7323
|
+
|
|
7324
|
+
Ограничение размера вложений
|
|
7325
|
+
```typescript
|
|
7326
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7327
|
+
import { maxFileSize } from '@reformer/core/validators';
|
|
7328
|
+
|
|
7329
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7330
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7331
|
+
validate(model.$.documents, [maxFileSize(5 * 1024 * 1024, { message: 'Файл больше 5 МБ' })]);
|
|
7332
|
+
});
|
|
7333
|
+
```
|
|
7334
|
+
|
|
7335
|
+
_Source: src/form/validators/max-file-size.ts_
|
|
7336
|
+
|
|
7337
|
+
### maxLength
|
|
7338
|
+
|
|
7339
|
+
**Kind:** `function`
|
|
7340
|
+
|
|
7341
|
+
Фабрика валидатора максимальной длины строки или массива.
|
|
7342
|
+
|
|
7343
|
+
Работает со строкой или массивом (проверяется `value.length`). Пустые значения
|
|
7344
|
+
(`null`/`undefined`/`''`) и значения без числового `length` пропускаются
|
|
7345
|
+
(используйте {@link required} для обязательности).
|
|
7346
|
+
|
|
7347
|
+
**Signature:**
|
|
7348
|
+
```typescript
|
|
7349
|
+
export function maxLength<TForm = unknown, TField = unknown>(
|
|
7350
|
+
maxLen: number,
|
|
7351
|
+
options?: ValidateOptions
|
|
7352
|
+
): Validator<TForm, TField>
|
|
7353
|
+
```
|
|
7354
|
+
|
|
7355
|
+
**Parameters:**
|
|
7356
|
+
- `maxLen` — - Максимально допустимая длина (включительно)
|
|
7357
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7358
|
+
попадают `maxLength` и `actualLength`.
|
|
7359
|
+
|
|
7360
|
+
**Returns:** Чистый валидатор {@link Validator} для строки или массива
|
|
7361
|
+
|
|
7362
|
+
**Examples:**
|
|
7363
|
+
|
|
7364
|
+
Максимальная длина строки
|
|
7365
|
+
```typescript
|
|
7366
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7367
|
+
import { maxLength } from '@reformer/core/validators';
|
|
7368
|
+
|
|
7369
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7370
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7371
|
+
validate(model.$.name, [maxLength(50)]);
|
|
7372
|
+
validate(model.$.bio, [maxLength(500, { message: 'Максимум 500 символов' })]);
|
|
7373
|
+
});
|
|
7374
|
+
```
|
|
7375
|
+
|
|
7376
|
+
_Source: src/form/validators/max-length.ts_
|
|
7377
|
+
|
|
7378
|
+
### maxTotalFileSize
|
|
7379
|
+
|
|
7380
|
+
**Kind:** `function`
|
|
7381
|
+
|
|
7382
|
+
Фабрика валидатора суммарного размера всех файлов.
|
|
7383
|
+
|
|
7384
|
+
Складывает `size` всех файлов значения (элементы без числового `size` в сумму
|
|
7385
|
+
не входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются
|
|
7386
|
+
(используйте {@link required} для обязательности).
|
|
7387
|
+
|
|
7388
|
+
**Signature:**
|
|
7389
|
+
```typescript
|
|
7390
|
+
export function maxTotalFileSize<TForm = unknown, TField = unknown>(
|
|
7391
|
+
maxTotal: number,
|
|
7392
|
+
options?: ValidateOptions
|
|
7393
|
+
): Validator<TForm, TField>
|
|
7394
|
+
```
|
|
7395
|
+
|
|
7396
|
+
**Parameters:**
|
|
7397
|
+
- `maxTotal` — - Максимально допустимый суммарный размер в байтах (включительно)
|
|
7398
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7399
|
+
попадают `maxTotalFileSize` и `actualTotal`.
|
|
7400
|
+
|
|
7401
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
7402
|
+
|
|
7403
|
+
**Examples:**
|
|
7404
|
+
|
|
7405
|
+
Суммарно не более 20 МБ
|
|
7406
|
+
```typescript
|
|
7407
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7408
|
+
import { maxTotalFileSize } from '@reformer/core/validators';
|
|
7409
|
+
|
|
7410
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7411
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7412
|
+
validate(model.$.documents, [maxTotalFileSize(20 * 1024 * 1024, { message: 'Суммарно не более 20 МБ' })]);
|
|
7413
|
+
});
|
|
7414
|
+
```
|
|
7415
|
+
|
|
7416
|
+
_Source: src/form/validators/max-total-file-size.ts_
|
|
7417
|
+
|
|
7418
|
+
### min
|
|
7419
|
+
|
|
7420
|
+
**Kind:** `function`
|
|
7421
|
+
|
|
7422
|
+
Фабрика валидатора минимального числового значения.
|
|
7423
|
+
|
|
7424
|
+
Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
|
|
7425
|
+
|
|
7426
|
+
**Signature:**
|
|
7427
|
+
```typescript
|
|
7428
|
+
export function min<TForm = unknown, TField extends number | null | undefined = number>(
|
|
7429
|
+
minValue: number,
|
|
7430
|
+
options?: ValidateOptions
|
|
7431
|
+
): Validator<TForm, TField>
|
|
7432
|
+
```
|
|
7433
|
+
|
|
7434
|
+
**Parameters:**
|
|
7435
|
+
- `minValue` — - Минимально допустимое значение (включительно)
|
|
7436
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7437
|
+
попадают `min` и `actual`.
|
|
7438
|
+
|
|
7439
|
+
**Returns:** Чистый валидатор {@link Validator} для числового поля
|
|
7440
|
+
|
|
7441
|
+
**Examples:**
|
|
7442
|
+
|
|
7443
|
+
Минимальное значение числового поля
|
|
7444
|
+
```typescript
|
|
7445
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7446
|
+
import { required, min } from '@reformer/core/validators';
|
|
7447
|
+
|
|
7448
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7449
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7450
|
+
validate(model.$.age, [min(18)]);
|
|
7451
|
+
validate(model.$.quantity, [required(), min(1, { message: 'Минимум 1' })]);
|
|
7452
|
+
});
|
|
7453
|
+
```
|
|
7454
|
+
|
|
7455
|
+
_Source: src/form/validators/min.ts_
|
|
7456
|
+
|
|
7457
|
+
### minAge
|
|
7458
|
+
|
|
7459
|
+
**Kind:** `function`
|
|
7460
|
+
|
|
7461
|
+
Фабрика валидатора минимального возраста (по дате рождения).
|
|
7462
|
+
|
|
7463
|
+
Возраст вычисляется по дате рождения относительно сегодняшнего дня. Пустые и невалидные
|
|
7464
|
+
даты пропускаются (используйте {@link required} и {@link isDate}).
|
|
7465
|
+
|
|
7466
|
+
**Signature:**
|
|
7467
|
+
```typescript
|
|
7468
|
+
export function minAge<
|
|
7469
|
+
TForm = unknown,
|
|
7470
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
7471
|
+
>(minAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>
|
|
7472
|
+
```
|
|
7473
|
+
|
|
7474
|
+
**Parameters:**
|
|
7475
|
+
- `minAgeValue` — - Минимально допустимый возраст (в полных годах)
|
|
7476
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7477
|
+
попадают `minAge` и `currentAge`.
|
|
7478
|
+
|
|
7479
|
+
**Returns:** Чистый валидатор {@link Validator} для поля даты рождения (`string | Date`)
|
|
7480
|
+
|
|
7481
|
+
**Examples:**
|
|
7482
|
+
|
|
7483
|
+
Минимальный возраст
|
|
7484
|
+
```typescript
|
|
7485
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7486
|
+
import { required, minAge } from '@reformer/core/validators';
|
|
7487
|
+
|
|
7488
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7489
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7490
|
+
validate(model.$.birthDate, [required(), minAge(18, { message: 'Вам должно быть не менее 18 лет' })]);
|
|
7491
|
+
});
|
|
7492
|
+
```
|
|
7493
|
+
|
|
7494
|
+
_Source: src/form/validators/min-age.ts_
|
|
7495
|
+
|
|
7496
|
+
### minDate
|
|
7497
|
+
|
|
7498
|
+
**Kind:** `function`
|
|
7499
|
+
|
|
7500
|
+
Фабрика валидатора минимальной даты (включительно).
|
|
7501
|
+
|
|
7502
|
+
Сравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты
|
|
7503
|
+
пропускаются (используйте {@link required} и {@link isDate}).
|
|
7504
|
+
|
|
7505
|
+
**Signature:**
|
|
7506
|
+
```typescript
|
|
7507
|
+
export function minDate<
|
|
7508
|
+
TForm = unknown,
|
|
7509
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
7510
|
+
>(minDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>
|
|
7511
|
+
```
|
|
7512
|
+
|
|
7513
|
+
**Parameters:**
|
|
7514
|
+
- `minDateValue` — - Минимально допустимая дата (включительно)
|
|
7515
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7516
|
+
попадает `minDate`.
|
|
7517
|
+
|
|
7518
|
+
**Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
|
|
7519
|
+
|
|
7520
|
+
**Examples:**
|
|
7521
|
+
|
|
7522
|
+
Минимальная дата
|
|
7523
|
+
```typescript
|
|
7524
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7525
|
+
import { required, minDate } from '@reformer/core/validators';
|
|
7526
|
+
|
|
7527
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7528
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7529
|
+
validate(model.$.startDate, [required(), minDate(new Date(), { message: 'Дата не раньше сегодня' })]);
|
|
7530
|
+
});
|
|
7531
|
+
```
|
|
7532
|
+
|
|
7533
|
+
_Source: src/form/validators/min-date.ts_
|
|
7534
|
+
|
|
7535
|
+
### minFiles
|
|
7536
|
+
|
|
7537
|
+
**Kind:** `function`
|
|
7538
|
+
|
|
7539
|
+
Фабрика валидатора минимального количества файлов.
|
|
7540
|
+
|
|
7541
|
+
Работает с массивом файлов (одиночный файл считается как 1). Пустые значения
|
|
7542
|
+
(`null`/`undefined`/`''`/`[]`) пропускаются — «нужен хотя бы один файл» выражается
|
|
7543
|
+
связкой `required()` + `minFiles(n)`, как и у {@link minLength}.
|
|
7544
|
+
|
|
7545
|
+
**Signature:**
|
|
7546
|
+
```typescript
|
|
7547
|
+
export function minFiles<TForm = unknown, TField = unknown>(
|
|
7548
|
+
min: number,
|
|
7549
|
+
options?: ValidateOptions
|
|
7550
|
+
): Validator<TForm, TField>
|
|
7551
|
+
```
|
|
7552
|
+
|
|
7553
|
+
**Parameters:**
|
|
7554
|
+
- `min` — - Минимально допустимое количество файлов (включительно)
|
|
7555
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7556
|
+
попадают `minFiles` и `actualCount`.
|
|
7557
|
+
|
|
7558
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
7559
|
+
|
|
7560
|
+
**Examples:**
|
|
7561
|
+
|
|
7562
|
+
Не менее двух документов
|
|
7563
|
+
```typescript
|
|
7564
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7565
|
+
import { required, minFiles } from '@reformer/core/validators';
|
|
7566
|
+
|
|
7567
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7568
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7569
|
+
validate(model.$.documents, [required(), minFiles(2, { message: 'Приложите минимум 2 документа' })]);
|
|
7570
|
+
});
|
|
7571
|
+
```
|
|
7572
|
+
|
|
7573
|
+
_Source: src/form/validators/min-files.ts_
|
|
7574
|
+
|
|
7575
|
+
### minFileSize
|
|
7576
|
+
|
|
7577
|
+
**Kind:** `function`
|
|
7578
|
+
|
|
7579
|
+
Фабрика валидатора минимального размера файла.
|
|
7580
|
+
|
|
7581
|
+
Работает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с
|
|
7582
|
+
`name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.
|
|
7583
|
+
Типовой сценарий — отсев пустых (0-байтовых) файлов: `minFileSize(1)`.
|
|
7584
|
+
Пустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`
|
|
7585
|
+
пропускаются (используйте {@link required} для обязательности).
|
|
7586
|
+
|
|
7587
|
+
**Signature:**
|
|
7588
|
+
```typescript
|
|
7589
|
+
export function minFileSize<TForm = unknown, TField = unknown>(
|
|
7590
|
+
minSize: number,
|
|
7591
|
+
options?: ValidateOptions
|
|
7592
|
+
): Validator<TForm, TField>
|
|
7593
|
+
```
|
|
7594
|
+
|
|
7595
|
+
**Parameters:**
|
|
7596
|
+
- `minSize` — - Минимально допустимый размер файла в байтах (включительно)
|
|
7597
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7598
|
+
попадают `minFileSize`, `fileName` и `actualSize`.
|
|
7599
|
+
|
|
7600
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
7601
|
+
|
|
7602
|
+
**Examples:**
|
|
7603
|
+
|
|
7604
|
+
Отсев пустых файлов
|
|
7605
|
+
```typescript
|
|
7606
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7607
|
+
import { minFileSize } from '@reformer/core/validators';
|
|
7608
|
+
|
|
7609
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7610
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7611
|
+
validate(model.$.documents, [minFileSize(1, { message: 'Файл пустой' })]);
|
|
7612
|
+
});
|
|
7613
|
+
```
|
|
7614
|
+
|
|
7615
|
+
_Source: src/form/validators/min-file-size.ts_
|
|
7616
|
+
|
|
7617
|
+
### minLength
|
|
7618
|
+
|
|
7619
|
+
**Kind:** `function`
|
|
7620
|
+
|
|
7621
|
+
Фабрика валидатора минимальной длины строки или массива.
|
|
7622
|
+
|
|
7623
|
+
Работает со строкой или массивом (проверяется `value.length`). Пустые значения
|
|
7624
|
+
(`null`/`undefined`/`''`) и значения без числового `length` пропускаются
|
|
7625
|
+
(используйте {@link required} для обязательности).
|
|
7626
|
+
|
|
7627
|
+
**Signature:**
|
|
7628
|
+
```typescript
|
|
7629
|
+
export function minLength<TForm = unknown, TField = unknown>(
|
|
7630
|
+
minLen: number,
|
|
7631
|
+
options?: ValidateOptions
|
|
7632
|
+
): Validator<TForm, TField>
|
|
7633
|
+
```
|
|
7634
|
+
|
|
7635
|
+
**Parameters:**
|
|
7636
|
+
- `minLen` — - Минимально допустимая длина (включительно)
|
|
7637
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7638
|
+
попадают `minLength` и `actualLength`.
|
|
7639
|
+
|
|
7640
|
+
**Returns:** Чистый валидатор {@link Validator} для строки или массива
|
|
7641
|
+
|
|
7642
|
+
**Examples:**
|
|
7643
|
+
|
|
7644
|
+
Минимальная длина строки
|
|
7645
|
+
```typescript
|
|
7646
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7647
|
+
import { required, minLength } from '@reformer/core/validators';
|
|
7648
|
+
|
|
7649
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7650
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7651
|
+
validate(model.$.name, [minLength(2)]);
|
|
7652
|
+
validate(model.$.password, [required(), minLength(8, { message: 'Минимум 8 символов' })]);
|
|
7653
|
+
});
|
|
7654
|
+
```
|
|
7655
|
+
|
|
7656
|
+
_Source: src/form/validators/min-length.ts_
|
|
7657
|
+
|
|
7658
|
+
### ModelApi
|
|
7659
|
+
|
|
7660
|
+
**Kind:** `interface`
|
|
7661
|
+
|
|
7662
|
+
API уровня модели (доступно на корне, под-моделях вложенных объектов-групп и элементов массива).
|
|
7663
|
+
|
|
7664
|
+
⚠️ Имена методов (`$`/`get`/`set`/`patch`/`isDirty`/`reset`/`signalAt`/`captureInitial`)
|
|
7665
|
+
зарезервированы: одноимённое поле формы их затеняет (редкий краевой случай).
|
|
7666
|
+
|
|
7667
|
+
**Signature:**
|
|
7668
|
+
```typescript
|
|
7669
|
+
export interface ModelApi<T> {
|
|
7670
|
+
/**
|
|
7671
|
+
* Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`.
|
|
7672
|
+
* Сам узел — {@link ReadonlySignal} модели целиком: `model.$.subscribe(v => …)`.
|
|
7673
|
+
*/
|
|
7674
|
+
readonly $: ModelGroupSignals<T>;
|
|
7675
|
+
/** Снимок значений (без подписки) — для submit. */
|
|
7676
|
+
get(): T;
|
|
7677
|
+
/**
|
|
7678
|
+
* Полная установка значений: принимает объект целиком (все ключи `T`) и записывает их в модель.
|
|
7679
|
+
* Производными полями (цели `compute`) владеет compute — их значения из payload игнорируются.
|
|
7680
|
+
* Не меняет initial-снимок. Для частичного обновления (только переданные ключи) — {@link ModelApi.patch}.
|
|
7681
|
+
*/
|
|
7682
|
+
set(value: T): void;
|
|
7683
|
+
/**
|
|
7684
|
+
* Частичное слияние значений (load/patch с сервера): обновляет только переданные ключи,
|
|
7685
|
+
* отсутствующие ключи НЕ трогаются. Не меняет initial-снимок.
|
|
7686
|
+
*/
|
|
7687
|
+
patch(value: Partial<T>): void;
|
|
7688
|
+
/** Отличаются ли текущие значения от initial-снимка (value-diff). */
|
|
7689
|
+
isDirty(): boolean;
|
|
7690
|
+
/** Сбросить значения к initial-снимку. */
|
|
7691
|
+
reset(): void;
|
|
7692
|
+
/** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */
|
|
7693
|
+
captureInitial(): void;
|
|
7694
|
+
/** Резолв строкового пути в сигнал (для error-routing/мостов). */
|
|
7695
|
+
signalAt(path: string): PathAwareSignal<unknown> | undefined;
|
|
7696
|
+
}
|
|
7697
|
+
```
|
|
7698
|
+
|
|
7699
|
+
_Source: src/model/types.ts_
|
|
7700
|
+
|
|
7701
|
+
### ModelArray
|
|
7702
|
+
|
|
7703
|
+
**Kind:** `interface`
|
|
7704
|
+
|
|
7705
|
+
Реактивный массив модели. Мутации (`push`/`removeAt`/…) меняют длину реактивно;
|
|
7706
|
+
`map`/`forEach`/`at` отдают под-модель элемента ({@link FormModel}) для объектных элементов.
|
|
7707
|
+
|
|
7708
|
+
**Signature:**
|
|
7709
|
+
```typescript
|
|
7710
|
+
export interface ModelArray<U> {
|
|
7711
|
+
/**
|
|
7712
|
+
* Путь массива в модели (dot-нотация). Предоставляется рантаймом (value-прокси) и требуется
|
|
7713
|
+
* рендер-слою для резолва узла массива (напр. `ArrayRenderNode` в `@reformer/renderer-react`).
|
|
7714
|
+
*/
|
|
7715
|
+
readonly __path: string;
|
|
7716
|
+
/** Реактивная длина. */
|
|
7717
|
+
readonly length: number;
|
|
7718
|
+
/** Добавить элемент в конец (значение элемента целиком). */
|
|
7719
|
+
push(item: U): void;
|
|
7720
|
+
/** Вставить элемент по индексу. */
|
|
7721
|
+
insertAt(index: number, item: U): void;
|
|
7722
|
+
/** Удалить элемент по индексу. */
|
|
7723
|
+
removeAt(index: number): void;
|
|
7724
|
+
/** Переместить элемент. */
|
|
7725
|
+
move(from: number, to: number): void;
|
|
7726
|
+
/** Поменять местами два элемента. */
|
|
7727
|
+
swap(a: number, b: number): void;
|
|
7728
|
+
/** Очистить массив. */
|
|
7729
|
+
clear(): void;
|
|
7730
|
+
/** Элемент по индексу: объект → под-модель, массив → {@link ModelArray}, лист → значение. */
|
|
7731
|
+
at(
|
|
7732
|
+
index: number
|
|
7733
|
+
): NonNullable<U> extends object ? ModelArrayItem<U> : ModelArrayItem<U> | undefined;
|
|
7734
|
+
/** Map по элементам (объект → {@link FormModel}, Opaque/примитив → значение, массив → {@link ModelArray}). */
|
|
7735
|
+
map<R>(fn: (item: ModelArrayItem<U>, index: number) => R): R[];
|
|
7736
|
+
/** Итерация по элементам (см. {@link ModelArrayItem}). */
|
|
7737
|
+
forEach(fn: (item: ModelArrayItem<U>, index: number) => void): void;
|
|
7738
|
+
/** Снимок массива значений (без подписки). */
|
|
7739
|
+
toArray(): U[];
|
|
7740
|
+
/** Индексный value-доступ. */
|
|
7741
|
+
[index: number]: ModelValue<U>;
|
|
7742
|
+
}
|
|
7743
|
+
```
|
|
7744
|
+
|
|
7745
|
+
_Source: src/model/types.ts_
|
|
6516
7746
|
|
|
6517
7747
|
### ModelArrayControl
|
|
6518
7748
|
|
|
@@ -6566,8 +7796,8 @@ export class ModelArrayNode<T extends object> extends FormNode<T[]> { /* … */
|
|
|
6566
7796
|
const model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });
|
|
6567
7797
|
|
|
6568
7798
|
const rowItem = (item: FormModel<{ name: string; qty: number }>) => ({
|
|
6569
|
-
name: { value: item.$.name, component:
|
|
6570
|
-
qty: { value: item.$.qty, component:
|
|
7799
|
+
name: { value: item.$.name, component: InputField },
|
|
7800
|
+
qty: { value: item.$.qty, component: InputField },
|
|
6571
7801
|
});
|
|
6572
7802
|
|
|
6573
7803
|
const form = createForm({
|
|
@@ -6666,104 +7896,399 @@ _Source: src/model/types.ts_
|
|
|
6666
7896
|
|
|
6667
7897
|
**Signature:**
|
|
6668
7898
|
```typescript
|
|
6669
|
-
export type ModelValue<V> =
|
|
6670
|
-
NonNullable<V> extends ReadonlyArray<infer U>
|
|
6671
|
-
? ModelArray<U>
|
|
6672
|
-
: NonNullable<V> extends Opaque
|
|
6673
|
-
? V
|
|
6674
|
-
: NonNullable<V> extends object
|
|
6675
|
-
? FormModel<NonNullable<V>>
|
|
6676
|
-
: V;
|
|
7899
|
+
export type ModelValue<V> =
|
|
7900
|
+
NonNullable<V> extends ReadonlyArray<infer U>
|
|
7901
|
+
? ModelArray<U>
|
|
7902
|
+
: NonNullable<V> extends Opaque
|
|
7903
|
+
? V
|
|
7904
|
+
: NonNullable<V> extends object
|
|
7905
|
+
? FormModel<NonNullable<V>>
|
|
7906
|
+
: V;
|
|
7907
|
+
```
|
|
7908
|
+
|
|
7909
|
+
_Source: src/model/types.ts_
|
|
7910
|
+
|
|
7911
|
+
### multipleOf
|
|
7912
|
+
|
|
7913
|
+
**Kind:** `function`
|
|
7914
|
+
|
|
7915
|
+
Фабрика валидатора, проверяющего что число кратно заданному.
|
|
7916
|
+
|
|
7917
|
+
Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
|
|
7918
|
+
|
|
7919
|
+
**Signature:**
|
|
7920
|
+
```typescript
|
|
7921
|
+
export function multipleOf<TForm = unknown, TField extends number | null | undefined = number>(
|
|
7922
|
+
divisor: number,
|
|
7923
|
+
options?: ValidateOptions
|
|
7924
|
+
): Validator<TForm, TField>
|
|
7925
|
+
```
|
|
7926
|
+
|
|
7927
|
+
**Parameters:**
|
|
7928
|
+
- `divisor` — - Делитель: значение должно быть кратно ему
|
|
7929
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7930
|
+
попадает `multipleOf` (делитель).
|
|
7931
|
+
|
|
7932
|
+
**Returns:** Чистый валидатор {@link Validator} для числового поля
|
|
7933
|
+
|
|
7934
|
+
**Examples:**
|
|
7935
|
+
|
|
7936
|
+
Проверка кратности
|
|
7937
|
+
```typescript
|
|
7938
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7939
|
+
import { multipleOf } from '@reformer/core/validators';
|
|
7940
|
+
|
|
7941
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
7942
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
7943
|
+
validate(model.$.rating, [multipleOf(0.5, { message: 'Только шаг 0.5' })]);
|
|
7944
|
+
});
|
|
7945
|
+
```
|
|
7946
|
+
|
|
7947
|
+
_Source: src/form/validators/multiple-of.ts_
|
|
7948
|
+
|
|
7949
|
+
### NodeFactory
|
|
7950
|
+
|
|
7951
|
+
**Kind:** `class`
|
|
7952
|
+
|
|
7953
|
+
Фабрика для создания узлов формы.
|
|
7954
|
+
|
|
7955
|
+
Определяет тип конфига и создаёт соответствующий узел (FieldNode, GroupNode, ArrayNode).
|
|
7956
|
+
Используется внутри `getReformerForm`/`group`/`array` — явно вызывать обычно не нужно.
|
|
7957
|
+
|
|
7958
|
+
**Signature:**
|
|
7959
|
+
```typescript
|
|
7960
|
+
export class NodeFactory {
|
|
7961
|
+
/**
|
|
7962
|
+
* Создает узел формы на основе конфигурации
|
|
7963
|
+
*
|
|
7964
|
+
* ✅ ОБНОВЛЕНО: Теперь поддерживает массивы напрямую
|
|
7965
|
+
*
|
|
7966
|
+
* Автоматически определяет тип узла:
|
|
7967
|
+
* - FieldNode: имеет value и component
|
|
7968
|
+
* - ArrayNode: массив [schema, ...items] или { schema, initialItems }
|
|
7969
|
+
* - GroupNode: объект без value, component, schema
|
|
7970
|
+
*
|
|
7971
|
+
* @param config Конфигурация узла
|
|
7972
|
+
* @returns Экземпляр FieldNode, GroupNode или ArrayNode
|
|
7973
|
+
* @throws Error если конфиг не соответствует ни одному типу
|
|
7974
|
+
*
|
|
7975
|
+
* @example
|
|
7976
|
+
* ```typescript
|
|
7977
|
+
* const factory = new NodeFactory();
|
|
7978
|
+
*
|
|
7979
|
+
* Конфиг node-level (не layout-схема M1, где `validators` уже нет).
|
|
7980
|
+
* // FieldNode
|
|
7981
|
+
* const field = factory.createNode({
|
|
7982
|
+
* value: 'test@mail.com',
|
|
7983
|
+
* component: InputField,
|
|
7984
|
+
* validators: [required, email]
|
|
7985
|
+
* });
|
|
7986
|
+
*
|
|
7987
|
+
* // GroupNode
|
|
7988
|
+
* const group = factory.createNode({
|
|
7989
|
+
* email: { value: '', component: InputField },
|
|
7990
|
+
* password: { value: '', component: InputField }
|
|
7991
|
+
* });
|
|
7992
|
+
*
|
|
7993
|
+
* // ArrayNode (объект)
|
|
7994
|
+
* const array = factory.createNode({
|
|
7995
|
+
* schema: { title: { value: '', component: InputField } },
|
|
7996
|
+
* initialItems: [{ title: 'Item 1' }]
|
|
7997
|
+
* });
|
|
7998
|
+
*
|
|
7999
|
+
* // ArrayNode (массив) - новый формат
|
|
8000
|
+
* const array2 = factory.createNode([
|
|
8001
|
+
* { title: { value: '', component: InputField } }, // schema
|
|
8002
|
+
* { title: 'Item 1' }, // initial item 1
|
|
8003
|
+
* { title: 'Item 2' } // initial item 2
|
|
8004
|
+
* ]);
|
|
8005
|
+
* ```
|
|
8006
|
+
*/ /* … */ }
|
|
8007
|
+
```
|
|
8008
|
+
|
|
8009
|
+
**Examples:**
|
|
8010
|
+
|
|
8011
|
+
```typescript
|
|
8012
|
+
import { NodeFactory } from '@reformer/core';
|
|
8013
|
+
|
|
8014
|
+
const factory = new NodeFactory();
|
|
8015
|
+
const node = factory.createNode({ value: '', component: InputField }); // → FieldNode<string>
|
|
8016
|
+
```
|
|
8017
|
+
|
|
8018
|
+
_Source: src/form/factories/node-factory.ts_
|
|
8019
|
+
|
|
8020
|
+
### nonNegative
|
|
8021
|
+
|
|
8022
|
+
**Kind:** `function`
|
|
8023
|
+
|
|
8024
|
+
Фабрика валидатора, проверяющего что число неотрицательное (`≥ 0`).
|
|
8025
|
+
|
|
8026
|
+
Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
|
|
8027
|
+
|
|
8028
|
+
**Signature:**
|
|
8029
|
+
```typescript
|
|
8030
|
+
export function nonNegative<TForm = unknown, TField extends number | null | undefined = number>(
|
|
8031
|
+
options?: ValidateOptions
|
|
8032
|
+
): Validator<TForm, TField>
|
|
8033
|
+
```
|
|
8034
|
+
|
|
8035
|
+
**Parameters:**
|
|
8036
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
8037
|
+
|
|
8038
|
+
**Returns:** Чистый валидатор {@link Validator} для числового поля
|
|
8039
|
+
|
|
8040
|
+
**Examples:**
|
|
8041
|
+
|
|
8042
|
+
Проверка неотрицательности
|
|
8043
|
+
```typescript
|
|
8044
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8045
|
+
import { nonNegative } from '@reformer/core/validators';
|
|
8046
|
+
|
|
8047
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8048
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8049
|
+
validate(model.$.balance, [nonNegative({ message: 'Баланс не может быть отрицательным' })]);
|
|
8050
|
+
});
|
|
8051
|
+
```
|
|
8052
|
+
|
|
8053
|
+
_Source: src/form/validators/non-negative.ts_
|
|
8054
|
+
|
|
8055
|
+
### nonZero
|
|
8056
|
+
|
|
8057
|
+
**Kind:** `function`
|
|
8058
|
+
|
|
8059
|
+
Фабрика валидатора, проверяющего что число не равно нулю.
|
|
8060
|
+
|
|
8061
|
+
Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
|
|
8062
|
+
|
|
8063
|
+
**Signature:**
|
|
8064
|
+
```typescript
|
|
8065
|
+
export function nonZero<TForm = unknown, TField extends number | null | undefined = number>(
|
|
8066
|
+
options?: ValidateOptions
|
|
8067
|
+
): Validator<TForm, TField>
|
|
8068
|
+
```
|
|
8069
|
+
|
|
8070
|
+
**Parameters:**
|
|
8071
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
8072
|
+
|
|
8073
|
+
**Returns:** Чистый валидатор {@link Validator} для числового поля
|
|
8074
|
+
|
|
8075
|
+
**Examples:**
|
|
8076
|
+
|
|
8077
|
+
Проверка «не ноль»
|
|
8078
|
+
```typescript
|
|
8079
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8080
|
+
import { nonZero } from '@reformer/core/validators';
|
|
8081
|
+
|
|
8082
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8083
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8084
|
+
validate(model.$.divisor, [nonZero({ message: 'Не может быть нулём' })]);
|
|
8085
|
+
});
|
|
8086
|
+
```
|
|
8087
|
+
|
|
8088
|
+
_Source: src/form/validators/non-zero.ts_
|
|
8089
|
+
|
|
8090
|
+
### onChange
|
|
8091
|
+
|
|
8092
|
+
**Kind:** `function`
|
|
8093
|
+
|
|
8094
|
+
Реакция на изменение поля; `{ debounce, immediate }`.
|
|
8095
|
+
|
|
8096
|
+
Колбэк выполняется ВНЕ effect-контекста (микротаск/таймер) — можно безопасно писать сигналы и ноды
|
|
8097
|
+
(`updateComponentProps`/`reset`/`clear`) без ручного `defer` и без «Cycle detected».
|
|
8098
|
+
|
|
8099
|
+
Для async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения
|
|
8100
|
+
предыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй
|
|
8101
|
+
`signal.aborted` перед применением результата — это убирает гонки устаревших ответов (F2).
|
|
8102
|
+
|
|
8103
|
+
**Signature:**
|
|
8104
|
+
```typescript
|
|
8105
|
+
export function onChange<T>(
|
|
8106
|
+
source: ReadonlySignal<T>,
|
|
8107
|
+
cb: (value: T, ctx: ChangeContext) => void,
|
|
8108
|
+
options?: { immediate?: boolean; debounce?: number }
|
|
8109
|
+
): void
|
|
8110
|
+
```
|
|
8111
|
+
|
|
8112
|
+
_Source: src/form/behaviors/operators.ts_
|
|
8113
|
+
|
|
8114
|
+
### onDispose
|
|
8115
|
+
|
|
8116
|
+
**Kind:** `function`
|
|
8117
|
+
|
|
8118
|
+
Зарегистрировать произвольную отписку в активной схеме.
|
|
8119
|
+
|
|
8120
|
+
**Signature:**
|
|
8121
|
+
```typescript
|
|
8122
|
+
export function onDispose(cleanup: BehaviorCleanup): void
|
|
8123
|
+
```
|
|
8124
|
+
|
|
8125
|
+
_Source: src/form/behaviors/context.ts_
|
|
8126
|
+
|
|
8127
|
+
### pastDate
|
|
8128
|
+
|
|
8129
|
+
**Kind:** `function`
|
|
8130
|
+
|
|
8131
|
+
Фабрика валидатора, проверяющего что дата не в будущем.
|
|
8132
|
+
|
|
8133
|
+
Дата не должна быть позже сегодняшнего дня (сравнение по нормализованным датам).
|
|
8134
|
+
Пустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).
|
|
8135
|
+
|
|
8136
|
+
**Signature:**
|
|
8137
|
+
```typescript
|
|
8138
|
+
export function pastDate<
|
|
8139
|
+
TForm = unknown,
|
|
8140
|
+
TField extends string | Date | null | undefined = string | Date,
|
|
8141
|
+
>(options?: ValidateOptions): Validator<TForm, TField>
|
|
8142
|
+
```
|
|
8143
|
+
|
|
8144
|
+
**Parameters:**
|
|
8145
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
8146
|
+
|
|
8147
|
+
**Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
|
|
8148
|
+
|
|
8149
|
+
**Examples:**
|
|
8150
|
+
|
|
8151
|
+
Дата не в будущем
|
|
8152
|
+
```typescript
|
|
8153
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8154
|
+
import { required, pastDate } from '@reformer/core/validators';
|
|
8155
|
+
|
|
8156
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8157
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8158
|
+
validate(model.$.birthDate, [required(), pastDate({ message: 'Дата рождения не может быть в будущем' })]);
|
|
8159
|
+
});
|
|
8160
|
+
```
|
|
8161
|
+
|
|
8162
|
+
_Source: src/form/validators/past-date.ts_
|
|
8163
|
+
|
|
8164
|
+
### PathAwareSignal
|
|
8165
|
+
|
|
8166
|
+
**Kind:** `type`
|
|
8167
|
+
|
|
8168
|
+
Сигнал, который знает свой путь в модели (`'personalData.lastName'`).
|
|
8169
|
+
Используется как «ручка поля»: и привязка value в схеме, и идентичность для testId/devtools.
|
|
8170
|
+
|
|
8171
|
+
**Signature:**
|
|
8172
|
+
```typescript
|
|
8173
|
+
export type PathAwareSignal<T> = Signal<T> & {
|
|
8174
|
+
/** Путь поля в модели (dot-нотация). На элементах массива включает индекс. */
|
|
8175
|
+
readonly __path: string;
|
|
8176
|
+
};
|
|
8177
|
+
```
|
|
8178
|
+
|
|
8179
|
+
_Source: src/model/types.ts_
|
|
8180
|
+
|
|
8181
|
+
### pattern
|
|
8182
|
+
|
|
8183
|
+
**Kind:** `function`
|
|
8184
|
+
|
|
8185
|
+
Фабрика валидатора регулярного выражения.
|
|
8186
|
+
|
|
8187
|
+
Пустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}
|
|
8188
|
+
для обязательности).
|
|
8189
|
+
|
|
8190
|
+
**Signature:**
|
|
8191
|
+
```typescript
|
|
8192
|
+
export function pattern<TForm = unknown, TField extends string | null | undefined = string>(
|
|
8193
|
+
regex: RegExp,
|
|
8194
|
+
options?: ValidateOptions
|
|
8195
|
+
): Validator<TForm, TField>
|
|
8196
|
+
```
|
|
8197
|
+
|
|
8198
|
+
**Parameters:**
|
|
8199
|
+
- `regex` — - Регулярное выражение для проверки значения
|
|
8200
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
8201
|
+
попадает `pattern` (строка-источник regex).
|
|
8202
|
+
|
|
8203
|
+
**Returns:** Чистый валидатор {@link Validator} для строкового поля
|
|
8204
|
+
|
|
8205
|
+
**Examples:**
|
|
8206
|
+
|
|
8207
|
+
Проверка по регулярному выражению
|
|
8208
|
+
```typescript
|
|
8209
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8210
|
+
import { required, pattern } from '@reformer/core/validators';
|
|
8211
|
+
|
|
8212
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8213
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8214
|
+
validate(model.$.name, [pattern(/^[a-zA-Zа-яА-Я]+$/, { message: 'Только буквы' })]);
|
|
8215
|
+
validate(model.$.phone, [required(), pattern(/^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$/, { message: 'Формат +7 (999) 123-45-67' })]);
|
|
8216
|
+
});
|
|
6677
8217
|
```
|
|
6678
8218
|
|
|
6679
|
-
_Source: src/
|
|
8219
|
+
_Source: src/form/validators/pattern.ts_
|
|
6680
8220
|
|
|
6681
|
-
###
|
|
8221
|
+
### phone
|
|
6682
8222
|
|
|
6683
|
-
**Kind:** `
|
|
8223
|
+
**Kind:** `function`
|
|
6684
8224
|
|
|
6685
|
-
Фабрика
|
|
8225
|
+
Фабрика валидатора номера телефона.
|
|
6686
8226
|
|
|
6687
|
-
|
|
6688
|
-
|
|
8227
|
+
Проверяет значение по regex выбранного {@link PhoneFormat}. Пустые значения
|
|
8228
|
+
(`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
|
|
6689
8229
|
|
|
6690
8230
|
**Signature:**
|
|
6691
8231
|
```typescript
|
|
6692
|
-
export
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
*
|
|
6696
|
-
* ✅ ОБНОВЛЕНО: Теперь поддерживает массивы напрямую
|
|
6697
|
-
*
|
|
6698
|
-
* Автоматически определяет тип узла:
|
|
6699
|
-
* - FieldNode: имеет value и component
|
|
6700
|
-
* - ArrayNode: массив [schema, ...items] или { schema, initialItems }
|
|
6701
|
-
* - GroupNode: объект без value, component, schema
|
|
6702
|
-
*
|
|
6703
|
-
* @param config Конфигурация узла
|
|
6704
|
-
* @returns Экземпляр FieldNode, GroupNode или ArrayNode
|
|
6705
|
-
* @throws Error если конфиг не соответствует ни одному типу
|
|
6706
|
-
*
|
|
6707
|
-
* @example
|
|
6708
|
-
* ```typescript
|
|
6709
|
-
* const factory = new NodeFactory();
|
|
6710
|
-
*
|
|
6711
|
-
* // FieldNode
|
|
6712
|
-
* const field = factory.createNode({
|
|
6713
|
-
* value: 'test@mail.com',
|
|
6714
|
-
* component: Input,
|
|
6715
|
-
* validators: [required, email]
|
|
6716
|
-
* });
|
|
6717
|
-
*
|
|
6718
|
-
* // GroupNode
|
|
6719
|
-
* const group = factory.createNode({
|
|
6720
|
-
* email: { value: '', component: Input },
|
|
6721
|
-
* password: { value: '', component: Input }
|
|
6722
|
-
* });
|
|
6723
|
-
*
|
|
6724
|
-
* // ArrayNode (объект)
|
|
6725
|
-
* const array = factory.createNode({
|
|
6726
|
-
* schema: { title: { value: '', component: Input } },
|
|
6727
|
-
* initialItems: [{ title: 'Item 1' }]
|
|
6728
|
-
* });
|
|
6729
|
-
*
|
|
6730
|
-
* // ArrayNode (массив) - новый формат
|
|
6731
|
-
* const array2 = factory.createNode([
|
|
6732
|
-
* { title: { value: '', component: Input } }, // schema
|
|
6733
|
-
* { title: 'Item 1' }, // initial item 1
|
|
6734
|
-
* { title: 'Item 2' } // initial item 2
|
|
6735
|
-
* ]);
|
|
6736
|
-
* ```
|
|
6737
|
-
*/ /* … */ }
|
|
8232
|
+
export function phone<TForm = unknown, TField extends string | null | undefined = string>(
|
|
8233
|
+
options?: PhoneValidatorOptions
|
|
8234
|
+
): Validator<TForm, TField>
|
|
6738
8235
|
```
|
|
6739
8236
|
|
|
8237
|
+
**Parameters:**
|
|
8238
|
+
- `options` — - Опции валидатора {@link PhoneValidatorOptions}. В `params` ошибки
|
|
8239
|
+
автоматически попадает выбранный `format`.
|
|
8240
|
+
|
|
8241
|
+
**Returns:** Чистый валидатор {@link Validator} для строкового поля
|
|
8242
|
+
|
|
6740
8243
|
**Examples:**
|
|
6741
8244
|
|
|
8245
|
+
Проверка номера телефона
|
|
6742
8246
|
```typescript
|
|
6743
|
-
import {
|
|
8247
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8248
|
+
import { required, phone } from '@reformer/core/validators';
|
|
6744
8249
|
|
|
6745
|
-
|
|
6746
|
-
const
|
|
8250
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8251
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8252
|
+
validate(model.$.phone, [required(), phone({ format: 'ru' })]);
|
|
8253
|
+
});
|
|
6747
8254
|
```
|
|
6748
8255
|
|
|
6749
|
-
_Source: src/form/
|
|
8256
|
+
_Source: src/form/validators/phone.ts_
|
|
6750
8257
|
|
|
6751
|
-
###
|
|
8258
|
+
### PhoneFormat
|
|
6752
8259
|
|
|
6753
8260
|
**Kind:** `type`
|
|
6754
8261
|
|
|
6755
|
-
|
|
6756
|
-
|
|
8262
|
+
Формат проверки номера телефона для валидатора {@link phone}.
|
|
8263
|
+
|
|
8264
|
+
- `international` — международный формат E.164 (`+?[1-9]\d{1,14}`);
|
|
8265
|
+
- `ru` — российские номера (`+7`/`7`/`8`, коды `4`/`8`/`9`, с разделителями);
|
|
8266
|
+
- `us` — североамериканские номера (NANP);
|
|
8267
|
+
- `any` — свободный формат: цифры, скобки и разделители (по умолчанию).
|
|
6757
8268
|
|
|
6758
8269
|
**Signature:**
|
|
6759
8270
|
```typescript
|
|
6760
|
-
export type
|
|
6761
|
-
/** Путь поля в модели (dot-нотация). На элементах массива включает индекс. */
|
|
6762
|
-
readonly __path: string;
|
|
6763
|
-
};
|
|
8271
|
+
export type PhoneFormat = 'international' | 'ru' | 'us' | 'any';
|
|
6764
8272
|
```
|
|
6765
8273
|
|
|
6766
|
-
_Source: src/
|
|
8274
|
+
_Source: src/form/validators/phone.ts_
|
|
8275
|
+
|
|
8276
|
+
### PhoneValidatorOptions
|
|
8277
|
+
|
|
8278
|
+
**Kind:** `interface`
|
|
8279
|
+
|
|
8280
|
+
Опции валидатора {@link phone}. Расширяют {@link ValidateOptions} (`message`, `params`)
|
|
8281
|
+
выбором формата номера.
|
|
8282
|
+
|
|
8283
|
+
**Signature:**
|
|
8284
|
+
```typescript
|
|
8285
|
+
export interface PhoneValidatorOptions extends ValidateOptions {
|
|
8286
|
+
/** Формат проверки {@link PhoneFormat}. По умолчанию `'any'`. */
|
|
8287
|
+
format?: PhoneFormat;
|
|
8288
|
+
}
|
|
8289
|
+
```
|
|
8290
|
+
|
|
8291
|
+
_Source: src/form/validators/phone.ts_
|
|
6767
8292
|
|
|
6768
8293
|
### registerSignalNode
|
|
6769
8294
|
|
|
@@ -6799,6 +8324,47 @@ if (sig && node) registerSignalNode(sig, node);
|
|
|
6799
8324
|
|
|
6800
8325
|
_Source: src/form/signal-node-registry.ts_
|
|
6801
8326
|
|
|
8327
|
+
### required
|
|
8328
|
+
|
|
8329
|
+
**Kind:** `function`
|
|
8330
|
+
|
|
8331
|
+
Фабрика валидатора обязательного поля.
|
|
8332
|
+
|
|
8333
|
+
Возвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.
|
|
8334
|
+
|
|
8335
|
+
Пустыми считаются: `null`, `undefined`, `''` (пустая строка), `[]` (пустой массив —
|
|
8336
|
+
обязательный multi-select / FormArray без выбранных элементов).
|
|
8337
|
+
Для boolean полей требуется значение `true`.
|
|
8338
|
+
|
|
8339
|
+
**Signature:**
|
|
8340
|
+
```typescript
|
|
8341
|
+
export function required<TForm = unknown, TField = unknown>(
|
|
8342
|
+
options?: ValidateOptions
|
|
8343
|
+
): Validator<TForm, TField>
|
|
8344
|
+
```
|
|
8345
|
+
|
|
8346
|
+
**Parameters:**
|
|
8347
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
8348
|
+
|
|
8349
|
+
**Returns:** Чистый валидатор {@link Validator} для поля схемы
|
|
8350
|
+
|
|
8351
|
+
**Examples:**
|
|
8352
|
+
|
|
8353
|
+
Обязательные поля в схеме формы
|
|
8354
|
+
```typescript
|
|
8355
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8356
|
+
import { required } from '@reformer/core/validators';
|
|
8357
|
+
|
|
8358
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8359
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8360
|
+
validate(model.$.email, [required()]);
|
|
8361
|
+
validate(model.$.phone, [required({ message: 'Укажите номер телефона' })]);
|
|
8362
|
+
validate(model.$.agreeToTerms, [required({ message: 'Необходимо принять условия' })]);
|
|
8363
|
+
});
|
|
8364
|
+
```
|
|
8365
|
+
|
|
8366
|
+
_Source: src/form/validators/required.ts_
|
|
8367
|
+
|
|
6802
8368
|
### resetWhen
|
|
6803
8369
|
|
|
6804
8370
|
**Kind:** `function`
|
|
@@ -6845,6 +8411,25 @@ revalidateWhen([model.$.maxAmount], () => void validateModel(model, schema));
|
|
|
6845
8411
|
|
|
6846
8412
|
_Source: src/model/behaviors-value.ts_
|
|
6847
8413
|
|
|
8414
|
+
### Rule
|
|
8415
|
+
|
|
8416
|
+
**Kind:** `type`
|
|
8417
|
+
|
|
8418
|
+
Синхронное правило поля типа `TField`. Проверяется ТОЛЬКО значение (`value`).
|
|
8419
|
+
|
|
8420
|
+
Позиционные `scope`/`root` помечены `never` намеренно: так встроенные value-only фабрики
|
|
8421
|
+
(`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`
|
|
8422
|
+
ОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля
|
|
8423
|
+
(`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).
|
|
8424
|
+
На вызове раннер приводит правило к callable и передаёт `(value, model, model)`.
|
|
8425
|
+
|
|
8426
|
+
**Signature:**
|
|
8427
|
+
```typescript
|
|
8428
|
+
export type Rule<TField> = (value: TField, scope: never, root: never) => ValidationError | null;
|
|
8429
|
+
```
|
|
8430
|
+
|
|
8431
|
+
_Source: src/form/validation/types.ts_
|
|
8432
|
+
|
|
6848
8433
|
### runOutsideEffect
|
|
6849
8434
|
|
|
6850
8435
|
**Kind:** `function`
|
|
@@ -7123,6 +8708,27 @@ syncFields(model.$.field1, model.$.field2);
|
|
|
7123
8708
|
|
|
7124
8709
|
_Source: src/model/behaviors-value.ts_
|
|
7125
8710
|
|
|
8711
|
+
### toFileArray
|
|
8712
|
+
|
|
8713
|
+
**Kind:** `function`
|
|
8714
|
+
|
|
8715
|
+
Нормализует значение file-поля в массив файлоподобных элементов.
|
|
8716
|
+
|
|
8717
|
+
Одиночный {@link FileLike} оборачивается в массив; из массива отбираются только
|
|
8718
|
+
файлоподобные элементы. Для прочих значений — `null` (валидатор пропускает).
|
|
8719
|
+
|
|
8720
|
+
**Signature:**
|
|
8721
|
+
```typescript
|
|
8722
|
+
export function toFileArray(value: unknown): FileLike[] | null
|
|
8723
|
+
```
|
|
8724
|
+
|
|
8725
|
+
**Parameters:**
|
|
8726
|
+
- `value` — - Значение поля (`FileLike`, `FileLike[]` или что-то ещё)
|
|
8727
|
+
|
|
8728
|
+
**Returns:** Массив файлоподобных элементов или `null`
|
|
8729
|
+
|
|
8730
|
+
_Source: src/form/validators/file-utils.ts_
|
|
8731
|
+
|
|
7126
8732
|
### transformValue
|
|
7127
8733
|
|
|
7128
8734
|
**Kind:** `function`
|
|
@@ -7196,6 +8802,67 @@ onDispose(() => unmarkDerived(model.$.total)); // при снятии compute с
|
|
|
7196
8802
|
|
|
7197
8803
|
_Source: src/model/derived-registry.ts_
|
|
7198
8804
|
|
|
8805
|
+
### url
|
|
8806
|
+
|
|
8807
|
+
**Kind:** `function`
|
|
8808
|
+
|
|
8809
|
+
Фабрика валидатора URL.
|
|
8810
|
+
|
|
8811
|
+
Пустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}
|
|
8812
|
+
для обязательности). При `requireProtocol` протокол обязателен; `allowedProtocols`
|
|
8813
|
+
дополнительно ограничивает набор допустимых протоколов.
|
|
8814
|
+
|
|
8815
|
+
**Signature:**
|
|
8816
|
+
```typescript
|
|
8817
|
+
export function url<TForm = unknown, TField extends string | null | undefined = string>(
|
|
8818
|
+
options?: UrlValidatorOptions
|
|
8819
|
+
): Validator<TForm, TField>
|
|
8820
|
+
```
|
|
8821
|
+
|
|
8822
|
+
**Parameters:**
|
|
8823
|
+
- `options` — - Опции валидатора {@link UrlValidatorOptions}
|
|
8824
|
+
|
|
8825
|
+
**Returns:** Чистый валидатор {@link Validator} для строкового поля
|
|
8826
|
+
|
|
8827
|
+
**Examples:**
|
|
8828
|
+
|
|
8829
|
+
Проверка URL
|
|
8830
|
+
```typescript
|
|
8831
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8832
|
+
import { required, url } from '@reformer/core/validators';
|
|
8833
|
+
|
|
8834
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8835
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8836
|
+
validate(model.$.website, [required(), url({ message: 'Введите корректный URL' })]);
|
|
8837
|
+
// Требовать протокол и ограничить схему только https:
|
|
8838
|
+
validate(model.$.homepage, [url({ requireProtocol: true, allowedProtocols: ['https'] })]);
|
|
8839
|
+
});
|
|
8840
|
+
```
|
|
8841
|
+
|
|
8842
|
+
_Source: src/form/validators/url.ts_
|
|
8843
|
+
|
|
8844
|
+
### UrlValidatorOptions
|
|
8845
|
+
|
|
8846
|
+
**Kind:** `interface`
|
|
8847
|
+
|
|
8848
|
+
Опции валидатора {@link url}. Расширяют {@link ValidateOptions} (`message`, `params`)
|
|
8849
|
+
настройками проверки протокола.
|
|
8850
|
+
|
|
8851
|
+
**Signature:**
|
|
8852
|
+
```typescript
|
|
8853
|
+
export interface UrlValidatorOptions extends ValidateOptions {
|
|
8854
|
+
/** Требовать наличие протокола (`http://` или `https://`). По умолчанию `false`. */
|
|
8855
|
+
requireProtocol?: boolean;
|
|
8856
|
+
/**
|
|
8857
|
+
* Список разрешённых протоколов (без `://`), например `['https']`. Если задан,
|
|
8858
|
+
* значение с иным протоколом даёт ошибку с кодом `url_protocol`.
|
|
8859
|
+
*/
|
|
8860
|
+
allowedProtocols?: string[];
|
|
8861
|
+
}
|
|
8862
|
+
```
|
|
8863
|
+
|
|
8864
|
+
_Source: src/form/validators/url.ts_
|
|
8865
|
+
|
|
7199
8866
|
### useArrayLength
|
|
7200
8867
|
|
|
7201
8868
|
**Kind:** `function`
|
|
@@ -7749,6 +9416,96 @@ export interface UseFormValidationResult {
|
|
|
7749
9416
|
|
|
7750
9417
|
_Source: src/platforms/react/hooks/use-form-validation.ts_
|
|
7751
9418
|
|
|
9419
|
+
### validate
|
|
9420
|
+
|
|
9421
|
+
**Kind:** `function`
|
|
9422
|
+
|
|
9423
|
+
Синхронные правила поля.
|
|
9424
|
+
|
|
9425
|
+
**Signature:**
|
|
9426
|
+
```typescript
|
|
9427
|
+
export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void
|
|
9428
|
+
```
|
|
9429
|
+
|
|
9430
|
+
**Examples:**
|
|
9431
|
+
|
|
9432
|
+
```ts
|
|
9433
|
+
validate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);
|
|
9434
|
+
```
|
|
9435
|
+
|
|
9436
|
+
_Source: src/form/validation/operators.ts_
|
|
9437
|
+
|
|
9438
|
+
### validateAsync
|
|
9439
|
+
|
|
9440
|
+
**Kind:** `function`
|
|
9441
|
+
|
|
9442
|
+
Асинхронные правила поля (зеркалит движковое разделение `validators` / `asyncValidators`).
|
|
9443
|
+
Раннер дожидается их и прокидывает `AbortSignal` для отмены устаревших ответов.
|
|
9444
|
+
|
|
9445
|
+
**Signature:**
|
|
9446
|
+
```typescript
|
|
9447
|
+
export function validateAsync<TField>(
|
|
9448
|
+
sig: PathAwareSignal<TField>,
|
|
9449
|
+
rules: AsyncRule<TField>[]
|
|
9450
|
+
): void
|
|
9451
|
+
```
|
|
9452
|
+
|
|
9453
|
+
**Examples:**
|
|
9454
|
+
|
|
9455
|
+
```ts
|
|
9456
|
+
validateAsync(model.$.username, [async (v, { signal }) => {
|
|
9457
|
+
const r = await fetch(`/api/free?u=${v}`, { signal });
|
|
9458
|
+
return (await r.json()).free ? null : { code: 'taken', message: 'Занято' };
|
|
9459
|
+
}]);
|
|
9460
|
+
```
|
|
9461
|
+
|
|
9462
|
+
_Source: src/form/validation/operators.ts_
|
|
9463
|
+
|
|
9464
|
+
### validateModel
|
|
9465
|
+
|
|
9466
|
+
**Kind:** `function`
|
|
9467
|
+
|
|
9468
|
+
Провалидировать модель ЛЮБОЙ схемой (шаг или вся форма). Открывает ambient-окно на время
|
|
9469
|
+
синхронного прогона `schema`, дожидается async-правил, разносит ошибки по нодам формы и гасит
|
|
9470
|
+
поля, ставшие валидными. Возвращает `true`, если нет блокирующих ошибок (`severity:'warning'` не блокирует).
|
|
9471
|
+
|
|
9472
|
+
Гашение — на пару (model, schema): `validateModel(model, step2)` и `validateModel(model, form)` не мешают
|
|
9473
|
+
друг другу. Устаревший прогон (быстрый повторный вызов той же (model, schema)) отменяется через `AbortSignal`
|
|
9474
|
+
и возвращает `false` (**fail-closed** — отменённому результату нельзя доверять для submit).
|
|
9475
|
+
|
|
9476
|
+
⚠️ `schema` должна быть СТАБИЛЬНОЙ ссылкой: отмена устаревших прогонов ключится по идентичности `schema`.
|
|
9477
|
+
Инлайн-стрелка (`validateModel(model, ({ model }) => …)`) каждый раз создаёт НОВЫЙ прогон без дедупликации —
|
|
9478
|
+
держите схемы в `const` / `defineValidationSchema`.
|
|
9479
|
+
|
|
9480
|
+
`options.touch` (§6): по завершении помечает `touched` ИМЕННО поля, которые схема проверяла
|
|
9481
|
+
(не всё поддерево). Ошибки становятся видимыми (`shouldShowError = invalid && (touched||dirty)`)
|
|
9482
|
+
без ручного `form.markAsTouched()`; при пошаговой валидации следующий шаг не показывается
|
|
9483
|
+
«тронутым» до ввода. Валидные проверенные поля тоже метятся — это безвредно (ошибка не покажется).
|
|
9484
|
+
|
|
9485
|
+
**Signature:**
|
|
9486
|
+
```typescript
|
|
9487
|
+
export async function validateModel<T>(
|
|
9488
|
+
model: FormModel<T>,
|
|
9489
|
+
schema: ValidationSchema<T>,
|
|
9490
|
+
options?: { touch?: boolean }
|
|
9491
|
+
): Promise<boolean>
|
|
9492
|
+
```
|
|
9493
|
+
|
|
9494
|
+
**Parameters:**
|
|
9495
|
+
- `model` — - Модель данных формы.
|
|
9496
|
+
- `schema` — - Схема валидации (стабильная ссылка).
|
|
9497
|
+
- `options` — - `{ touch }` — пометить провалидированные поля `touched` для показа ошибок.
|
|
9498
|
+
|
|
9499
|
+
**Examples:**
|
|
9500
|
+
|
|
9501
|
+
```ts
|
|
9502
|
+
const ok = await validateModel(model, step2Validation); // один шаг
|
|
9503
|
+
const all = await validateModel(model, formValidation); // вся форма
|
|
9504
|
+
const stepOk = await validateModel(model, step2Validation, { touch: true }); // + показать ошибки шага
|
|
9505
|
+
```
|
|
9506
|
+
|
|
9507
|
+
_Source: src/form/validation/run.ts_
|
|
9508
|
+
|
|
7752
9509
|
### ValidateOptions
|
|
7753
9510
|
|
|
7754
9511
|
**Kind:** `interface`
|
|
@@ -7769,6 +9526,20 @@ export interface ValidateOptions {
|
|
|
7769
9526
|
|
|
7770
9527
|
_Source: src/form/types/validation-schema.ts_
|
|
7771
9528
|
|
|
9529
|
+
### validateWhen
|
|
9530
|
+
|
|
9531
|
+
**Kind:** `function`
|
|
9532
|
+
|
|
9533
|
+
Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля
|
|
9534
|
+
ГАСЯТСЯ (пустой bucket → `setErrors([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).
|
|
9535
|
+
|
|
9536
|
+
**Signature:**
|
|
9537
|
+
```typescript
|
|
9538
|
+
export function validateWhen(cond: () => boolean, cb: () => void): void
|
|
9539
|
+
```
|
|
9540
|
+
|
|
9541
|
+
_Source: src/form/validation/operators.ts_
|
|
9542
|
+
|
|
7772
9543
|
### ValidationError
|
|
7773
9544
|
|
|
7774
9545
|
**Kind:** `interface`
|
|
@@ -7788,6 +9559,54 @@ export interface ValidationError {
|
|
|
7788
9559
|
|
|
7789
9560
|
_Source: src/form/types/contracts.ts_
|
|
7790
9561
|
|
|
9562
|
+
### ValidationSchema
|
|
9563
|
+
|
|
9564
|
+
**Kind:** `type`
|
|
9565
|
+
|
|
9566
|
+
Схема валидации — обычная функция над (под)моделью. First-class значение (можно `apply`/тестировать/переиспользовать).
|
|
9567
|
+
|
|
9568
|
+
**Signature:**
|
|
9569
|
+
```typescript
|
|
9570
|
+
export type ValidationSchema<T> = (ctx: { model: FormModel<T> }) => void;
|
|
9571
|
+
```
|
|
9572
|
+
|
|
9573
|
+
_Source: src/form/validation/types.ts_
|
|
9574
|
+
|
|
9575
|
+
### ValidationStrategyKind
|
|
9576
|
+
|
|
9577
|
+
**Kind:** `type`
|
|
9578
|
+
|
|
9579
|
+
Когда запускается schema-валидация:
|
|
9580
|
+
- `submit` (по умолчанию) — только явным вызовом `validate()` (на отправке);
|
|
9581
|
+
- `blur` — при потере фокуса поля (смена `touched` любого листа);
|
|
9582
|
+
- `change` — на каждый ввод (с опц. `debounce`);
|
|
9583
|
+
- `afterFirstSubmit` — тихо до первого `validate()`, затем live (см. `liveAfterSubmit`).
|
|
9584
|
+
|
|
9585
|
+
**Signature:**
|
|
9586
|
+
```typescript
|
|
9587
|
+
export type ValidationStrategyKind = 'submit' | 'blur' | 'change' | 'afterFirstSubmit';
|
|
9588
|
+
```
|
|
9589
|
+
|
|
9590
|
+
_Source: src/form/validation/strategy.ts_
|
|
9591
|
+
|
|
9592
|
+
### ValidationStrategyOptions
|
|
9593
|
+
|
|
9594
|
+
**Kind:** `interface`
|
|
9595
|
+
|
|
9596
|
+
**Signature:**
|
|
9597
|
+
```typescript
|
|
9598
|
+
export interface ValidationStrategyOptions {
|
|
9599
|
+
/** Стратегия запуска. Default `'submit'`. */
|
|
9600
|
+
strategy?: ValidationStrategyKind;
|
|
9601
|
+
/** Debounce (мс) для live-фаз (`change` и live-часть `afterFirstSubmit`). Default `0`. */
|
|
9602
|
+
debounce?: number;
|
|
9603
|
+
/** Режим live-фазы для `afterFirstSubmit`. Default `'change'`. */
|
|
9604
|
+
liveAfterSubmit?: 'change' | 'blur';
|
|
9605
|
+
}
|
|
9606
|
+
```
|
|
9607
|
+
|
|
9608
|
+
_Source: src/form/validation/strategy.ts_
|
|
9609
|
+
|
|
7791
9610
|
### Validator
|
|
7792
9611
|
|
|
7793
9612
|
**Kind:** `type`
|