@reformer/core 11.0.0-beta.4 → 11.0.0-beta.6

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 (47) hide show
  1. package/dist/form/create-form.d.ts +6 -4
  2. package/dist/form/factories/node-factory.d.ts +1 -0
  3. package/dist/form/nodes/field-node.d.ts +3 -0
  4. package/dist/form/validators/email.d.ts +5 -6
  5. package/dist/form/validators/file-type.d.ts +5 -6
  6. package/dist/form/validators/future-date.d.ts +5 -6
  7. package/dist/form/validators/index.d.ts +3 -2
  8. package/dist/form/validators/integer.d.ts +5 -6
  9. package/dist/form/validators/is-date.d.ts +5 -6
  10. package/dist/form/validators/is-number.d.ts +5 -6
  11. package/dist/form/validators/max-age.d.ts +5 -6
  12. package/dist/form/validators/max-date.d.ts +5 -6
  13. package/dist/form/validators/max-file-size.d.ts +5 -6
  14. package/dist/form/validators/max-files.d.ts +5 -6
  15. package/dist/form/validators/max-length.d.ts +6 -7
  16. package/dist/form/validators/max-total-file-size.d.ts +5 -6
  17. package/dist/form/validators/max.d.ts +6 -7
  18. package/dist/form/validators/min-age.d.ts +5 -6
  19. package/dist/form/validators/min-date.d.ts +5 -6
  20. package/dist/form/validators/min-file-size.d.ts +5 -6
  21. package/dist/form/validators/min-files.d.ts +5 -6
  22. package/dist/form/validators/min-length.d.ts +6 -7
  23. package/dist/form/validators/min.d.ts +6 -7
  24. package/dist/form/validators/multiple-of.d.ts +5 -6
  25. package/dist/form/validators/non-negative.d.ts +5 -6
  26. package/dist/form/validators/non-zero.d.ts +5 -6
  27. package/dist/form/validators/past-date.d.ts +5 -6
  28. package/dist/form/validators/pattern.d.ts +6 -14
  29. package/dist/form/validators/phone.d.ts +5 -6
  30. package/dist/form/validators/required.d.ts +7 -12
  31. package/dist/form/validators/url.d.ts +7 -12
  32. package/dist/index.js +423 -376
  33. package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
  34. package/dist/platforms/react/field-binding/field-handle.d.ts +50 -0
  35. package/dist/platforms/react/index.d.ts +4 -0
  36. package/docs/llms/02-quick-start.md +3 -3
  37. package/docs/llms/03-api-signatures.md +4 -4
  38. package/docs/llms/09-formschema.md +5 -5
  39. package/docs/llms/10-arrays.md +2 -2
  40. package/docs/llms/15-project-structure.md +19 -16
  41. package/docs/llms/16-ui-components.md +34 -7
  42. package/docs/llms/17-nonexistent-api.md +14 -0
  43. package/docs/llms/29-async-preload.md +3 -3
  44. package/docs/llms/30-type-safety-recipes.md +1 -1
  45. package/llms-index.json +1 -0
  46. package/llms.txt +2146 -68
  47. package/package.json +2 -1
package/llms.txt CHANGED
@@ -42,6 +42,8 @@
42
42
 
43
43
  ## 1. 1. API Reference
44
44
 
45
+ **api-reference**
46
+
45
47
  ### Imports (CRITICALLY IMPORTANT)
46
48
 
47
49
  Архитектура M1: значения живут в **модели** (`createModel`), а форма (`createForm`) строит ноды поверх сигналов модели. Behaviors работают на сигналах (`model.$.field`), а не на строковых путях.
@@ -106,6 +108,8 @@ const { value, errors, disabled } = useFormControl(control.loanType);
106
108
 
107
109
  ## 2. 1.5 QUICK START - Minimal Working Form
108
110
 
111
+ **quick-start**
112
+
109
113
  > **Schema-driven UI rule (read first)**: компонент И его пропсы (label, placeholder,
110
114
  > options, type) объявляются в **схеме поля** (`component` + `componentProps`).
111
115
  > В JSX рендерится один универсальный `<FormField control={form.x} />` из
@@ -219,14 +223,14 @@ 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: SelectAsync,
223
227
  componentProps: { label: 'Тип', options: [/* ... */] },
224
228
  },
225
229
  description: { value: item.$.description, component: Textarea, componentProps: { label: 'Описание' } },
226
230
  estimatedValue: {
227
231
  value: item.$.estimatedValue,
228
- component: Input,
229
- componentProps: { label: 'Стоимость', type: 'number' },
232
+ component: InputNumber,
233
+ componentProps: { label: 'Стоимость' },
230
234
  },
231
235
  });
232
236
 
@@ -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
@@ -322,16 +328,16 @@ import { createModel, createForm } from '@reformer/core';
322
328
  import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
323
329
  import { required, min, max, email } from '@reformer/core/validators';
324
330
 
325
- type Loan = { email: string; age: number; amount: number };
326
- const model = createModel<Loan>({ email: '', age: 0, amount: 0 });
331
+ type Loan = { email: string; age: number | null; amount: number | null };
332
+ const model = createModel<Loan>({ email: '', age: null, amount: null });
327
333
 
328
334
  // layout-схема createForm НЕ несёт validators — только привязка поля к сигналу + компонент
329
335
  const form = createForm({
330
336
  model,
331
337
  schema: {
332
338
  email: { value: model.$.email, component: Input },
333
- age: { value: model.$.age, component: Input },
334
- amount: { value: model.$.amount, component: Input },
339
+ age: { value: model.$.age, component: InputNumber },
340
+ amount: { value: model.$.amount, component: InputNumber },
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`.
@@ -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` из
@@ -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 { Input, InputNumber, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
1094
1112
 
1095
1113
  const model = createModel<MyForm>({ name: '', age: null, agree: false, status: 'active' });
1096
1114
 
@@ -1107,17 +1125,17 @@ const schema = {
1107
1125
  },
1108
1126
  age: {
1109
1127
  value: model.$.age,
1110
- component: Input,
1111
- componentProps: { type: 'number', label: 'Age' },
1128
+ component: InputNumber,
1129
+ componentProps: { label: 'Age' },
1112
1130
  },
1113
1131
  agree: {
1114
1132
  value: model.$.agree,
1115
- component: Checkbox,
1133
+ component: CheckboxWithLabel,
1116
1134
  componentProps: { label: 'I agree to terms' },
1117
1135
  },
1118
1136
  status: {
1119
1137
  value: model.$.status,
1120
- component: Select,
1138
+ component: SelectAsync,
1121
1139
  componentProps: {
1122
1140
  label: 'Status',
1123
1141
  options: [
@@ -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 { Input, InputNumber } from '@reformer/ui-kit';
1217
1237
 
1218
1238
  type Item = { id: string; name: string; price: number };
1219
1239
  type MyForm = { items: Item[] };
@@ -1224,7 +1244,7 @@ const model = createModel<MyForm>({ items: [] });
1224
1244
  const itemSchema = (item: FormModel<Item>) => ({
1225
1245
  id: { value: item.$.id, component: Input },
1226
1246
  name: { value: item.$.name, component: Input },
1227
- price: { value: item.$.price, component: Input, componentProps: { type: 'number' } },
1247
+ price: { value: item.$.price, component: InputNumber },
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
@@ -1645,13 +1673,17 @@ 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
- `index.tsx`. Filenames are flat with no prefix — **the dot-prefix (`form.` / `renderer.`) is
1651
- carried only by `schema` and `behavior`**, the two concerns that have both a model-layer and a
1652
- render-layer version (`form.schema.ts` / `form.behavior.ts` here; renderer slices add
1653
- `renderer.schema.*` / `renderer.behavior.ts`). No `lib/` / `schema/` / `components/steps/`
1654
- nesting until the form grows (see "Scaling up" below). Arrays are declared in `form.schema.ts`
1680
+ `index.tsx`. Naming rule: **`form.<role>` is a form artefact, the suffix names its role** —
1681
+ `form.schema.*` (layout), `form.behavior.ts` (model behavior), `form.render.ts` (render-layer
1682
+ behavior, renderer targets only), `form.validation.ts` (validation rules); every other file has
1683
+ no prefix. A wizard may instead keep each
1684
+ step's code in `steps/<slug>/` (same file names inside) with a `steps/index.ts` aggregator. The old
1685
+ `renderer.schema.*` / `renderer.behavior.ts` / `renderer.wizard.tsx` / `validation.ts` names still
1686
+ work with a warning. No `lib/` / `schema/` / `components/` nesting until the form grows (see "Scaling up" below). Arrays are declared in `form.schema.ts`
1655
1687
  and rendered with `FormArraySection` — no per-step component files.
1656
1688
 
1657
1689
  ```
@@ -1665,13 +1697,13 @@ src/
1665
1697
  │ ├── model.ts # createModel + initial values + empty-array-element factories
1666
1698
  │ ├── form.schema.ts # FormSchema: { value: model.$.x, component, componentProps }
1667
1699
  │ ├── form.behavior.ts # defineFormBehavior: compute / enableWhen / hideWhen / copyFrom / onChange
1668
- │ ├── validation.ts # ALL validation → { validateStep, validateAll }
1700
+ │ ├── form.validation.ts # ALL validation → { validateStep, validateAll }
1669
1701
  │ ├── data-sources.ts # options + async loaders (dataSources)
1670
1702
  │ └── api.ts # submit + prefill/load
1671
1703
  ```
1672
1704
 
1673
1705
  Rule of thumb: **one concern → one file at the module root; the whole component tree (all steps) →
1674
- `index.tsx`; validation is a single `validation.ts`.** This is the working default for
1706
+ `index.tsx`; validation is a single `form.validation.ts`.** This is the working default for
1675
1707
  almost every form — reach for folders only when a file stops fitting on a screen.
1676
1708
 
1677
1709
  ### Key Files
@@ -1692,7 +1724,7 @@ export const createCreditApplicationModel = (): FormModel<CreditApplicationForm>
1692
1724
  // forms/credit-application/form.schema.ts
1693
1725
  import type { FormModel } from '@reformer/core';
1694
1726
  export const creditApplicationSchema = (model: FormModel<CreditApplicationForm>) => ({
1695
- loanType: { value: model.$.loanType, component: Select, componentProps: { /* ... */ } },
1727
+ loanType: { value: model.$.loanType, component: SelectAsync, componentProps: { /* ... */ } },
1696
1728
  personalData: personalDataNodes(model.$.personalData),
1697
1729
  properties: { array: model.properties, item: propertyItem },
1698
1730
  });
@@ -1739,9 +1771,9 @@ forms/
1739
1771
  │
1740
1772
  ├── schema/ # The form definition
1741
1773
  │ ├── model.ts # createModel factory + initial values + array-element factories
1742
- │ ├── schema.ts # schema builder (model) => tree ({ value: model.$.x, component })
1743
- │ ├── validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
1744
- │ ├── behavior.ts # defineFormBehavior(...)
1774
+ │ ├── form.schema.ts # schema builder (model) => tree ({ value: model.$.x, component })
1775
+ │ ├── form.validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
1776
+ │ ├── form.behavior.ts # defineFormBehavior(...)
1745
1777
  │ └── create-form.ts # Assembly: createCoreForm({ model, schema, behavior, validation })
1746
1778
  │
1747
1779
  └── components/
@@ -1752,7 +1784,7 @@ forms/
1752
1784
 
1753
1785
  Rule of thumb for this layout: **domain raw material → `lib/`; anything describing the form →
1754
1786
  `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
1787
+ may co-locate each step's `form.schema.ts` / `form.validation.ts` / `form.behavior.ts` inside its
1756
1788
  `steps/[Step]/` folder, keeping only the shared `model` and cross-step rules in `schema/` — see
1757
1789
  the guide below.
1758
1790
 
@@ -1768,21 +1800,25 @@ the guide below.
1768
1800
  > when registering the MCP server to choose which structure the generators lead with. Default
1769
1801
  > is `minimalist`.
1770
1802
 
1771
- > 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`).
1803
+ > Cross-target variants (renderer-react `form.schema.ts` + `form.render.ts` /
1804
+ > renderer-json `form.schema.ts` + `form.render.ts` + `registry.ts`, plus an optional
1805
+ > `wizard.tsx` shim), the `.tsx` / `.json` schema variants, the centralized-vs-co-located
1806
+ > choice, and the full reuse map live in the **form-directory-layout** guide (`@reformer/mcp`) —
1807
+ > `find_recipe directory-layout`.
1775
1808
 
1776
1809
  ## 17. 14.5 UI COMPONENT PATTERNS
1777
1810
 
1811
+ **ui-components**
1812
+
1778
1813
  > **Default rule (read first)**: для UI используй `FormField` из
1779
1814
  > [`@reformer/ui-kit`](../../reformer-ui-kit/) — он покрывает 95% случаев одной
1780
- > строкой `<FormField control={form.x} />`. Свои field-обёртки пиши ТОЛЬКО если
1815
+ > строкой `<FormField control={form.x} />`. Свои обёртки поля пиши ТОЛЬКО если
1781
1816
  > ui-kit не подходит (другая design system, особый low-level input).
1782
1817
  >
1783
1818
  > Канонический schema-driven подход:
1784
1819
  >
1785
- > - **компонент** объявляется в схеме как `component: Input` (или `Select`, `Checkbox`, etc.)
1820
+ > - **компонент** объявляется в схеме как `component: Input` (или `SelectAsync`, `CheckboxWithLabel`, …) —
1821
+ > сам компонент кита, отдельных «field-версий» нет: связывает его с полем обёртка (`FormField`)
1786
1822
  > - **пропсы** компонента — в `componentProps: { label, placeholder, options, type, ... }`
1787
1823
  > - **JSX рендерит**: `<FormField control={form.x} />` БЕЗ дополнительных props
1788
1824
  >
@@ -1794,7 +1830,7 @@ the guide below.
1794
1830
  ```tsx
1795
1831
  import { useMemo } from 'react';
1796
1832
  import { createModel, createForm } from '@reformer/core';
1797
- import { FormField, Input, Select, Checkbox, Button } from '@reformer/ui-kit';
1833
+ import { FormField, Input, SelectAsync, CheckboxWithLabel, Button } from '@reformer/ui-kit';
1798
1834
 
1799
1835
  type RegistrationForm = {
1800
1836
  email: string;
@@ -1813,7 +1849,7 @@ function RegistrationPage() {
1813
1849
  },
1814
1850
  country: {
1815
1851
  value: model.$.country,
1816
- component: Select,
1852
+ component: SelectAsync,
1817
1853
  componentProps: {
1818
1854
  label: 'Country',
1819
1855
  options: [
@@ -1824,7 +1860,7 @@ function RegistrationPage() {
1824
1860
  },
1825
1861
  agree: {
1826
1862
  value: model.$.agree,
1827
- component: Checkbox,
1863
+ component: CheckboxWithLabel,
1828
1864
  componentProps: { label: 'I agree to terms' },
1829
1865
  },
1830
1866
  };
@@ -1888,7 +1924,8 @@ import { InputMask } from 'react-input-mask';
1888
1924
  ```
1889
1925
 
1890
1926
  `children` оборачивается в `CdkFormField.Control asChild` и получает все нужные
1891
- props (`value`, `onChange`, `onBlur`, `aria-invalid`).
1927
+ props (`value`, `onChange`, `onBlur`, `aria-invalid`) — в диалекте ребёнка, если тот объявил
1928
+ статику `reformerAdapter` (`defineFieldControl` из `@reformer/ui-kit/fields`), иначе value-based.
1892
1929
 
1893
1930
  ### Advanced — write your own from scratch (rare)
1894
1931
 
@@ -1930,12 +1967,37 @@ function MyFormField<T>({ control }: MyFormFieldProps<T>) {
1930
1967
  <MyFormField control={form.email} /> // ← без label-prop
1931
1968
  ```
1932
1969
 
1970
+ ### Связывание поля с контролом — `FieldAdapter` (`@reformer/core`)
1971
+
1972
+ Контролы говорят на разных диалектах (`checked` + `onCheckedChange`, `onValueChange`, DOM-событие
1973
+ в `onChange`), форма — на value-based seam (`value` + `onChange(value)` + `onBlur`). Перевод
1974
+ описывает `FieldAdapter` (`valueProp`, `changeProp`, `fromEmit`, `toValue`, `bindBlur`, `strip`,
1975
+ `passControl` — все необязательны). Компонент объявляет его статикой `reformerAdapter`, а обёртка
1976
+ поля (`FormField.Control` из `@reformer/cdk`, рендерер `@reformer/renderer-react`) читает статику
1977
+ и связывает поле сама — отдельные «field-версии» компонентов не нужны.
1978
+
1979
+ | Символ | Назначение |
1980
+ | ----------------------------- | --------------------------------------------------------------------------- |
1981
+ | `FieldAdapter` | Тип адаптера: диалект контрола |
1982
+ | `getFieldAdapter(component)` | Статика `component.reformerAdapter` либо `undefined` |
1983
+ | `bindFieldProps(adapter, seam, props)` | Props контрола под адаптер (без адаптера — seam как есть) |
1984
+ | `FieldHandle` | Императивный handle поля: `focus` / `blur` / `scrollIntoView` / `getElement` |
1985
+ | `makeElementFieldHandle(ref)` | Базовый `FieldHandle` из DOM-узла |
1986
+ | `useFieldHandleRef(outerRef)` | Ref для контрола + публикация handle потребителю (строит ОБЁРТКА поля) |
1987
+
1988
+ В приложении обычно достаточно `defineFieldControl(MyControl, { adapter })` из
1989
+ `@reformer/ui-kit/fields` (плюс готовые пресеты `checkedAdapter`, `valueChangeAdapter`, …); для
1990
+ компонентов чужой библиотеки без статики — `RendererSettings.resolveFieldAdapter`.
1991
+
1933
1992
  ### Integration with UI libraries (shadcn etc.)
1934
1993
 
1935
1994
  > **Через рендерер — без обёртки.** Если ты рендеришь форму через `@reformer/renderer-react` /
1936
1995
  > `@reformer/renderer-json`, вместо ручной обёртки на каждый контрол можно зарегистрировать сырой
1937
1996
  > компонент и передать `settings.resolveFieldAdapter` — рендерер сам сведёт value-seam к диалекту
1938
- > контрола. Ручная обёртка на `useFormControl` (ниже) нужна для прямого JSX без рендерера.
1997
+ > контрола. Свой компонент можно подготовить и один раз — статикой
1998
+ > `defineFieldControl(MyInput, { adapter })` из `@reformer/ui-kit/fields`: его поймут и рендерер,
1999
+ > и `FormField.Control` из `@reformer/cdk`. Ручная обёртка на `useFormControl` (ниже) нужна для
2000
+ > прямого JSX без `FormField` и рендерера.
1939
2001
 
1940
2002
  Если есть существующая design system — оборачивай её компоненты в один
1941
2003
  `MyFormField` (как выше) и используй один прop `control`. Не множь обёртки на
@@ -1965,6 +2027,8 @@ function ShadcnFormField({ control }: { control: FieldNode<string> }) {
1965
2027
 
1966
2028
  ## 18. 15. NON-EXISTENT API (DO NOT USE)
1967
2029
 
2030
+ **nonexistent-api**
2031
+
1968
2032
  **Следующего API НЕТ в @reformer/core** (наследие старой path-based архитектуры,
1969
2033
  удалено при переходе на M1 и при разделении валидации/поведения):
1970
2034
 
@@ -1991,6 +2055,20 @@ function ShadcnFormField({ control }: { control: FieldNode<string> }) {
1991
2055
  | `FormProvider`, `control` prop, `register()` | `<Component form={form} />`, `useFormControl(form.field)` | Форма передаётся через props |
1992
2056
  | `getFieldValue()` | `model.field` / `useFormControlValue(form.field)`| Не существует |
1993
2057
 
2058
+ ### UI: удалённые field-версии компонентов (`@reformer/ui-kit`)
2059
+
2060
+ Отдельных «field-версий» у компонентов кита больше нет: в `component` поля кладётся сам
2061
+ компонент, диалект он объявляет статикой `reformerAdapter`, связывает поле обёртка
2062
+ (`FormField.Control` / рендерер).
2063
+
2064
+ | Wrong | Correct |
2065
+ | ---------------------------------------------- | --------------------------------------------------------- |
2066
+ | `InputField`, `SelectField`, `CheckboxField`, … (любой `*Field`) | `Input`, `SelectAsync`, `CheckboxWithLabel`, … — таблица в ui-kit `01-overview.md` |
2067
+ | `InputField` + `type: 'number'` | `InputNumber` |
2068
+ | `InputField` + `suggestions` | `InputSuggest` |
2069
+ | `FileUploadField` + `variant: 'dropzone' \| 'input'` | `FileUploadDropzone` / `FileUploadInput` (без `variant`) |
2070
+ | `withFormControl(MyControl, adapter)` | `defineFieldControl(MyControl, { adapter })` из `@reformer/ui-kit/fields` |
2071
+
1994
2072
  ### Common Import Errors
1995
2073
 
1996
2074
  ```typescript
@@ -2059,6 +2137,8 @@ revalidateWhen([model.$.password], () => void validateModel(model, validation));
2059
2137
 
2060
2138
  ## 19. Purpose
2061
2139
 
2140
+ **Условные поля — видимость, доступность и валидация**
2141
+
2062
2142
  «Показать поле X только если Y» под M1 решается тремя независимыми механизмами — выбирай по
2063
2143
  тому, что именно должно быть условным:
2064
2144
 
@@ -2241,6 +2321,8 @@ function MortgageFields({ form }: { form: CreditForm }) {
2241
2321
 
2242
2322
  ## 25. 16. READING FIELD VALUES (CRITICALLY IMPORTANT)
2243
2323
 
2324
+ **reading-values**
2325
+
2244
2326
  Под M1 значения живут в модели. Есть три контекста чтения: value-доступ модели, сигналы, и React-хуки.
2245
2327
 
2246
2328
  ### В React-компоненте — хуки
@@ -2330,6 +2412,8 @@ const behavior = defineFormBehavior<MyForm>(({ model, form }) => {
2330
2412
 
2331
2413
  ## 26. 17. COMPUTE vs ONCHANGE
2332
2414
 
2415
+ **compute-vs-watch**
2416
+
2333
2417
  Под M1 behaviors работают на сигналах модели. Есть два способа их писать:
2334
2418
 
2335
2419
  - **примитивы из `@reformer/core`** — принимают сигналы, возвращают cleanup, вызываются
@@ -2478,6 +2562,8 @@ watchField(model.$, (all) => autosave(all)); // любое поле ф
2478
2562
 
2479
2563
  ## 27. 18. ARRAY OPERATIONS
2480
2564
 
2565
+ **array-operations**
2566
+
2481
2567
  Массивы объектов — model-owned. Мутации делаются через `ModelArray` (`model.arrayField`);
2482
2568
  рендер — через ноду формы (`form.items.map` / `.at`).
2483
2569
 
@@ -2592,6 +2678,8 @@ cross(model.$.totalPercent, percentagesSumTo100); // носитель — ска
2592
2678
 
2593
2679
  ## 28. Как устроено под M1
2594
2680
 
2681
+ **Cycle Detection — предотвращение «Cycle detected»**
2682
+
2595
2683
  Behaviors на сигналах уже защищены от типичных циклов:
2596
2684
 
2597
2685
  - `compute`/`computeFrom` пишут в цель только если значение изменилось (**peek-guard**), а цель
@@ -2677,6 +2765,8 @@ enableWhen([model.$.propertyValue, model.$.initialPayment], () => model.loanType
2677
2765
 
2678
2766
  ## 32. Purpose
2679
2767
 
2768
+ **copyFrom — Копирование значений между полями**
2769
+
2680
2770
  `copyFrom` декларативно копирует значение одного поля (или группы) в другое при выполнении
2681
2771
  условия `when`. Используется для UX-сценариев «совпадает с …»: «адрес проживания = адрес
2682
2772
  регистрации», «email для уведомлений = основной email», «billing = shipping». Оператор сам
@@ -2817,6 +2907,8 @@ A: `copyFrom` при `when === false` просто не пишет (не сбр
2817
2907
 
2818
2908
  ## 38. Purpose
2819
2909
 
2910
+ **syncFields — Двусторонняя синхронизация полей**
2911
+
2820
2912
  `syncFields` создаёт двунаправленную связь между двумя полями: изменение любого из них
2821
2913
  переписывает второе. Применяется для дублей одного значения в разных частях формы
2822
2914
  (тех. поле + видимое представление, mirror-поля). Внутренний флаг + `runOutsideEffect`
@@ -2930,6 +3022,8 @@ A: У `syncFields` нет `when`. Эмулируй через два `copyFrom(a
2930
3022
 
2931
3023
  ## 44. Purpose
2932
3024
 
3025
+ **resetWhen — Условный сброс полей**
3026
+
2933
3027
  `resetWhen` сбрасывает значение поля к `resetValue`, когда `condition` истинно. Это
2934
3028
  альтернатива `enableWhen({ resetOnDisable: true })`, когда поле остаётся **enabled**, но
2935
3029
  содержимое нужно очистить (например, переключение способа оплаты обнуляет «номер карты», но
@@ -3044,6 +3138,8 @@ A: `resetWhen` рассчитан на скалярные сигналы. Для
3044
3138
 
3045
3139
  ## 50. Purpose
3046
3140
 
3141
+ **transformValue — Автоматическая трансформация значений**
3142
+
3047
3143
  `transformValue` подписывается на изменение поля и переписывает его трансформированной
3048
3144
  версией: uppercase для кодов, trim+toLowerCase для email, округление для чисел. Применяется
3049
3145
  единообразно ко всем источникам изменения (пользователь, `model.field = …`, `model.set/patch`,
@@ -3179,6 +3275,8 @@ A: Симптом частых записей. Форматирование с
3179
3275
 
3180
3276
  ## 56. Purpose
3181
3277
 
3278
+ **revalidateWhen — Перевалидация по триггерам**
3279
+
3182
3280
  `revalidateWhen` вызывает переданный колбэк ревалидации, когда изменяется любая из
3183
3281
  зависимостей. Применяется, когда правило зависит от значений других полей
3184
3282
  (`amount <= maxAmount`, `confirmPassword === password`, `initialPayment >= propertyValue * 0.2`).
@@ -3310,6 +3408,8 @@ A: Схема — обычная функция, поэтому опиши ст
3310
3408
 
3311
3409
  ## 62. Purpose
3312
3410
 
3411
+ **Submit и Reset — Жизненный цикл отправки формы**
3412
+
3313
3413
  Канонический submit-флоу под M1: «запустить полную валидацию данных (`validateModel`) →
3314
3414
  проверить `boolean`-результат → достать снимок (`model.get()`) → сделать запрос → `model.reset()`».
3315
3415
  Валидация — **отдельный слой** (`@reformer/core/validation`, схема через `defineValidationSchema`), она
@@ -3568,6 +3668,8 @@ A: `model.get()` возвращает значения всех полей. Фи
3568
3668
 
3569
3669
  ## 68. Purpose
3570
3670
 
3671
+ **Async Preload — Загрузка начальных значений и справочников**
3672
+
3571
3673
  Загрузка формы данными с сервера под M1: (1) **initial values** в `createModel(...)`; (2)
3572
3674
  **`model.set` / `model.patch`** для загрузки/обновления значений; (3) **external React-hook**
3573
3675
  для full-blown async preload (параллельный fetch заявки + справочников, обработка ошибок,
@@ -3597,7 +3699,7 @@ Initial values задаются в `createModel(initial)`; `reset()` возвр
3597
3699
 
3598
3700
  ```typescript
3599
3701
  import { createModel, createForm } from '@reformer/core';
3600
- import { Input, Select } from '@reformer/ui-kit';
3702
+ import { Input, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
3601
3703
 
3602
3704
  type ProfileForm = { username: string; language: 'ru' | 'en'; marketing: boolean };
3603
3705
 
@@ -3606,7 +3708,7 @@ const schema = {
3606
3708
  username: { value: model.$.username, component: Input, componentProps: { label: 'Username' } },
3607
3709
  language: {
3608
3710
  value: model.$.language,
3609
- component: Select,
3711
+ component: SelectAsync,
3610
3712
  componentProps: {
3611
3713
  label: 'Язык',
3612
3714
  options: [
@@ -3615,7 +3717,7 @@ const schema = {
3615
3717
  ],
3616
3718
  },
3617
3719
  },
3618
- marketing: { value: model.$.marketing, component: Input, componentProps: { type: 'checkbox' } },
3720
+ marketing: { value: model.$.marketing, component: CheckboxWithLabel, componentProps: { label: 'Рассылка' } },
3619
3721
  };
3620
3722
  const form = createForm({ model, schema });
3621
3723
  // model.get() === { username: '', language: 'ru', marketing: true }
@@ -3760,6 +3862,8 @@ A: `reset()` возвращает к initial-снимку из `createModel`, н
3760
3862
 
3761
3863
  ## 74. 30. TYPE-SAFETY RECIPES
3762
3864
 
3865
+ **type-safety-recipes**
3866
+
3763
3867
  Идиоматичные паттерны, которые держат сгенерированный код без `any` и `as`-кастов под M1.
3764
3868
 
3765
3869
  ### Recipe 1 — Imports (root cause prevention)
@@ -3819,7 +3923,7 @@ Layout-схема БЕЗ валидаторов — правила живут в
3819
3923
  const model = createModel<CreditApplicationForm>(initial);
3820
3924
 
3821
3925
  const schema = {
3822
- loanAmount: { value: model.$.loanAmount, component: Input },
3926
+ loanAmount: { value: model.$.loanAmount, component: InputNumber },
3823
3927
  // вложенная группа — builder, принимающий ModelSignals<Sub>
3824
3928
  personalData: personalDataNodes(model.$.personalData),
3825
3929
  // массив — { array, item }
@@ -3920,6 +4024,8 @@ validate(model.$.birthDate, [validateAdultAge]);
3920
4024
 
3921
4025
  ## 75. 31. ASYNC VALIDATOR
3922
4026
 
4027
+ **async-validator-debounce**
4028
+
3923
4029
  Для проверок типа «уникальность email», «валидация ИНН через API», «проверка адреса» —
3924
4030
  async-правило это `AsyncRule<T>` = `(value, { signal }) => Promise<ValidationError | null>`.
3925
4031
  Оно регистрируется оператором `validateAsync(sig, [asyncRules])` внутри схемы валидации
@@ -4056,6 +4162,8 @@ return checking ? <Spinner /> : errors.length ? <Error errors={errors} /> : null
4056
4162
 
4057
4163
  ## 76. 32. ASYNC OPTIONS LOADING
4058
4164
 
4165
+ **async-options-loading**
4166
+
4059
4167
  Для динамической подгрузки опций dropdown'а по значению другого поля (`region` → `city options`,
4060
4168
  `carBrand` → `carModel options`) — используй `onChange` из `@reformer/core/behaviors` +
4061
4169
  `form.field.updateComponentProps({ options })`. `onChange` даёт debounce и AbortSignal из коробки.
@@ -4165,6 +4273,8 @@ loadOptionsOn(model.$.carBrand, form.carModel, fetchCarModels, { resetTarget: tr
4165
4273
 
4166
4274
  ## 77. Purpose
4167
4275
 
4276
+ **useFormValidation — Единый выбор стратегии валидации**
4277
+
4168
4278
  Одна декларативная точка выбора «когда прогонять схему валидации» вместо ручной разводки
4169
4279
  `markAsTouched` + `validateModel` + `revalidateWhen` + `useState(pending)`. Аддитивный слой над
4170
4280
  функциональной схемой (`@reformer/core/validation`): переиспользует `validateModel` как
@@ -4359,6 +4469,34 @@ if (typeof window !== 'undefined') ctrl.start();
4359
4469
 
4360
4470
  _Auto-generated from JSDoc on public exports._
4361
4471
 
4472
+ ### aggregateInto
4473
+
4474
+ **Kind:** `function`
4475
+
4476
+ Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список
4477
+ `{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на
4478
+ финальном состоянии — поэтому массовые синхронные мутации (push в цикле) не вызывают каскад на
4479
+ промежуточных состояниях. `derive` должна сходиться (на фикспоинте возвращать те же значения).
4480
+
4481
+ **Signature:**
4482
+ ```typescript
4483
+ export function aggregateInto<TItem>(
4484
+ array: object,
4485
+ derive: (rows: TItem[]) => Array<{ index: number; patch: Partial<TItem> }>
4486
+ ): void
4487
+ ```
4488
+
4489
+ **Examples:**
4490
+
4491
+ // последняя строка = 100 − Σ(остальные)
4492
+ aggregateInto(model.$.rows, (rows) => {
4493
+ const n = rows.length; if (n === 0) return [];
4494
+ const others = rows.slice(0, n - 1).reduce((s, r) => s + r.percent, 0);
4495
+ return [{ index: n - 1, patch: { percent: 100 - others } }];
4496
+ });
4497
+
4498
+ _Source: src/form/behaviors/collections.ts_
4499
+
4362
4500
  ### AnyFunction
4363
4501
 
4364
4502
  **Kind:** `type`
@@ -4373,6 +4511,47 @@ export type AnyFunction = (...args: never[]) => unknown;
4373
4511
 
4374
4512
  _Source: src/form/types/index.ts_
4375
4513
 
4514
+ ### apply
4515
+
4516
+ **Kind:** `function`
4517
+
4518
+ Композиция под-схем в текущую (над той же моделью scope). Заменяет пошаговую группировку.
4519
+
4520
+ **Signature:**
4521
+ ```typescript
4522
+ export function apply<T>(...schemas: ValidationSchema<T>[]): void
4523
+ ```
4524
+
4525
+ _Source: src/form/validation/operators.ts_
4526
+
4527
+ ### applyEach
4528
+
4529
+ **Kind:** `function`
4530
+
4531
+ Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).
4532
+ Реагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.
4533
+
4534
+ Под-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).
4535
+ - Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.
4536
+ - Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был
4537
+ МАТЕРИАЛИЗОВАН в форме (узел `{ array, item }` в схеме) — тогда `form` строки = та же нода, что
4538
+ рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
4539
+ к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
4540
+
4541
+ **Signature:**
4542
+ ```typescript
4543
+ export function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void
4544
+ ```
4545
+
4546
+ **Examples:**
4547
+
4548
+ applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
4549
+ compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
4550
+ enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
4551
+ }));
4552
+
4553
+ _Source: src/form/behaviors/collections.ts_
4554
+
4376
4555
  ### ArrayControlState
4377
4556
 
4378
4557
  **Kind:** `interface`
@@ -4585,6 +4764,23 @@ export interface ArrayNodeLike {
4585
4764
 
4586
4765
  _Source: src/form/types/index.ts_
4587
4766
 
4767
+ ### AsyncRule
4768
+
4769
+ **Kind:** `type`
4770
+
4771
+ Асинхронное правило поля. Получает `AbortSignal` для отмены устаревших ответов (быстрый повторный
4772
+ прогон той же схемы отменяет предыдущий). Сетевой сбой не должен блокировать — ловите и возвращайте `null`.
4773
+
4774
+ **Signature:**
4775
+ ```typescript
4776
+ export type AsyncRule<TField> = (
4777
+ value: TField,
4778
+ ctx: { signal: AbortSignal }
4779
+ ) => Promise<ValidationError | null>;
4780
+ ```
4781
+
4782
+ _Source: src/form/validation/types.ts_
4783
+
4588
4784
  ### AsyncValidatorFn
4589
4785
 
4590
4786
  **Kind:** `type`
@@ -4657,6 +4853,48 @@ export type BehaviorCleanup = () => void;
4657
4853
 
4658
4854
  _Source: src/model/behaviors-value.ts_
4659
4855
 
4856
+ ### BehaviorScope
4857
+
4858
+ **Kind:** `type`
4859
+
4860
+ Контекст схемы поведения: модель (значения/сигналы) + форма (ноды).
4861
+
4862
+ **Signature:**
4863
+ ```typescript
4864
+ export type BehaviorScope<T> = { model: FormModel<T>; form: FormProxy<T> };
4865
+ ```
4866
+
4867
+ _Source: src/form/behaviors/types.ts_
4868
+
4869
+ ### bindFieldProps
4870
+
4871
+ **Kind:** `function`
4872
+
4873
+ Props контрола под адаптер: срезает служебные ключи обёртки и `strip` адаптера, кладёт значение
4874
+ в `valueProp` (через `toValue`), вешает `changeProp`, который прогоняет эмит контрола через
4875
+ `fromEmit` в value-based `onChange` поля, и пробрасывает blur (`bindBlur` либо `onBlur`).
4876
+ Без адаптера seam проходит как есть (`value` / `onChange` / `onBlur`).
4877
+
4878
+ `componentProps` спредятся ПЕРВЫМИ — seam их перекрывает при совпадении ключей.
4879
+
4880
+ **Signature:**
4881
+ ```typescript
4882
+ export function bindFieldProps(
4883
+ adapter: FieldAdapter | undefined,
4884
+ seam: FieldSeam,
4885
+ componentProps: Record<string, unknown> = {}
4886
+ ): Record<string, unknown>
4887
+ ```
4888
+
4889
+ **Parameters:**
4890
+ - `adapter` — - Адаптер контрола или `undefined` (value-based контрол).
4891
+ - `seam` — - Значение поля и его колбэки.
4892
+ - `componentProps` — - Прочие props контрола.
4893
+
4894
+ **Returns:** Плоский объект props для спреда в контрол.
4895
+
4896
+ _Source: src/platforms/react/field-binding/field-adapter.ts_
4897
+
4660
4898
  ### buildValidation
4661
4899
 
4662
4900
  **Kind:** `function`
@@ -4694,6 +4932,38 @@ await v!.validateAll(); // все шаги + extras
4694
4932
 
4695
4933
  _Source: src/form/validation/config.ts_
4696
4934
 
4935
+ ### ChangeContext
4936
+
4937
+ **Kind:** `interface`
4938
+
4939
+ Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка.
4940
+
4941
+ **Signature:**
4942
+ ```typescript
4943
+ export interface ChangeContext {
4944
+ signal: AbortSignal;
4945
+ }
4946
+ ```
4947
+
4948
+ _Source: src/form/behaviors/types.ts_
4949
+
4950
+ ### compute
4951
+
4952
+ **Kind:** `function`
4953
+
4954
+ Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов.
4955
+
4956
+ **Signature:**
4957
+ ```typescript
4958
+ export function compute<R>(
4959
+ target: Signal<R>,
4960
+ read: () => R,
4961
+ options?: { when?: () => boolean }
4962
+ ): void
4963
+ ```
4964
+
4965
+ _Source: src/form/behaviors/operators.ts_
4966
+
4697
4967
  ### computeFrom
4698
4968
 
4699
4969
  **Kind:** `function`
@@ -4976,7 +5246,7 @@ _Source: src/form/create-core-form.ts_
4976
5246
 
4977
5247
  Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
4978
5248
  ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
4979
- модели, привязывает конфиг поля (component/componentProps/validators) из схемы, материализует
5249
+ модели, привязывает конфиг поля (component/componentProps) из схемы, материализует
4980
5250
  top-level массивы как {@link ModelArrayNode}, заполняет реестр сигнал→нода (для `enableWhen`/
4981
5251
  роутинга ошибок) и, при наличии, запускает декларативное поведение (cleanup живёт на форме).
4982
5252
 
@@ -5008,7 +5278,8 @@ const model = createModel<Form>({ email: '', profile: { name: '', age: 0 } });
5008
5278
  const schema = {
5009
5279
  component: Section,
5010
5280
  children: [
5011
- { value: model.$.email, component: Input, validators: [required] },
5281
+ // Layout несёт только component/componentProps; правила — в отдельной ValidationSchema.
5282
+ { value: model.$.email, component: Input },
5012
5283
  // вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)
5013
5284
  { value: model.$.profile.name, component: Input },
5014
5285
  ],
@@ -5031,7 +5302,7 @@ _Source: src/form/create-form.ts_
5031
5302
  **Kind:** `interface`
5032
5303
 
5033
5304
  Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
5034
- конфиг полей (component/componentProps/validators) — из единой схемы.
5305
+ конфиг полей (component/componentProps) — из единой схемы.
5035
5306
 
5036
5307
  **Signature:**
5037
5308
  ```typescript
@@ -5053,6 +5324,41 @@ export interface CreateFormFromModelArgs<T> {
5053
5324
 
5054
5325
  _Source: src/form/create-form.ts_
5055
5326
 
5327
+ ### createFormValidation
5328
+
5329
+ **Kind:** `function`
5330
+
5331
+ Собрать контроллер валидации формы с выбранной стратегией запуска. Переиспользует
5332
+ {@link validateModel} как единственный движок прогона.
5333
+
5334
+ Фабрика ЧИСТАЯ до `start()` (никаких подписок) — безопасна для SSR/headless. Реактивные триггеры
5335
+ арминуются только в `start()` (в React — из `useEffect`).
5336
+
5337
+ **Signature:**
5338
+ ```typescript
5339
+ export function createFormValidation<T>(
5340
+ model: FormModel<T>,
5341
+ schema: ValidationSchema<T>,
5342
+ options: ValidationStrategyOptions = {}
5343
+ ): FormValidationController
5344
+ ```
5345
+
5346
+ **Parameters:**
5347
+ - `model` — - Модель данных.
5348
+ - `schema` — - Схема валидации. **Стабильная ссылка** (иначе `validateModel` не отменит устаревший прогон).
5349
+ - `options` — - {@link ValidationStrategyOptions}.
5350
+
5351
+ **Examples:**
5352
+
5353
+ ```ts
5354
+ const ctrl = createFormValidation(model, schema, { strategy: 'afterFirstSubmit', debounce: 300 });
5355
+ const dispose = ctrl.start(); // на клиенте
5356
+ const ok = await ctrl.validate(); // submit → раскрыть все ошибки
5357
+ dispose();
5358
+ ```
5359
+
5360
+ _Source: src/form/validation/strategy.ts_
5361
+
5056
5362
  ### createModel
5057
5363
 
5058
5364
  **Kind:** `function`
@@ -5089,6 +5395,81 @@ model.get(); // { email: 'a@b.c', profile: { name: 'Ada' }, tag
5089
5395
 
5090
5396
  _Source: src/model/create-model.ts_
5091
5397
 
5398
+ ### cross
5399
+
5400
+ **Kind:** `function`
5401
+
5402
+ Cross-field правило: `fn` получает СНАПШОТ модели текущего scope (`model.get()`) и вешает ошибку на `sig`.
5403
+ Для элементов массива / под-моделей захватывайте нужный снапшот в замыкание (`const item = im.get()`),
5404
+ т.к. `fn` всегда получает модель ТЕКУЩЕГО scope (корень прогона), а не под-модель.
5405
+
5406
+ **Signature:**
5407
+ ```typescript
5408
+ export function cross<TSnapshot>(
5409
+ sig: PathAwareSignal<unknown>,
5410
+ fn: (form: TSnapshot) => ValidationError | null
5411
+ ): void
5412
+ ```
5413
+
5414
+ _Source: src/form/validation/operators.ts_
5415
+
5416
+ ### defer
5417
+
5418
+ **Kind:** `function`
5419
+
5420
+ Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected».
5421
+
5422
+ **Signature:**
5423
+ ```typescript
5424
+ export function defer(fn: () => void): void
5425
+ ```
5426
+
5427
+ _Source: src/form/behaviors/context.ts_
5428
+
5429
+ ### defineFormBehavior
5430
+
5431
+ **Kind:** `function`
5432
+
5433
+ Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
5434
+
5435
+ **Signature:**
5436
+ ```typescript
5437
+ export function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>
5438
+ ```
5439
+
5440
+ **Examples:**
5441
+
5442
+ ```ts
5443
+ export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
5444
+ compute(model.$.total, () => model.price * model.qty);
5445
+ enableWhen([model.$.city], () => Boolean(model.country));
5446
+ onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
5447
+ });
5448
+ ```
5449
+
5450
+ _Source: src/form/behaviors/context.ts_
5451
+
5452
+ ### defineValidationSchema
5453
+
5454
+ **Kind:** `function`
5455
+
5456
+ Тонкая identity-обёртка для типизации/discoverability (как `defineFormBehavior`). Возвращает схему как есть.
5457
+
5458
+ **Signature:**
5459
+ ```typescript
5460
+ export function defineValidationSchema<T>(schema: ValidationSchema<T>): ValidationSchema<T>
5461
+ ```
5462
+
5463
+ **Examples:**
5464
+
5465
+ ```ts
5466
+ export const step1 = defineValidationSchema<LoanForm>(({ model }) => {
5467
+ validate(model.$.loanAmount, [required(), min(50000)]);
5468
+ });
5469
+ ```
5470
+
5471
+ _Source: src/form/validation/run.ts_
5472
+
5092
5473
  ### disableWhen
5093
5474
 
5094
5475
  **Kind:** `function`
@@ -5121,6 +5502,23 @@ disableWhen(model.$.discount, () => model.plan !== 'promo', { resetOnDisable: tr
5121
5502
 
5122
5503
  _Source: src/form/behaviors/node.ts_
5123
5504
 
5505
+ ### each
5506
+
5507
+ **Kind:** `function`
5508
+
5509
+ Применить под-правила к КАЖДОМУ элементу текущего массива модели.
5510
+ `U extends object` — элементы должны быть под-моделями (объектами); для массива примитивов валидируйте лист напрямую.
5511
+
5512
+ **Signature:**
5513
+ ```typescript
5514
+ export function each<U extends object>(
5515
+ arr: ModelArray<U>,
5516
+ itemFn: (item: FormModel<U>) => void
5517
+ ): void
5518
+ ```
5519
+
5520
+ _Source: src/form/validation/operators.ts_
5521
+
5124
5522
  ### eachLeafSignal
5125
5523
 
5126
5524
  **Kind:** `function`
@@ -5142,6 +5540,55 @@ export function eachLeafSignal<T>(
5142
5540
 
5143
5541
  _Source: src/model/create-model.ts_
5144
5542
 
5543
+ ### effect
5544
+
5545
+ **Kind:** `function`
5546
+
5547
+ Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup.
5548
+
5549
+ **Signature:**
5550
+ ```typescript
5551
+ export function effect(fn: () => void | (() => void)): void
5552
+ ```
5553
+
5554
+ _Source: src/form/behaviors/context.ts_
5555
+
5556
+ ### email
5557
+
5558
+ **Kind:** `function`
5559
+
5560
+ Фабрика валидатора формата email.
5561
+
5562
+ Проверяет по упрощённому regex `^[^\s@]+@[^\s@]+\.[^\s@]+$`. Пустые значения
5563
+ (`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
5564
+
5565
+ **Signature:**
5566
+ ```typescript
5567
+ export function email<TForm = unknown, TField extends string | null | undefined = string>(
5568
+ options?: ValidateOptions
5569
+ ): Validator<TForm, TField>
5570
+ ```
5571
+
5572
+ **Parameters:**
5573
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
5574
+
5575
+ **Returns:** Чистый валидатор {@link Validator} для строкового поля
5576
+
5577
+ **Examples:**
5578
+
5579
+ Проверка формата email
5580
+ ```typescript
5581
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
5582
+ import { required, email } from '@reformer/core/validators';
5583
+
5584
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
5585
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
5586
+ validate(model.$.email, [required(), email({ message: 'Введите корректный email' })]);
5587
+ });
5588
+ ```
5589
+
5590
+ _Source: src/form/validators/email.ts_
5591
+
5145
5592
  ### enableWhen
5146
5593
 
5147
5594
  **Kind:** `function`
@@ -5244,20 +5691,119 @@ export enum ErrorStrategy {
5244
5691
 
5245
5692
  _Source: src/form/validation/error-handler.ts_
5246
5693
 
5247
- ### FieldConfig
5694
+ ### exclusiveFlag
5248
5695
 
5249
- **Kind:** `interface`
5696
+ **Kind:** `function`
5250
5697
 
5251
- Конфигурация поля
5698
+ Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).
5699
+ Когда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:
5700
+ новые строки с флагом false исключения не запускают.
5252
5701
 
5253
5702
  **Signature:**
5254
5703
  ```typescript
5255
- export interface FieldConfig<T> {
5256
- /**
5257
- * Начальное значение-литерал (legacy-путь). Под архитектурой M1 значение приходит из
5258
- * {@link FieldConfig.valueSignal} (сигнал {@link FormModel}); тогда `value` не требуется.
5259
- */
5260
- value?: T | null;
5704
+ export function exclusiveFlag<TItem>(
5705
+ array: object,
5706
+ getFlag: (row: FormModel<TItem>) => Signal<boolean>
5707
+ ): void
5708
+ ```
5709
+
5710
+ **Examples:**
5711
+
5712
+ exclusiveFlag(model.$.contacts, (row) => row.$.primary);
5713
+
5714
+ _Source: src/form/behaviors/collections.ts_
5715
+
5716
+ ### FIELD_WRAPPER_ONLY_PROPS
5717
+
5718
+ **Kind:** `const`
5719
+
5720
+ Props, которые адресованы обёртке поля, а не контролу: `labelTooltip` рисует `FormField` у
5721
+ подписи. Они приходят в общем мешке `componentProps`, и без среза утекли бы в DOM контрола.
5722
+
5723
+ **Signature:**
5724
+ ```typescript
5725
+ export const FIELD_WRAPPER_ONLY_PROPS: readonly string[]
5726
+ ```
5727
+
5728
+ _Source: src/platforms/react/field-binding/field-adapter.ts_
5729
+
5730
+ ### FieldAdapter
5731
+
5732
+ **Kind:** `interface`
5733
+
5734
+ Адаптер поля: описывает диалект контрола (`checked`/`onCheckedChange`, `value`/`onValueChange`,
5735
+ DOM-событие в `onChange`, …). Все ключи необязательны — по умолчанию seam проходит как есть.
5736
+
5737
+ **Signature:**
5738
+ ```typescript
5739
+ export interface FieldAdapter {
5740
+ /** Проп, из которого контрол читает значение (по умолчанию `'value'`). */
5741
+ valueProp?: string;
5742
+ /** Колбэк, через который контрол эмитит изменение (по умолчанию `'onChange'`). */
5743
+ changeProp?: string;
5744
+ /**
5745
+ * Эмит контрола → значение поля (по умолчанию — как есть). `rest` — прочие props контрола
5746
+ * (например, чтобы достать `options` при резолве значения).
5747
+ */
5748
+ fromEmit?: (arg: unknown, rest: Record<string, unknown>) => unknown;
5749
+ /** Значение поля → `valueProp` контрола (коэрсия `null`/`undefined`; по умолчанию — как есть). */
5750
+ toValue?: (value: unknown) => unknown;
5751
+ /** Проброс blur нестандартным каналом (по умолчанию — `onBlur`). */
5752
+ bindBlur?: (onBlur: () => void) => Record<string, unknown>;
5753
+ /** Ключи, которые убрать из `componentProps` перед спредом в контрол. */
5754
+ strip?: string[];
5755
+ /**
5756
+ * Передавать ли контролу ноду формы пропом `control` (только рендерер). По умолчанию `false`:
5757
+ * errors/touched обслуживает обёртка поля. Ставь `true`, если контрол сам вызывает
5758
+ * `useFormControl(control)`.
5759
+ */
5760
+ passControl?: boolean;
5761
+ }
5762
+ ```
5763
+
5764
+ **Examples:**
5765
+
5766
+ Radix Checkbox
5767
+ ```ts
5768
+ const checkedAdapter: FieldAdapter = {
5769
+ valueProp: 'checked',
5770
+ changeProp: 'onCheckedChange',
5771
+ fromEmit: (c) => c === true,
5772
+ toValue: (v) => v ?? false,
5773
+ };
5774
+ ```
5775
+
5776
+ _Source: src/platforms/react/field-binding/field-adapter.ts_
5777
+
5778
+ ### FieldAdapterCarrier
5779
+
5780
+ **Kind:** `interface`
5781
+
5782
+ Компонент, объявивший свой диалект статикой.
5783
+
5784
+ **Signature:**
5785
+ ```typescript
5786
+ export interface FieldAdapterCarrier {
5787
+ reformerAdapter?: FieldAdapter;
5788
+ }
5789
+ ```
5790
+
5791
+ _Source: src/platforms/react/field-binding/field-adapter.ts_
5792
+
5793
+ ### FieldConfig
5794
+
5795
+ **Kind:** `interface`
5796
+
5797
+ Конфигурация поля
5798
+
5799
+ **Signature:**
5800
+ ```typescript
5801
+ export interface FieldConfig<T> {
5802
+ /**
5803
+ * Начальное значение-литерал (legacy-путь). Под архитектурой M1 значение приходит из
5804
+ * {@link FieldConfig.valueSignal} (сигнал {@link FormModel}); тогда `value` не требуется.
5805
+ */
5806
+ value?: T | null;
5261
5807
  /**
5262
5808
  * Сигнал значения из {@link FormModel} (M1). Если задан — служит источником истины значения
5263
5809
  * поля (нода не владеет значением, а ссылается на этот сигнал). Имеет приоритет над `value`.
@@ -5506,6 +6052,38 @@ onChange={e => control.setValue(e.target.value)}
5506
6052
 
5507
6053
  _Source: src/platforms/react/hooks/types.ts_
5508
6054
 
6055
+ ### FieldHandle
6056
+
6057
+ **Kind:** `interface`
6058
+
6059
+ Базовый императивный контракт поля формы.
6060
+
6061
+ Обёртка поля (`FormField.Control` из `@reformer/cdk`, рендерер `@reformer/renderer-react`)
6062
+ строит его из DOM-узла контрола. Композиты со своим handle (`open`/`close`/`reload`/…) отдают
6063
+ его как есть — он расширяет этот контракт.
6064
+
6065
+ Достаётся из render-схемы по селектору:
6066
+ `schema.node(sel).getRef<FieldHandle>().current?.focus()`.
6067
+
6068
+ Покрывает ТОЛЬКО императивные действия. Реактивное состояние (value / disabled / options /
6069
+ validation) живёт в behaviors и через handle не дублируется.
6070
+
6071
+ **Signature:**
6072
+ ```typescript
6073
+ export interface FieldHandle {
6074
+ /** Сфокусировать поле (делегирует на DOM-элемент контрола). */
6075
+ focus(): void;
6076
+ /** Снять фокус. */
6077
+ blur(): void;
6078
+ /** Проскроллить поле в область видимости. */
6079
+ scrollIntoView(opts?: ScrollIntoViewOptions): void;
6080
+ /** Живой DOM-элемент поля (или `null` до монтирования / для размонтированной ноды). */
6081
+ getElement(): HTMLElement | null;
6082
+ }
6083
+ ```
6084
+
6085
+ _Source: src/platforms/react/field-binding/field-handle.ts_
6086
+
5509
6087
  ### FieldNode
5510
6088
 
5511
6089
  **Kind:** `class`
@@ -5522,6 +6100,7 @@ export class FieldNode<T> extends FormNode<T> {
5522
6100
 
5523
6101
  **Examples:**
5524
6102
 
6103
+ Пример node-level: конфиг УЗЛА, а не layout-схема M1 (в ней поля `validators` нет).
5525
6104
  ```typescript
5526
6105
  const field = new FieldNode({
5527
6106
  value: '',
@@ -5536,6 +6115,23 @@ console.log(field.valid.value); // true
5536
6115
 
5537
6116
  _Source: src/form/nodes/field-node.ts_
5538
6117
 
6118
+ ### FieldSeam
6119
+
6120
+ **Kind:** `interface`
6121
+
6122
+ Seam поля: текущее значение и value-based колбэки.
6123
+
6124
+ **Signature:**
6125
+ ```typescript
6126
+ export interface FieldSeam {
6127
+ value: unknown;
6128
+ onChange: (value: unknown) => void;
6129
+ onBlur: () => void;
6130
+ }
6131
+ ```
6132
+
6133
+ _Source: src/platforms/react/field-binding/field-adapter.ts_
6134
+
5539
6135
  ### FieldStatus
5540
6136
 
5541
6137
  **Kind:** `type`
@@ -5549,6 +6145,69 @@ export type FieldStatus = 'valid' | 'invalid' | 'pending' | 'disabled';
5549
6145
 
5550
6146
  _Source: src/form/types/contracts.ts_
5551
6147
 
6148
+ ### FileLike
6149
+
6150
+ **Kind:** `interface`
6151
+
6152
+ Минимальный файлоподобный контракт, с которым работают file-валидаторы:
6153
+ нативный `File`, сериализуемый дескриптор загруженного файла и т.п.
6154
+
6155
+ **Signature:**
6156
+ ```typescript
6157
+ export interface FileLike {
6158
+ name: string;
6159
+ size?: number;
6160
+ type?: string;
6161
+ }
6162
+ ```
6163
+
6164
+ _Source: src/form/validators/file-utils.ts_
6165
+
6166
+ ### fileType
6167
+
6168
+ **Kind:** `function`
6169
+
6170
+ Фабрика валидатора типа файла.
6171
+
6172
+ Проверяет каждый файл против accept-строки в синтаксисе нативного атрибута
6173
+ `<input type="file" accept>`: расширения (`.pdf`), точные MIME (`image/png`),
6174
+ wildcard-категории (`image/*`), список через запятую. Матчинг регистронезависимый,
6175
+ по `type` и/или расширению из `name` ({@link matchesFileAccept}).
6176
+
6177
+ Нужен потому, что нативный `accept` — только подсказка пикеру: на drag-and-drop
6178
+ и «All files» он не действует. Пустые значения (`null`/`undefined`/`''`/`[]`)
6179
+ пропускаются (используйте {@link required} для обязательности).
6180
+
6181
+ **Signature:**
6182
+ ```typescript
6183
+ export function fileType<TForm = unknown, TField = unknown>(
6184
+ accept: string,
6185
+ options?: ValidateOptions
6186
+ ): Validator<TForm, TField>
6187
+ ```
6188
+
6189
+ **Parameters:**
6190
+ - `accept` — - Accept-строка допустимых типов, например `'image/*,.pdf'`
6191
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
6192
+ попадают `accept` и `fileName` (первый нарушивший файл).
6193
+
6194
+ **Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
6195
+
6196
+ **Examples:**
6197
+
6198
+ Только изображения и PDF
6199
+ ```typescript
6200
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
6201
+ import { fileType } from '@reformer/core/validators';
6202
+
6203
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
6204
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
6205
+ validate(model.$.documents, [fileType('image/*,.pdf', { message: 'Только изображения или PDF' })]);
6206
+ });
6207
+ ```
6208
+
6209
+ _Source: src/form/validators/file-type.ts_
6210
+
5552
6211
  ### FormArrayProxy
5553
6212
 
5554
6213
  **Kind:** `type`
@@ -5603,6 +6262,22 @@ todos.forEach((item, i) => {
5603
6262
 
5604
6263
  _Source: src/form/types/form-proxy.ts_
5605
6264
 
6265
+ ### FormBehavior
6266
+
6267
+ **Kind:** `interface`
6268
+
6269
+ Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`.
6270
+
6271
+ **Signature:**
6272
+ ```typescript
6273
+ export interface FormBehavior<T> {
6274
+ /** @internal Запускается `createForm` после построения нод и заполнения реестра сигнал→нода. */
6275
+ __run(model: FormModel<T>, form: FormProxy<T>): BehaviorCleanup;
6276
+ }
6277
+ ```
6278
+
6279
+ _Source: src/form/behaviors/types.ts_
6280
+
5606
6281
  ### FormBundleLike
5607
6282
 
5608
6283
  **Kind:** `interface`
@@ -6074,6 +6749,37 @@ export interface FormValidationBundle<T> {
6074
6749
 
6075
6750
  _Source: src/form/validation/config.ts_
6076
6751
 
6752
+ ### FormValidationController
6753
+
6754
+ **Kind:** `interface`
6755
+
6756
+ **Signature:**
6757
+ ```typescript
6758
+ export interface FormValidationController {
6759
+ /**
6760
+ * Полный прогон схемы с раскрытием ошибок (`touch: true`) — для submit. Также переводит
6761
+ * `afterFirstSubmit` в live-фазу. Возвращает `false`, если есть блокирующие ошибки.
6762
+ */
6763
+ validate(): Promise<boolean>;
6764
+ /**
6765
+ * Армировать реактивные подписки стратегии. Идемпотентно. Возвращает `dispose`. НЕ звать при SSR.
6766
+ *
6767
+ * После {@link FormValidationController.dispose} контроллер армируется ЗАНОВО: React монтирует
6768
+ * компоненты повторно (StrictMode в разработке, remount по роуту), и «одноразовый» контроллер
6769
+ * после такого цикла молча оставался бы без живой валидации.
6770
+ */
6771
+ start(): () => void;
6772
+ /** Снять подписки/таймеры. Идемпотентно; после него `start()` снова армирует стратегию. */
6773
+ dispose(): void;
6774
+ /** Идёт ли прогон (submit или live) — снапшот. */
6775
+ readonly isValidating: boolean;
6776
+ /** Реактивный сигнал состояния прогона (для тонкой подписки в UI). */
6777
+ readonly validating: ReadonlySignal<boolean>;
6778
+ }
6779
+ ```
6780
+
6781
+ _Source: src/form/validation/strategy.ts_
6782
+
6077
6783
  ### FormValue
6078
6784
 
6079
6785
  **Kind:** `type`
@@ -6097,6 +6803,63 @@ export type FormValue =
6097
6803
 
6098
6804
  _Source: src/form/types/contracts.ts_
6099
6805
 
6806
+ ### futureDate
6807
+
6808
+ **Kind:** `function`
6809
+
6810
+ Фабрика валидатора, проверяющего что дата не в прошлом.
6811
+
6812
+ Дата не должна быть раньше сегодняшнего дня (сравнение по нормализованным датам).
6813
+ Пустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).
6814
+
6815
+ **Signature:**
6816
+ ```typescript
6817
+ export function futureDate<
6818
+ TForm = unknown,
6819
+ TField extends string | Date | null | undefined = string | Date,
6820
+ >(options?: ValidateOptions): Validator<TForm, TField>
6821
+ ```
6822
+
6823
+ **Parameters:**
6824
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
6825
+
6826
+ **Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
6827
+
6828
+ **Examples:**
6829
+
6830
+ Дата не в прошлом
6831
+ ```typescript
6832
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
6833
+ import { required, futureDate } from '@reformer/core/validators';
6834
+
6835
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
6836
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
6837
+ validate(model.$.appointmentDate, [required(), futureDate({ message: 'Дата записи должна быть в будущем' })]);
6838
+ });
6839
+ ```
6840
+
6841
+ _Source: src/form/validators/future-date.ts_
6842
+
6843
+ ### getFieldAdapter
6844
+
6845
+ **Kind:** `function`
6846
+
6847
+ Адаптер, объявленный компонентом статикой `reformerAdapter`, либо `undefined`.
6848
+
6849
+ **Signature:**
6850
+ ```typescript
6851
+ export function getFieldAdapter(component: unknown): FieldAdapter | undefined
6852
+ ```
6853
+
6854
+ **Examples:**
6855
+
6856
+ ```ts
6857
+ Checkbox.reformerAdapter = checkedAdapter;
6858
+ getFieldAdapter(Checkbox); // → checkedAdapter
6859
+ ```
6860
+
6861
+ _Source: src/platforms/react/field-binding/field-adapter.ts_
6862
+
6100
6863
  ### getNodeForSignal
6101
6864
 
6102
6865
  **Kind:** `function`
@@ -6158,6 +6921,19 @@ console.log('Node type:', getNodeType(node)); // "FieldNode" | "GroupNode" | "Ar
6158
6921
 
6159
6922
  _Source: src/form/type-guards.ts_
6160
6923
 
6924
+ ### getScope
6925
+
6926
+ **Kind:** `function`
6927
+
6928
+ Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов).
6929
+
6930
+ **Signature:**
6931
+ ```typescript
6932
+ export function getScope<T>(): BehaviorScope<T>
6933
+ ```
6934
+
6935
+ _Source: src/form/behaviors/context.ts_
6936
+
6161
6937
  ### GroupNode
6162
6938
 
6163
6939
  **Kind:** `class`
@@ -6212,6 +6988,41 @@ export interface GroupNodeConfig<T> {
6212
6988
 
6213
6989
  _Source: src/form/types/index.ts_
6214
6990
 
6991
+ ### integer
6992
+
6993
+ **Kind:** `function`
6994
+
6995
+ Фабрика валидатора, проверяющего что число — целое.
6996
+
6997
+ Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
6998
+
6999
+ **Signature:**
7000
+ ```typescript
7001
+ export function integer<TForm = unknown, TField extends number | null | undefined = number>(
7002
+ options?: ValidateOptions
7003
+ ): Validator<TForm, TField>
7004
+ ```
7005
+
7006
+ **Parameters:**
7007
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
7008
+
7009
+ **Returns:** Чистый валидатор {@link Validator} для числового поля
7010
+
7011
+ **Examples:**
7012
+
7013
+ Проверка целого числа
7014
+ ```typescript
7015
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7016
+ import { required, integer } from '@reformer/core/validators';
7017
+
7018
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7019
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7020
+ validate(model.$.count, [required(), integer({ message: 'Должно быть целым числом' })]);
7021
+ });
7022
+ ```
7023
+
7024
+ _Source: src/form/validators/integer.ts_
7025
+
6215
7026
  ### isArrayNode
6216
7027
 
6217
7028
  **Kind:** `function`
@@ -6243,6 +7054,43 @@ if (isArrayNode(node)) {
6243
7054
 
6244
7055
  _Source: src/form/type-guards.ts_
6245
7056
 
7057
+ ### isDate
7058
+
7059
+ **Kind:** `function`
7060
+
7061
+ Фабрика валидатора, проверяющего что значение — валидная дата.
7062
+
7063
+ Принимает `Date` или строку, парсимую в дату. Пустые значения (`''`/`null`/`undefined`)
7064
+ пропускаются (используйте {@link required} для обязательности).
7065
+
7066
+ **Signature:**
7067
+ ```typescript
7068
+ export function isDate<
7069
+ TForm = unknown,
7070
+ TField extends string | Date | null | undefined = string | Date,
7071
+ >(options?: ValidateOptions): Validator<TForm, TField>
7072
+ ```
7073
+
7074
+ **Parameters:**
7075
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
7076
+
7077
+ **Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
7078
+
7079
+ **Examples:**
7080
+
7081
+ Проверка валидности даты
7082
+ ```typescript
7083
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7084
+ import { required, isDate } from '@reformer/core/validators';
7085
+
7086
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7087
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7088
+ validate(model.$.eventDate, [required(), isDate({ message: 'Введите корректную дату' })]);
7089
+ });
7090
+ ```
7091
+
7092
+ _Source: src/form/validators/is-date.ts_
7093
+
6246
7094
  ### isDerived
6247
7095
 
6248
7096
  **Kind:** `function`
@@ -6308,6 +7156,25 @@ if (isFieldNode(node)) {
6308
7156
 
6309
7157
  _Source: src/form/type-guards.ts_
6310
7158
 
7159
+ ### isFileLike
7160
+
7161
+ **Kind:** `function`
7162
+
7163
+ Проверяет, что значение файлоподобно: объект со строковым `name` и опциональными
7164
+ числовым `size` / строковым `type`.
7165
+
7166
+ **Signature:**
7167
+ ```typescript
7168
+ export function isFileLike(value: unknown): value is FileLike
7169
+ ```
7170
+
7171
+ **Parameters:**
7172
+ - `value` — - Проверяемое значение
7173
+
7174
+ **Returns:** `true`, если значение соответствует {@link FileLike}
7175
+
7176
+ _Source: src/form/validators/file-utils.ts_
7177
+
6311
7178
  ### isFormNode
6312
7179
 
6313
7180
  **Kind:** `function`
@@ -6389,31 +7256,82 @@ const isLeaf = (v: unknown) => isSignalLike(v) && !isModelContainerSignal(v);
6389
7256
 
6390
7257
  _Source: src/model/model-signals-proxy.ts_
6391
7258
 
6392
- ### markDerived
7259
+ ### isNumber
6393
7260
 
6394
7261
  **Kind:** `function`
6395
7262
 
6396
- Пометить сигнал производным (вычисляемым).
6397
-
6398
- Вызывается операторами {@link computeFrom}/`compute` для их сигнала-цели. После этого
6399
- bulk-сеттеры (`model.set`/`model.patch`, `patchValue`/`setValue`) пропускают это поле, чтобы
6400
- значение из payload не затирало вычисляемое. Прикладной код напрямую обычно не вызывает.
7263
+ Фабрика валидатора, проверяющего что значение — конечное число (не NaN, не строка).
6401
7264
 
6402
- Идемпотентна на уровне флага: повторные вызовы наращивают счётчик ссылок, чтобы каждый оператор,
6403
- пишущий в этот сигнал, был сбалансирован своим {@link unmarkDerived} при dispose.
7265
+ Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для
7266
+ обязательности). В отличие от других number-валидаторов, **не** пропускает не-числа
7267
+ и `NaN` — это его задача.
6404
7268
 
6405
7269
  **Signature:**
6406
7270
  ```typescript
6407
- export function markDerived(signal: Signal<any>): void
7271
+ export function isNumber<TForm = unknown, TField extends number | null | undefined = number>(
7272
+ options?: ValidateOptions
7273
+ ): Validator<TForm, TField>
6408
7274
  ```
6409
7275
 
6410
7276
  **Parameters:**
6411
- - `signal` — - Сигнал значения из {@link FormModel}, которым владеет compute
7277
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
7278
+
7279
+ **Returns:** Чистый валидатор {@link Validator} для числового поля
6412
7280
 
6413
7281
  **Examples:**
6414
7282
 
7283
+ Проверка, что значение — число
6415
7284
  ```typescript
6416
- import { markDerived } from '@reformer/core';
7285
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7286
+ import { required, isNumber } from '@reformer/core/validators';
7287
+
7288
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7289
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7290
+ validate(model.$.amount, [required(), isNumber({ message: 'Введите число' })]);
7291
+ });
7292
+ ```
7293
+
7294
+ _Source: src/form/validators/is-number.ts_
7295
+
7296
+ ### makeElementFieldHandle
7297
+
7298
+ **Kind:** `function`
7299
+
7300
+ Собирает базовый {@link FieldHandle}, делегирующий на DOM-элемент по ссылке `el`.
7301
+ Все вызовы null-safe: до монтирования (`el.current === null`) — no-op, без исключений.
7302
+
7303
+ **Signature:**
7304
+ ```typescript
7305
+ export function makeElementFieldHandle(el: RefObject<HTMLElement | null>): FieldHandle
7306
+ ```
7307
+
7308
+ _Source: src/platforms/react/field-binding/field-handle.ts_
7309
+
7310
+ ### markDerived
7311
+
7312
+ **Kind:** `function`
7313
+
7314
+ Пометить сигнал производным (вычисляемым).
7315
+
7316
+ Вызывается операторами {@link computeFrom}/`compute` для их сигнала-цели. После этого
7317
+ bulk-сеттеры (`model.set`/`model.patch`, `patchValue`/`setValue`) пропускают это поле, чтобы
7318
+ значение из payload не затирало вычисляемое. Прикладной код напрямую обычно не вызывает.
7319
+
7320
+ Идемпотентна на уровне флага: повторные вызовы наращивают счётчик ссылок, чтобы каждый оператор,
7321
+ пишущий в этот сигнал, был сбалансирован своим {@link unmarkDerived} при dispose.
7322
+
7323
+ **Signature:**
7324
+ ```typescript
7325
+ export function markDerived(signal: Signal<any>): void
7326
+ ```
7327
+
7328
+ **Parameters:**
7329
+ - `signal` — - Сигнал значения из {@link FormModel}, которым владеет compute
7330
+
7331
+ **Examples:**
7332
+
7333
+ ```typescript
7334
+ import { markDerived } from '@reformer/core';
6417
7335
 
6418
7336
  // Помечаем поле total как вычисляемое — bulk-set его не перезапишет
6419
7337
  markDerived(model.$.total);
@@ -6425,6 +7343,550 @@ markDerived(model.$.total);
6425
7343
 
6426
7344
  _Source: src/model/derived-registry.ts_
6427
7345
 
7346
+ ### matchesFileAccept
7347
+
7348
+ **Kind:** `function`
7349
+
7350
+ Проверяет файл против accept-паттерна в синтаксисе нативного атрибута
7351
+ `<input type="file" accept>`: список через запятую из расширений (`.pdf`),
7352
+ точных MIME (`image/png`) и wildcard-категорий (`image/*`). Регистронезависимо.
7353
+
7354
+ Нужен собственный матчер, потому что `accept` у нативного input — только подсказка
7355
+ пикеру: на drag-and-drop и на программное значение он не действует.
7356
+
7357
+ **Signature:**
7358
+ ```typescript
7359
+ export function matchesFileAccept(file: Pick<FileLike, 'name' | 'type'>, accept: string): boolean
7360
+ ```
7361
+
7362
+ **Parameters:**
7363
+ - `file` — - Файлоподобное значение (`name` + опциональный `type`)
7364
+ - `accept` — - Accept-строка; пустая/пробельная строка матчит всё
7365
+
7366
+ **Returns:** `true`, если файл подходит хотя бы под один паттерн
7367
+
7368
+ _Source: src/form/validators/file-utils.ts_
7369
+
7370
+ ### max
7371
+
7372
+ **Kind:** `function`
7373
+
7374
+ Фабрика валидатора максимального числового значения.
7375
+
7376
+ Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
7377
+
7378
+ **Signature:**
7379
+ ```typescript
7380
+ export function max<TForm = unknown, TField extends number | null | undefined = number>(
7381
+ maxValue: number,
7382
+ options?: ValidateOptions
7383
+ ): Validator<TForm, TField>
7384
+ ```
7385
+
7386
+ **Parameters:**
7387
+ - `maxValue` — - Максимально допустимое значение (включительно)
7388
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7389
+ попадают `max` и `actual`.
7390
+
7391
+ **Returns:** Чистый валидатор {@link Validator} для числового поля
7392
+
7393
+ **Examples:**
7394
+
7395
+ Максимальное значение числового поля
7396
+ ```typescript
7397
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7398
+ import { required, max } from '@reformer/core/validators';
7399
+
7400
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7401
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7402
+ validate(model.$.quantity, [max(100)]);
7403
+ validate(model.$.discount, [required(), max(50, { message: 'Не более 50%' })]);
7404
+ });
7405
+ ```
7406
+
7407
+ _Source: src/form/validators/max.ts_
7408
+
7409
+ ### maxAge
7410
+
7411
+ **Kind:** `function`
7412
+
7413
+ Фабрика валидатора максимального возраста (по дате рождения).
7414
+
7415
+ Возраст вычисляется по дате рождения относительно сегодняшнего дня. Пустые и невалидные
7416
+ даты пропускаются (используйте {@link required} и {@link isDate}).
7417
+
7418
+ **Signature:**
7419
+ ```typescript
7420
+ export function maxAge<
7421
+ TForm = unknown,
7422
+ TField extends string | Date | null | undefined = string | Date,
7423
+ >(maxAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>
7424
+ ```
7425
+
7426
+ **Parameters:**
7427
+ - `maxAgeValue` — - Максимально допустимый возраст (в полных годах)
7428
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7429
+ попадают `maxAge` и `currentAge`.
7430
+
7431
+ **Returns:** Чистый валидатор {@link Validator} для поля даты рождения (`string | Date`)
7432
+
7433
+ **Examples:**
7434
+
7435
+ Максимальный возраст
7436
+ ```typescript
7437
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7438
+ import { maxAge } from '@reformer/core/validators';
7439
+
7440
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7441
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7442
+ validate(model.$.birthDate, [maxAge(100, { message: 'Проверьте дату рождения' })]);
7443
+ });
7444
+ ```
7445
+
7446
+ _Source: src/form/validators/max-age.ts_
7447
+
7448
+ ### maxDate
7449
+
7450
+ **Kind:** `function`
7451
+
7452
+ Фабрика валидатора максимальной даты (включительно).
7453
+
7454
+ Сравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты
7455
+ пропускаются (используйте {@link required} и {@link isDate}).
7456
+
7457
+ **Signature:**
7458
+ ```typescript
7459
+ export function maxDate<
7460
+ TForm = unknown,
7461
+ TField extends string | Date | null | undefined = string | Date,
7462
+ >(maxDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>
7463
+ ```
7464
+
7465
+ **Parameters:**
7466
+ - `maxDateValue` — - Максимально допустимая дата (включительно)
7467
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7468
+ попадает `maxDate`.
7469
+
7470
+ **Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
7471
+
7472
+ **Examples:**
7473
+
7474
+ Максимальная дата
7475
+ ```typescript
7476
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7477
+ import { maxDate } from '@reformer/core/validators';
7478
+
7479
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7480
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7481
+ validate(model.$.birthDate, [maxDate(new Date(), { message: 'Дата не может быть в будущем' })]);
7482
+ });
7483
+ ```
7484
+
7485
+ _Source: src/form/validators/max-date.ts_
7486
+
7487
+ ### maxFiles
7488
+
7489
+ **Kind:** `function`
7490
+
7491
+ Фабрика валидатора максимального количества файлов.
7492
+
7493
+ Работает с массивом файлов (одиночный файл считается как 1). Пустые значения
7494
+ (`null`/`undefined`/`''`/`[]`) пропускаются (используйте {@link required}
7495
+ для обязательности).
7496
+
7497
+ **Signature:**
7498
+ ```typescript
7499
+ export function maxFiles<TForm = unknown, TField = unknown>(
7500
+ max: number,
7501
+ options?: ValidateOptions
7502
+ ): Validator<TForm, TField>
7503
+ ```
7504
+
7505
+ **Parameters:**
7506
+ - `max` — - Максимально допустимое количество файлов (включительно)
7507
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7508
+ попадают `maxFiles` и `actualCount`.
7509
+
7510
+ **Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
7511
+
7512
+ **Examples:**
7513
+
7514
+ Не более трёх вложений
7515
+ ```typescript
7516
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7517
+ import { maxFiles } from '@reformer/core/validators';
7518
+
7519
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7520
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7521
+ validate(model.$.documents, [maxFiles(3, { message: 'Максимум 3 файла' })]);
7522
+ });
7523
+ ```
7524
+
7525
+ _Source: src/form/validators/max-files.ts_
7526
+
7527
+ ### maxFileSize
7528
+
7529
+ **Kind:** `function`
7530
+
7531
+ Фабрика валидатора максимального размера файла.
7532
+
7533
+ Работает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с
7534
+ `name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.
7535
+ Пустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`
7536
+ (например, дескриптор загруженного файла без размера) пропускаются
7537
+ (используйте {@link required} для обязательности).
7538
+
7539
+ **Signature:**
7540
+ ```typescript
7541
+ export function maxFileSize<TForm = unknown, TField = unknown>(
7542
+ maxSize: number,
7543
+ options?: ValidateOptions
7544
+ ): Validator<TForm, TField>
7545
+ ```
7546
+
7547
+ **Parameters:**
7548
+ - `maxSize` — - Максимально допустимый размер файла в байтах (включительно)
7549
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7550
+ попадают `maxFileSize`, `fileName` и `actualSize`.
7551
+
7552
+ **Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
7553
+
7554
+ **Examples:**
7555
+
7556
+ Ограничение размера вложений
7557
+ ```typescript
7558
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7559
+ import { maxFileSize } from '@reformer/core/validators';
7560
+
7561
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7562
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7563
+ validate(model.$.documents, [maxFileSize(5 * 1024 * 1024, { message: 'Файл больше 5 МБ' })]);
7564
+ });
7565
+ ```
7566
+
7567
+ _Source: src/form/validators/max-file-size.ts_
7568
+
7569
+ ### maxLength
7570
+
7571
+ **Kind:** `function`
7572
+
7573
+ Фабрика валидатора максимальной длины строки или массива.
7574
+
7575
+ Работает со строкой или массивом (проверяется `value.length`). Пустые значения
7576
+ (`null`/`undefined`/`''`) и значения без числового `length` пропускаются
7577
+ (используйте {@link required} для обязательности).
7578
+
7579
+ **Signature:**
7580
+ ```typescript
7581
+ export function maxLength<TForm = unknown, TField = unknown>(
7582
+ maxLen: number,
7583
+ options?: ValidateOptions
7584
+ ): Validator<TForm, TField>
7585
+ ```
7586
+
7587
+ **Parameters:**
7588
+ - `maxLen` — - Максимально допустимая длина (включительно)
7589
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7590
+ попадают `maxLength` и `actualLength`.
7591
+
7592
+ **Returns:** Чистый валидатор {@link Validator} для строки или массива
7593
+
7594
+ **Examples:**
7595
+
7596
+ Максимальная длина строки
7597
+ ```typescript
7598
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7599
+ import { maxLength } from '@reformer/core/validators';
7600
+
7601
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7602
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7603
+ validate(model.$.name, [maxLength(50)]);
7604
+ validate(model.$.bio, [maxLength(500, { message: 'Максимум 500 символов' })]);
7605
+ });
7606
+ ```
7607
+
7608
+ _Source: src/form/validators/max-length.ts_
7609
+
7610
+ ### maxTotalFileSize
7611
+
7612
+ **Kind:** `function`
7613
+
7614
+ Фабрика валидатора суммарного размера всех файлов.
7615
+
7616
+ Складывает `size` всех файлов значения (элементы без числового `size` в сумму
7617
+ не входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются
7618
+ (используйте {@link required} для обязательности).
7619
+
7620
+ **Signature:**
7621
+ ```typescript
7622
+ export function maxTotalFileSize<TForm = unknown, TField = unknown>(
7623
+ maxTotal: number,
7624
+ options?: ValidateOptions
7625
+ ): Validator<TForm, TField>
7626
+ ```
7627
+
7628
+ **Parameters:**
7629
+ - `maxTotal` — - Максимально допустимый суммарный размер в байтах (включительно)
7630
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7631
+ попадают `maxTotalFileSize` и `actualTotal`.
7632
+
7633
+ **Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
7634
+
7635
+ **Examples:**
7636
+
7637
+ Суммарно не более 20 МБ
7638
+ ```typescript
7639
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7640
+ import { maxTotalFileSize } from '@reformer/core/validators';
7641
+
7642
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7643
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7644
+ validate(model.$.documents, [maxTotalFileSize(20 * 1024 * 1024, { message: 'Суммарно не более 20 МБ' })]);
7645
+ });
7646
+ ```
7647
+
7648
+ _Source: src/form/validators/max-total-file-size.ts_
7649
+
7650
+ ### min
7651
+
7652
+ **Kind:** `function`
7653
+
7654
+ Фабрика валидатора минимального числового значения.
7655
+
7656
+ Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
7657
+
7658
+ **Signature:**
7659
+ ```typescript
7660
+ export function min<TForm = unknown, TField extends number | null | undefined = number>(
7661
+ minValue: number,
7662
+ options?: ValidateOptions
7663
+ ): Validator<TForm, TField>
7664
+ ```
7665
+
7666
+ **Parameters:**
7667
+ - `minValue` — - Минимально допустимое значение (включительно)
7668
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7669
+ попадают `min` и `actual`.
7670
+
7671
+ **Returns:** Чистый валидатор {@link Validator} для числового поля
7672
+
7673
+ **Examples:**
7674
+
7675
+ Минимальное значение числового поля
7676
+ ```typescript
7677
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7678
+ import { required, min } from '@reformer/core/validators';
7679
+
7680
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7681
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7682
+ validate(model.$.age, [min(18)]);
7683
+ validate(model.$.quantity, [required(), min(1, { message: 'Минимум 1' })]);
7684
+ });
7685
+ ```
7686
+
7687
+ _Source: src/form/validators/min.ts_
7688
+
7689
+ ### minAge
7690
+
7691
+ **Kind:** `function`
7692
+
7693
+ Фабрика валидатора минимального возраста (по дате рождения).
7694
+
7695
+ Возраст вычисляется по дате рождения относительно сегодняшнего дня. Пустые и невалидные
7696
+ даты пропускаются (используйте {@link required} и {@link isDate}).
7697
+
7698
+ **Signature:**
7699
+ ```typescript
7700
+ export function minAge<
7701
+ TForm = unknown,
7702
+ TField extends string | Date | null | undefined = string | Date,
7703
+ >(minAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>
7704
+ ```
7705
+
7706
+ **Parameters:**
7707
+ - `minAgeValue` — - Минимально допустимый возраст (в полных годах)
7708
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7709
+ попадают `minAge` и `currentAge`.
7710
+
7711
+ **Returns:** Чистый валидатор {@link Validator} для поля даты рождения (`string | Date`)
7712
+
7713
+ **Examples:**
7714
+
7715
+ Минимальный возраст
7716
+ ```typescript
7717
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7718
+ import { required, minAge } from '@reformer/core/validators';
7719
+
7720
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7721
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7722
+ validate(model.$.birthDate, [required(), minAge(18, { message: 'Вам должно быть не менее 18 лет' })]);
7723
+ });
7724
+ ```
7725
+
7726
+ _Source: src/form/validators/min-age.ts_
7727
+
7728
+ ### minDate
7729
+
7730
+ **Kind:** `function`
7731
+
7732
+ Фабрика валидатора минимальной даты (включительно).
7733
+
7734
+ Сравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты
7735
+ пропускаются (используйте {@link required} и {@link isDate}).
7736
+
7737
+ **Signature:**
7738
+ ```typescript
7739
+ export function minDate<
7740
+ TForm = unknown,
7741
+ TField extends string | Date | null | undefined = string | Date,
7742
+ >(minDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>
7743
+ ```
7744
+
7745
+ **Parameters:**
7746
+ - `minDateValue` — - Минимально допустимая дата (включительно)
7747
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7748
+ попадает `minDate`.
7749
+
7750
+ **Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
7751
+
7752
+ **Examples:**
7753
+
7754
+ Минимальная дата
7755
+ ```typescript
7756
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7757
+ import { required, minDate } from '@reformer/core/validators';
7758
+
7759
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7760
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7761
+ validate(model.$.startDate, [required(), minDate(new Date(), { message: 'Дата не раньше сегодня' })]);
7762
+ });
7763
+ ```
7764
+
7765
+ _Source: src/form/validators/min-date.ts_
7766
+
7767
+ ### minFiles
7768
+
7769
+ **Kind:** `function`
7770
+
7771
+ Фабрика валидатора минимального количества файлов.
7772
+
7773
+ Работает с массивом файлов (одиночный файл считается как 1). Пустые значения
7774
+ (`null`/`undefined`/`''`/`[]`) пропускаются — «нужен хотя бы один файл» выражается
7775
+ связкой `required()` + `minFiles(n)`, как и у {@link minLength}.
7776
+
7777
+ **Signature:**
7778
+ ```typescript
7779
+ export function minFiles<TForm = unknown, TField = unknown>(
7780
+ min: number,
7781
+ options?: ValidateOptions
7782
+ ): Validator<TForm, TField>
7783
+ ```
7784
+
7785
+ **Parameters:**
7786
+ - `min` — - Минимально допустимое количество файлов (включительно)
7787
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7788
+ попадают `minFiles` и `actualCount`.
7789
+
7790
+ **Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
7791
+
7792
+ **Examples:**
7793
+
7794
+ Не менее двух документов
7795
+ ```typescript
7796
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7797
+ import { required, minFiles } from '@reformer/core/validators';
7798
+
7799
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7800
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7801
+ validate(model.$.documents, [required(), minFiles(2, { message: 'Приложите минимум 2 документа' })]);
7802
+ });
7803
+ ```
7804
+
7805
+ _Source: src/form/validators/min-files.ts_
7806
+
7807
+ ### minFileSize
7808
+
7809
+ **Kind:** `function`
7810
+
7811
+ Фабрика валидатора минимального размера файла.
7812
+
7813
+ Работает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с
7814
+ `name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.
7815
+ Типовой сценарий — отсев пустых (0-байтовых) файлов: `minFileSize(1)`.
7816
+ Пустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`
7817
+ пропускаются (используйте {@link required} для обязательности).
7818
+
7819
+ **Signature:**
7820
+ ```typescript
7821
+ export function minFileSize<TForm = unknown, TField = unknown>(
7822
+ minSize: number,
7823
+ options?: ValidateOptions
7824
+ ): Validator<TForm, TField>
7825
+ ```
7826
+
7827
+ **Parameters:**
7828
+ - `minSize` — - Минимально допустимый размер файла в байтах (включительно)
7829
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7830
+ попадают `minFileSize`, `fileName` и `actualSize`.
7831
+
7832
+ **Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
7833
+
7834
+ **Examples:**
7835
+
7836
+ Отсев пустых файлов
7837
+ ```typescript
7838
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7839
+ import { minFileSize } from '@reformer/core/validators';
7840
+
7841
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7842
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7843
+ validate(model.$.documents, [minFileSize(1, { message: 'Файл пустой' })]);
7844
+ });
7845
+ ```
7846
+
7847
+ _Source: src/form/validators/min-file-size.ts_
7848
+
7849
+ ### minLength
7850
+
7851
+ **Kind:** `function`
7852
+
7853
+ Фабрика валидатора минимальной длины строки или массива.
7854
+
7855
+ Работает со строкой или массивом (проверяется `value.length`). Пустые значения
7856
+ (`null`/`undefined`/`''`) и значения без числового `length` пропускаются
7857
+ (используйте {@link required} для обязательности).
7858
+
7859
+ **Signature:**
7860
+ ```typescript
7861
+ export function minLength<TForm = unknown, TField = unknown>(
7862
+ minLen: number,
7863
+ options?: ValidateOptions
7864
+ ): Validator<TForm, TField>
7865
+ ```
7866
+
7867
+ **Parameters:**
7868
+ - `minLen` — - Минимально допустимая длина (включительно)
7869
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
7870
+ попадают `minLength` и `actualLength`.
7871
+
7872
+ **Returns:** Чистый валидатор {@link Validator} для строки или массива
7873
+
7874
+ **Examples:**
7875
+
7876
+ Минимальная длина строки
7877
+ ```typescript
7878
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
7879
+ import { required, minLength } from '@reformer/core/validators';
7880
+
7881
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
7882
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
7883
+ validate(model.$.name, [minLength(2)]);
7884
+ validate(model.$.password, [required(), minLength(8, { message: 'Минимум 8 символов' })]);
7885
+ });
7886
+ ```
7887
+
7888
+ _Source: src/form/validators/min-length.ts_
7889
+
6428
7890
  ### ModelApi
6429
7891
 
6430
7892
  **Kind:** `interface`
@@ -6678,6 +8140,44 @@ export type ModelValue<V> =
6678
8140
 
6679
8141
  _Source: src/model/types.ts_
6680
8142
 
8143
+ ### multipleOf
8144
+
8145
+ **Kind:** `function`
8146
+
8147
+ Фабрика валидатора, проверяющего что число кратно заданному.
8148
+
8149
+ Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
8150
+
8151
+ **Signature:**
8152
+ ```typescript
8153
+ export function multipleOf<TForm = unknown, TField extends number | null | undefined = number>(
8154
+ divisor: number,
8155
+ options?: ValidateOptions
8156
+ ): Validator<TForm, TField>
8157
+ ```
8158
+
8159
+ **Parameters:**
8160
+ - `divisor` — - Делитель: значение должно быть кратно ему
8161
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
8162
+ попадает `multipleOf` (делитель).
8163
+
8164
+ **Returns:** Чистый валидатор {@link Validator} для числового поля
8165
+
8166
+ **Examples:**
8167
+
8168
+ Проверка кратности
8169
+ ```typescript
8170
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
8171
+ import { multipleOf } from '@reformer/core/validators';
8172
+
8173
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
8174
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
8175
+ validate(model.$.rating, [multipleOf(0.5, { message: 'Только шаг 0.5' })]);
8176
+ });
8177
+ ```
8178
+
8179
+ _Source: src/form/validators/multiple-of.ts_
8180
+
6681
8181
  ### NodeFactory
6682
8182
 
6683
8183
  **Kind:** `class`
@@ -6708,6 +8208,7 @@ export class NodeFactory {
6708
8208
  * ```typescript
6709
8209
  * const factory = new NodeFactory();
6710
8210
  *
8211
+ * Конфиг node-level (не layout-схема M1, где `validators` уже нет).
6711
8212
  * // FieldNode
6712
8213
  * const field = factory.createNode({
6713
8214
  * value: 'test@mail.com',
@@ -6740,13 +8241,157 @@ export class NodeFactory {
6740
8241
  **Examples:**
6741
8242
 
6742
8243
  ```typescript
6743
- import { NodeFactory } from '@reformer/core';
8244
+ import { NodeFactory } from '@reformer/core';
8245
+
8246
+ const factory = new NodeFactory();
8247
+ const node = factory.createNode({ value: '', component: Input }); // → FieldNode<string>
8248
+ ```
8249
+
8250
+ _Source: src/form/factories/node-factory.ts_
8251
+
8252
+ ### nonNegative
8253
+
8254
+ **Kind:** `function`
8255
+
8256
+ Фабрика валидатора, проверяющего что число неотрицательное (`≥ 0`).
8257
+
8258
+ Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
8259
+
8260
+ **Signature:**
8261
+ ```typescript
8262
+ export function nonNegative<TForm = unknown, TField extends number | null | undefined = number>(
8263
+ options?: ValidateOptions
8264
+ ): Validator<TForm, TField>
8265
+ ```
8266
+
8267
+ **Parameters:**
8268
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
8269
+
8270
+ **Returns:** Чистый валидатор {@link Validator} для числового поля
8271
+
8272
+ **Examples:**
8273
+
8274
+ Проверка неотрицательности
8275
+ ```typescript
8276
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
8277
+ import { nonNegative } from '@reformer/core/validators';
8278
+
8279
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
8280
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
8281
+ validate(model.$.balance, [nonNegative({ message: 'Баланс не может быть отрицательным' })]);
8282
+ });
8283
+ ```
8284
+
8285
+ _Source: src/form/validators/non-negative.ts_
8286
+
8287
+ ### nonZero
8288
+
8289
+ **Kind:** `function`
8290
+
8291
+ Фабрика валидатора, проверяющего что число не равно нулю.
8292
+
8293
+ Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
8294
+
8295
+ **Signature:**
8296
+ ```typescript
8297
+ export function nonZero<TForm = unknown, TField extends number | null | undefined = number>(
8298
+ options?: ValidateOptions
8299
+ ): Validator<TForm, TField>
8300
+ ```
8301
+
8302
+ **Parameters:**
8303
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
8304
+
8305
+ **Returns:** Чистый валидатор {@link Validator} для числового поля
8306
+
8307
+ **Examples:**
8308
+
8309
+ Проверка «не ноль»
8310
+ ```typescript
8311
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
8312
+ import { nonZero } from '@reformer/core/validators';
8313
+
8314
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
8315
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
8316
+ validate(model.$.divisor, [nonZero({ message: 'Не может быть нулём' })]);
8317
+ });
8318
+ ```
8319
+
8320
+ _Source: src/form/validators/non-zero.ts_
8321
+
8322
+ ### onChange
8323
+
8324
+ **Kind:** `function`
8325
+
8326
+ Реакция на изменение поля; `{ debounce, immediate }`.
8327
+
8328
+ Колбэк выполняется ВНЕ effect-контекста (микротаск/таймер) — можно безопасно писать сигналы и ноды
8329
+ (`updateComponentProps`/`reset`/`clear`) без ручного `defer` и без «Cycle detected».
8330
+
8331
+ Для async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения
8332
+ предыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй
8333
+ `signal.aborted` перед применением результата — это убирает гонки устаревших ответов (F2).
8334
+
8335
+ **Signature:**
8336
+ ```typescript
8337
+ export function onChange<T>(
8338
+ source: ReadonlySignal<T>,
8339
+ cb: (value: T, ctx: ChangeContext) => void,
8340
+ options?: { immediate?: boolean; debounce?: number }
8341
+ ): void
8342
+ ```
8343
+
8344
+ _Source: src/form/behaviors/operators.ts_
8345
+
8346
+ ### onDispose
8347
+
8348
+ **Kind:** `function`
8349
+
8350
+ Зарегистрировать произвольную отписку в активной схеме.
8351
+
8352
+ **Signature:**
8353
+ ```typescript
8354
+ export function onDispose(cleanup: BehaviorCleanup): void
8355
+ ```
8356
+
8357
+ _Source: src/form/behaviors/context.ts_
8358
+
8359
+ ### pastDate
8360
+
8361
+ **Kind:** `function`
8362
+
8363
+ Фабрика валидатора, проверяющего что дата не в будущем.
8364
+
8365
+ Дата не должна быть позже сегодняшнего дня (сравнение по нормализованным датам).
8366
+ Пустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).
8367
+
8368
+ **Signature:**
8369
+ ```typescript
8370
+ export function pastDate<
8371
+ TForm = unknown,
8372
+ TField extends string | Date | null | undefined = string | Date,
8373
+ >(options?: ValidateOptions): Validator<TForm, TField>
8374
+ ```
8375
+
8376
+ **Parameters:**
8377
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
8378
+
8379
+ **Returns:** Чистый валидатор {@link Validator} для поля даты (`string | Date`)
8380
+
8381
+ **Examples:**
8382
+
8383
+ Дата не в будущем
8384
+ ```typescript
8385
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
8386
+ import { required, pastDate } from '@reformer/core/validators';
6744
8387
 
6745
- const factory = new NodeFactory();
6746
- const node = factory.createNode({ value: '', component: Input }); // → FieldNode<string>
8388
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
8389
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
8390
+ validate(model.$.birthDate, [required(), pastDate({ message: 'Дата рождения не может быть в будущем' })]);
8391
+ });
6747
8392
  ```
6748
8393
 
6749
- _Source: src/form/factories/node-factory.ts_
8394
+ _Source: src/form/validators/past-date.ts_
6750
8395
 
6751
8396
  ### PathAwareSignal
6752
8397
 
@@ -6765,6 +8410,118 @@ export type PathAwareSignal<T> = Signal<T> & {
6765
8410
 
6766
8411
  _Source: src/model/types.ts_
6767
8412
 
8413
+ ### pattern
8414
+
8415
+ **Kind:** `function`
8416
+
8417
+ Фабрика валидатора регулярного выражения.
8418
+
8419
+ Пустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}
8420
+ для обязательности).
8421
+
8422
+ **Signature:**
8423
+ ```typescript
8424
+ export function pattern<TForm = unknown, TField extends string | null | undefined = string>(
8425
+ regex: RegExp,
8426
+ options?: ValidateOptions
8427
+ ): Validator<TForm, TField>
8428
+ ```
8429
+
8430
+ **Parameters:**
8431
+ - `regex` — - Регулярное выражение для проверки значения
8432
+ - `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
8433
+ попадает `pattern` (строка-источник regex).
8434
+
8435
+ **Returns:** Чистый валидатор {@link Validator} для строкового поля
8436
+
8437
+ **Examples:**
8438
+
8439
+ Проверка по регулярному выражению
8440
+ ```typescript
8441
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
8442
+ import { required, pattern } from '@reformer/core/validators';
8443
+
8444
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
8445
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
8446
+ validate(model.$.name, [pattern(/^[a-zA-Zа-яА-Я]+$/, { message: 'Только буквы' })]);
8447
+ validate(model.$.phone, [required(), pattern(/^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$/, { message: 'Формат +7 (999) 123-45-67' })]);
8448
+ });
8449
+ ```
8450
+
8451
+ _Source: src/form/validators/pattern.ts_
8452
+
8453
+ ### phone
8454
+
8455
+ **Kind:** `function`
8456
+
8457
+ Фабрика валидатора номера телефона.
8458
+
8459
+ Проверяет значение по regex выбранного {@link PhoneFormat}. Пустые значения
8460
+ (`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
8461
+
8462
+ **Signature:**
8463
+ ```typescript
8464
+ export function phone<TForm = unknown, TField extends string | null | undefined = string>(
8465
+ options?: PhoneValidatorOptions
8466
+ ): Validator<TForm, TField>
8467
+ ```
8468
+
8469
+ **Parameters:**
8470
+ - `options` — - Опции валидатора {@link PhoneValidatorOptions}. В `params` ошибки
8471
+ автоматически попадает выбранный `format`.
8472
+
8473
+ **Returns:** Чистый валидатор {@link Validator} для строкового поля
8474
+
8475
+ **Examples:**
8476
+
8477
+ Проверка номера телефона
8478
+ ```typescript
8479
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
8480
+ import { required, phone } from '@reformer/core/validators';
8481
+
8482
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
8483
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
8484
+ validate(model.$.phone, [required(), phone({ format: 'ru' })]);
8485
+ });
8486
+ ```
8487
+
8488
+ _Source: src/form/validators/phone.ts_
8489
+
8490
+ ### PhoneFormat
8491
+
8492
+ **Kind:** `type`
8493
+
8494
+ Формат проверки номера телефона для валидатора {@link phone}.
8495
+
8496
+ - `international` — международный формат E.164 (`+?[1-9]\d{1,14}`);
8497
+ - `ru` — российские номера (`+7`/`7`/`8`, коды `4`/`8`/`9`, с разделителями);
8498
+ - `us` — североамериканские номера (NANP);
8499
+ - `any` — свободный формат: цифры, скобки и разделители (по умолчанию).
8500
+
8501
+ **Signature:**
8502
+ ```typescript
8503
+ export type PhoneFormat = 'international' | 'ru' | 'us' | 'any';
8504
+ ```
8505
+
8506
+ _Source: src/form/validators/phone.ts_
8507
+
8508
+ ### PhoneValidatorOptions
8509
+
8510
+ **Kind:** `interface`
8511
+
8512
+ Опции валидатора {@link phone}. Расширяют {@link ValidateOptions} (`message`, `params`)
8513
+ выбором формата номера.
8514
+
8515
+ **Signature:**
8516
+ ```typescript
8517
+ export interface PhoneValidatorOptions extends ValidateOptions {
8518
+ /** Формат проверки {@link PhoneFormat}. По умолчанию `'any'`. */
8519
+ format?: PhoneFormat;
8520
+ }
8521
+ ```
8522
+
8523
+ _Source: src/form/validators/phone.ts_
8524
+
6768
8525
  ### registerSignalNode
6769
8526
 
6770
8527
  **Kind:** `function`
@@ -6799,6 +8556,47 @@ if (sig && node) registerSignalNode(sig, node);
6799
8556
 
6800
8557
  _Source: src/form/signal-node-registry.ts_
6801
8558
 
8559
+ ### required
8560
+
8561
+ **Kind:** `function`
8562
+
8563
+ Фабрика валидатора обязательного поля.
8564
+
8565
+ Возвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.
8566
+
8567
+ Пустыми считаются: `null`, `undefined`, `''` (пустая строка), `[]` (пустой массив —
8568
+ обязательный multi-select / FormArray без выбранных элементов).
8569
+ Для boolean полей требуется значение `true`.
8570
+
8571
+ **Signature:**
8572
+ ```typescript
8573
+ export function required<TForm = unknown, TField = unknown>(
8574
+ options?: ValidateOptions
8575
+ ): Validator<TForm, TField>
8576
+ ```
8577
+
8578
+ **Parameters:**
8579
+ - `options` — - Опции валидатора ({@link ValidateOptions}): `message`, `params`
8580
+
8581
+ **Returns:** Чистый валидатор {@link Validator} для поля схемы
8582
+
8583
+ **Examples:**
8584
+
8585
+ Обязательные поля в схеме формы
8586
+ ```typescript
8587
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
8588
+ import { required } from '@reformer/core/validators';
8589
+
8590
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
8591
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
8592
+ validate(model.$.email, [required()]);
8593
+ validate(model.$.phone, [required({ message: 'Укажите номер телефона' })]);
8594
+ validate(model.$.agreeToTerms, [required({ message: 'Необходимо принять условия' })]);
8595
+ });
8596
+ ```
8597
+
8598
+ _Source: src/form/validators/required.ts_
8599
+
6802
8600
  ### resetWhen
6803
8601
 
6804
8602
  **Kind:** `function`
@@ -6845,6 +8643,25 @@ revalidateWhen([model.$.maxAmount], () => void validateModel(model, schema));
6845
8643
 
6846
8644
  _Source: src/model/behaviors-value.ts_
6847
8645
 
8646
+ ### Rule
8647
+
8648
+ **Kind:** `type`
8649
+
8650
+ Синхронное правило поля типа `TField`. Проверяется ТОЛЬКО значение (`value`).
8651
+
8652
+ Позиционные `scope`/`root` помечены `never` намеренно: так встроенные value-only фабрики
8653
+ (`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`
8654
+ ОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля
8655
+ (`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).
8656
+ На вызове раннер приводит правило к callable и передаёт `(value, model, model)`.
8657
+
8658
+ **Signature:**
8659
+ ```typescript
8660
+ export type Rule<TField> = (value: TField, scope: never, root: never) => ValidationError | null;
8661
+ ```
8662
+
8663
+ _Source: src/form/validation/types.ts_
8664
+
6848
8665
  ### runOutsideEffect
6849
8666
 
6850
8667
  **Kind:** `function`
@@ -7123,6 +8940,27 @@ syncFields(model.$.field1, model.$.field2);
7123
8940
 
7124
8941
  _Source: src/model/behaviors-value.ts_
7125
8942
 
8943
+ ### toFileArray
8944
+
8945
+ **Kind:** `function`
8946
+
8947
+ Нормализует значение file-поля в массив файлоподобных элементов.
8948
+
8949
+ Одиночный {@link FileLike} оборачивается в массив; из массива отбираются только
8950
+ файлоподобные элементы. Для прочих значений — `null` (валидатор пропускает).
8951
+
8952
+ **Signature:**
8953
+ ```typescript
8954
+ export function toFileArray(value: unknown): FileLike[] | null
8955
+ ```
8956
+
8957
+ **Parameters:**
8958
+ - `value` — - Значение поля (`FileLike`, `FileLike[]` или что-то ещё)
8959
+
8960
+ **Returns:** Массив файлоподобных элементов или `null`
8961
+
8962
+ _Source: src/form/validators/file-utils.ts_
8963
+
7126
8964
  ### transformValue
7127
8965
 
7128
8966
  **Kind:** `function`
@@ -7196,6 +9034,67 @@ onDispose(() => unmarkDerived(model.$.total)); // при снятии compute с
7196
9034
 
7197
9035
  _Source: src/model/derived-registry.ts_
7198
9036
 
9037
+ ### url
9038
+
9039
+ **Kind:** `function`
9040
+
9041
+ Фабрика валидатора URL.
9042
+
9043
+ Пустые значения (`''`/`null`/`undefined`) пропускаются (используйте {@link required}
9044
+ для обязательности). При `requireProtocol` протокол обязателен; `allowedProtocols`
9045
+ дополнительно ограничивает набор допустимых протоколов.
9046
+
9047
+ **Signature:**
9048
+ ```typescript
9049
+ export function url<TForm = unknown, TField extends string | null | undefined = string>(
9050
+ options?: UrlValidatorOptions
9051
+ ): Validator<TForm, TField>
9052
+ ```
9053
+
9054
+ **Parameters:**
9055
+ - `options` — - Опции валидатора {@link UrlValidatorOptions}
9056
+
9057
+ **Returns:** Чистый валидатор {@link Validator} для строкового поля
9058
+
9059
+ **Examples:**
9060
+
9061
+ Проверка URL
9062
+ ```typescript
9063
+ import { defineValidationSchema, validate } from '@reformer/core/validation';
9064
+ import { required, url } from '@reformer/core/validators';
9065
+
9066
+ // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
9067
+ const validation = defineValidationSchema<MyForm>(({ model }) => {
9068
+ validate(model.$.website, [required(), url({ message: 'Введите корректный URL' })]);
9069
+ // Требовать протокол и ограничить схему только https:
9070
+ validate(model.$.homepage, [url({ requireProtocol: true, allowedProtocols: ['https'] })]);
9071
+ });
9072
+ ```
9073
+
9074
+ _Source: src/form/validators/url.ts_
9075
+
9076
+ ### UrlValidatorOptions
9077
+
9078
+ **Kind:** `interface`
9079
+
9080
+ Опции валидатора {@link url}. Расширяют {@link ValidateOptions} (`message`, `params`)
9081
+ настройками проверки протокола.
9082
+
9083
+ **Signature:**
9084
+ ```typescript
9085
+ export interface UrlValidatorOptions extends ValidateOptions {
9086
+ /** Требовать наличие протокола (`http://` или `https://`). По умолчанию `false`. */
9087
+ requireProtocol?: boolean;
9088
+ /**
9089
+ * Список разрешённых протоколов (без `://`), например `['https']`. Если задан,
9090
+ * значение с иным протоколом даёт ошибку с кодом `url_protocol`.
9091
+ */
9092
+ allowedProtocols?: string[];
9093
+ }
9094
+ ```
9095
+
9096
+ _Source: src/form/validators/url.ts_
9097
+
7199
9098
  ### useArrayLength
7200
9099
 
7201
9100
  **Kind:** `function`
@@ -7234,6 +9133,33 @@ function ArrayRenderer({ arrayNode }) {
7234
9133
 
7235
9134
  _Source: src/platforms/react/hooks/useArrayLength.ts_
7236
9135
 
9136
+ ### useFieldHandleRef
9137
+
9138
+ **Kind:** `function`
9139
+
9140
+ Ref для контрола поля + императивный handle для потребителя.
9141
+
9142
+ Возвращённый ref вешается на контрол. На `outerRef` публикуется:
9143
+ - handle самого контрола, если тот его реализует (`useImperativeHandle` композита);
9144
+ - иначе — базовый {@link FieldHandle}, построенный из DOM-узла контрола.
9145
+
9146
+ Хук вызывается безусловно; вешать ref на контрол стоит только при заданном `outerRef` —
9147
+ так function-компонент без ref-поддержки не получает лишний ref.
9148
+
9149
+ **Signature:**
9150
+ ```typescript
9151
+ export function useFieldHandleRef<H = FieldHandle>(
9152
+ outerRef: Ref<H> | undefined
9153
+ ): RefObject<unknown>
9154
+ ```
9155
+
9156
+ **Parameters:**
9157
+ - `outerRef` — - ref потребителя (из `forwardRef` или `schema.node(sel).getRef()`).
9158
+
9159
+ **Returns:** ref, который нужно передать контролу.
9160
+
9161
+ _Source: src/platforms/react/field-binding/field-handle.ts_
9162
+
7237
9163
  ### useFormBundle
7238
9164
 
7239
9165
  **Kind:** `function`
@@ -7749,6 +9675,96 @@ export interface UseFormValidationResult {
7749
9675
 
7750
9676
  _Source: src/platforms/react/hooks/use-form-validation.ts_
7751
9677
 
9678
+ ### validate
9679
+
9680
+ **Kind:** `function`
9681
+
9682
+ Синхронные правила поля.
9683
+
9684
+ **Signature:**
9685
+ ```typescript
9686
+ export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void
9687
+ ```
9688
+
9689
+ **Examples:**
9690
+
9691
+ ```ts
9692
+ validate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);
9693
+ ```
9694
+
9695
+ _Source: src/form/validation/operators.ts_
9696
+
9697
+ ### validateAsync
9698
+
9699
+ **Kind:** `function`
9700
+
9701
+ Асинхронные правила поля (зеркалит движковое разделение `validators` / `asyncValidators`).
9702
+ Раннер дожидается их и прокидывает `AbortSignal` для отмены устаревших ответов.
9703
+
9704
+ **Signature:**
9705
+ ```typescript
9706
+ export function validateAsync<TField>(
9707
+ sig: PathAwareSignal<TField>,
9708
+ rules: AsyncRule<TField>[]
9709
+ ): void
9710
+ ```
9711
+
9712
+ **Examples:**
9713
+
9714
+ ```ts
9715
+ validateAsync(model.$.username, [async (v, { signal }) => {
9716
+ const r = await fetch(`/api/free?u=${v}`, { signal });
9717
+ return (await r.json()).free ? null : { code: 'taken', message: 'Занято' };
9718
+ }]);
9719
+ ```
9720
+
9721
+ _Source: src/form/validation/operators.ts_
9722
+
9723
+ ### validateModel
9724
+
9725
+ **Kind:** `function`
9726
+
9727
+ Провалидировать модель ЛЮБОЙ схемой (шаг или вся форма). Открывает ambient-окно на время
9728
+ синхронного прогона `schema`, дожидается async-правил, разносит ошибки по нодам формы и гасит
9729
+ поля, ставшие валидными. Возвращает `true`, если нет блокирующих ошибок (`severity:'warning'` не блокирует).
9730
+
9731
+ Гашение — на пару (model, schema): `validateModel(model, step2)` и `validateModel(model, form)` не мешают
9732
+ друг другу. Устаревший прогон (быстрый повторный вызов той же (model, schema)) отменяется через `AbortSignal`
9733
+ и возвращает `false` (**fail-closed** — отменённому результату нельзя доверять для submit).
9734
+
9735
+ ⚠️ `schema` должна быть СТАБИЛЬНОЙ ссылкой: отмена устаревших прогонов ключится по идентичности `schema`.
9736
+ Инлайн-стрелка (`validateModel(model, ({ model }) => …)`) каждый раз создаёт НОВЫЙ прогон без дедупликации —
9737
+ держите схемы в `const` / `defineValidationSchema`.
9738
+
9739
+ `options.touch` (§6): по завершении помечает `touched` ИМЕННО поля, которые схема проверяла
9740
+ (не всё поддерево). Ошибки становятся видимыми (`shouldShowError = invalid && (touched||dirty)`)
9741
+ без ручного `form.markAsTouched()`; при пошаговой валидации следующий шаг не показывается
9742
+ «тронутым» до ввода. Валидные проверенные поля тоже метятся — это безвредно (ошибка не покажется).
9743
+
9744
+ **Signature:**
9745
+ ```typescript
9746
+ export async function validateModel<T>(
9747
+ model: FormModel<T>,
9748
+ schema: ValidationSchema<T>,
9749
+ options?: { touch?: boolean }
9750
+ ): Promise<boolean>
9751
+ ```
9752
+
9753
+ **Parameters:**
9754
+ - `model` — - Модель данных формы.
9755
+ - `schema` — - Схема валидации (стабильная ссылка).
9756
+ - `options` — - `{ touch }` — пометить провалидированные поля `touched` для показа ошибок.
9757
+
9758
+ **Examples:**
9759
+
9760
+ ```ts
9761
+ const ok = await validateModel(model, step2Validation); // один шаг
9762
+ const all = await validateModel(model, formValidation); // вся форма
9763
+ const stepOk = await validateModel(model, step2Validation, { touch: true }); // + показать ошибки шага
9764
+ ```
9765
+
9766
+ _Source: src/form/validation/run.ts_
9767
+
7752
9768
  ### ValidateOptions
7753
9769
 
7754
9770
  **Kind:** `interface`
@@ -7769,6 +9785,20 @@ export interface ValidateOptions {
7769
9785
 
7770
9786
  _Source: src/form/types/validation-schema.ts_
7771
9787
 
9788
+ ### validateWhen
9789
+
9790
+ **Kind:** `function`
9791
+
9792
+ Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля
9793
+ ГАСЯТСЯ (пустой bucket → `setErrors([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).
9794
+
9795
+ **Signature:**
9796
+ ```typescript
9797
+ export function validateWhen(cond: () => boolean, cb: () => void): void
9798
+ ```
9799
+
9800
+ _Source: src/form/validation/operators.ts_
9801
+
7772
9802
  ### ValidationError
7773
9803
 
7774
9804
  **Kind:** `interface`
@@ -7788,6 +9818,54 @@ export interface ValidationError {
7788
9818
 
7789
9819
  _Source: src/form/types/contracts.ts_
7790
9820
 
9821
+ ### ValidationSchema
9822
+
9823
+ **Kind:** `type`
9824
+
9825
+ Схема валидации — обычная функция над (под)моделью. First-class значение (можно `apply`/тестировать/переиспользовать).
9826
+
9827
+ **Signature:**
9828
+ ```typescript
9829
+ export type ValidationSchema<T> = (ctx: { model: FormModel<T> }) => void;
9830
+ ```
9831
+
9832
+ _Source: src/form/validation/types.ts_
9833
+
9834
+ ### ValidationStrategyKind
9835
+
9836
+ **Kind:** `type`
9837
+
9838
+ Когда запускается schema-валидация:
9839
+ - `submit` (по умолчанию) — только явным вызовом `validate()` (на отправке);
9840
+ - `blur` — при потере фокуса поля (смена `touched` любого листа);
9841
+ - `change` — на каждый ввод (с опц. `debounce`);
9842
+ - `afterFirstSubmit` — тихо до первого `validate()`, затем live (см. `liveAfterSubmit`).
9843
+
9844
+ **Signature:**
9845
+ ```typescript
9846
+ export type ValidationStrategyKind = 'submit' | 'blur' | 'change' | 'afterFirstSubmit';
9847
+ ```
9848
+
9849
+ _Source: src/form/validation/strategy.ts_
9850
+
9851
+ ### ValidationStrategyOptions
9852
+
9853
+ **Kind:** `interface`
9854
+
9855
+ **Signature:**
9856
+ ```typescript
9857
+ export interface ValidationStrategyOptions {
9858
+ /** Стратегия запуска. Default `'submit'`. */
9859
+ strategy?: ValidationStrategyKind;
9860
+ /** Debounce (мс) для live-фаз (`change` и live-часть `afterFirstSubmit`). Default `0`. */
9861
+ debounce?: number;
9862
+ /** Режим live-фазы для `afterFirstSubmit`. Default `'change'`. */
9863
+ liveAfterSubmit?: 'change' | 'blur';
9864
+ }
9865
+ ```
9866
+
9867
+ _Source: src/form/validation/strategy.ts_
9868
+
7791
9869
  ### Validator
7792
9870
 
7793
9871
  **Kind:** `type`