@reformer/core 11.0.0-beta.8 → 11.0.0-beta.9

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 (224) hide show
  1. package/README.md +91 -73
  2. package/dist/behaviors-value-HA_Q03hc.js +108 -0
  3. package/dist/behaviors.js +370 -242
  4. package/dist/{create-model-BBRHhHwK.js → create-model-BvcbNPgL.js} +6 -6
  5. package/dist/date-utils-90-qZbse.js +43 -0
  6. package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
  7. package/dist/form/aggregate-signals.d.ts +21 -63
  8. package/dist/form/behaviors/collections.d.ts +28 -7
  9. package/dist/form/behaviors/context.d.ts +19 -2
  10. package/dist/form/behaviors/index.d.ts +3 -3
  11. package/dist/form/behaviors/internals.d.ts +9 -2
  12. package/dist/form/behaviors/node.d.ts +25 -2
  13. package/dist/form/behaviors/operators.d.ts +17 -9
  14. package/dist/form/behaviors/schema-operators.d.ts +6 -4
  15. package/dist/form/behaviors/types.d.ts +12 -4
  16. package/dist/form/create-form.d.ts +47 -35
  17. package/dist/form/flow/flow.d.ts +87 -0
  18. package/dist/form/form-bundle.d.ts +45 -28
  19. package/dist/form/form-config-base.d.ts +46 -0
  20. package/dist/form/form-level.d.ts +121 -0
  21. package/dist/form/form-proxy-builder.d.ts +6 -22
  22. package/dist/form/index.d.ts +16 -21
  23. package/dist/form/nodes/form-array-node.d.ts +100 -0
  24. package/dist/form/nodes/form-control-node.d.ts +73 -0
  25. package/dist/form/nodes/form-group-node.d.ts +66 -0
  26. package/dist/form/nodes/node-kind.d.ts +29 -0
  27. package/dist/form/nodes/state/availability.d.ts +6 -0
  28. package/dist/form/nodes/state/component.d.ts +6 -0
  29. package/dist/form/nodes/state/index.d.ts +12 -0
  30. package/dist/form/nodes/state/interaction.d.ts +13 -0
  31. package/dist/form/nodes/state/issues.d.ts +9 -0
  32. package/dist/form/nodes/state/pending.d.ts +6 -0
  33. package/dist/form/schema/compile.d.ts +59 -0
  34. package/dist/form/schema/compiled.d.ts +143 -0
  35. package/dist/form/schema/row-key.d.ts +11 -0
  36. package/dist/form/schema-controller.d.ts +30 -14
  37. package/dist/form/signal-node-registry.d.ts +6 -7
  38. package/dist/form/submission.d.ts +93 -0
  39. package/dist/form/type-guards.d.ts +14 -73
  40. package/dist/form/types/contracts.d.ts +19 -92
  41. package/dist/form/types/field-config.d.ts +27 -0
  42. package/dist/form/types/index.d.ts +5 -57
  43. package/dist/form/types/nodes.d.ts +159 -0
  44. package/dist/form/types/schema-node.d.ts +139 -63
  45. package/dist/form/types/validation-schema.d.ts +23 -27
  46. package/dist/form/validation/context.d.ts +42 -7
  47. package/dist/form/validation/index.d.ts +9 -8
  48. package/dist/form/validation/operators.d.ts +22 -30
  49. package/dist/form/validation/run.d.ts +93 -18
  50. package/dist/form/validation/strategy.d.ts +41 -13
  51. package/dist/form/validation/types.d.ts +74 -20
  52. package/dist/form/validators/date-utils.d.ts +13 -1
  53. package/dist/form/validators/email.d.ts +8 -7
  54. package/dist/form/validators/file-type.d.ts +8 -7
  55. package/dist/form/validators/file-utils.d.ts +2 -1
  56. package/dist/form/validators/future-date.d.ts +8 -7
  57. package/dist/form/validators/index.d.ts +1 -1
  58. package/dist/form/validators/integer.d.ts +8 -7
  59. package/dist/form/validators/is-date.d.ts +11 -9
  60. package/dist/form/validators/is-number.d.ts +11 -9
  61. package/dist/form/validators/max-age.d.ts +13 -9
  62. package/dist/form/validators/max-date.d.ts +8 -7
  63. package/dist/form/validators/max-file-size.d.ts +8 -7
  64. package/dist/form/validators/max-files.d.ts +8 -7
  65. package/dist/form/validators/max-length.d.ts +14 -7
  66. package/dist/form/validators/max-total-file-size.d.ts +8 -7
  67. package/dist/form/validators/max.d.ts +10 -8
  68. package/dist/form/validators/min-age.d.ts +12 -9
  69. package/dist/form/validators/min-date.d.ts +8 -7
  70. package/dist/form/validators/min-file-size.d.ts +8 -7
  71. package/dist/form/validators/min-files.d.ts +11 -9
  72. package/dist/form/validators/min-length.d.ts +14 -7
  73. package/dist/form/validators/min.d.ts +10 -8
  74. package/dist/form/validators/multiple-of.d.ts +10 -8
  75. package/dist/form/validators/non-negative.d.ts +8 -7
  76. package/dist/form/validators/non-zero.d.ts +8 -7
  77. package/dist/form/validators/past-date.d.ts +8 -7
  78. package/dist/form/validators/pattern.d.ts +9 -8
  79. package/dist/form/validators/phone.d.ts +8 -7
  80. package/dist/form/validators/required.d.ts +11 -9
  81. package/dist/form/validators/url.d.ts +20 -13
  82. package/dist/form/validators/validation-issue.d.ts +8 -0
  83. package/dist/i18n/descriptor.d.ts +1 -1
  84. package/dist/i18n/en.json.d.ts +5 -1
  85. package/dist/i18n/locale-store.d.ts +7 -0
  86. package/dist/i18n/message-format.d.ts +24 -4
  87. package/dist/i18n/ru.json.d.ts +5 -1
  88. package/dist/i18n/translator.d.ts +11 -0
  89. package/dist/i18n/validation-message.d.ts +42 -7
  90. package/dist/i18n.d.ts +7 -12
  91. package/dist/i18n.js +66 -283
  92. package/dist/index.d.ts +16 -7
  93. package/dist/index.js +924 -2855
  94. package/dist/loader-BWkigpVe.js +52 -0
  95. package/dist/locale/en.js +1 -1
  96. package/dist/locale/en.json +4 -1
  97. package/dist/locale/ru.js +4 -1
  98. package/dist/locale/ru.json +4 -1
  99. package/dist/locale-CG54AU2Q.js +219 -0
  100. package/dist/locale.js +1 -1
  101. package/dist/model/behaviors-value.d.ts +24 -6
  102. package/dist/model/create-model.d.ts +2 -2
  103. package/dist/model/derived-registry.d.ts +1 -1
  104. package/dist/model/index.d.ts +11 -5
  105. package/dist/model/model-nodes.d.ts +79 -12
  106. package/dist/model/model-signals-proxy.d.ts +5 -6
  107. package/dist/model/model-value-proxy.d.ts +20 -15
  108. package/dist/model/safe-effect.d.ts +33 -1
  109. package/dist/model/types.d.ts +63 -13
  110. package/dist/model.js +20 -18
  111. package/dist/node-kind-C-r6ZMdo.js +15 -0
  112. package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
  113. package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
  114. package/dist/platforms/react/form-bundle-context.d.ts +2 -2
  115. package/dist/platforms/react/hooks/types.d.ts +57 -38
  116. package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
  117. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  118. package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
  119. package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
  120. package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
  121. package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
  122. package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
  123. package/dist/platforms/react/i18n/context.d.ts +6 -1
  124. package/dist/platforms/react/i18n/hooks.d.ts +16 -10
  125. package/dist/platforms/react/i18n/provider.d.ts +9 -2
  126. package/dist/platforms/react/index.d.ts +6 -4
  127. package/dist/react.d.ts +22 -0
  128. package/dist/react.js +505 -0
  129. package/dist/runtime-token.d.ts +1 -2
  130. package/dist/safe-effect-BbtX3KuN.js +643 -0
  131. package/dist/schema-controller-CpiO2JoK.js +143 -0
  132. package/dist/signals.d.ts +15 -9
  133. package/dist/signals.js +11 -6
  134. package/dist/strategy-CoFSemuP.js +375 -0
  135. package/dist/type-guards-D-N_5Ddh.js +19 -0
  136. package/dist/validation-issue-B9mjI8jz.js +7 -0
  137. package/dist/validation-message-Ced9MGxs.js +194 -0
  138. package/dist/validation.js +9 -9
  139. package/dist/validators/email.js +5 -5
  140. package/dist/validators/future-date.js +8 -8
  141. package/dist/validators/integer.js +3 -3
  142. package/dist/validators/is-date.js +4 -4
  143. package/dist/validators/is-number.js +4 -4
  144. package/dist/validators/max-age.js +8 -9
  145. package/dist/validators/max-date.js +8 -8
  146. package/dist/validators/max-length.js +7 -7
  147. package/dist/validators/max.js +4 -4
  148. package/dist/validators/min-age.js +10 -8
  149. package/dist/validators/min-date.js +3 -3
  150. package/dist/validators/min-length.js +5 -5
  151. package/dist/validators/min.js +4 -4
  152. package/dist/validators/multiple-of.js +11 -7
  153. package/dist/validators/non-negative.js +3 -3
  154. package/dist/validators/non-zero.js +3 -3
  155. package/dist/validators/past-date.js +8 -8
  156. package/dist/validators/pattern.js +5 -5
  157. package/dist/validators/phone.js +11 -9
  158. package/dist/validators/required.js +2 -2
  159. package/dist/validators/url.js +48 -12
  160. package/dist/validators.js +17 -17
  161. package/docs/llms/01-api-reference.md +60 -34
  162. package/docs/llms/02-quick-start.md +111 -80
  163. package/docs/llms/03-api-signatures.md +368 -153
  164. package/docs/llms/04-common-patterns.md +58 -56
  165. package/docs/llms/05-common-mistakes.md +69 -44
  166. package/docs/llms/06-troubleshooting.md +14 -9
  167. package/docs/llms/07-complete-import.md +74 -43
  168. package/docs/llms/08-form-types.md +11 -4
  169. package/docs/llms/09-formschema.md +129 -81
  170. package/docs/llms/10-arrays.md +106 -75
  171. package/docs/llms/11-async-watchfield.md +2 -2
  172. package/docs/llms/13-multi-step.md +134 -63
  173. package/docs/llms/14-extended-mistakes.md +20 -17
  174. package/docs/llms/15-project-structure.md +221 -74
  175. package/docs/llms/16-ui-components.md +55 -42
  176. package/docs/llms/17-nonexistent-api.md +199 -32
  177. package/docs/llms/18-conditional-fields.md +66 -32
  178. package/docs/llms/19-reading-values.md +15 -3
  179. package/docs/llms/20-compute-vs-watch.md +16 -10
  180. package/docs/llms/21-array-operations.md +51 -27
  181. package/docs/llms/22-cycle-detection.md +1 -1
  182. package/docs/llms/23-copy-from.md +2 -2
  183. package/docs/llms/24-sync-fields.md +6 -5
  184. package/docs/llms/25-reset-when.md +14 -7
  185. package/docs/llms/26-transform-value.md +4 -4
  186. package/docs/llms/27-revalidate-when.md +16 -15
  187. package/docs/llms/28-submit-and-reset.md +276 -128
  188. package/docs/llms/29-async-preload.md +39 -29
  189. package/docs/llms/30-type-safety-recipes.md +105 -51
  190. package/docs/llms/31-async-validator-debounce.md +96 -53
  191. package/docs/llms/33-validation-strategy.md +177 -65
  192. package/docs/llms/34-i18n.md +69 -43
  193. package/docs/llms/35-unified-contract.md +100 -39
  194. package/docs/llms/36-issue-messages.md +284 -0
  195. package/docs/llms/37-compiled-schema.md +332 -0
  196. package/docs/llms/38-form-flow.md +397 -0
  197. package/llms-index.json +1 -1
  198. package/llms.txt +6084 -3420
  199. package/package.json +23 -4
  200. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  201. package/dist/date-utils-dTT_x_Be.js +0 -31
  202. package/dist/form/create-core-form.d.ts +0 -72
  203. package/dist/form/factories/index.d.ts +0 -6
  204. package/dist/form/factories/node-factory.d.ts +0 -189
  205. package/dist/form/form-submitter.d.ts +0 -121
  206. package/dist/form/nodes/array-node.d.ts +0 -276
  207. package/dist/form/nodes/field-node.d.ts +0 -297
  208. package/dist/form/nodes/form-node.d.ts +0 -326
  209. package/dist/form/nodes/group-node.d.ts +0 -284
  210. package/dist/form/nodes/model-array-node.d.ts +0 -126
  211. package/dist/form/nodes/subscription-manager.d.ts +0 -180
  212. package/dist/form/status-machine.d.ts +0 -115
  213. package/dist/form/types/deep-schema.d.ts +0 -107
  214. package/dist/form/types/form-proxy.d.ts +0 -141
  215. package/dist/form/unique-id.d.ts +0 -45
  216. package/dist/form/validation/config.d.ts +0 -73
  217. package/dist/form/validation/error-handler.d.ts +0 -166
  218. package/dist/form/validators/validation-error.d.ts +0 -8
  219. package/dist/index-BrNkQj1y.js +0 -105
  220. package/dist/loader-Dv_Hl7JC.js +0 -216
  221. package/dist/model-value-proxy-caTmAiUd.js +0 -419
  222. package/dist/node-BeV_lV5i.js +0 -100
  223. package/dist/strategy-DgzELWno.js +0 -204
  224. package/dist/validation-error-B-U3RsV0.js +0 -11
@@ -1,23 +1,27 @@
1
1
  ## 15. NON-EXISTENT API (DO NOT USE)
2
2
 
3
- **Следующего API НЕТ в @reformer/core** (наследие старой path-based архитектуры,
4
- удалено при переходе на M1 и при разделении валидации/поведения):
3
+ **Следующего API НЕТ в @reformer/core** (наследие старой path-based архитектуры и прежних
4
+ контрактов модели, валидации, схемы, визарда и нод):
5
5
 
6
- > ⚠️ Операторы валидации `validate`/`validateAsync`/`validateWhen`/`cross`/`each`/`apply`
7
- > **существуют** — но живут в сабпути `@reformer/core/validation` (ambient-схема
8
- > `defineValidationSchema(({ model }) => …)`, раннер `validateModel`), а НЕ в корне и НЕ
6
+ > ⚠️ Операторы валидации `validate`/`validateAsync`/`validateWhen`/`apply`/`applyEach`
7
+ > **существуют** — но живут в сабпути `@reformer/core/validation` (схема
8
+ > `defineValidation(({ model, cross }) => …)`, раннер `validateModel`), а НЕ в корне и НЕ
9
9
  > в `@reformer/core/validators`. Ниже перечислено то, чего действительно нет.
10
10
 
11
11
  | Wrong | Correct | Notes |
12
12
  | -------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
13
- | `useForm` | `createModel` + `createForm` | Хука useForm нет |
13
+ | `useForm` | `createForm` + `useFormBundle` | Хука useForm нет |
14
+ | `FormValue` | тип значения самого поля либо `unknown` | Тип удалён: модель обобщённая (`createModel<T>`), параметры замечания — `Record<string, unknown>` |
15
+ | `value instanceof Signal` | `isSignal(value)` из `@reformer/core/signals` | Проверка по классу не узнаёт сигнал другой копии рантайма; ручку поля проверяет `isValueSignal` |
16
+ | `import { useFormControl, useFormBundle, … } from '@reformer/core'` | те же имена из `@reformer/core/react` | Корень ядра без React: хуки (`useFormControl`, `useFormControlValue`, `useArrayLength`, `useFormValidation`, `useFormBundle`, `useFormFlow`), `FormBundleContext`, связка поля (`bindFieldProps`, `getFieldAdapter`, `FieldAdapter`, `FieldHandle`) и типы снимков (`FieldControlState`, `ArrayControlState`) отдаёт только сабпат `/react` |
17
+ | `import { I18nProvider, useI18n, … } from '@reformer/core/i18n'` | те же имена из `@reformer/core/react` | `/i18n` — локаль, перевод и тексты замечаний без React; провайдер и хуки (`I18nProvider`, `useI18n`, `useMessages`, `useIssueMessage`) — в `/react` |
18
+ | `ElementType` / `ComponentType` в типе узла схемы | `ComponentRef` из `@reformer/core` | Типы схемы и ноды не импортируют типы React: `component` — непрозрачная ссылка (`string | object`), к типу своего UI-слоя её приводит рендерер |
14
19
  | `validateForm`, `validateFormModel` | `validateModel(model, schema)` из `@reformer/core/validation` | Legacy-движок дерева `{ value, validators }` удалён; внешний раннер — `validateModel` |
15
20
  | `validateModelSync` | `await validateModel(model, schema)` | Синхронного раннера нет — прогон асинхронный (`Promise<boolean>`) |
16
21
  | `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-схему валидации |
22
+ | `validateItems`, `validateGroup`, `validateTree` | `applyEach(model.$.arr, rowRules)` / `apply(model.$.group, groupRules)` | Правила строк и подформы — отдельные схемы, подключённые привязкой |
23
+ | `ValidationSchemaFn`, `BehaviorSchemaFn` | `ValidationDefinition<T>` (`defineValidation`) / `defineFormBehavior` | Типы path-схем удалены |
24
+ | `equalTo`, `custom`, `notEmpty` (validators) | inline `Rule<T>` `(value) => issue \| null` в `validate(sig, [...])`; сравнение полей — `cross(sig, fn)` | Таких фабрик нет; кастомное правило — обычная функция значения, cross-field — `cross` |
21
25
  | `FieldPath`, `FieldPathNode` | `model.$.field` (`PathAwareSignal`) | Пути заменены сигналами |
22
26
  | `ctx.form.x.value.value` | `model.x` / `model.$.x.value` | В behaviors читаем модель напрямую |
23
27
  | `ctx.setFieldValue(name, value)` | `model.x = value` / `compute(...)` | Не существует |
@@ -26,6 +30,156 @@
26
30
  | `FormProvider`, `control` prop, `register()` | `<Component form={form} />`, `useFormControl(form.field)` | Форма передаётся через props |
27
31
  | `getFieldValue()` | `model.field` / `useFormControlValue(form.field)`| Не существует |
28
32
 
33
+ ### Удалено: модель (этап 1)
34
+
35
+ | Wrong | Correct | Notes |
36
+ | -------------------------------------- | ---------------------------------------------------- | --------------------------------------- |
37
+ | `model.set(partial)` для части полей | `model.patch(partial)` | `set` — полная установка: недостающие ключи в dev дают предупреждение и не меняются; `patch` сливает переданные ключи, вложенные группы — тоже частично, массив — целиком |
38
+ | `FormValue`, `T extends FormValue` | `T` без ограничения / `Record<string, unknown>` | Тип удалён |
39
+
40
+ ### Удалено: компонент массива (этап 2)
41
+
42
+ Имя `FormArray` теперь занято **типом ноды** массива под-форм в `@reformer/core`; компонент массива
43
+ в cdk и ui-kit называется `FieldArray`.
44
+
45
+ | Wrong | Correct | Notes |
46
+ | ------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
47
+ | компонент `FormArray` (`@reformer/cdk`, `@reformer/ui-kit`) | `FieldArray` | `{ model: model.$.items, component: FieldArray, item }` |
48
+ | `FormArraySection` | `FieldArraySection` | ui-kit |
49
+ | `useFormArray` | `useFieldArray` | cdk |
50
+ | `@reformer/cdk/form-array`, `@reformer/ui-kit/form-array` | `@reformer/cdk/field-array`, `@reformer/ui-kit/field-array` | сабпаты |
51
+ | ключи локали `kit.formArray.*` | `kit.fieldArray.*` | ui-kit |
52
+
53
+ JSON-документ схемы со старыми именами `FormArray` / `FormArraySection` реестр renderer-json ещё
54
+ принимает как псевдонимы (dev-предупреждение).
55
+
56
+ ### Удалено: замечание валидации без текста (этап 4)
57
+
58
+ Замечание несёт код и параметры; текст по коду даёт словарь формы — см. `36-issue-messages.md`.
59
+
60
+ | Wrong | Correct | Notes |
61
+ | ------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
62
+ | `required({ message: '…' })`, `{ messageKey }` | `required({ code: 'phone.required' })` + `defineIssueMessages({ 'phone.required': '…' })` | У `RuleOptions` только `code` и `params` |
63
+ | `ValidationError` (`{ code, message, … }`) | `ValidationIssue` (`{ code, params?, severity? }`) | Поля `message` нет — текст через резолвер |
64
+ | `ValidateOptions` | `RuleOptions` | `{ code?, params? }` |
65
+ | `ValidationSchema`, `defineValidationSchema` | `ValidationDefinition`, `defineValidation` | `@reformer/core/validation` |
66
+ | `resolveValidationError` | `resolveIssueMessage(issue, i18n, messages?)` | `@reformer/core/i18n` |
67
+ | `useValidationMessage` | `useIssueMessage(messages?)` | `@reformer/core/react` |
68
+ | `node.errors`, `setErrors`, `clearErrors` | `node.issues`, `setIssues`, `clearIssues` | И в снимке `useFormControl` — `issues` |
69
+ | `result.errors` у `ValidationResult` | `result.issues` | Замечания по ручке поля |
70
+ | `getErrors(filter)`, `ErrorFilterOptions` | `node.issues.value.filter(…)` | Фильтр — обычный `filter` |
71
+ | `errors[0].message` в разметке | `useIssueMessage(messages)(issues[0])` | Строку даёт резолвер при показе |
72
+ | cdk `createMessageResolver`, `defaultErrorResolver`, `ValidationMessageTable` | `<ValidationMessagesProvider messages={defineIssueMessages({…})}>` | Словарь формы поверх локали |
73
+ | cdk `ValidationErrorResolver`, `useValidationErrorResolver` | `IssueMessageResolver`, `useIssueMessageResolver` | `@reformer/cdk` |
74
+
75
+ ### Удалено: схема (этап 5)
76
+
77
+ | Wrong | Correct | Notes |
78
+ | ------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
79
+ | `bundle.render.tree` как авторское дерево | `bundle.render.tree` — `CompiledSchema`; авторское дерево — `bundle.render.tree.root.source` | См. `37-compiled-schema.md` |
80
+ | повтор `selector` в одной области «с предупреждением» | разные селекторы | Повтор — исключение при сборке |
81
+ | свой разбор ключей `model` / `item` / `part` у узла | `compileSchema(tree, model)` — `kind` узла | `field \| array \| part \| container \| text` |
82
+
83
+ ### Удалено: уровень формы, поток шагов, визард (этап 6)
84
+
85
+ | Wrong | Correct | Notes |
86
+ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87
+ | `bundle.validation.validateAll()` / `.runAll()` | `bundle.validate()` → `ValidationResult` | Поле `bundle.validation` осталось служебным (`{ rules, controller }` для `useFormBundle`) — методов проверки на нём нет |
88
+ | `bundle.validation.validateStep(n)` / `.runStep(n)` | `bundle.validateStep(id)` → `ValidationResult` | Идентификатор шага — `selector`, не номер |
89
+ | `createForm({ validation: { steps: { loan: rules } } })` | `createForm({ flow: { steps: [{ id: 'loan', rules }] } })` | Правила вне шагов — `validation: { extras }` |
90
+ | `buildValidation`, `FormValidationBundle` | `createForm({ validation, flow })` / `createFormLevel` | Уровень формы собирает сборка |
91
+ | `form.submit(handler)`, `SubmitOptions`, `form.submitting` | `bundle.submission.submit(handler)` → `SubmitResult`, `bundle.submission.submitting` | Один прогон правил; обработчик — только при `valid` |
92
+ | `bundle.submit(handler)`, `bundle.submitting` | `bundle.submission.submit(handler)` → `SubmitResult`, `bundle.submission.submitting` | Отправка — отдельный объект сборки; псевдонимов нет. То же у `level` в `renderBehavior` и бандлов `createReactForm` / `createJsonForm`: `level.submission.submit(handler)` |
93
+ | `form.validate()` | `bundle.validate()` | У нод методов проверки нет |
94
+ | `<FormWizard config={…}>`, `FormWizardConfig` | `<FormWizard flow={bundle.flow}>` | Без `flow` и сборки в контексте — позиционный поток без правил |
95
+ | `<FormWizard ref={…}>`, `FormWizardHandle`, `ref.current.goToStep(n)` | `bundle.flow.goTo(id)` / `next()` / `previous()` | Навигация — методами потока |
96
+ | `defineSteps(model, { steps, extras, strategy })` | `createForm({ validation: { extras, strategy }, flow })` | Живая проверка — стратегия формы |
97
+ | `useWizardStepValidation(config)` | `validation: { strategy }` сборки | Стратегию армирует `useFormBundle` |
98
+ | `makeValidationConfig(model)` (`validateStep(n)` / `validateAll()`) | `flow` сборки | Визард берёт поток, а не конфиг |
99
+
100
+ ### Удалено: ноды формы — контракты вместо классов (этап 7)
101
+
102
+ | Wrong | Correct | Notes |
103
+ | ------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
104
+ | классы `FormNode`, `FieldNode`, `GroupNode`, `ModelArrayNode` | интерфейсы `FormNode`, `FormControl`, `FormGroup`, `FormArray` | Классы реализации внутренние; ноды строит сборка |
105
+ | `FormProxy<T>` | `FormGroup<T>` | Тип группы |
106
+ | `isFieldNode`, `isGroupNode`, `isArrayNode` | `isFormControl`, `isFormGroup`, `isFormArray` | Гарды по метке вида; `isFormNode` остался |
107
+ | `node instanceof FieldNode` | `isFormControl(node)` | `instanceof` по интерфейсу невозможен |
108
+ | `node.status.value`, `FieldStatus` | `node.valid` / `invalid` / `pending` / `disabled` | Отдельного статуса нет |
109
+ | `node.patchValue(v)` | `node.setValue(v)` / `model.patch(partial)` | Частичное обновление — у модели |
110
+ | `markAsDirty()`, `markAsPristine()` | `dirty` ставит `setValue(v)`, снимает `reset()` | Изменились ли значения модели — `model.isDirty()` |
111
+ | `node.validate()` | `bundle.validate()` / `bundle.validateStep(id)` | |
112
+ | `group.fields` (Map), `group.getProxy()` | `form.<поле>`, `form.$` (все поля), `Object.values(form.$)` | `fields` теперь обычное поле данных |
113
+ | `node.id` | ключ строки — `array.keyAt(index)` | `id` теперь обычное поле данных |
114
+ | `array.items` | `array.at(i)` / `map` / `forEach` | |
115
+ | `array.length.value` | `array.length` (число; в эффекте подписывает) | Не сигнал |
116
+ | `key={row.id}` / `key={index}` у строк массива | `key={array.keyAt(index)}` | Ключ не меняется при `move` / `swap` |
117
+
118
+ Затеняют поле группы только члены `FormNode` (`value`, `issues`, `reset`, …): такое поле берут через
119
+ `form.$.value`. Имена `id`, `status`, `fields`, `submit` — обычные поля данных.
120
+
121
+ ### Удалено вместе со старым путём сборки формы
122
+
123
+ Форма собирается только из модели: `createForm({ initial | model, schema })`. Ноды строятся по
124
+ виду узла модели, правила живут в схеме валидации.
125
+
126
+ | Wrong | Correct | Notes |
127
+ | -------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
128
+ | `createLegacyForm(schema)` | `createForm({ initial, schema })` | Формы без модели нет |
129
+ | `new FormGroup({ email: { value, component } })` | `createFormFromModel({ model, schema })` | `FormGroup` — тип, ноды строит сборка |
130
+ | `new FormControl({ value: '' })` | `createForm({ initial: { email: '' } }).form.email` | `FormControl` — тип; значение принадлежит модели |
131
+ | `FieldConfig.validators` / `asyncValidators` | `validate` / `validateAsync` в `defineValidation` | Собственных валидаторов у ноды нет |
132
+ | `updateOn`, `debounce` узла поля; `field.setUpdateOn()` / `getUpdateOn()` | `validation: { strategy, debounce }` в `createForm` | Когда запускать проверку, решает стратегия валидации формы |
133
+ | `field.setValue(value, { emitEvent: false })` | `field.setValue(value)` | Опций у `setValue` нет |
134
+ | `ArrayNode` | `FormArray` | Нода массива одна — над массивом модели |
135
+ | `NodeFactory` | — | Сборка по ключам конфига удалена |
136
+ | `FormErrorHandler`, `ErrorStrategy` | — | Обслуживали валидаторы ноды |
137
+ | `FormSchema<T>`, `GroupNodeConfig<T>` | `FormSchemaNode` | Схема — дерево узлов, а не запись по полям |
138
+ | `ValidatorFn`, `AsyncValidatorFn`, `Validator<TForm, TField>` | `Rule<T>`, `AsyncRule<T>` из `@reformer/core/validation` | Правило — функция одного аргумента |
139
+
140
+ ### Удалено: подписки на нодах, доступ по строковому пути, псевдонимы
141
+
142
+ Реакции живут на слое модели (`defineFormBehavior`), поле берётся через форму, у каждой
143
+ операции одно имя.
144
+
145
+ | Wrong | Correct | Notes |
146
+ | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
147
+ | `field.watch(cb)`, `form.watchField('email', cb)`, `form.watchFieldByPath(path, cb)` | `onChange(model.$.email, cb)` в `defineFormBehavior`; вне поведения — `watchField(model.$.email, cb)` из `@reformer/core/model` | Подписок на ноде нет |
148
+ | `field.computeFrom(sources, fn)`, `form.linkFields('a', 'b', fn)` | `compute(model.$.b, () => fn(model.a))` в `defineFormBehavior` | Вычисляемое значение — оператор поведения |
149
+ | `field.dispose()` | сборку — `bundle.dispose()`; форму из `createFormFromModel` — `disposeForm(form)` | В контракте нод `dispose` нет |
150
+ | `form.getFieldByPath('address.city')`, `form.getField('email')` | `form.address.city`, `form.email`; имя, занятое членом ноды, — `form.$.value` | Строковых путей к нодам нет |
151
+ | `form.getAllFields()` | `Object.values(form.$)` | `form.$` — ноды всех полей группы по имени |
152
+ | `form.touchAll()` | `form.markAsTouched()` | Отметка каскадная: уходит во все вложенные поля |
153
+ | `form.resetToInitial()`, `field.resetToInitial()` | `form.reset()` — к значениям создания формы; `model.reset()` — к точке отсчёта модели | См. `28-submit-and-reset.md` |
154
+ | `form.submitWithResult(handler)` | `bundle.submission.submit(handler)` → `SubmitResult` | Проверка и отправка одним вызовом |
155
+ | `getNodeType(node)` | `isFormControl` / `isFormGroup` / `isFormArray` | Вид ноды — гардами |
156
+ | `each(model.items, (item) => …)` | `applyEach(model.$.items, itemRules)` | Правила строки — отдельная схема над элементом |
157
+ | `import { cross } from '@reformer/core/validation'` | `defineValidation(({ model, cross }) => …)` | `cross` — только из аргумента схемы |
158
+ | `createCoreForm`, `CreateCoreFormConfig` | `createForm`, `CreateFormConfig` | Сборка одна |
159
+ | `FormArrayProxy<T>` | `FormArray<T>` | Псевдоним типа удалён |
160
+ | `FormSubmitter`, `SubmittableForm`, `SubscriptionManager`, `createSchemaOverrideMaps`, `ModelArrayControl`, `FormControlsProxy` | — | Внутренние детали, из пакета не экспортируются |
161
+
162
+ ### Прежние ключи узла схемы
163
+
164
+ | Wrong | Correct | Notes |
165
+ | -------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
166
+ | `{ value: model.$.email, component }` | `{ model: model.$.email, component }` | Привязка поля — ключ `model` |
167
+ | `{ array: model.items, item }` | `{ model: model.$.items, item }` | Привязка массива под-форм — ручка, а не фасад |
168
+ | `{ email: { model, component } }` (запись «имя → узел») | `{ children: [{ model, component }] }` | Вложенные узлы читаются только из `children` |
169
+ | узел внутри `componentProps` | узел в `children` | В пропсы обход схемы не заглядывает |
170
+ | `testId` ключом узла | `componentProps: { testId }` | Ключа узла `testId` нет |
171
+
172
+ ### Операторы над сигналами — не в корне
173
+
174
+ `computeFrom`, `copyFrom`, `watchField`, `transformValue`, `resetWhen`, `syncFields`,
175
+ `revalidateWhen`, `enableWhen`, `disableWhen` из `@reformer/core` не импортируются.
176
+
177
+ | Wrong | Correct |
178
+ | -------------------------------- | ------------------------------------------------ |
179
+ | `import { enableWhen, copyFrom } from '@reformer/core'` | `import { enableWhen, copyFrom } from '@reformer/core/behaviors'` — внутри `defineFormBehavior` |
180
+ | `import { watchField } from '@reformer/core'` | `onChange` из `@reformer/core/behaviors`; сам примитив — `@reformer/core/model` |
181
+ | `import { computeFrom } from '@reformer/core'` | `compute` / `computeFrom` из `@reformer/core/behaviors` |
182
+
29
183
  ### UI: удалённые field-версии компонентов (`@reformer/ui-kit`)
30
184
 
31
185
  Отдельных «field-версий» у компонентов кита больше нет: в `component` поля кладётся сам
@@ -45,46 +199,59 @@
45
199
  ```typescript
46
200
  // WRONG - этих символов / путей НЕ существует
47
201
  import { useForm, validateForm, validateFormModel } from '@reformer/core'; // NO!
202
+ import { useFormControl, useFormBundle } from '@reformer/core'; // NO! хуки — из /react
48
203
  import { validate, applyWhen, equalTo } from '@reformer/core/validators'; // NO! операторов тут нет
204
+ import { defineValidationSchema, type ValidationSchema } from '@reformer/core/validation'; // NO!
205
+ import { resolveValidationError, useValidationMessage } from '@reformer/core/i18n'; // NO!
206
+ import { FieldNode, GroupNode, isFieldNode, type FormProxy } from '@reformer/core'; // NO!
207
+ import { defineSteps, useWizardStepValidation } from '@reformer/cdk/form-wizard'; // NO!
208
+ import { FormArray, useFormArray } from '@reformer/cdk/form-array'; // NO! — field-array
49
209
  import { transformers } from '@reformer/core/behaviors'; // NO!
50
- import type { FieldPath, ValidationSchemaFn } from '@reformer/core'; // NO!
210
+ import type { FieldPath, ValidationSchemaFn, ValidationError } from '@reformer/core'; // NO!
51
211
 
52
212
  // CORRECT
53
- import { createModel, createForm, useFormControl } from '@reformer/core';
54
- import type { FormModel, ValidationError, FieldConfig } from '@reformer/core';
55
- // Операторы валидации + раннер + defineValidationSchema — отдельный сабпуть:
213
+ import { createModel, createForm, isFormControl } from '@reformer/core';
214
+ import { useFormBundle, useFormControl, useIssueMessage } from '@reformer/core/react';
215
+ import type { FormModel, FormGroup, FormSchemaNode, ValidationIssue } from '@reformer/core';
216
+ // Операторы валидации + раннер + defineValidation — отдельный сабпуть:
56
217
  import {
57
- validate, validateAsync, validateWhen, cross, each, apply,
58
- defineValidationSchema, validateModel,
59
- type Rule, type AsyncRule, type ValidationSchema,
218
+ validate, validateAsync, validateWhen, apply, applyEach,
219
+ defineValidation, validateModel, runValidation, applyValidationResult,
220
+ type Rule, type AsyncRule, type ValidationDefinition, type ValidationResult,
60
221
  } from '@reformer/core/validation';
61
- // Фабрики-валидаторы (value-only, принимают nullable):
222
+ // Фабрики-валидаторы — правила значения `Rule<T>`, пустое значение пропускают:
62
223
  import { required, email, min, minLength, pattern } from '@reformer/core/validators';
63
- // Поведение (отдельный слой, контракт не менялся):
224
+ // Тексты замечаний — словарь формы:
225
+ import { defineIssueMessages } from '@reformer/core/i18n';
226
+ // Поведение (отдельный слой):
64
227
  import { defineFormBehavior, compute, onChange, revalidateWhen } from '@reformer/core/behaviors';
228
+ // Компонент массива — FieldArray:
229
+ import { FieldArray, useFieldArray } from '@reformer/cdk/field-array';
65
230
  ```
66
231
 
67
232
  ### Schema Common Mistakes
68
233
 
69
- Валидация и layout — **разные** контракты. Layout-узел привязывает поле к сигналу и
70
- НЕ несёт `validators`; правила живут в отдельной `defineValidationSchema`.
234
+ Валидация и разметка — **разные** контракты. Узел схемы привязывает поле к ручке модели и
235
+ правил не несёт; правила живут в отдельной `defineValidation`.
71
236
 
72
237
  ```typescript
73
- // WRONG - примитив/литерал вместо сигнала, и validators прямо в layout-узле
238
+ // WRONG - запись «имя → узел», литерал вместо ручки и validators прямо в узле
74
239
  const schema = {
75
- name: '', // нет привязки к сигналу
76
- email: { value: '', validators: [required()] }, // value — литерал, а validators в layout больше нет
240
+ name: '', // нет привязки к модели
241
+ email: { value: '', validators: [required()] }, // ни ключа value, ни validators у узла нет
77
242
  };
78
243
 
79
- // CORRECT - layout только связывает поле с сигналом модели...
80
- const layout = {
81
- name: { value: model.$.name, component: Input, componentProps: { label: 'Name' } },
82
- email: { value: model.$.email, component: Input, componentProps: { label: 'Email' } },
244
+ // CORRECT - схема только связывает поле с ручкой модели...
245
+ const schema: FormSchemaNode = {
246
+ children: [
247
+ { model: model.$.name, component: Input, componentProps: { label: 'Name' } },
248
+ { model: model.$.email, component: Input, componentProps: { label: 'Email' } },
249
+ ],
83
250
  };
84
251
 
85
- // ...а правила — отдельная ambient-схема (прогоняется раннером validateModel):
86
- const validation = defineValidationSchema<Form>(({ model }) => {
87
- validate(model.$.email, [required({ message: 'Email обязателен' }), email()]);
252
+ // ...а правила — отдельная схема (уходит в createForm({ validation }) либо в validateModel):
253
+ const validation = defineValidation<Form>(({ model }) => {
254
+ validate(model.$.email, [required({ code: 'email.required' }), email()]);
88
255
  });
89
256
  ```
90
257
 
@@ -99,7 +266,7 @@ enableWhen(model.$.city, () => Boolean(model.country), { resetOnDisable: true })
99
266
  ```
100
267
 
101
268
  Поведение **не владеет** валидацией. Чтобы поведение инициировало прогон схемы — мост
102
- `revalidateWhen` (validate/submit сами схему не запускают):
269
+ `revalidateWhen` (или живая стратегия сборки `validation: { strategy: 'change' }`):
103
270
 
104
271
  ```typescript
105
272
  // CORRECT - поведение дёргает внешний раннер валидации при изменении зависимости
@@ -2,14 +2,17 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- «Показать поле X только если Y» под M1 решается тремя независимыми механизмами — выбирай по
6
- тому, что именно должно быть условным:
5
+ «Показать поле X только если Y» решается тремя независимыми механизмами — выбирай по тому, что
6
+ именно должно быть условным:
7
7
 
8
8
  1. **Видимость / доступность** (поле остаётся в модели, но выключается/включается и не валидируется)
9
9
  — behaviors: `enableWhen`, а для условной записи значения — `compute` / `copyFrom` с опцией `{ when }`.
10
10
  2. **Условная валидация** (правила применяются только в истинной ветке) — оператор
11
- `validateWhen(() => cond, () => {…})` внутри схемы `defineValidationSchema`, прогоняется раннером `validateModel`.
12
- 3. **Скрытие из разметки** (узел вообще не рендерится) — `useFormControlValue` + условный рендер в JSX.
11
+ `validateWhen(() => cond, () => {…})` внутри схемы `defineValidation`; прогон — уровнем формы
12
+ (`bundle.validate()` / `bundle.submission.submit`) либо раннером `validateModel`.
13
+ 3. **Скрытие из разметки** (узел вообще не рендерится) — в схеме под рендерером — оператор
14
+ `hideWhen(schema.node(selector), cond)`; при разметке в JSX — `useFormControlValue` + условный
15
+ рендер.
13
16
 
14
17
  > Условная **валидация** — оператор `validateWhen` из `@reformer/core/validation`; старого branch-узла
15
18
  > `{ when, children }` и хелпера `applyWhen` больше нет — см.
@@ -56,36 +59,51 @@ export const creditBehavior = defineFormBehavior<CreditForm>(({ model }) => {
56
59
  - **Все поля должны быть материализованы в модели** (`createModel`), даже те, что показываются
57
60
  только под условием. `enableWhen`/`compute`/`copyFrom` принимают сигналы `model.$.field`, а не
58
61
  строковые пути; сигнала для несуществующего поля нет.
59
- - Поведение подключается к форме через `createFormFromModel({ model, schema, behavior })` — форма владеет
60
- жизненным циклом, DSL-операторы регистрируют свой cleanup сами (ручной массив cleanup'ов не нужен).
62
+ - Поведение подключается к форме через `createForm({ …, behavior })` (или
63
+ `createFormFromModel({ model, schema, behavior })`) — форма владеет жизненным циклом,
64
+ DSL-операторы регистрируют свой cleanup сами (ручной массив cleanup'ов не нужен).
61
65
  - Таргетом может быть и **группа**: `enableWhen(model.$.residenceAddress, () => model.sameAsRegistration === false)`
62
66
  проходит по поддереву; без `resetOnDisable` значение группы сохраняется (удобно, когда оно копируется
63
67
  из другого источника).
64
68
 
65
- > **Групповой таргет работает только у DSL-`enableWhen` из `@reformer/core/behaviors`**, не у
66
- > одноимённого корневого `enableWhen` из `@reformer/core` (тот резолвит только leaf-сигналы через
67
- > реестр → на group-сигнале это тихий no-op). `get_symbol_docs("enableWhen")` возвращает именно
68
- > корневой, не group-capable вариант.
69
+ > `enableWhen` / `disableWhen` существуют только как операторы поведения
70
+ > (`@reformer/core/behaviors`): в корне `@reformer/core` и в `@reformer/core/model` их нет.
71
+
72
+ Скрыть узел разметки, когда форму рисует рендерер, — оператор узла схемы в том же поведении:
73
+
74
+ ```typescript
75
+ import { defineFormBehavior, enableWhen, hideWhen } from '@reformer/core/behaviors';
76
+
77
+ export const mortgageBehavior = defineFormBehavior<CreditForm>(({ model, schema }) => {
78
+ const isMortgage = () => model.loanType === 'mortgage';
79
+ enableWhen([model.$.propertyValue, model.$.initialPayment], isMortgage, { resetOnDisable: true });
80
+ hideWhen(schema.node('mortgage'), () => !isMortgage()); // узел с selector: 'mortgage'
81
+ });
82
+ ```
83
+
84
+ `hideWhen` только записывает правило — исполняет его рендерер; при разметке в JSX он ничего не
85
+ делает (см. раздел «Скрытие в JSX»).
69
86
 
70
87
  ## Условная валидация
71
88
 
72
89
  Правила, действующие только при выполнении условия, оборачиваются оператором
73
- `validateWhen(() => cond, () => {…})` внутри схемы `defineValidationSchema<T>(({ model }) => …)`.
90
+ `validateWhen(() => cond, () => {…})` внутри схемы `defineValidation<T>(({ model }) => …)`.
74
91
  Условие `cond` читает модель напрямую (`model.field` — снимок на момент прогона): при истинном условии
75
92
  раннер применяет вложенные `validate`/`cross`/…, при ложном — правила ветки **гасятся** (поля,
76
- затронутые ранее, получают `setErrors([])`). Прогон — внешним раннером
77
- `validateModel(model, schema): Promise<boolean>` (ошибки сами роутятся в ноды формы).
93
+ затронутые ранее, получают `setIssues([])`). Прогон — уровнем формы (`createForm({ validation })`,
94
+ затем `bundle.validate()` / `bundle.submission.submit(handler)`) либо раннером
95
+ `validateModel(model, schema): Promise<boolean>`; замечания сами доезжают до нод формы.
78
96
 
79
97
  ```typescript
80
98
  import {
81
99
  validate,
82
100
  validateWhen,
83
- defineValidationSchema,
101
+ defineValidation,
84
102
  validateModel,
85
103
  } from '@reformer/core/validation';
86
104
  import { required, min } from '@reformer/core/validators';
87
105
 
88
- const schema = defineValidationSchema<CreditForm>(({ model }) => {
106
+ const schema = defineValidation<CreditForm>(({ model }) => {
89
107
  validate(model.$.loanType, [required()]);
90
108
 
91
109
  // ветка применяется только для ипотеки
@@ -102,19 +120,28 @@ const ok: boolean = await validateModel(model, schema);
102
120
  ```
103
121
 
104
122
  Переключение `loanType` с `mortgage` на другой тип автоматически снимает ошибки `required` с
105
- `propertyValue`/`initialPayment` — при ложном условии ветка `validateWhen` гасит их (`setErrors([])`).
123
+ `propertyValue`/`initialPayment` — при ложном условии ветка `validateWhen` гасит их (`setIssues([])`).
106
124
  `validateWhen` управляет только **валидацией**; чтобы поля ещё и выпадали из состояния (сброс
107
125
  значения), держи их под `enableWhen` из раздела выше — слои независимы.
108
126
 
127
+ Выключенное поле (`enableWhen`, `disable()`) в проверке **уровня формы** не участвует:
128
+ `bundle.validate`, `validateStep`, `submission.submit`, `flow.next()` и живая проверка гасят его
129
+ замечания, и статус прогона совпадает с `valid` формы. Значение выключенного поля при этом остаётся
130
+ в модели и уходит в обработчик отправки — модель передаётся целиком; убрать его из отправки —
131
+ забота обработчика либо `enableWhen(…, { resetOnDisable: true })`. `validateWhen` нужен, когда
132
+ поле остаётся доступным, но правило по условию не действует, — и в независимом раннере
133
+ `validateModel(model, rules)`, который о нодах не знает.
134
+
109
135
  ## Скрытие в JSX
110
136
 
111
137
  Чтобы условный блок вообще не рендерился, читай значение-триггер хуком `useFormControlValue(form.field)`
112
138
  и делай ранний `return null`. Хук возвращает **только значение** (без деструктуризации `{ value }`).
113
139
 
114
140
  ```tsx
115
- import { useFormControlValue } from '@reformer/core';
141
+ import type { FormGroup } from '@reformer/core';
142
+ import { useFormControlValue } from '@reformer/core/react';
116
143
 
117
- function MortgageFields({ form }: { form: CreditForm }) {
144
+ function MortgageFields({ form }: { form: FormGroup<CreditForm> }) {
118
145
  const loanType = useFormControlValue(form.loanType);
119
146
  if (loanType !== 'mortgage') return null;
120
147
 
@@ -137,7 +164,7 @@ function MortgageFields({ form }: { form: CreditForm }) {
137
164
 
138
165
  - **Нет branch-узла `{ when, children }` в дереве схемы.** Старая схема была деревом
139
166
  `{ value, validators: [...] }` с ветками `{ when: (scope, root) => boolean, children: [...] }`,
140
- которое обходил `validateFormModel`. Теперь схема — функция `({ model }) => void`, а условие —
167
+ которое обходил `validateFormModel`. Теперь схема — функция `({ model, cross }) => void`, а условие —
141
168
  оператор `validateWhen(() => cond, () => {…})`; отдельного объекта-узла нет.
142
169
 
143
170
  ```typescript
@@ -154,25 +181,32 @@ function MortgageFields({ form }: { form: CreditForm }) {
154
181
  - **`applyWhen` не существует** — ни как экспорт, ни как локальный сахар: он эмитил branch-узел,
155
182
  которого больше нет. Оборачивай условные правила оператором `validateWhen`.
156
183
 
157
- - **`ValidationSchemaFn` удалён** (тип старой path-схемы). Тип схемы — `ValidationSchema<T>`
158
- (`(ctx: { model: FormModel<T> }) => void`), фабрика-обёртка — `defineValidationSchema<T>(fn)`.
184
+ - **`ValidationSchemaFn` удалён** (тип старой path-схемы). Тип схемы — `ValidationDefinition<T>`
185
+ (`(scope: { model: FormModel<T>; cross }) => void`), фабрика-обёртка — `defineValidation<T>(fn)`.
159
186
 
160
- - **Cross-field — обычная функция над снапшотом, а не `ModelValidator (value, scope, root)`.**
161
- Соседние поля читаются из снимка модели (`f: Root`, эквивалент `model.get()`), а правило навешивается
162
- оператором `cross(sig, fn)`. Каноничное использование — `complex-multy-step-form/schemas/validation.ts`.
187
+ - **Cross-field — обычная функция над снимком, а не `ModelValidator (value, scope, root)`.**
188
+ Правило значения — `Rule<T> = (value: T) => error | null`, параметров `scope` / `root` у него нет.
189
+ Соседние поля читаются из снимка модели, а правило вешается оператором `cross(sig, check)` из
190
+ аргумента схемы. Каноничное использование — каталог `validation/` формы `complex-multy-step-form`.
163
191
 
164
192
  ```typescript
165
- import { cross } from '@reformer/core/validation';
166
- import type { ValidationError } from '@reformer/core';
193
+ import { defineValidation } from '@reformer/core/validation';
194
+ import type { ValidationIssue } from '@reformer/core';
167
195
 
168
- // (f: Root) => error — сравнение с соседним полем, без scope/root-параметров
169
- const passwordsMatch = (f: { password: string; confirmPassword: string }): ValidationError | null =>
170
- f.confirmPassword && f.password && f.confirmPassword !== f.password
171
- ? { code: 'mismatch', message: 'Пароли не совпадают' }
196
+ type Passwords = { password: string; confirmPassword: string };
197
+
198
+ // (snapshot) => error — сравнение с соседним полем
199
+ const passwordsMatch = (passwords: Passwords): ValidationIssue | null =>
200
+ passwords.confirmPassword &&
201
+ passwords.password &&
202
+ passwords.confirmPassword !== passwords.password
203
+ ? { code: 'confirmPassword.mismatch' } // текст кода — в словаре формы
172
204
  : null;
173
205
 
174
- // внутри defineValidationSchema — fn получает снапшот текущего scope (model.get()):
175
- cross(model.$.confirmPassword, passwordsMatch);
206
+ // cross — из аргумента схемы; check получает снимок модели области:
207
+ const passwordRules = defineValidation<Passwords>(({ model, cross }) => {
208
+ cross(model.$.confirmPassword, passwordsMatch);
209
+ });
176
210
  ```
177
211
 
178
212
  ## See also
@@ -1,12 +1,14 @@
1
1
  ## 16. READING FIELD VALUES (CRITICALLY IMPORTANT)
2
2
 
3
- Под M1 значения живут в модели. Есть три контекста чтения: value-доступ модели, сигналы, и React-хуки.
3
+ Значения живут в модели. Есть три контекста чтения: value-доступ модели, сигналы, и React-хуки
4
+ (`@reformer/core/react`).
4
5
 
5
6
  ### В React-компоненте — хуки
6
7
 
7
8
  ```typescript
8
- // Полное состояние поля (объект)
9
- const { value, errors, disabled, touched, shouldShowError } = useFormControl(control.email);
9
+ // Полное состояние поля (объект); текст замечания — useIssueMessage(messages)(issues[0])
10
+ // shouldShowIssues — показывать ли тексты замечаний; shouldShowError — aria-invalid
11
+ const { value, issues, disabled, touched, shouldShowIssues } = useFormControl(control.email);
10
12
 
11
13
  // Только значение (напрямую, БЕЗ деструктуризации!)
12
14
  const email = useFormControlValue(control.email);
@@ -15,6 +17,16 @@ const email = useFormControlValue(control.email);
15
17
  const count = useArrayLength(control.items);
16
18
  ```
17
19
 
20
+ ### Вне React — нода формы
21
+
22
+ ```typescript
23
+ form.email.value.value; // сигнал значения ноды (реактивно в effect/computed)
24
+ form.email.getValue(); // значение без подписки
25
+ form.email.issues.value; // замечания: ValidationIssue[] — код и параметры, без текста
26
+ form.items.length; // число строк массива (число; в эффекте подписывает)
27
+ form.getValue(); // значение группы целиком
28
+ ```
29
+
18
30
  ### Вне React — модель
19
31
 
20
32
  ```typescript
@@ -1,11 +1,12 @@
1
1
  ## 17. COMPUTE vs ONCHANGE
2
2
 
3
- Под M1 behaviors работают на сигналах модели. Есть два способа их писать:
3
+ Поведение работает на сигналах модели. Писать его можно двумя способами:
4
4
 
5
- - **примитивы из `@reformer/core`** — принимают сигналы, возвращают cleanup, вызываются
6
- императивно (например, в `useEffect`), cleanup складывается в массив;
7
5
  - **декларативный DSL из `@reformer/core/behaviors`** — `defineFormBehavior(...)` + операторы,
8
- cleanup управляется формой, передаётся в `createForm({ behavior })`.
6
+ cleanup управляется формой, передаётся в `createForm({ behavior })`. Это способ для формы;
7
+ - **примитивы из `@reformer/core/model`** — принимают сигналы, возвращают cleanup, вызываются
8
+ императивно (например, в `useEffect`), cleanup складывается в массив. Нужны вне формы; в корне
9
+ `@reformer/core` их нет.
9
10
 
10
11
  Для производных значений — `compute`/`computeFrom`. Для side-эффектов на изменение (async,
11
12
  обновление componentProps) — `onChange` (DSL) или примитив `watchField`.
@@ -14,7 +15,9 @@
14
15
 
15
16
  `compute(target, read)` подписывается на сигналы, прочитанные внутри `read()`, и пишет
16
17
  результат в `target`. Цель не входит в источники → цикла нет; запись идемпотентна (peek-guard).
17
- Кросс-уровневые вычисления работают так же — читай любые поля модели.
18
+ Кросс-уровневые вычисления работают так же — читай любые поля модели. Тип результата задаёт поле:
19
+ `compute(model.$.total, () => model.amount)` при `amount: number | null` и `total: number` не
20
+ компилируется — вычисление писало бы `null` в число (то же у `computeFrom`).
18
21
 
19
22
  ```typescript
20
23
  import { defineFormBehavior, compute } from '@reformer/core/behaviors';
@@ -39,7 +42,7 @@ const behavior = defineFormBehavior<MyForm>(({ model }) => {
39
42
  > отследится и пересчёта не будет. Читай поля по отдельности (`model.field` или `model.$.field.value`).
40
43
  > `model.get()` — только вне реактивного контекста (в `onChange`, обработчиках событий).
41
44
 
42
- ### Computed sum over a FormArray
45
+ ### Computed sum over a FieldArray
43
46
 
44
47
  Агрегат по массиву (сумма доходов созаёмщиков и т.п.) считается **реактивно** через value-proxy
45
48
  массива — `model.<array>.map(...)`:
@@ -79,7 +82,7 @@ compute(model.$.interestRate, () => computeRate(model.properties.map(() => null)
79
82
  источников приходят в `fn` позиционно.
80
83
 
81
84
  ```typescript
82
- import { computeFrom } from '@reformer/core/behaviors'; // или из '@reformer/core' как примитив
85
+ import { computeFrom } from '@reformer/core/behaviors'; // примитив — из '@reformer/core/model'
83
86
 
84
87
  computeFrom(
85
88
  [model.$.loanAmount, model.$.loanTerm, model.$.interestRate],
@@ -88,7 +91,10 @@ computeFrom(
88
91
  );
89
92
  ```
90
93
 
91
- > Примитив `computeFrom` из `@reformer/core` имеет ту же сигнатуру и возвращает cleanup-функцию.
94
+ > Примитив `computeFrom` из `@reformer/core/model` имеет ту же сигнатуру и возвращает cleanup-функцию,
95
+ > но цель вычисляемой НЕ помечает: `model.set` / `patch` / `reset` её перезаписывают, `isDirty()` её
96
+ > учитывает — до следующей смены источников. Оператор из `@reformer/core/behaviors` цель помечает
97
+ > (`markDerived`), и массовая запись её не трогает. В поведении формы — оператор.
92
98
 
93
99
  ### onChange — реакция на изменение (async, side-effects)
94
100
 
@@ -113,11 +119,11 @@ const behavior = defineFormBehavior<MyForm>(({ model, form }) => {
113
119
 
114
120
  ### Примитив watchField
115
121
 
116
- Низкоуровневая подписка из `@reformer/core` (без debounce/AbortSignal). `onChange` построен
122
+ Низкоуровневая подписка из `@reformer/core/model` (без debounce/AbortSignal). `onChange` построен
117
123
  поверх неё. Для простых синхронных реакций:
118
124
 
119
125
  ```typescript
120
- import { watchField } from '@reformer/core';
126
+ import { watchField } from '@reformer/core/model';
121
127
  const stop = watchField(model.$.country, () => { model.city = ''; });
122
128
  ```
123
129