@reformer/core 10.1.0 → 11.0.0-beta.1

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 (67) hide show
  1. package/dist/{behaviors-node-DUrqAqp1.js → behaviors-node-DGEFMTQu.js} +1 -1
  2. package/dist/{behaviors-value-lCpqicfa.js → behaviors-value-C7dSEhCc.js} +43 -57
  3. package/dist/behaviors.js +108 -107
  4. package/dist/derived-registry-CluZCQ7U.js +16 -0
  5. package/dist/form/form-submitter.d.ts +0 -4
  6. package/dist/form/hooks/types.d.ts +3 -3
  7. package/dist/form/hooks/use-form-validation.d.ts +48 -0
  8. package/dist/form/index.d.ts +2 -3
  9. package/dist/form/nodes/array-node.d.ts +0 -84
  10. package/dist/form/nodes/form-node.d.ts +0 -14
  11. package/dist/form/nodes/group-node.d.ts +0 -4
  12. package/dist/form/status-machine.d.ts +0 -38
  13. package/dist/form/types/contracts.d.ts +0 -6
  14. package/dist/form/types/deep-schema.d.ts +0 -9
  15. package/dist/form/types/index.d.ts +4 -38
  16. package/dist/form/types/schema-node.d.ts +13 -16
  17. package/dist/form/types/validation-schema.d.ts +0 -25
  18. package/dist/form/unique-id.d.ts +3 -11
  19. package/dist/form/validation-schema.d.ts +16 -3
  20. package/dist/form/validation-strategy.d.ts +55 -0
  21. package/dist/form/validators.d.ts +8 -3
  22. package/dist/form-model-DYCzGGKF.js +286 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +477 -648
  25. package/dist/runtime-token.d.ts +23 -0
  26. package/dist/state/form-model.d.ts +12 -1
  27. package/dist/state.js +17 -15
  28. package/dist/subscription-manager-705bls97.js +171 -0
  29. package/dist/validation.js +133 -81
  30. package/dist/validators.d.ts +0 -4
  31. package/dist/validators.js +88 -122
  32. package/docs/llms/01-api-reference.md +63 -0
  33. package/docs/llms/02-quick-start.md +143 -0
  34. package/docs/llms/03-api-signatures.md +273 -0
  35. package/docs/llms/04-common-patterns.md +202 -0
  36. package/docs/llms/05-common-mistakes.md +167 -0
  37. package/docs/llms/06-troubleshooting.md +11 -0
  38. package/docs/llms/07-complete-import.md +99 -0
  39. package/docs/llms/08-form-types.md +47 -0
  40. package/docs/llms/09-formschema.md +143 -0
  41. package/docs/llms/10-arrays.md +161 -0
  42. package/docs/llms/11-async-watchfield.md +53 -0
  43. package/docs/llms/12-array-cleanup.md +31 -0
  44. package/docs/llms/13-multi-step.md +91 -0
  45. package/docs/llms/14-extended-mistakes.md +98 -0
  46. package/docs/llms/15-project-structure.md +125 -0
  47. package/docs/llms/16-ui-components.md +189 -0
  48. package/docs/llms/17-nonexistent-api.md +93 -0
  49. package/docs/llms/18-conditional-fields.md +183 -0
  50. package/docs/llms/19-reading-values.md +59 -0
  51. package/docs/llms/20-compute-vs-watch.md +138 -0
  52. package/docs/llms/21-array-operations.md +113 -0
  53. package/docs/llms/22-cycle-detection.md +86 -0
  54. package/docs/llms/23-copy-from.md +141 -0
  55. package/docs/llms/24-sync-fields.md +114 -0
  56. package/docs/llms/25-reset-when.md +115 -0
  57. package/docs/llms/26-transform-value.md +136 -0
  58. package/docs/llms/27-revalidate-when.md +132 -0
  59. package/docs/llms/28-submit-and-reset.md +259 -0
  60. package/docs/llms/29-async-preload.md +193 -0
  61. package/docs/llms/30-type-safety-recipes.md +159 -0
  62. package/docs/llms/31-async-validator-debounce.md +135 -0
  63. package/docs/llms/32-async-options-loading.md +108 -0
  64. package/docs/llms/33-validation-strategy.md +193 -0
  65. package/llms.txt +435 -1580
  66. package/package.json +33 -10
  67. package/dist/form-model-C8Fi83k4.js +0 -439
@@ -0,0 +1,98 @@
1
+ ## 13. EXTENDED COMMON MISTAKES
2
+
3
+ ### Reuse via apply / applyEach — не дублируй код
4
+
5
+ ```typescript
6
+ import { defineFormBehavior, apply, applyEach, compute } from '@reformer/core/behaviors';
7
+
8
+ // ✅ apply — одна под-схема на несколько групп
9
+ const addressBehavior = defineFormBehavior<Address>(({ model }) => {
10
+ compute(model.$.full, () => `${model.city}, ${model.street}`);
11
+ });
12
+
13
+ const behavior = defineFormBehavior<Form>(({ model }) => {
14
+ apply([model.$.homeAddress, model.$.workAddress], addressBehavior);
15
+
16
+ // ✅ applyEach — под-схема на КАЖДЫЙ элемент массива (реагирует на add/remove)
17
+ applyEach(
18
+ model.$.items,
19
+ defineFormBehavior<Item>(({ model: row }) => {
20
+ compute(row.$.lineTotal, () => row.qty * row.price);
21
+ })
22
+ );
23
+ });
24
+ ```
25
+
26
+ ### Идемпотентность transformValue
27
+
28
+ ```typescript
29
+ // ❌ неидемпотентный transformer — бесконечный цикл setValue → callback → setValue
30
+ transformValue(model.$.field, (v) => `prefix-${v}`); // f(f(x)) ≠ f(x)
31
+
32
+ // ✅ guard «уже преобразовано»
33
+ transformValue(model.$.field, (v) => (v?.startsWith('prefix-') ? v : `prefix-${v}`));
34
+ ```
35
+
36
+ ### compute для производных полей вместо ручной синхронизации
37
+
38
+ ```typescript
39
+ // ❌ ручной onChange + запись — легко зациклить/забыть кейс
40
+ onChange(model.$.firstName, () => { model.fullName = `${model.firstName} ${model.lastName}`; });
41
+
42
+ // ✅ compute: цель не входит в источники → цикла нет, запись идемпотентна (peek-guard)
43
+ compute(model.$.fullName, () => [model.firstName, model.lastName].filter(Boolean).join(' '));
44
+ ```
45
+
46
+ ### Расходящийся цикл compute (Cycle detected)
47
+
48
+ Взаимные `compute`/`computeFrom` без стабилизации preact обрывает как «Cycle detected».
49
+ DSL перехватывает это и бросает понятную ошибку с именем поля. Решение — разорвать цикл
50
+ условием `when` или читать одну сторону через `peek()`:
51
+
52
+ ```typescript
53
+ // ❌ взаимный пересчёт без стабилизации
54
+ compute(model.$.a, () => model.b + 1);
55
+ compute(model.$.b, () => model.a + 1); // расходится → Cycle detected
56
+
57
+ // ✅ добавь стабилизирующее условие или однонаправленную зависимость
58
+ compute(model.$.total, () => model.price * model.qty); // одно направление
59
+ ```
60
+
61
+ ### Cross-field валидация — через `cross(sig, fn)`
62
+
63
+ Cross-field живёт в схеме валидации (`@reformer/core/validation`), а не в layout: правило вешается на
64
+ ПОЛЕ-НОСИТЕЛЬ ошибки оператором `cross(sig, fn)`, а `fn` читает снапшот текущего scope (`model.get()`).
65
+
66
+ ```typescript
67
+ import { defineValidationSchema, validate, cross, validateModel } from '@reformer/core/validation';
68
+ import { required } from '@reformer/core/validators';
69
+ import { revalidateWhen, type ValidationError } from '@reformer/core';
70
+
71
+ // ❌ старое (УДАЛЕНО): дерево { value, validators }, ModelValidator (value, scope, root), validateFormModel
72
+ const legacyRule: ModelValidator<number, unknown, Form> = (_value, _scope, root) =>
73
+ root.field1 > root.field2 ? { code: 'error', message: 'Invalid' } : null;
74
+ const legacySchema = { field1: { value: model.$.field1, component: Input, validators: [legacyRule] } };
75
+ validateFormModel(model, legacySchema);
76
+
77
+ // ✅ новое: cross-правило — обычная функция над снапшотом (model.get()), не читает scope/root
78
+ const field1LessThanField2 = (f: Form): ValidationError | null =>
79
+ f.field1 > f.field2 ? { code: 'error', message: 'Invalid' } : null;
80
+
81
+ const schema = defineValidationSchema<Form>(({ model }) => {
82
+ validate(model.$.field1, [required()]);
83
+ cross(model.$.field1, field1LessThanField2); // правило на поле-носителе ошибки
84
+ });
85
+
86
+ // Прогон — ТОЛЬКО внешним раннером; ошибки сами доезжают до нод формы.
87
+ // ⚠️ form.validate()/submit() схему валидации больше НЕ прогоняют.
88
+ await validateModel(model, schema);
89
+
90
+ // Перезапуск правила при изменении соседнего поля — мост «поведение → валидация»:
91
+ revalidateWhen([model.$.field2], () => void validateModel(model, schema));
92
+ ```
93
+
94
+ > **Актуальный vs удалённый API.** Операторы валидации `validate`/`validateAsync`/`validateWhen`/`cross`/`each`/`apply`
95
+ > ТЕПЕРЬ существуют — в `@reformer/core/validation` с новыми сигнатурами (ambient, активны только внутри прогона
96
+ > `validateModel`). УДАЛЕНЫ: `applyWhen`, `validateGroup`/`validateTree`/`validateForm`/`validateFormModel`,
97
+ > типы `FieldPath`/`ValidationSchemaFn`/`BehaviorSchemaFn` и `ctx.form.*`/`ctx.setFieldValue` (наследие
98
+ > path-based архитектуры). См. `17-nonexistent-api.md`.
@@ -0,0 +1,125 @@
1
+ ## 14. PROJECT STRUCTURE (COLOCATION)
2
+
3
+ One form is one module folder. **Default layout — flat minimalist:** every concern is a single
4
+ file at the module root, and the whole form (entry + all wizard steps inline) lives in one
5
+ `index.tsx`. Filenames are flat with no prefix — **the dot-prefix (`form.` / `renderer.`) is
6
+ carried only by `schema` and `behavior`**, the two concerns that have both a model-layer and a
7
+ render-layer version (`form.schema.ts` / `form.behavior.ts` here; renderer slices add
8
+ `renderer.schema.*` / `renderer.behavior.ts`). No `lib/` / `schema/` / `components/steps/`
9
+ nesting until the form grows (see "Scaling up" below). Arrays are declared in `form.schema.ts`
10
+ and rendered with `FormArraySection` — no per-step component files.
11
+
12
+ ```
13
+ src/
14
+ ├── components/ui/ # App-wide reusable UI (FormField, FormArraySection, ...)
15
+ │
16
+ ├── forms/
17
+ │ └── [form-name]/ # Form module — flat, one file per concern
18
+ │ ├── index.tsx # entry + whole form: createModel→createForm→<FormWizard> with all steps inline; arrays via FormArraySection
19
+ │ ├── types.ts # form type + enums + { value, label } option type + constant dictionaries
20
+ │ ├── model.ts # createModel + initial values + empty-array-element factories
21
+ │ ├── form.schema.ts # FormSchema: { value: model.$.x, component, componentProps }
22
+ │ ├── form.behavior.ts # defineFormBehavior: compute / enableWhen / hideWhen / copyFrom / onChange
23
+ │ ├── validation.ts # ALL validation → { validateStep, validateAll }
24
+ │ ├── data-sources.ts # options + async loaders (dataSources)
25
+ │ └── api.ts # submit + prefill/load
26
+ ```
27
+
28
+ Rule of thumb: **one concern → one file at the module root; the whole component tree (all steps) →
29
+ `index.tsx`; validation is a single `validation.ts`.** This is the working default for
30
+ almost every form — reach for folders only when a file stops fitting on a screen.
31
+
32
+ ### Key Files
33
+
34
+ ```typescript
35
+ // forms/credit-application/types.ts
36
+ export type CreditApplicationForm = {
37
+ loanType: LoanType;
38
+ loanAmount: number | null;
39
+ // ...
40
+ };
41
+
42
+ // forms/credit-application/model.ts
43
+ import { createModel, type FormModel } from '@reformer/core';
44
+ export const createCreditApplicationModel = (): FormModel<CreditApplicationForm> =>
45
+ createModel<CreditApplicationForm>(createInitialCreditApplication());
46
+
47
+ // forms/credit-application/form.schema.ts
48
+ import type { FormModel } from '@reformer/core';
49
+ export const creditApplicationSchema = (model: FormModel<CreditApplicationForm>) => ({
50
+ loanType: { value: model.$.loanType, component: Select, componentProps: { /* ... */ } },
51
+ personalData: personalDataNodes(model.$.personalData),
52
+ properties: { array: model.properties, item: propertyItem },
53
+ });
54
+
55
+ // forms/credit-application/form.behavior.ts
56
+ import { defineFormBehavior, compute, enableWhen } from '@reformer/core/behaviors';
57
+ export const creditApplicationBehavior = defineFormBehavior<CreditApplicationForm>(({ model }) => {
58
+ compute(model.$.monthlyPayment, () => computeMonthlyPayment(model));
59
+ enableWhen([model.$.propertyValue], () => model.loanType === 'mortgage', { resetOnDisable: true });
60
+ });
61
+
62
+ // forms/credit-application/index.tsx — entry: assembles the form, renders <FormWizard> with all steps inline
63
+ import { createForm } from '@reformer/core';
64
+ export const createCreditApplicationForm = () => {
65
+ const model = createCreditApplicationModel();
66
+ return createForm({ model, schema: creditApplicationSchema(model), behavior: creditApplicationBehavior });
67
+ };
68
+ ```
69
+
70
+ ### Scaling up: folders (large forms)
71
+
72
+ When the flat module gets unwieldy — steps you want in their own files, sub-forms reused across
73
+ steps, calc/validators that outgrow one file — promote it to a three-folder module: `lib/`
74
+ (domain raw material), `schema/` (the form definition), `components/` (React layout), plus the
75
+ entry component and `index.ts`. Keep the module root to just the entry + `index.ts`; everything
76
+ else lives in a folder.
77
+
78
+ ```
79
+ forms/
80
+ └── [form-name]/ # Form module — folders (scale-up)
81
+ ├── [FormName]Form.tsx # Entry: builds model+form, renders FormWizard + step components
82
+ ├── index.ts # Public re-exports of the module
83
+ │
84
+ ├── lib/ # Domain helpers (target-agnostic)
85
+ │ ├── types.ts # Form interface + field enums + { value, label } option type
86
+ │ ├── constants.ts # Option dictionaries (LOAN_TYPES, GENDERS, ...)
87
+ │ ├── calc.ts # Pure fns for derived fields (age, monthlyPayment, ...)
88
+ │ ├── custom-validators.ts # Reusable validator factories
89
+ │ └── api.ts # Data sources + submit
90
+ │
91
+ ├── schema/ # The form definition
92
+ │ ├── model.ts # createModel factory + initial values + array-element factories
93
+ │ ├── schema.ts # createForm schema tree ({ value: model.$.x, component })
94
+ │ ├── validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
95
+ │ ├── behavior.ts # defineFormBehavior(...)
96
+ │ └── create-form.ts # Assembly: createForm({ model, schema, behavior })
97
+ │
98
+ └── components/
99
+ ├── steps/ # One component per wizard step
100
+ ├── nested-forms/ # Reusable sub-forms (Address, PersonalData, ...)
101
+ └── ui/ # Form-specific helper blocks (summary, warnings, sections)
102
+ ```
103
+
104
+ Rule of thumb for this layout: **domain raw material → `lib/`; anything describing the form →
105
+ `schema/`; React layout → `components/`; root = entry + `index.ts`.** In very large forms you
106
+ may co-locate each step's `schema.ts` / `validation.ts` / `behavior.ts` inside its
107
+ `steps/[Step]/` folder, keeping only the shared `model` and cross-step rules in `schema/` — see
108
+ the guide below.
109
+
110
+ ### Scaling
111
+
112
+ | Complexity | Structure |
113
+ | ---------- | --------------------------------------------------------------------------------------- |
114
+ | Simple | Single file: `index.tsx` (model + schema + behavior + component) |
115
+ | **Minimalist (flat)** | **Default.** One file per concern at the module root (`types` / `model` / `form.schema` / `form.behavior` / `validation` / `data-sources` / `api`) + `index.tsx` with all steps inline |
116
+ | Folders (`lib/` + `schema/` + `components/`) | Large forms: split concerns into folders, one component per step, reusable `nested-forms/` |
117
+
118
+ > The leading layout is configurable: set `REFORMER_FORM_LAYOUT` (`minimalist` | `folders`)
119
+ > when registering the MCP server to choose which structure the generators lead with. Default
120
+ > is `minimalist`.
121
+
122
+ > Cross-target variants (renderer-react `renderer.schema.ts` + `renderer.behavior.ts` /
123
+ > renderer-json `renderer.schema.json` + `renderer.behavior.ts` + `registry.ts`), the
124
+ > centralized-vs-co-located choice, and the full reuse map live in the
125
+ > **form-directory-layout** guide (`@reformer/mcp`).
@@ -0,0 +1,189 @@
1
+ ## 14.5 UI COMPONENT PATTERNS
2
+
3
+ > **Default rule (read first)**: для UI используй `FormField` из
4
+ > [`@reformer/ui-kit`](../../reformer-ui-kit/) — он покрывает 95% случаев одной
5
+ > строкой `<FormField control={form.x} />`. Свои field-обёртки пиши ТОЛЬКО если
6
+ > ui-kit не подходит (другая design system, особый low-level input).
7
+ >
8
+ > Канонический schema-driven подход:
9
+ >
10
+ > - **компонент** объявляется в схеме как `component: Input` (или `Select`, `Checkbox`, etc.)
11
+ > - **пропсы** компонента — в `componentProps: { label, placeholder, options, type, ... }`
12
+ > - **JSX рендерит**: `<FormField control={form.x} />` БЕЗ дополнительных props
13
+ >
14
+ > См. `find_recipe(package="@reformer/ui-kit", topic="form-field-integration")`
15
+ > для полного руководства.
16
+
17
+ ### Default — FormField из ui-kit (canonical)
18
+
19
+ ```tsx
20
+ import { useMemo } from 'react';
21
+ import { createModel, createForm } from '@reformer/core';
22
+ import { FormField, Input, Select, Checkbox, Button } from '@reformer/ui-kit';
23
+
24
+ type RegistrationForm = {
25
+ email: string;
26
+ country: string;
27
+ agree: boolean;
28
+ };
29
+
30
+ function RegistrationPage() {
31
+ const form = useMemo(() => {
32
+ const model = createModel<RegistrationForm>({ email: '', country: 'ru', agree: false });
33
+ const schema = {
34
+ email: {
35
+ value: model.$.email,
36
+ component: Input,
37
+ componentProps: { label: 'Email', type: 'email', placeholder: 'you@example.com' },
38
+ },
39
+ country: {
40
+ value: model.$.country,
41
+ component: Select,
42
+ componentProps: {
43
+ label: 'Country',
44
+ options: [
45
+ { value: 'ru', label: 'Россия' },
46
+ { value: 'by', label: 'Беларусь' },
47
+ ],
48
+ },
49
+ },
50
+ agree: {
51
+ value: model.$.agree,
52
+ component: Checkbox,
53
+ componentProps: { label: 'I agree to terms' },
54
+ },
55
+ };
56
+ return createForm<RegistrationForm>({ model, schema });
57
+ }, []);
58
+
59
+ return (
60
+ <form>
61
+ <FormField control={form.email} testId="email" />
62
+ <FormField control={form.country} testId="country" />
63
+ <FormField control={form.agree} testId="agree" />
64
+ <Button type="submit">Register</Button>
65
+ </form>
66
+ );
67
+ }
68
+ ```
69
+
70
+ `FormField` сам читает `componentProps.label`, `componentProps.placeholder`,
71
+ `componentProps.options` через `useFormControl(...).componentProps` и применяет
72
+ их к нужному `<input>`/`<select>`/etc. Error rendering, `pending` для async-валидаций,
73
+ `data-testid` для e2e — всё из коробки.
74
+
75
+ ### Anti-patterns (не делай так)
76
+
77
+ ❌ **Свои field-компоненты с label-prop'ами в JSX**:
78
+
79
+ ```tsx
80
+ // WRONG — дублирует логику FormField, ломает schema-driven архитектуру
81
+ <Input control={form.email} label="Email" placeholder="..." />
82
+ <Select control={form.country} options={[...]} />
83
+ ```
84
+
85
+ ❌ **Передача компонент-пропсов через JSX вместо схемы**:
86
+
87
+ ```tsx
88
+ // WRONG — нарушает single source of truth (схема)
89
+ <FormField control={form.email} label="Email" />
90
+ ```
91
+
92
+ ✅ Всё это в схеме:
93
+
94
+ ```ts
95
+ { email: { component: Input, componentProps: { label: 'Email' } } }
96
+ ```
97
+
98
+ ```tsx
99
+ <FormField control={form.email} />
100
+ ```
101
+
102
+ ### Advanced — кастомный input через `children` slot
103
+
104
+ Когда нужен низкоуровневый input, которого нет в ui-kit (маска, особый combobox):
105
+
106
+ ```tsx
107
+ import { FormField } from '@reformer/ui-kit';
108
+ import { InputMask } from 'react-input-mask';
109
+
110
+ <FormField control={form.phone} testId="phone">
111
+ <InputMask mask="+7 (999) 999-99-99" />
112
+ </FormField>;
113
+ ```
114
+
115
+ `children` оборачивается в `CdkFormField.Control asChild` и получает все нужные
116
+ props (`value`, `onChange`, `onBlur`, `aria-invalid`).
117
+
118
+ ### Advanced — write your own from scratch (rare)
119
+
120
+ Если ты не хочешь подключать `@reformer/ui-kit`, пиши свои компоненты на основе
121
+ `useFormControl` — но **сохраняй schema-driven подход**: читай label/placeholder
122
+ из `componentProps`, не из JSX-props.
123
+
124
+ ```tsx
125
+ import type { FieldNode } from '@reformer/core';
126
+ import { useFormControl } from '@reformer/core';
127
+
128
+ type MyFormFieldProps<T> = { control: FieldNode<T> }; // ← ОДИН prop
129
+
130
+ function MyFormField<T>({ control }: MyFormFieldProps<T>) {
131
+ const { value, errors, disabled, shouldShowError, componentProps } = useFormControl(control);
132
+ // componentProps = { label, placeholder, type, options, ... } — из СХЕМЫ
133
+ const cp = (componentProps ?? {}) as Record<string, unknown>;
134
+
135
+ return (
136
+ <label>
137
+ {cp.label && <span>{cp.label as string}</span>}
138
+ <input
139
+ type={(cp.type as string) ?? 'text'}
140
+ value={(value ?? '') as string}
141
+ placeholder={cp.placeholder as string | undefined}
142
+ disabled={disabled}
143
+ onChange={(e) => (control.setValue as (v: unknown) => void)(e.target.value)}
144
+ onBlur={() => control.markAsTouched()}
145
+ />
146
+ {shouldShowError && errors[0] && <span>{errors[0].message}</span>}
147
+ </label>
148
+ );
149
+ }
150
+ ```
151
+
152
+ Использование — как у `FormField`:
153
+
154
+ ```tsx
155
+ <MyFormField control={form.email} /> // ← без label-prop
156
+ ```
157
+
158
+ ### Integration with UI libraries (shadcn etc.)
159
+
160
+ > **Через рендерер — без обёртки.** Если ты рендеришь форму через `@reformer/renderer-react` /
161
+ > `@reformer/renderer-json`, вместо ручной обёртки на каждый контрол можно зарегистрировать сырой
162
+ > компонент и передать `settings.resolveFieldAdapter` — рендерер сам сведёт value-seam к диалекту
163
+ > контрола. Ручная обёртка на `useFormControl` (ниже) нужна для прямого JSX без рендерера.
164
+
165
+ Если есть существующая design system — оборачивай её компоненты в один
166
+ `MyFormField` (как выше) и используй один прop `control`. Не множь обёртки на
167
+ тип input'а — пусть `componentProps.type` диспатчит внутри.
168
+
169
+ ```tsx
170
+ import { Input } from '@/components/ui/input';
171
+ import { Label } from '@/components/ui/label';
172
+
173
+ function ShadcnFormField({ control }: { control: FieldNode<string> }) {
174
+ const { value, errors, disabled, componentProps } = useFormControl(control);
175
+ const cp = (componentProps ?? {}) as Record<string, unknown>;
176
+
177
+ return (
178
+ <div className="space-y-2">
179
+ {cp.label && <Label>{cp.label as string}</Label>}
180
+ <Input
181
+ value={(value ?? '') as string}
182
+ onChange={(e) => control.setValue(e.target.value)}
183
+ disabled={disabled}
184
+ />
185
+ {errors[0] && <p className="text-red-500">{errors[0].message}</p>}
186
+ </div>
187
+ );
188
+ }
189
+ ```
@@ -0,0 +1,93 @@
1
+ ## 15. NON-EXISTENT API (DO NOT USE)
2
+
3
+ **Следующего API НЕТ в @reformer/core** (наследие старой path-based архитектуры,
4
+ удалено при переходе на M1 и при разделении валидации/поведения):
5
+
6
+ > ⚠️ Операторы валидации `validate`/`validateAsync`/`validateWhen`/`cross`/`each`/`apply`
7
+ > **существуют** — но живут в сабпути `@reformer/core/validation` (ambient-схема
8
+ > `defineValidationSchema(({ model }) => …)`, раннер `validateModel`), а НЕ в корне и НЕ
9
+ > в `@reformer/core/validators`. Ниже перечислено то, чего действительно нет.
10
+
11
+ | Wrong | Correct | Notes |
12
+ | -------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
13
+ | `useForm` | `createModel` + `createForm` | Хука useForm нет |
14
+ | `validateForm`, `validateFormModel` | `validateModel(model, schema)` из `@reformer/core/validation` | Legacy-движок дерева `{ value, validators }` удалён; внешний раннер — `validateModel` |
15
+ | `validateModelSync` | `await validateModel(model, schema)` | Синхронного раннера нет — прогон асинхронный (`Promise<boolean>`) |
16
+ | `applyWhen` | `validateWhen(() => cond, () => { … })` | Условная валидация — оператором `validateWhen`, не узлом `{ when, children }` |
17
+ | `validateItems`, `validateGroup`, `validateTree` | `each(model.arr, (im) => { … })` | Per-item — оператором `each`; под-схема — прямой вызов `sub({ model: model.child })` |
18
+ | `ValidationSchemaFn`, `BehaviorSchemaFn` | `ValidationSchema<T>` (`defineValidationSchema`) / `defineFormBehavior` | Типы path-схем удалены |
19
+ | `equalTo`, `custom`, `notEmpty` (validators) | inline `Rule<T>` `(value) => err \| null` в `validate(sig, [...])`; сравнение полей — `cross(sig, fn)` | Таких фабрик нет; кастомное правило — обычная функция значения, cross-field — `cross` |
20
+ | `form.submit()` / `form.validate()` прогоняют схему | `await validateModel(model, schema)` перед submit/шагом | submit/validate НЕ запускают ambient-схему валидации |
21
+ | `FieldPath`, `FieldPathNode` | `model.$.field` (`PathAwareSignal`) | Пути заменены сигналами |
22
+ | `ctx.form.x.value.value` | `model.x` / `model.$.x.value` | В behaviors читаем модель напрямую |
23
+ | `ctx.setFieldValue(name, value)` | `model.x = value` / `compute(...)` | Не существует |
24
+ | `transformers`, `createTransformer` | `transformValue(signal, fn)` | Готового набора трансформеров нет |
25
+ | `useHiddenCondition` | `useFormControlValue` + условный рендер в JSX | Хука нет |
26
+ | `FormProvider`, `control` prop, `register()` | `<Component form={form} />`, `useFormControl(form.field)` | Форма передаётся через props |
27
+ | `getFieldValue()` | `model.field` / `useFormControlValue(form.field)`| Не существует |
28
+
29
+ ### Common Import Errors
30
+
31
+ ```typescript
32
+ // WRONG - этих символов / путей НЕ существует
33
+ import { useForm, validateForm, validateFormModel } from '@reformer/core'; // NO!
34
+ import { validate, applyWhen, equalTo } from '@reformer/core/validators'; // NO! операторов тут нет
35
+ import { transformers } from '@reformer/core/behaviors'; // NO!
36
+ import type { FieldPath, ValidationSchemaFn } from '@reformer/core'; // NO!
37
+
38
+ // CORRECT
39
+ import { createModel, createForm, useFormControl } from '@reformer/core';
40
+ import type { FormModel, ValidationError, FieldConfig } from '@reformer/core';
41
+ // Операторы валидации + раннер + defineValidationSchema — отдельный сабпуть:
42
+ import {
43
+ validate, validateAsync, validateWhen, cross, each, apply,
44
+ defineValidationSchema, validateModel,
45
+ type Rule, type AsyncRule, type ValidationSchema,
46
+ } from '@reformer/core/validation';
47
+ // Фабрики-валидаторы (value-only, принимают nullable):
48
+ import { required, email, min, minLength, pattern } from '@reformer/core/validators';
49
+ // Поведение (отдельный слой, контракт не менялся):
50
+ import { defineFormBehavior, compute, onChange, revalidateWhen } from '@reformer/core/behaviors';
51
+ ```
52
+
53
+ ### Schema Common Mistakes
54
+
55
+ Валидация и layout — **разные** контракты. Layout-узел привязывает поле к сигналу и
56
+ НЕ несёт `validators`; правила живут в отдельной `defineValidationSchema`.
57
+
58
+ ```typescript
59
+ // WRONG - примитив/литерал вместо сигнала, и validators прямо в layout-узле
60
+ const schema = {
61
+ name: '', // нет привязки к сигналу
62
+ email: { value: '', validators: [required()] }, // value — литерал, а validators в layout больше нет
63
+ };
64
+
65
+ // CORRECT - layout только связывает поле с сигналом модели...
66
+ const layout = {
67
+ name: { value: model.$.name, component: Input, componentProps: { label: 'Name' } },
68
+ email: { value: model.$.email, component: Input, componentProps: { label: 'Email' } },
69
+ };
70
+
71
+ // ...а правила — отдельная ambient-схема (прогоняется раннером validateModel):
72
+ const validation = defineValidationSchema<Form>(({ model }) => {
73
+ validate(model.$.email, [required({ message: 'Email обязателен' }), email()]);
74
+ });
75
+ ```
76
+
77
+ ### Behaviors Common Mistakes
78
+
79
+ ```typescript
80
+ // WRONG - строковые пути и (form) => ... — старый API
81
+ enableWhen(path.city, (form) => Boolean(form.country));
82
+
83
+ // CORRECT - сигналы модели, условие читает model
84
+ enableWhen(model.$.city, () => Boolean(model.country), { resetOnDisable: true });
85
+ ```
86
+
87
+ Поведение **не владеет** валидацией. Чтобы поведение инициировало прогон схемы — мост
88
+ `revalidateWhen` (validate/submit сами схему не запускают):
89
+
90
+ ```typescript
91
+ // CORRECT - поведение дёргает внешний раннер валидации при изменении зависимости
92
+ revalidateWhen([model.$.password], () => void validateModel(model, validation));
93
+ ```