@reformer/core 11.0.0-beta.3 → 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/behaviors.d.ts +1 -1
- package/dist/behaviors.js +37 -37
- package/dist/create-model-Dxh9cfLp.js +97 -0
- package/dist/form/behaviors/collections.d.ts +50 -0
- package/dist/form/behaviors/context.d.ts +23 -0
- package/dist/form/behaviors/index.d.ts +28 -0
- package/dist/form/behaviors/internals.d.ts +26 -0
- package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +3 -3
- package/dist/form/behaviors/operators.d.ts +51 -0
- package/dist/form/behaviors/types.d.ts +17 -0
- package/dist/form/create-core-form.d.ts +3 -3
- package/dist/form/create-form.d.ts +12 -10
- package/dist/form/factories/node-factory.d.ts +17 -16
- package/dist/form/index.d.ts +10 -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 +3 -3
- package/dist/form/types/contracts.d.ts +9 -6
- package/dist/form/types/deep-schema.d.ts +5 -5
- package/dist/form/types/validation-schema.d.ts +1 -1
- package/dist/form/{validation-config.d.ts → validation/config.d.ts} +3 -3
- package/dist/form/validation/context.d.ts +27 -0
- package/dist/form/{error-handler.d.ts → validation/error-handler.d.ts} +1 -1
- package/dist/form/validation/index.d.ts +23 -28
- package/dist/form/validation/operators.d.ts +42 -0
- package/dist/form/validation/run.d.ts +45 -0
- package/dist/form/{validation-strategy.d.ts → validation/strategy.d.ts} +2 -2
- package/dist/form/validation/types.d.ts +28 -0
- package/dist/form/{validation/validators → validators}/date-utils.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/email.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/file-type.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/file-utils.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/future-date.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
- package/dist/form/{validation/validators → validators}/integer.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/is-date.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/is-number.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/max-age.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/max-date.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/max-file-size.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/max-files.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/max-length.d.ts +7 -8
- package/dist/form/{validation/validators → validators}/max-total-file-size.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/max.d.ts +7 -8
- package/dist/form/{validation/validators → validators}/min-age.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/min-date.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/min-file-size.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/min-files.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/min-length.d.ts +7 -8
- package/dist/form/{validation/validators → validators}/min.d.ts +7 -8
- package/dist/form/{validation/validators → validators}/multiple-of.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/non-negative.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/non-zero.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/past-date.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/pattern.d.ts +7 -15
- package/dist/form/{validation/validators → validators}/phone.d.ts +6 -7
- package/dist/form/{validation/validators → validators}/required.d.ts +8 -13
- package/dist/form/{validation/validators → validators}/url.d.ts +8 -13
- package/dist/index.d.ts +7 -4
- package/dist/index.js +272 -94
- package/dist/{state/form-model.d.ts → model/create-model.d.ts} +0 -14
- package/dist/{state → model}/index.d.ts +10 -11
- package/dist/model/model-nodes.d.ts +60 -0
- package/dist/model/model-signals-proxy.d.ts +32 -0
- package/dist/model/model-value-proxy.d.ts +3 -0
- package/dist/{state → model}/safe-effect.d.ts +1 -1
- package/dist/model-signals-proxy-mV3WGdlO.js +233 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +21 -0
- package/dist/{form → platforms/react}/hooks/types.d.ts +1 -1
- package/dist/{form → platforms/react}/hooks/use-form-bundle.d.ts +1 -1
- package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +3 -3
- package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +1 -1
- package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +3 -3
- package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +2 -2
- package/dist/platforms/react/index.d.ts +23 -0
- package/dist/signals.d.ts +1 -1
- package/dist/strategy-DA3JwxsR.js +170 -0
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +10 -165
- package/dist/validators/email.d.ts +1 -1
- package/dist/validators/future-date.d.ts +1 -1
- package/dist/validators/integer.d.ts +1 -1
- package/dist/validators/is-date.d.ts +1 -1
- package/dist/validators/is-number.d.ts +1 -1
- package/dist/validators/max-age.d.ts +1 -1
- package/dist/validators/max-date.d.ts +1 -1
- package/dist/validators/max-length.d.ts +1 -1
- package/dist/validators/max.d.ts +1 -1
- package/dist/validators/min-age.d.ts +1 -1
- package/dist/validators/min-date.d.ts +1 -1
- package/dist/validators/min-length.d.ts +1 -1
- package/dist/validators/min.d.ts +1 -1
- package/dist/validators/multiple-of.d.ts +1 -1
- package/dist/validators/non-negative.d.ts +1 -1
- package/dist/validators/non-zero.d.ts +1 -1
- package/dist/validators/past-date.d.ts +1 -1
- package/dist/validators/pattern.d.ts +1 -1
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators.d.ts +1 -1
- 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 +2102 -262
- package/package.json +5 -4
- package/dist/form/behaviors.d.ts +0 -133
- package/dist/form/validation-schema.d.ts +0 -106
- package/dist/form/validators.d.ts +0 -11
- package/dist/form-model-DhW2HZOB.js +0 -323
- package/dist/state.d.ts +0 -2
- package/dist/state.js +0 -21
- package/dist/subscription-manager-705bls97.js +0 -171
- /package/dist/{state → form/nodes}/subscription-manager.d.ts +0 -0
- /package/dist/{state → model}/behaviors-value.d.ts +0 -0
- /package/dist/{state → model}/derived-registry.d.ts +0 -0
- /package/dist/{state → model}/types.d.ts +0 -0
- /package/dist/{behaviors-node-DGEFMTQu.js → node-DGEFMTQu.js} +0 -0
- /package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +0 -0
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`
|
|
@@ -4537,7 +4673,7 @@ Add Phone
|
|
|
4537
4673
|
- {@link useFormControl} - хук для получения состояния
|
|
4538
4674
|
- {@link FieldControlState} - состояние для полей
|
|
4539
4675
|
|
|
4540
|
-
_Source: src/
|
|
4676
|
+
_Source: src/platforms/react/hooks/types.ts_
|
|
4541
4677
|
|
|
4542
4678
|
### ArrayNode
|
|
4543
4679
|
|
|
@@ -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`
|
|
@@ -4655,7 +4808,20 @@ _Source: src/form/types/contracts.ts_
|
|
|
4655
4808
|
export type BehaviorCleanup = () => void;
|
|
4656
4809
|
```
|
|
4657
4810
|
|
|
4658
|
-
_Source: src/
|
|
4811
|
+
_Source: src/model/behaviors-value.ts_
|
|
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_
|
|
4659
4825
|
|
|
4660
4826
|
### buildValidation
|
|
4661
4827
|
|
|
@@ -4692,7 +4858,39 @@ await v!.validateStep(1); // правила шага `loan`
|
|
|
4692
4858
|
await v!.validateAll(); // все шаги + extras
|
|
4693
4859
|
```
|
|
4694
4860
|
|
|
4695
|
-
_Source: src/form/validation
|
|
4861
|
+
_Source: src/form/validation/config.ts_
|
|
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_
|
|
4696
4894
|
|
|
4697
4895
|
### computeFrom
|
|
4698
4896
|
|
|
@@ -4723,7 +4921,7 @@ export function computeFrom<R>(
|
|
|
4723
4921
|
computeFrom([model.$.price, model.$.qty], model.$.total, (price, qty) => price * qty);
|
|
4724
4922
|
```
|
|
4725
4923
|
|
|
4726
|
-
_Source: src/
|
|
4924
|
+
_Source: src/model/behaviors-value.ts_
|
|
4727
4925
|
|
|
4728
4926
|
### ConfigWithSchema
|
|
4729
4927
|
|
|
@@ -4777,7 +4975,7 @@ export function copyFrom<T>(
|
|
|
4777
4975
|
copyFrom(model.$.email, model.$.emailAdditional, { when: () => model.sameEmail });
|
|
4778
4976
|
```
|
|
4779
4977
|
|
|
4780
|
-
_Source: src/
|
|
4978
|
+
_Source: src/model/behaviors-value.ts_
|
|
4781
4979
|
|
|
4782
4980
|
### CORE_RUNTIME_TOKEN
|
|
4783
4981
|
|
|
@@ -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`
|
|
@@ -5087,7 +5321,82 @@ model.tags.push('x');
|
|
|
5087
5321
|
model.get(); // { email: 'a@b.c', profile: { name: 'Ada' }, tags: ['x'] }
|
|
5088
5322
|
```
|
|
5089
5323
|
|
|
5090
|
-
_Source: src/
|
|
5324
|
+
_Source: src/model/create-model.ts_
|
|
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_
|
|
5091
5400
|
|
|
5092
5401
|
### disableWhen
|
|
5093
5402
|
|
|
@@ -5119,7 +5428,94 @@ export function disableWhen(
|
|
|
5119
5428
|
disableWhen(model.$.discount, () => model.plan !== 'promo', { resetOnDisable: true });
|
|
5120
5429
|
```
|
|
5121
5430
|
|
|
5122
|
-
_Source: src/form/behaviors
|
|
5431
|
+
_Source: src/form/behaviors/node.ts_
|
|
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
|
+
|
|
5450
|
+
### eachLeafSignal
|
|
5451
|
+
|
|
5452
|
+
**Kind:** `function`
|
|
5453
|
+
|
|
5454
|
+
Обойти ВСЕ листовые сигналы модели (включая элементы массивов), вызвав `visit` на каждом.
|
|
5455
|
+
|
|
5456
|
+
Внутри реактивного `effect` служит подпиской «любое поле изменилось»: `visit(sig => void sig.value)`
|
|
5457
|
+
подписывает на значения листьев, а обход массивов через `items.value` — на их состав. Так строятся
|
|
5458
|
+
триггеры `change`/`blur` стратегий валидации (см. `createFormValidation`), тем же паттерном, что
|
|
5459
|
+
`revalidateWhen`, но без ручного перечисления зависимостей.
|
|
5460
|
+
|
|
5461
|
+
**Signature:**
|
|
5462
|
+
```typescript
|
|
5463
|
+
export function eachLeafSignal<T>(
|
|
5464
|
+
model: FormModel<T>,
|
|
5465
|
+
visit: (signal: PathAwareSignal<unknown>) => void
|
|
5466
|
+
): void
|
|
5467
|
+
```
|
|
5468
|
+
|
|
5469
|
+
_Source: src/model/create-model.ts_
|
|
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_
|
|
5123
5519
|
|
|
5124
5520
|
### enableWhen
|
|
5125
5521
|
|
|
@@ -5163,7 +5559,7 @@ compute(model.$.initialPayment, () => model.propertyValue * 0.2, {
|
|
|
5163
5559
|
});
|
|
5164
5560
|
```
|
|
5165
5561
|
|
|
5166
|
-
_Source: src/form/behaviors
|
|
5562
|
+
_Source: src/form/behaviors/node.ts_
|
|
5167
5563
|
|
|
5168
5564
|
### ErrorFilterOptions
|
|
5169
5565
|
|
|
@@ -5221,7 +5617,29 @@ export enum ErrorStrategy {
|
|
|
5221
5617
|
}
|
|
5222
5618
|
```
|
|
5223
5619
|
|
|
5224
|
-
_Source: src/form/error-handler.ts_
|
|
5620
|
+
_Source: src/form/validation/error-handler.ts_
|
|
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_
|
|
5225
5643
|
|
|
5226
5644
|
### FieldConfig
|
|
5227
5645
|
|
|
@@ -5483,7 +5901,7 @@ onChange={e => control.setValue(e.target.value)}
|
|
|
5483
5901
|
- {@link useFormControl} - хук для получения состояния
|
|
5484
5902
|
- {@link ArrayControlState} - состояние для массивов
|
|
5485
5903
|
|
|
5486
|
-
_Source: src/
|
|
5904
|
+
_Source: src/platforms/react/hooks/types.ts_
|
|
5487
5905
|
|
|
5488
5906
|
### FieldNode
|
|
5489
5907
|
|
|
@@ -5501,10 +5919,11 @@ export class FieldNode<T> extends FormNode<T> {
|
|
|
5501
5919
|
|
|
5502
5920
|
**Examples:**
|
|
5503
5921
|
|
|
5922
|
+
Пример node-level: конфиг УЗЛА, а не layout-схема M1 (в ней поля `validators` нет).
|
|
5504
5923
|
```typescript
|
|
5505
5924
|
const field = new FieldNode({
|
|
5506
5925
|
value: '',
|
|
5507
|
-
component:
|
|
5926
|
+
component: InputField,
|
|
5508
5927
|
validators: [required, email],
|
|
5509
5928
|
});
|
|
5510
5929
|
|
|
@@ -5528,24 +5947,87 @@ export type FieldStatus = 'valid' | 'invalid' | 'pending' | 'disabled';
|
|
|
5528
5947
|
|
|
5529
5948
|
_Source: src/form/types/contracts.ts_
|
|
5530
5949
|
|
|
5531
|
-
###
|
|
5532
|
-
|
|
5533
|
-
**Kind:** `type`
|
|
5950
|
+
### FileLike
|
|
5534
5951
|
|
|
5535
|
-
|
|
5952
|
+
**Kind:** `interface`
|
|
5536
5953
|
|
|
5537
|
-
|
|
5954
|
+
Минимальный файлоподобный контракт, с которым работают file-валидаторы:
|
|
5955
|
+
нативный `File`, сериализуемый дескриптор загруженного файла и т.п.
|
|
5538
5956
|
|
|
5539
5957
|
**Signature:**
|
|
5540
5958
|
```typescript
|
|
5541
|
-
export
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5959
|
+
export interface FileLike {
|
|
5960
|
+
name: string;
|
|
5961
|
+
size?: number;
|
|
5962
|
+
type?: string;
|
|
5963
|
+
}
|
|
5964
|
+
```
|
|
5965
|
+
|
|
5966
|
+
_Source: src/form/validators/file-utils.ts_
|
|
5967
|
+
|
|
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
|
+
/**
|
|
5549
6031
|
* Итерация по элементам массива с типизированными элементами
|
|
5550
6032
|
*/
|
|
5551
6033
|
forEach(callback: (item: FormProxy<T>, index: number) => void): void;
|
|
@@ -5582,6 +6064,22 @@ todos.forEach((item, i) => {
|
|
|
5582
6064
|
|
|
5583
6065
|
_Source: src/form/types/form-proxy.ts_
|
|
5584
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
|
+
|
|
5585
6083
|
### FormBundleLike
|
|
5586
6084
|
|
|
5587
6085
|
**Kind:** `interface`
|
|
@@ -5595,7 +6093,7 @@ export interface FormBundleLike {
|
|
|
5595
6093
|
}
|
|
5596
6094
|
```
|
|
5597
6095
|
|
|
5598
|
-
_Source: src/
|
|
6096
|
+
_Source: src/platforms/react/hooks/use-form-bundle.ts_
|
|
5599
6097
|
|
|
5600
6098
|
### FormControlsProxy
|
|
5601
6099
|
|
|
@@ -5709,7 +6207,7 @@ try {
|
|
|
5709
6207
|
}
|
|
5710
6208
|
```
|
|
5711
6209
|
|
|
5712
|
-
_Source: src/form/error-handler.ts_
|
|
6210
|
+
_Source: src/form/validation/error-handler.ts_
|
|
5713
6211
|
|
|
5714
6212
|
### FormModel
|
|
5715
6213
|
|
|
@@ -5724,7 +6222,7 @@ FormModel — под-модель объекта `T`: value-доступ ({@link
|
|
|
5724
6222
|
export type FormModel<T> = ModelObject<T> & ModelApi<T>;
|
|
5725
6223
|
```
|
|
5726
6224
|
|
|
5727
|
-
_Source: src/
|
|
6225
|
+
_Source: src/model/types.ts_
|
|
5728
6226
|
|
|
5729
6227
|
### FormNode
|
|
5730
6228
|
|
|
@@ -5876,14 +6374,14 @@ interface Form {
|
|
|
5876
6374
|
}
|
|
5877
6375
|
|
|
5878
6376
|
const schema: FormSchema<Form> = {
|
|
5879
|
-
name: { value: '', component:
|
|
6377
|
+
name: { value: '', component: InputField },
|
|
5880
6378
|
address: {
|
|
5881
|
-
city: { value: '', component:
|
|
5882
|
-
street: { value: '', component:
|
|
6379
|
+
city: { value: '', component: InputField },
|
|
6380
|
+
street: { value: '', component: InputField },
|
|
5883
6381
|
},
|
|
5884
6382
|
items: [{
|
|
5885
|
-
title: { value: '', component:
|
|
5886
|
-
price: { value: 0, component:
|
|
6383
|
+
title: { value: '', component: InputField },
|
|
6384
|
+
price: { value: 0, component: InputField },
|
|
5887
6385
|
}],
|
|
5888
6386
|
};
|
|
5889
6387
|
```
|
|
@@ -6020,7 +6518,7 @@ export interface FormValidation<T> {
|
|
|
6020
6518
|
}
|
|
6021
6519
|
```
|
|
6022
6520
|
|
|
6023
|
-
_Source: src/form/validation
|
|
6521
|
+
_Source: src/form/validation/config.ts_
|
|
6024
6522
|
|
|
6025
6523
|
### FormValidationBundle
|
|
6026
6524
|
|
|
@@ -6051,7 +6549,38 @@ export interface FormValidationBundle<T> {
|
|
|
6051
6549
|
}
|
|
6052
6550
|
```
|
|
6053
6551
|
|
|
6054
|
-
_Source: src/form/validation
|
|
6552
|
+
_Source: src/form/validation/config.ts_
|
|
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_
|
|
6055
6584
|
|
|
6056
6585
|
### FormValue
|
|
6057
6586
|
|
|
@@ -6076,6 +6605,43 @@ export type FormValue =
|
|
|
6076
6605
|
|
|
6077
6606
|
_Source: src/form/types/contracts.ts_
|
|
6078
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
|
+
|
|
6079
6645
|
### getNodeForSignal
|
|
6080
6646
|
|
|
6081
6647
|
**Kind:** `function`
|
|
@@ -6137,6 +6703,19 @@ console.log('Node type:', getNodeType(node)); // "FieldNode" | "GroupNode" | "Ar
|
|
|
6137
6703
|
|
|
6138
6704
|
_Source: src/form/type-guards.ts_
|
|
6139
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
|
+
|
|
6140
6719
|
### GroupNode
|
|
6141
6720
|
|
|
6142
6721
|
**Kind:** `class`
|
|
@@ -6159,8 +6738,8 @@ export class GroupNode<T> extends FormNode<T> {
|
|
|
6159
6738
|
|
|
6160
6739
|
```typescript
|
|
6161
6740
|
const form = new GroupNode({
|
|
6162
|
-
email: { valueSignal: model.$.email, component:
|
|
6163
|
-
password: { valueSignal: model.$.password, component:
|
|
6741
|
+
email: { valueSignal: model.$.email, component: InputField },
|
|
6742
|
+
password: { valueSignal: model.$.password, component: InputField },
|
|
6164
6743
|
});
|
|
6165
6744
|
|
|
6166
6745
|
// Прямой доступ к полям через Proxy
|
|
@@ -6191,6 +6770,41 @@ export interface GroupNodeConfig<T> {
|
|
|
6191
6770
|
|
|
6192
6771
|
_Source: src/form/types/index.ts_
|
|
6193
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
|
+
|
|
6194
6808
|
### isArrayNode
|
|
6195
6809
|
|
|
6196
6810
|
**Kind:** `function`
|
|
@@ -6222,6 +6836,43 @@ if (isArrayNode(node)) {
|
|
|
6222
6836
|
|
|
6223
6837
|
_Source: src/form/type-guards.ts_
|
|
6224
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
|
+
|
|
6225
6876
|
### isDerived
|
|
6226
6877
|
|
|
6227
6878
|
**Kind:** `function`
|
|
@@ -6254,7 +6905,7 @@ if (!isDerived(model.$.total)) {
|
|
|
6254
6905
|
**See also:**
|
|
6255
6906
|
- {@link markDerived} - пометить сигнал производным
|
|
6256
6907
|
|
|
6257
|
-
_Source: src/
|
|
6908
|
+
_Source: src/model/derived-registry.ts_
|
|
6258
6909
|
|
|
6259
6910
|
### isFieldNode
|
|
6260
6911
|
|
|
@@ -6287,6 +6938,25 @@ if (isFieldNode(node)) {
|
|
|
6287
6938
|
|
|
6288
6939
|
_Source: src/form/type-guards.ts_
|
|
6289
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
|
+
|
|
6290
6960
|
### isFormNode
|
|
6291
6961
|
|
|
6292
6962
|
**Kind:** `function`
|
|
@@ -6366,7 +7036,44 @@ export function isModelContainerSignal(value: unknown): boolean
|
|
|
6366
7036
|
const isLeaf = (v: unknown) => isSignalLike(v) && !isModelContainerSignal(v);
|
|
6367
7037
|
```
|
|
6368
7038
|
|
|
6369
|
-
_Source: src/
|
|
7039
|
+
_Source: src/model/model-signals-proxy.ts_
|
|
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_
|
|
6370
7077
|
|
|
6371
7078
|
### markDerived
|
|
6372
7079
|
|
|
@@ -6402,98 +7109,642 @@ markDerived(model.$.total);
|
|
|
6402
7109
|
- {@link isDerived} - проверка пометки
|
|
6403
7110
|
- {@link unmarkDerived} - снять пометку при dispose оператора
|
|
6404
7111
|
|
|
6405
|
-
_Source: src/
|
|
7112
|
+
_Source: src/model/derived-registry.ts_
|
|
6406
7113
|
|
|
6407
|
-
###
|
|
7114
|
+
### matchesFileAccept
|
|
6408
7115
|
|
|
6409
|
-
**Kind:** `
|
|
7116
|
+
**Kind:** `function`
|
|
6410
7117
|
|
|
6411
|
-
|
|
7118
|
+
Проверяет файл против accept-паттерна в синтаксисе нативного атрибута
|
|
7119
|
+
`<input type="file" accept>`: список через запятую из расширений (`.pdf`),
|
|
7120
|
+
точных MIME (`image/png`) и wildcard-категорий (`image/*`). Регистронезависимо.
|
|
6412
7121
|
|
|
6413
|
-
|
|
6414
|
-
|
|
7122
|
+
Нужен собственный матчер, потому что `accept` у нативного input — только подсказка
|
|
7123
|
+
пикеру: на drag-and-drop и на программное значение он не действует.
|
|
6415
7124
|
|
|
6416
7125
|
**Signature:**
|
|
6417
7126
|
```typescript
|
|
6418
|
-
export
|
|
6419
|
-
/**
|
|
6420
|
-
* Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`.
|
|
6421
|
-
* Сам узел — {@link ReadonlySignal} модели целиком: `model.$.subscribe(v => …)`.
|
|
6422
|
-
*/
|
|
6423
|
-
readonly $: ModelGroupSignals<T>;
|
|
6424
|
-
/** Снимок значений (без подписки) — для submit. */
|
|
6425
|
-
get(): T;
|
|
6426
|
-
/**
|
|
6427
|
-
* Полная установка значений: принимает объект целиком (все ключи `T`) и записывает их в модель.
|
|
6428
|
-
* Производными полями (цели `compute`) владеет compute — их значения из payload игнорируются.
|
|
6429
|
-
* Не меняет initial-снимок. Для частичного обновления (только переданные ключи) — {@link ModelApi.patch}.
|
|
6430
|
-
*/
|
|
6431
|
-
set(value: T): void;
|
|
6432
|
-
/**
|
|
6433
|
-
* Частичное слияние значений (load/patch с сервера): обновляет только переданные ключи,
|
|
6434
|
-
* отсутствующие ключи НЕ трогаются. Не меняет initial-снимок.
|
|
6435
|
-
*/
|
|
6436
|
-
patch(value: Partial<T>): void;
|
|
6437
|
-
/** Отличаются ли текущие значения от initial-снимка (value-diff). */
|
|
6438
|
-
isDirty(): boolean;
|
|
6439
|
-
/** Сбросить значения к initial-снимку. */
|
|
6440
|
-
reset(): void;
|
|
6441
|
-
/** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */
|
|
6442
|
-
captureInitial(): void;
|
|
6443
|
-
/** Резолв строкового пути в сигнал (для error-routing/мостов). */
|
|
6444
|
-
signalAt(path: string): PathAwareSignal<unknown> | undefined;
|
|
6445
|
-
}
|
|
7127
|
+
export function matchesFileAccept(file: Pick<FileLike, 'name' | 'type'>, accept: string): boolean
|
|
6446
7128
|
```
|
|
6447
7129
|
|
|
6448
|
-
|
|
7130
|
+
**Parameters:**
|
|
7131
|
+
- `file` — - Файлоподобное значение (`name` + опциональный `type`)
|
|
7132
|
+
- `accept` — - Accept-строка; пустая/пробельная строка матчит всё
|
|
6449
7133
|
|
|
6450
|
-
|
|
7134
|
+
**Returns:** `true`, если файл подходит хотя бы под один паттерн
|
|
6451
7135
|
|
|
6452
|
-
|
|
7136
|
+
_Source: src/form/validators/file-utils.ts_
|
|
6453
7137
|
|
|
6454
|
-
|
|
6455
|
-
|
|
7138
|
+
### max
|
|
7139
|
+
|
|
7140
|
+
**Kind:** `function`
|
|
7141
|
+
|
|
7142
|
+
Фабрика валидатора максимального числового значения.
|
|
7143
|
+
|
|
7144
|
+
Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
|
|
6456
7145
|
|
|
6457
7146
|
**Signature:**
|
|
6458
7147
|
```typescript
|
|
6459
|
-
export
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
*/
|
|
6464
|
-
readonly __path: string;
|
|
6465
|
-
/** Реактивная длина. */
|
|
6466
|
-
readonly length: number;
|
|
6467
|
-
/** Добавить элемент в конец (значение элемента целиком). */
|
|
6468
|
-
push(item: U): void;
|
|
6469
|
-
/** Вставить элемент по индексу. */
|
|
6470
|
-
insertAt(index: number, item: U): void;
|
|
6471
|
-
/** Удалить элемент по индексу. */
|
|
6472
|
-
removeAt(index: number): void;
|
|
6473
|
-
/** Переместить элемент. */
|
|
6474
|
-
move(from: number, to: number): void;
|
|
6475
|
-
/** Поменять местами два элемента. */
|
|
6476
|
-
swap(a: number, b: number): void;
|
|
6477
|
-
/** Очистить массив. */
|
|
6478
|
-
clear(): void;
|
|
6479
|
-
/** Элемент по индексу: объект → под-модель, массив → {@link ModelArray}, лист → значение. */
|
|
6480
|
-
at(
|
|
6481
|
-
index: number
|
|
6482
|
-
): NonNullable<U> extends object ? ModelArrayItem<U> : ModelArrayItem<U> | undefined;
|
|
6483
|
-
/** Map по элементам (объект → {@link FormModel}, Opaque/примитив → значение, массив → {@link ModelArray}). */
|
|
6484
|
-
map<R>(fn: (item: ModelArrayItem<U>, index: number) => R): R[];
|
|
6485
|
-
/** Итерация по элементам (см. {@link ModelArrayItem}). */
|
|
6486
|
-
forEach(fn: (item: ModelArrayItem<U>, index: number) => void): void;
|
|
6487
|
-
/** Снимок массива значений (без подписки). */
|
|
6488
|
-
toArray(): U[];
|
|
6489
|
-
/** Индексный value-доступ. */
|
|
6490
|
-
[index: number]: ModelValue<U>;
|
|
6491
|
-
}
|
|
7148
|
+
export function max<TForm = unknown, TField extends number | null | undefined = number>(
|
|
7149
|
+
maxValue: number,
|
|
7150
|
+
options?: ValidateOptions
|
|
7151
|
+
): Validator<TForm, TField>
|
|
6492
7152
|
```
|
|
6493
7153
|
|
|
6494
|
-
|
|
7154
|
+
**Parameters:**
|
|
7155
|
+
- `maxValue` — - Максимально допустимое значение (включительно)
|
|
7156
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7157
|
+
попадают `max` и `actual`.
|
|
6495
7158
|
|
|
6496
|
-
|
|
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_
|
|
7746
|
+
|
|
7747
|
+
### ModelArrayControl
|
|
6497
7748
|
|
|
6498
7749
|
**Kind:** `interface`
|
|
6499
7750
|
|
|
@@ -6545,8 +7796,8 @@ export class ModelArrayNode<T extends object> extends FormNode<T[]> { /* … */
|
|
|
6545
7796
|
const model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });
|
|
6546
7797
|
|
|
6547
7798
|
const rowItem = (item: FormModel<{ name: string; qty: number }>) => ({
|
|
6548
|
-
name: { value: item.$.name, component:
|
|
6549
|
-
qty: { value: item.$.qty, component:
|
|
7799
|
+
name: { value: item.$.name, component: InputField },
|
|
7800
|
+
qty: { value: item.$.qty, component: InputField },
|
|
6550
7801
|
});
|
|
6551
7802
|
|
|
6552
7803
|
const form = createForm({
|
|
@@ -6580,7 +7831,7 @@ export type ModelArraySignals<U, V> = ContainerSignal<V, 'length'> & {
|
|
|
6580
7831
|
};
|
|
6581
7832
|
```
|
|
6582
7833
|
|
|
6583
|
-
_Source: src/
|
|
7834
|
+
_Source: src/model/types.ts_
|
|
6584
7835
|
|
|
6585
7836
|
### ModelGroupSignals
|
|
6586
7837
|
|
|
@@ -6597,7 +7848,7 @@ _Source: src/state/types.ts_
|
|
|
6597
7848
|
export type ModelGroupSignals<T> = ModelSignals<T> & ContainerSignal<T, keyof T>;
|
|
6598
7849
|
```
|
|
6599
7850
|
|
|
6600
|
-
_Source: src/
|
|
7851
|
+
_Source: src/model/types.ts_
|
|
6601
7852
|
|
|
6602
7853
|
### ModelObject
|
|
6603
7854
|
|
|
@@ -6614,7 +7865,7 @@ export type ModelObject<T> = {
|
|
|
6614
7865
|
};
|
|
6615
7866
|
```
|
|
6616
7867
|
|
|
6617
|
-
_Source: src/
|
|
7868
|
+
_Source: src/model/types.ts_
|
|
6618
7869
|
|
|
6619
7870
|
### ModelSignals
|
|
6620
7871
|
|
|
@@ -6630,7 +7881,7 @@ export type ModelSignals<T> = {
|
|
|
6630
7881
|
};
|
|
6631
7882
|
```
|
|
6632
7883
|
|
|
6633
|
-
_Source: src/
|
|
7884
|
+
_Source: src/model/types.ts_
|
|
6634
7885
|
|
|
6635
7886
|
### ModelValue
|
|
6636
7887
|
|
|
@@ -6643,106 +7894,401 @@ _Source: src/state/types.ts_
|
|
|
6643
7894
|
промоутится рантаймом (`makeFormModel`); сигналы идентичны `model.$.<path>`
|
|
6644
7895
|
- примитив → значение
|
|
6645
7896
|
|
|
6646
|
-
**Signature:**
|
|
7897
|
+
**Signature:**
|
|
7898
|
+
```typescript
|
|
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
|
+
Проверка по регулярному выражению
|
|
6647
8208
|
```typescript
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
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
|
+
});
|
|
6656
8217
|
```
|
|
6657
8218
|
|
|
6658
|
-
_Source: src/
|
|
8219
|
+
_Source: src/form/validators/pattern.ts_
|
|
6659
8220
|
|
|
6660
|
-
###
|
|
8221
|
+
### phone
|
|
6661
8222
|
|
|
6662
|
-
**Kind:** `
|
|
8223
|
+
**Kind:** `function`
|
|
6663
8224
|
|
|
6664
|
-
Фабрика
|
|
8225
|
+
Фабрика валидатора номера телефона.
|
|
6665
8226
|
|
|
6666
|
-
|
|
6667
|
-
|
|
8227
|
+
Проверяет значение по regex выбранного {@link PhoneFormat}. Пустые значения
|
|
8228
|
+
(`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
|
|
6668
8229
|
|
|
6669
8230
|
**Signature:**
|
|
6670
8231
|
```typescript
|
|
6671
|
-
export
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
*
|
|
6675
|
-
* ✅ ОБНОВЛЕНО: Теперь поддерживает массивы напрямую
|
|
6676
|
-
*
|
|
6677
|
-
* Автоматически определяет тип узла:
|
|
6678
|
-
* - FieldNode: имеет value и component
|
|
6679
|
-
* - ArrayNode: массив [schema, ...items] или { schema, initialItems }
|
|
6680
|
-
* - GroupNode: объект без value, component, schema
|
|
6681
|
-
*
|
|
6682
|
-
* @param config Конфигурация узла
|
|
6683
|
-
* @returns Экземпляр FieldNode, GroupNode или ArrayNode
|
|
6684
|
-
* @throws Error если конфиг не соответствует ни одному типу
|
|
6685
|
-
*
|
|
6686
|
-
* @example
|
|
6687
|
-
* ```typescript
|
|
6688
|
-
* const factory = new NodeFactory();
|
|
6689
|
-
*
|
|
6690
|
-
* // FieldNode
|
|
6691
|
-
* const field = factory.createNode({
|
|
6692
|
-
* value: 'test@mail.com',
|
|
6693
|
-
* component: Input,
|
|
6694
|
-
* validators: [required, email]
|
|
6695
|
-
* });
|
|
6696
|
-
*
|
|
6697
|
-
* // GroupNode
|
|
6698
|
-
* const group = factory.createNode({
|
|
6699
|
-
* email: { value: '', component: Input },
|
|
6700
|
-
* password: { value: '', component: Input }
|
|
6701
|
-
* });
|
|
6702
|
-
*
|
|
6703
|
-
* // ArrayNode (объект)
|
|
6704
|
-
* const array = factory.createNode({
|
|
6705
|
-
* schema: { title: { value: '', component: Input } },
|
|
6706
|
-
* initialItems: [{ title: 'Item 1' }]
|
|
6707
|
-
* });
|
|
6708
|
-
*
|
|
6709
|
-
* // ArrayNode (массив) - новый формат
|
|
6710
|
-
* const array2 = factory.createNode([
|
|
6711
|
-
* { title: { value: '', component: Input } }, // schema
|
|
6712
|
-
* { title: 'Item 1' }, // initial item 1
|
|
6713
|
-
* { title: 'Item 2' } // initial item 2
|
|
6714
|
-
* ]);
|
|
6715
|
-
* ```
|
|
6716
|
-
*/ /* … */ }
|
|
8232
|
+
export function phone<TForm = unknown, TField extends string | null | undefined = string>(
|
|
8233
|
+
options?: PhoneValidatorOptions
|
|
8234
|
+
): Validator<TForm, TField>
|
|
6717
8235
|
```
|
|
6718
8236
|
|
|
8237
|
+
**Parameters:**
|
|
8238
|
+
- `options` — - Опции валидатора {@link PhoneValidatorOptions}. В `params` ошибки
|
|
8239
|
+
автоматически попадает выбранный `format`.
|
|
8240
|
+
|
|
8241
|
+
**Returns:** Чистый валидатор {@link Validator} для строкового поля
|
|
8242
|
+
|
|
6719
8243
|
**Examples:**
|
|
6720
8244
|
|
|
8245
|
+
Проверка номера телефона
|
|
6721
8246
|
```typescript
|
|
6722
|
-
import {
|
|
8247
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8248
|
+
import { required, phone } from '@reformer/core/validators';
|
|
6723
8249
|
|
|
6724
|
-
|
|
6725
|
-
const
|
|
8250
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8251
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8252
|
+
validate(model.$.phone, [required(), phone({ format: 'ru' })]);
|
|
8253
|
+
});
|
|
6726
8254
|
```
|
|
6727
8255
|
|
|
6728
|
-
_Source: src/form/
|
|
8256
|
+
_Source: src/form/validators/phone.ts_
|
|
6729
8257
|
|
|
6730
|
-
###
|
|
8258
|
+
### PhoneFormat
|
|
6731
8259
|
|
|
6732
8260
|
**Kind:** `type`
|
|
6733
8261
|
|
|
6734
|
-
|
|
6735
|
-
|
|
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` — свободный формат: цифры, скобки и разделители (по умолчанию).
|
|
6736
8268
|
|
|
6737
8269
|
**Signature:**
|
|
6738
8270
|
```typescript
|
|
6739
|
-
export type
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
8271
|
+
export type PhoneFormat = 'international' | 'ru' | 'us' | 'any';
|
|
8272
|
+
```
|
|
8273
|
+
|
|
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
|
+
}
|
|
6743
8289
|
```
|
|
6744
8290
|
|
|
6745
|
-
_Source: src/
|
|
8291
|
+
_Source: src/form/validators/phone.ts_
|
|
6746
8292
|
|
|
6747
8293
|
### registerSignalNode
|
|
6748
8294
|
|
|
@@ -6778,6 +8324,47 @@ if (sig && node) registerSignalNode(sig, node);
|
|
|
6778
8324
|
|
|
6779
8325
|
_Source: src/form/signal-node-registry.ts_
|
|
6780
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
|
+
|
|
6781
8368
|
### resetWhen
|
|
6782
8369
|
|
|
6783
8370
|
**Kind:** `function`
|
|
@@ -6799,7 +8386,7 @@ export function resetWhen<T>(
|
|
|
6799
8386
|
resetWhen(model.$.cardNumber, () => model.paymentType !== 'card', { resetValue: '' });
|
|
6800
8387
|
```
|
|
6801
8388
|
|
|
6802
|
-
_Source: src/
|
|
8389
|
+
_Source: src/model/behaviors-value.ts_
|
|
6803
8390
|
|
|
6804
8391
|
### revalidateWhen
|
|
6805
8392
|
|
|
@@ -6822,7 +8409,26 @@ export function revalidateWhen(
|
|
|
6822
8409
|
revalidateWhen([model.$.maxAmount], () => void validateModel(model, schema));
|
|
6823
8410
|
```
|
|
6824
8411
|
|
|
6825
|
-
_Source: src/
|
|
8412
|
+
_Source: src/model/behaviors-value.ts_
|
|
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_
|
|
6826
8432
|
|
|
6827
8433
|
### runOutsideEffect
|
|
6828
8434
|
|
|
@@ -6856,7 +8462,7 @@ effect(() => {
|
|
|
6856
8462
|
});
|
|
6857
8463
|
```
|
|
6858
8464
|
|
|
6859
|
-
_Source: src/
|
|
8465
|
+
_Source: src/model/safe-effect.ts_
|
|
6860
8466
|
|
|
6861
8467
|
### safeCallback
|
|
6862
8468
|
|
|
@@ -6895,7 +8501,7 @@ effect(() => {
|
|
|
6895
8501
|
});
|
|
6896
8502
|
```
|
|
6897
8503
|
|
|
6898
|
-
_Source: src/
|
|
8504
|
+
_Source: src/model/safe-effect.ts_
|
|
6899
8505
|
|
|
6900
8506
|
### safeDebouncedCallback
|
|
6901
8507
|
|
|
@@ -6933,7 +8539,7 @@ return effect(() => {
|
|
|
6933
8539
|
});
|
|
6934
8540
|
```
|
|
6935
8541
|
|
|
6936
|
-
_Source: src/
|
|
8542
|
+
_Source: src/model/safe-effect.ts_
|
|
6937
8543
|
|
|
6938
8544
|
### SchemaArrayControl
|
|
6939
8545
|
|
|
@@ -7076,7 +8682,7 @@ class FieldNode {
|
|
|
7076
8682
|
}
|
|
7077
8683
|
```
|
|
7078
8684
|
|
|
7079
|
-
_Source: src/
|
|
8685
|
+
_Source: src/form/nodes/subscription-manager.ts_
|
|
7080
8686
|
|
|
7081
8687
|
### syncFields
|
|
7082
8688
|
|
|
@@ -7100,7 +8706,28 @@ export function syncFields<T>(
|
|
|
7100
8706
|
syncFields(model.$.field1, model.$.field2);
|
|
7101
8707
|
```
|
|
7102
8708
|
|
|
7103
|
-
_Source: src/
|
|
8709
|
+
_Source: src/model/behaviors-value.ts_
|
|
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_
|
|
7104
8731
|
|
|
7105
8732
|
### transformValue
|
|
7106
8733
|
|
|
@@ -7123,7 +8750,7 @@ export function transformValue<T>(
|
|
|
7123
8750
|
transformValue(model.$.promoCode, (v) => (v ?? '').toUpperCase());
|
|
7124
8751
|
```
|
|
7125
8752
|
|
|
7126
|
-
_Source: src/
|
|
8753
|
+
_Source: src/model/behaviors-value.ts_
|
|
7127
8754
|
|
|
7128
8755
|
### UnknownRecord
|
|
7129
8756
|
|
|
@@ -7173,7 +8800,68 @@ onDispose(() => unmarkDerived(model.$.total)); // при снятии compute с
|
|
|
7173
8800
|
**See also:**
|
|
7174
8801
|
- {@link markDerived} - пометить сигнал производным
|
|
7175
8802
|
|
|
7176
|
-
_Source: src/
|
|
8803
|
+
_Source: src/model/derived-registry.ts_
|
|
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_
|
|
7177
8865
|
|
|
7178
8866
|
### useArrayLength
|
|
7179
8867
|
|
|
@@ -7211,7 +8899,7 @@ function ArrayRenderer({ arrayNode }) {
|
|
|
7211
8899
|
}
|
|
7212
8900
|
```
|
|
7213
8901
|
|
|
7214
|
-
_Source: src/
|
|
8902
|
+
_Source: src/platforms/react/hooks/useArrayLength.ts_
|
|
7215
8903
|
|
|
7216
8904
|
### useFormBundle
|
|
7217
8905
|
|
|
@@ -7236,7 +8924,7 @@ export function useFormBundle<B extends FormBundleLike>(factory: () => B): B
|
|
|
7236
8924
|
const credit = useFormBundle(() => createCoreForm<CreditForm>({ model: createCreditModel() }));
|
|
7237
8925
|
```
|
|
7238
8926
|
|
|
7239
|
-
_Source: src/
|
|
8927
|
+
_Source: src/platforms/react/hooks/use-form-bundle.ts_
|
|
7240
8928
|
|
|
7241
8929
|
### useFormControl
|
|
7242
8930
|
|
|
@@ -7470,7 +9158,7 @@ return <div>Items: {length}</div>;
|
|
|
7470
9158
|
- {@link FieldControlState} - тип состояния для FieldNode
|
|
7471
9159
|
- {@link ArrayControlState} - тип состояния для ArrayNode
|
|
7472
9160
|
|
|
7473
|
-
_Source: src/
|
|
9161
|
+
_Source: src/platforms/react/hooks/useFormControl.ts_
|
|
7474
9162
|
|
|
7475
9163
|
### useFormControlValue
|
|
7476
9164
|
|
|
@@ -7651,7 +9339,7 @@ return (
|
|
|
7651
9339
|
- {@link useFormControl} - для полного состояния поля
|
|
7652
9340
|
- {@link FieldNode} - тип контрола поля
|
|
7653
9341
|
|
|
7654
|
-
_Source: src/
|
|
9342
|
+
_Source: src/platforms/react/hooks/useFormControlValue.ts_
|
|
7655
9343
|
|
|
7656
9344
|
### useFormValidation
|
|
7657
9345
|
|
|
@@ -7686,7 +9374,7 @@ function MyForm() {
|
|
|
7686
9374
|
}
|
|
7687
9375
|
```
|
|
7688
9376
|
|
|
7689
|
-
_Source: src/
|
|
9377
|
+
_Source: src/platforms/react/hooks/use-form-validation.ts_
|
|
7690
9378
|
|
|
7691
9379
|
### UseFormValidationArgs
|
|
7692
9380
|
|
|
@@ -7708,7 +9396,7 @@ export interface UseFormValidationArgs<T> {
|
|
|
7708
9396
|
}
|
|
7709
9397
|
```
|
|
7710
9398
|
|
|
7711
|
-
_Source: src/
|
|
9399
|
+
_Source: src/platforms/react/hooks/use-form-validation.ts_
|
|
7712
9400
|
|
|
7713
9401
|
### UseFormValidationResult
|
|
7714
9402
|
|
|
@@ -7726,7 +9414,97 @@ export interface UseFormValidationResult {
|
|
|
7726
9414
|
}
|
|
7727
9415
|
```
|
|
7728
9416
|
|
|
7729
|
-
_Source: src/
|
|
9417
|
+
_Source: src/platforms/react/hooks/use-form-validation.ts_
|
|
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_
|
|
7730
9508
|
|
|
7731
9509
|
### ValidateOptions
|
|
7732
9510
|
|
|
@@ -7748,6 +9526,20 @@ export interface ValidateOptions {
|
|
|
7748
9526
|
|
|
7749
9527
|
_Source: src/form/types/validation-schema.ts_
|
|
7750
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
|
+
|
|
7751
9543
|
### ValidationError
|
|
7752
9544
|
|
|
7753
9545
|
**Kind:** `interface`
|
|
@@ -7767,6 +9559,54 @@ export interface ValidationError {
|
|
|
7767
9559
|
|
|
7768
9560
|
_Source: src/form/types/contracts.ts_
|
|
7769
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
|
+
|
|
7770
9610
|
### Validator
|
|
7771
9611
|
|
|
7772
9612
|
**Kind:** `type`
|
|
@@ -7829,4 +9669,4 @@ watchField(model.$.country, async (country) => {
|
|
|
7829
9669
|
});
|
|
7830
9670
|
```
|
|
7831
9671
|
|
|
7832
|
-
_Source: src/
|
|
9672
|
+
_Source: src/model/behaviors-value.ts_
|