@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.
Files changed (130) hide show
  1. package/dist/behaviors.d.ts +1 -1
  2. package/dist/behaviors.js +37 -37
  3. package/dist/create-model-Dxh9cfLp.js +97 -0
  4. package/dist/form/behaviors/collections.d.ts +50 -0
  5. package/dist/form/behaviors/context.d.ts +23 -0
  6. package/dist/form/behaviors/index.d.ts +28 -0
  7. package/dist/form/behaviors/internals.d.ts +26 -0
  8. package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +3 -3
  9. package/dist/form/behaviors/operators.d.ts +51 -0
  10. package/dist/form/behaviors/types.d.ts +17 -0
  11. package/dist/form/create-core-form.d.ts +3 -3
  12. package/dist/form/create-form.d.ts +12 -10
  13. package/dist/form/factories/node-factory.d.ts +17 -16
  14. package/dist/form/index.d.ts +10 -16
  15. package/dist/form/nodes/array-node.d.ts +3 -3
  16. package/dist/form/nodes/field-node.d.ts +6 -3
  17. package/dist/form/nodes/group-node.d.ts +7 -7
  18. package/dist/form/nodes/model-array-node.d.ts +3 -3
  19. package/dist/form/types/contracts.d.ts +9 -6
  20. package/dist/form/types/deep-schema.d.ts +5 -5
  21. package/dist/form/types/validation-schema.d.ts +1 -1
  22. package/dist/form/{validation-config.d.ts → validation/config.d.ts} +3 -3
  23. package/dist/form/validation/context.d.ts +27 -0
  24. package/dist/form/{error-handler.d.ts → validation/error-handler.d.ts} +1 -1
  25. package/dist/form/validation/index.d.ts +23 -28
  26. package/dist/form/validation/operators.d.ts +42 -0
  27. package/dist/form/validation/run.d.ts +45 -0
  28. package/dist/form/{validation-strategy.d.ts → validation/strategy.d.ts} +2 -2
  29. package/dist/form/validation/types.d.ts +28 -0
  30. package/dist/form/{validation/validators → validators}/date-utils.d.ts +1 -1
  31. package/dist/form/{validation/validators → validators}/email.d.ts +6 -7
  32. package/dist/form/{validation/validators → validators}/file-type.d.ts +6 -7
  33. package/dist/form/{validation/validators → validators}/file-utils.d.ts +1 -1
  34. package/dist/form/{validation/validators → validators}/future-date.d.ts +6 -7
  35. package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
  36. package/dist/form/{validation/validators → validators}/integer.d.ts +6 -7
  37. package/dist/form/{validation/validators → validators}/is-date.d.ts +6 -7
  38. package/dist/form/{validation/validators → validators}/is-number.d.ts +6 -7
  39. package/dist/form/{validation/validators → validators}/max-age.d.ts +6 -7
  40. package/dist/form/{validation/validators → validators}/max-date.d.ts +6 -7
  41. package/dist/form/{validation/validators → validators}/max-file-size.d.ts +6 -7
  42. package/dist/form/{validation/validators → validators}/max-files.d.ts +6 -7
  43. package/dist/form/{validation/validators → validators}/max-length.d.ts +7 -8
  44. package/dist/form/{validation/validators → validators}/max-total-file-size.d.ts +6 -7
  45. package/dist/form/{validation/validators → validators}/max.d.ts +7 -8
  46. package/dist/form/{validation/validators → validators}/min-age.d.ts +6 -7
  47. package/dist/form/{validation/validators → validators}/min-date.d.ts +6 -7
  48. package/dist/form/{validation/validators → validators}/min-file-size.d.ts +6 -7
  49. package/dist/form/{validation/validators → validators}/min-files.d.ts +6 -7
  50. package/dist/form/{validation/validators → validators}/min-length.d.ts +7 -8
  51. package/dist/form/{validation/validators → validators}/min.d.ts +7 -8
  52. package/dist/form/{validation/validators → validators}/multiple-of.d.ts +6 -7
  53. package/dist/form/{validation/validators → validators}/non-negative.d.ts +6 -7
  54. package/dist/form/{validation/validators → validators}/non-zero.d.ts +6 -7
  55. package/dist/form/{validation/validators → validators}/past-date.d.ts +6 -7
  56. package/dist/form/{validation/validators → validators}/pattern.d.ts +7 -15
  57. package/dist/form/{validation/validators → validators}/phone.d.ts +6 -7
  58. package/dist/form/{validation/validators → validators}/required.d.ts +8 -13
  59. package/dist/form/{validation/validators → validators}/url.d.ts +8 -13
  60. package/dist/index.d.ts +7 -4
  61. package/dist/index.js +272 -94
  62. package/dist/{state/form-model.d.ts → model/create-model.d.ts} +0 -14
  63. package/dist/{state → model}/index.d.ts +10 -11
  64. package/dist/model/model-nodes.d.ts +60 -0
  65. package/dist/model/model-signals-proxy.d.ts +32 -0
  66. package/dist/model/model-value-proxy.d.ts +3 -0
  67. package/dist/{state → model}/safe-effect.d.ts +1 -1
  68. package/dist/model-signals-proxy-mV3WGdlO.js +233 -0
  69. package/dist/model.d.ts +2 -0
  70. package/dist/model.js +21 -0
  71. package/dist/{form → platforms/react}/hooks/types.d.ts +1 -1
  72. package/dist/{form → platforms/react}/hooks/use-form-bundle.d.ts +1 -1
  73. package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +3 -3
  74. package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +1 -1
  75. package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +3 -3
  76. package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +2 -2
  77. package/dist/platforms/react/index.d.ts +23 -0
  78. package/dist/signals.d.ts +1 -1
  79. package/dist/strategy-DA3JwxsR.js +170 -0
  80. package/dist/validation.d.ts +1 -1
  81. package/dist/validation.js +10 -165
  82. package/dist/validators/email.d.ts +1 -1
  83. package/dist/validators/future-date.d.ts +1 -1
  84. package/dist/validators/integer.d.ts +1 -1
  85. package/dist/validators/is-date.d.ts +1 -1
  86. package/dist/validators/is-number.d.ts +1 -1
  87. package/dist/validators/max-age.d.ts +1 -1
  88. package/dist/validators/max-date.d.ts +1 -1
  89. package/dist/validators/max-length.d.ts +1 -1
  90. package/dist/validators/max.d.ts +1 -1
  91. package/dist/validators/min-age.d.ts +1 -1
  92. package/dist/validators/min-date.d.ts +1 -1
  93. package/dist/validators/min-length.d.ts +1 -1
  94. package/dist/validators/min.d.ts +1 -1
  95. package/dist/validators/multiple-of.d.ts +1 -1
  96. package/dist/validators/non-negative.d.ts +1 -1
  97. package/dist/validators/non-zero.d.ts +1 -1
  98. package/dist/validators/past-date.d.ts +1 -1
  99. package/dist/validators/pattern.d.ts +1 -1
  100. package/dist/validators/phone.d.ts +1 -1
  101. package/dist/validators/required.d.ts +1 -1
  102. package/dist/validators/url.d.ts +1 -1
  103. package/dist/validators.d.ts +1 -1
  104. package/docs/llms/02-quick-start.md +6 -6
  105. package/docs/llms/03-api-signatures.md +3 -3
  106. package/docs/llms/05-common-mistakes.md +1 -1
  107. package/docs/llms/09-formschema.md +11 -11
  108. package/docs/llms/10-arrays.md +4 -4
  109. package/docs/llms/14-extended-mistakes.md +1 -1
  110. package/docs/llms/15-project-structure.md +8 -7
  111. package/docs/llms/16-ui-components.md +6 -6
  112. package/docs/llms/17-nonexistent-api.md +2 -2
  113. package/docs/llms/29-async-preload.md +4 -4
  114. package/docs/llms/30-type-safety-recipes.md +1 -1
  115. package/llms-index.json +1 -0
  116. package/llms.txt +2102 -262
  117. package/package.json +5 -4
  118. package/dist/form/behaviors.d.ts +0 -133
  119. package/dist/form/validation-schema.d.ts +0 -106
  120. package/dist/form/validators.d.ts +0 -11
  121. package/dist/form-model-DhW2HZOB.js +0 -323
  122. package/dist/state.d.ts +0 -2
  123. package/dist/state.js +0 -21
  124. package/dist/subscription-manager-705bls97.js +0 -171
  125. /package/dist/{state → form/nodes}/subscription-manager.d.ts +0 -0
  126. /package/dist/{state → model}/behaviors-value.d.ts +0 -0
  127. /package/dist/{state → model}/derived-registry.d.ts +0 -0
  128. /package/dist/{state → model}/types.d.ts +0 -0
  129. /package/dist/{behaviors-node-DGEFMTQu.js → node-DGEFMTQu.js} +0 -0
  130. /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, Input, Button } from '@reformer/ui-kit';
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: Input,
147
+ component: InputField,
144
148
  componentProps: { label: 'Name', placeholder: 'Your name' },
145
149
  },
146
150
  email: {
147
151
  value: model.$.email,
148
- component: Input,
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: Select,
226
+ component: SelectField,
223
227
  componentProps: { label: 'Тип', options: [/* ... */] },
224
228
  },
225
- description: { value: item.$.description, component: Textarea, componentProps: { label: 'Описание' } },
229
+ description: { value: item.$.description, component: TextareaField, componentProps: { label: 'Описание' } },
226
230
  estimatedValue: {
227
231
  value: item.$.estimatedValue,
228
- component: Input,
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: Input },
333
- age: { value: model.$.age, component: Input },
334
- amount: { value: model.$.amount, component: Input },
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: Input, validators: [required(), email()] },
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: Input, // React-компонент
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 { Input, Select, Checkbox } from '@reformer/ui-kit';
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: Input,
1123
+ component: InputField,
1106
1124
  componentProps: { label: 'Name', placeholder: 'Enter name' },
1107
1125
  },
1108
1126
  age: {
1109
1127
  value: model.$.age,
1110
- component: Input,
1128
+ component: InputField,
1111
1129
  componentProps: { type: 'number', label: 'Age' },
1112
1130
  },
1113
1131
  agree: {
1114
1132
  value: model.$.agree,
1115
- component: Checkbox,
1133
+ component: CheckboxField,
1116
1134
  componentProps: { label: 'I agree to terms' },
1117
1135
  },
1118
1136
  status: {
1119
1137
  value: model.$.status,
1120
- component: Select,
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: Input, componentProps: { label: 'Street' } },
1145
- city: { value: m.$.city, component: Input, componentProps: { label: 'City' } },
1146
- zip: { value: m.$.zip, component: Input, componentProps: { label: 'ZIP' } },
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: Input, componentProps: { label: 'ID' } },
1164
- name: { value: item.$.name, component: Input, componentProps: { label: 'Name' } },
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 { Input } from '@reformer/ui-kit';
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: Input },
1226
- name: { value: item.$.name, component: Input },
1227
- price: { value: item.$.price, component: Input, componentProps: { type: 'number' } },
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: Input, validators: [legacyRule] } };
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: Select, componentProps: { /* ... */ } },
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 # schema builder (model) => tree ({ value: model.$.x, component })
1772
+ │ ├── form.schema.ts # schema builder (model) => tree ({ value: model.$.x, component })
1743
1773
  │ ├── validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
1744
- │ ├── behavior.ts # defineFormBehavior(...)
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.json` + `renderer.behavior.ts` + `registry.ts`), the
1773
- > centralized-vs-co-located choice, and the full reuse map live in the
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: Input` (или `Select`, `Checkbox`, etc.)
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, Input, Select, Checkbox, Button } from '@reformer/ui-kit';
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: Input,
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: Select,
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: Checkbox,
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: Input, componentProps: { label: 'Email' } } }
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: Input, componentProps: { label: 'Name' } },
2033
- email: { value: model.$.email, component: Input, componentProps: { label: 'Email' } },
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 { Input, Select } from '@reformer/ui-kit';
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: Input, componentProps: { label: 'Username' } },
3665
+ username: { value: model.$.username, component: InputField, componentProps: { label: 'Username' } },
3607
3666
  language: {
3608
3667
  value: model.$.language,
3609
- component: Select,
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: Input, componentProps: { type: 'checkbox' } },
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: Input },
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/form/hooks/types.ts_
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: Input },
4561
- price: { value: 0, component: Input },
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/state/behaviors-value.ts_
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-config.ts_
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/state/behaviors-value.ts_
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/state/behaviors-value.ts_
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: Input },
4920
- { value: model.$.password, component: Input },
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/validators) из схемы, материализует
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
- { value: model.$.email, component: Input, validators: [required] },
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: Input },
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/validators) — из единой схемы.
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/state/form-model.ts_
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-node.ts_
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-node.ts_
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/form/hooks/types.ts_
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: Input,
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
- ### FormArrayProxy
5532
-
5533
- **Kind:** `type`
5950
+ ### FileLike
5534
5951
 
5535
- Комбинированный тип для ArrayNode с Proxy доступом к элементам
5952
+ **Kind:** `interface`
5536
5953
 
5537
- Объединяет методы и свойства ArrayNode с типизированным доступом к элементам массива.
5954
+ Минимальный файлоподобный контракт, с которым работают file-валидаторы:
5955
+ нативный `File`, сериализуемый дескриптор загруженного файла и т.п.
5538
5956
 
5539
5957
  **Signature:**
5540
5958
  ```typescript
5541
- export type FormArrayProxy<T extends object> = ArrayNode<T> & {
5542
- /**
5543
- * Безопасный доступ к элементу массива по индексу
5544
- * Возвращает GroupNode с типизированными полями или undefined
5545
- */
5546
- at(index: number): FormProxy<T> | undefined;
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/form/hooks/use-form-bundle.ts_
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/state/types.ts_
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: Input },
6377
+ name: { value: '', component: InputField },
5880
6378
  address: {
5881
- city: { value: '', component: Input },
5882
- street: { value: '', component: Input },
6379
+ city: { value: '', component: InputField },
6380
+ street: { value: '', component: InputField },
5883
6381
  },
5884
6382
  items: [{
5885
- title: { value: '', component: Input },
5886
- price: { value: 0, component: Input },
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-config.ts_
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-config.ts_
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: Input },
6163
- password: { valueSignal: model.$.password, component: Input },
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/state/derived-registry.ts_
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/state/form-model.ts_
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/state/derived-registry.ts_
7112
+ _Source: src/model/derived-registry.ts_
6406
7113
 
6407
- ### ModelApi
7114
+ ### matchesFileAccept
6408
7115
 
6409
- **Kind:** `interface`
7116
+ **Kind:** `function`
6410
7117
 
6411
- API уровня модели (доступно на корне, под-моделях вложенных объектов-групп и элементов массива).
7118
+ Проверяет файл против accept-паттерна в синтаксисе нативного атрибута
7119
+ `<input type="file" accept>`: список через запятую из расширений (`.pdf`),
7120
+ точных MIME (`image/png`) и wildcard-категорий (`image/*`). Регистронезависимо.
6412
7121
 
6413
- ⚠️ Имена методов (`$`/`get`/`set`/`patch`/`isDirty`/`reset`/`signalAt`/`captureInitial`)
6414
- зарезервированы: одноимённое поле формы их затеняет (редкий краевой случай).
7122
+ Нужен собственный матчер, потому что `accept` у нативного input — только подсказка
7123
+ пикеру: на drag-and-drop и на программное значение он не действует.
6415
7124
 
6416
7125
  **Signature:**
6417
7126
  ```typescript
6418
- export interface ModelApi<T> {
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
- _Source: src/state/types.ts_
7130
+ **Parameters:**
7131
+ - `file` — - Файлоподобное значение (`name` + опциональный `type`)
7132
+ - `accept` — - Accept-строка; пустая/пробельная строка матчит всё
6449
7133
 
6450
- ### ModelArray
7134
+ **Returns:** `true`, если файл подходит хотя бы под один паттерн
6451
7135
 
6452
- **Kind:** `interface`
7136
+ _Source: src/form/validators/file-utils.ts_
6453
7137
 
6454
- Реактивный массив модели. Мутации (`push`/`removeAt`/…) меняют длину реактивно;
6455
- `map`/`forEach`/`at` отдают под-модель элемента ({@link FormModel}) для объектных элементов.
7138
+ ### max
7139
+
7140
+ **Kind:** `function`
7141
+
7142
+ Фабрика валидатора максимального числового значения.
7143
+
7144
+ Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
6456
7145
 
6457
7146
  **Signature:**
6458
7147
  ```typescript
6459
- export interface ModelArray<U> {
6460
- /**
6461
- * Путь массива в модели (dot-нотация). Предоставляется рантаймом (value-прокси) и требуется
6462
- * рендер-слою для резолва узла массива (напр. `ArrayRenderNode` в `@reformer/renderer-react`).
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
- _Source: src/state/types.ts_
7154
+ **Parameters:**
7155
+ - `maxValue` — - Максимально допустимое значение (включительно)
7156
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7157
+ попадают `max` и `actual`.
6495
7158
 
6496
- ### ModelArrayControl
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: Input },
6549
- qty: { value: item.$.qty, component: Input },
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/state/types.ts_
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/state/types.ts_
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/state/types.ts_
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/state/types.ts_
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
- export type ModelValue<V> =
6649
- NonNullable<V> extends ReadonlyArray<infer U>
6650
- ? ModelArray<U>
6651
- : NonNullable<V> extends Opaque
6652
- ? V
6653
- : NonNullable<V> extends object
6654
- ? FormModel<NonNullable<V>>
6655
- : V;
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/state/types.ts_
8219
+ _Source: src/form/validators/pattern.ts_
6659
8220
 
6660
- ### NodeFactory
8221
+ ### phone
6661
8222
 
6662
- **Kind:** `class`
8223
+ **Kind:** `function`
6663
8224
 
6664
- Фабрика для создания узлов формы.
8225
+ Фабрика валидатора номера телефона.
6665
8226
 
6666
- Определяет тип конфига и создаёт соответствующий узел (FieldNode, GroupNode, ArrayNode).
6667
- Используется внутри `getReformerForm`/`group`/`array` — явно вызывать обычно не нужно.
8227
+ Проверяет значение по regex выбранного {@link PhoneFormat}. Пустые значения
8228
+ (`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
6668
8229
 
6669
8230
  **Signature:**
6670
8231
  ```typescript
6671
- export class NodeFactory {
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 { NodeFactory } from '@reformer/core';
8247
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
8248
+ import { required, phone } from '@reformer/core/validators';
6723
8249
 
6724
- const factory = new NodeFactory();
6725
- const node = factory.createNode({ value: '', component: Input }); // → FieldNode<string>
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/factories/node-factory.ts_
8256
+ _Source: src/form/validators/phone.ts_
6729
8257
 
6730
- ### PathAwareSignal
8258
+ ### PhoneFormat
6731
8259
 
6732
8260
  **Kind:** `type`
6733
8261
 
6734
- Сигнал, который знает свой путь в модели (`'personalData.lastName'`).
6735
- Используется как «ручка поля»: и привязка value в схеме, и идентичность для testId/devtools.
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 PathAwareSignal<T> = Signal<T> & {
6740
- /** Путь поля в модели (dot-нотация). На элементах массива включает индекс. */
6741
- readonly __path: string;
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/state/types.ts_
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/state/behaviors-value.ts_
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/state/behaviors-value.ts_
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/state/safe-effect.ts_
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/state/safe-effect.ts_
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/state/safe-effect.ts_
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/state/subscription-manager.ts_
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/state/behaviors-value.ts_
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/state/behaviors-value.ts_
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/state/derived-registry.ts_
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/form/hooks/useArrayLength.ts_
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/form/hooks/use-form-bundle.ts_
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/form/hooks/useFormControl.ts_
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/form/hooks/useFormControlValue.ts_
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/form/hooks/use-form-validation.ts_
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/form/hooks/use-form-validation.ts_
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/form/hooks/use-form-validation.ts_
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/state/behaviors-value.ts_
9672
+ _Source: src/model/behaviors-value.ts_