@reformer/core 11.0.0-beta.1 → 11.0.0-beta.10

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 (283) hide show
  1. package/README.md +90 -68
  2. package/dist/behaviors-value-D05z3my6.js +109 -0
  3. package/dist/behaviors.d.ts +1 -1
  4. package/dist/behaviors.js +376 -211
  5. package/dist/create-model-DPSG3I0O.js +30 -0
  6. package/dist/date-utils-90-qZbse.js +43 -0
  7. package/dist/en-HP8XEych.js +44 -0
  8. package/dist/form/aggregate-signals.d.ts +21 -63
  9. package/dist/form/behaviors/collections.d.ts +84 -0
  10. package/dist/form/behaviors/context.d.ts +56 -0
  11. package/dist/form/behaviors/index.d.ts +30 -0
  12. package/dist/form/behaviors/internals.d.ts +41 -0
  13. package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +28 -5
  14. package/dist/form/behaviors/operators.d.ts +59 -0
  15. package/dist/form/behaviors/schema-operators.d.ts +83 -0
  16. package/dist/form/behaviors/types.d.ts +37 -0
  17. package/dist/form/create-form.d.ts +59 -70
  18. package/dist/form/flow/flow.d.ts +87 -0
  19. package/dist/form/form-bundle.d.ts +153 -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 +23 -24
  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 +159 -0
  37. package/dist/form/schema-subtree.d.ts +28 -0
  38. package/dist/form/signal-node-registry.d.ts +27 -22
  39. package/dist/form/submission.d.ts +93 -0
  40. package/dist/form/type-guards.d.ts +14 -73
  41. package/dist/form/types/contracts.d.ts +21 -85
  42. package/dist/form/types/field-config.d.ts +27 -0
  43. package/dist/form/types/index.d.ts +5 -57
  44. package/dist/form/types/nodes.d.ts +159 -0
  45. package/dist/form/types/schema-node.d.ts +147 -46
  46. package/dist/form/types/validation-schema.d.ts +25 -17
  47. package/dist/form/validation/context.d.ts +67 -0
  48. package/dist/form/validation/index.d.ts +24 -28
  49. package/dist/form/validation/operators.d.ts +101 -0
  50. package/dist/form/validation/run.d.ts +120 -0
  51. package/dist/form/validation/strategy.d.ts +89 -0
  52. package/dist/form/validation/types.d.ts +83 -0
  53. package/dist/form/validators/date-utils.d.ts +42 -0
  54. package/dist/form/validators/email.d.ts +23 -0
  55. package/dist/form/{validation/validators → validators}/file-type.d.ts +10 -10
  56. package/dist/form/{validation/validators → validators}/file-utils.d.ts +3 -2
  57. package/dist/form/validators/future-date.d.ts +23 -0
  58. package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
  59. package/dist/form/validators/integer.d.ts +22 -0
  60. package/dist/form/validators/is-date.d.ts +24 -0
  61. package/dist/form/validators/is-number.d.ts +25 -0
  62. package/dist/form/validators/max-age.d.ts +28 -0
  63. package/dist/form/validators/max-date.d.ts +25 -0
  64. package/dist/form/{validation/validators → validators}/max-file-size.d.ts +10 -10
  65. package/dist/form/validators/max-files.d.ts +26 -0
  66. package/dist/form/validators/max-length.d.ts +33 -0
  67. package/dist/form/validators/max-total-file-size.d.ts +26 -0
  68. package/dist/form/validators/max.d.ts +26 -0
  69. package/dist/form/validators/min-age.d.ts +27 -0
  70. package/dist/form/validators/min-date.d.ts +25 -0
  71. package/dist/form/{validation/validators → validators}/min-file-size.d.ts +10 -10
  72. package/dist/form/validators/min-files.d.ts +27 -0
  73. package/dist/form/validators/min-length.d.ts +33 -0
  74. package/dist/form/validators/min.d.ts +26 -0
  75. package/dist/form/validators/multiple-of.d.ts +25 -0
  76. package/dist/form/validators/non-negative.d.ts +22 -0
  77. package/dist/form/validators/non-zero.d.ts +22 -0
  78. package/dist/form/validators/past-date.d.ts +23 -0
  79. package/dist/form/validators/pattern.d.ts +26 -0
  80. package/dist/form/{validation/validators → validators}/phone.d.ts +11 -11
  81. package/dist/form/validators/required.d.ts +29 -0
  82. package/dist/form/validators/url.d.ts +45 -0
  83. package/dist/form/validators/validation-issue.d.ts +8 -0
  84. package/dist/i18n/descriptor.d.ts +54 -0
  85. package/dist/i18n/en.json.d.ts +49 -0
  86. package/dist/i18n/loader.d.ts +71 -0
  87. package/dist/i18n/locale-store.d.ts +46 -0
  88. package/dist/i18n/locale.d.ts +90 -0
  89. package/dist/i18n/message-format.d.ts +136 -0
  90. package/dist/i18n/resolve-localized.d.ts +17 -0
  91. package/dist/i18n/ru.json.d.ts +49 -0
  92. package/dist/i18n/translator.d.ts +61 -0
  93. package/dist/i18n/validation-message.d.ts +53 -0
  94. package/dist/i18n.d.ts +26 -0
  95. package/dist/i18n.js +86 -0
  96. package/dist/index.d.ts +17 -5
  97. package/dist/index.js +942 -2601
  98. package/dist/loader-BWkigpVe.js +52 -0
  99. package/dist/locale/en.d.ts +2 -0
  100. package/dist/locale/en.js +5 -0
  101. package/dist/locale/en.json +45 -0
  102. package/dist/locale/index.d.ts +18 -0
  103. package/dist/locale/ru.d.ts +2 -0
  104. package/dist/locale/ru.js +44 -0
  105. package/dist/locale/ru.json +45 -0
  106. package/dist/locale-CG54AU2Q.js +219 -0
  107. package/dist/locale.d.ts +2 -0
  108. package/dist/locale.js +9 -0
  109. package/dist/{state → model}/behaviors-value.d.ts +24 -6
  110. package/dist/{state/form-model.d.ts → model/create-model.d.ts} +18 -4
  111. package/dist/{state → model}/derived-registry.d.ts +1 -1
  112. package/dist/model/index.d.ts +42 -0
  113. package/dist/model/model-nodes.d.ts +190 -0
  114. package/dist/model/model-signals-proxy.d.ts +63 -0
  115. package/dist/model/model-value-proxy.d.ts +84 -0
  116. package/dist/{state → model}/safe-effect.d.ts +50 -2
  117. package/dist/model/types.d.ts +239 -0
  118. package/dist/model.d.ts +2 -0
  119. package/dist/model.js +27 -0
  120. package/dist/node-kind-C-r6ZMdo.js +15 -0
  121. package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
  122. package/dist/platforms/react/field-binding/field-handle.d.ts +53 -0
  123. package/dist/platforms/react/form-bundle-context.d.ts +27 -0
  124. package/dist/{form → platforms/react}/hooks/types.d.ts +58 -36
  125. package/dist/platforms/react/hooks/use-form-bundle.d.ts +41 -0
  126. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  127. package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +13 -7
  128. package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +6 -6
  129. package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +7 -9
  130. package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +18 -18
  131. package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +9 -1
  132. package/dist/platforms/react/i18n/context.d.ts +18 -0
  133. package/dist/platforms/react/i18n/hooks.d.ts +65 -0
  134. package/dist/platforms/react/i18n/provider.d.ts +59 -0
  135. package/dist/platforms/react/index.d.ts +30 -0
  136. package/dist/react.d.ts +22 -0
  137. package/dist/react.js +505 -0
  138. package/dist/runtime-token.d.ts +1 -2
  139. package/dist/safe-effect-BazLOv1g.js +669 -0
  140. package/dist/schema-controller-CpiO2JoK.js +143 -0
  141. package/dist/signals.d.ts +15 -9
  142. package/dist/signals.js +11 -6
  143. package/dist/strategy-DzvnsWLh.js +375 -0
  144. package/dist/type-guards-D-N_5Ddh.js +19 -0
  145. package/dist/validation-issue-B9mjI8jz.js +7 -0
  146. package/dist/validation-message-Ced9MGxs.js +194 -0
  147. package/dist/validation.d.ts +1 -1
  148. package/dist/validation.js +11 -165
  149. package/dist/validators/email.d.ts +1 -1
  150. package/dist/validators/email.js +5 -8
  151. package/dist/validators/future-date.d.ts +1 -1
  152. package/dist/validators/future-date.js +10 -13
  153. package/dist/validators/integer.d.ts +1 -1
  154. package/dist/validators/integer.js +4 -7
  155. package/dist/validators/is-date.d.ts +1 -1
  156. package/dist/validators/is-date.js +5 -8
  157. package/dist/validators/is-number.d.ts +1 -1
  158. package/dist/validators/is-number.js +4 -7
  159. package/dist/validators/max-age.d.ts +1 -1
  160. package/dist/validators/max-age.js +8 -12
  161. package/dist/validators/max-date.d.ts +1 -1
  162. package/dist/validators/max-date.js +10 -13
  163. package/dist/validators/max-length.d.ts +1 -1
  164. package/dist/validators/max-length.js +7 -10
  165. package/dist/validators/max.d.ts +1 -1
  166. package/dist/validators/max.js +4 -7
  167. package/dist/validators/min-age.d.ts +1 -1
  168. package/dist/validators/min-age.js +10 -11
  169. package/dist/validators/min-date.d.ts +1 -1
  170. package/dist/validators/min-date.js +8 -11
  171. package/dist/validators/min-length.d.ts +1 -1
  172. package/dist/validators/min-length.js +4 -7
  173. package/dist/validators/min.d.ts +1 -1
  174. package/dist/validators/min.js +4 -7
  175. package/dist/validators/multiple-of.d.ts +1 -1
  176. package/dist/validators/multiple-of.js +11 -10
  177. package/dist/validators/non-negative.d.ts +1 -1
  178. package/dist/validators/non-negative.js +2 -5
  179. package/dist/validators/non-zero.d.ts +1 -1
  180. package/dist/validators/non-zero.js +4 -7
  181. package/dist/validators/past-date.d.ts +1 -1
  182. package/dist/validators/past-date.js +10 -13
  183. package/dist/validators/pattern.d.ts +1 -1
  184. package/dist/validators/pattern.js +5 -8
  185. package/dist/validators/phone.d.ts +1 -1
  186. package/dist/validators/phone.js +10 -11
  187. package/dist/validators/required.d.ts +1 -1
  188. package/dist/validators/required.js +4 -7
  189. package/dist/validators/url.d.ts +1 -1
  190. package/dist/validators/url.js +48 -17
  191. package/dist/validators.d.ts +1 -1
  192. package/dist/validators.js +86 -108
  193. package/docs/llms/01-api-reference.md +60 -34
  194. package/docs/llms/02-quick-start.md +112 -74
  195. package/docs/llms/03-api-signatures.md +369 -148
  196. package/docs/llms/04-common-patterns.md +58 -56
  197. package/docs/llms/05-common-mistakes.md +69 -44
  198. package/docs/llms/06-troubleshooting.md +14 -9
  199. package/docs/llms/07-complete-import.md +74 -43
  200. package/docs/llms/08-form-types.md +11 -4
  201. package/docs/llms/09-formschema.md +132 -84
  202. package/docs/llms/10-arrays.md +141 -61
  203. package/docs/llms/11-async-watchfield.md +2 -2
  204. package/docs/llms/13-multi-step.md +134 -63
  205. package/docs/llms/14-extended-mistakes.md +20 -17
  206. package/docs/llms/15-project-structure.md +224 -70
  207. package/docs/llms/16-ui-components.md +86 -46
  208. package/docs/llms/17-nonexistent-api.md +217 -32
  209. package/docs/llms/18-conditional-fields.md +71 -32
  210. package/docs/llms/19-reading-values.md +45 -4
  211. package/docs/llms/20-compute-vs-watch.md +25 -10
  212. package/docs/llms/21-array-operations.md +76 -26
  213. package/docs/llms/22-cycle-detection.md +1 -1
  214. package/docs/llms/23-copy-from.md +2 -2
  215. package/docs/llms/24-sync-fields.md +6 -5
  216. package/docs/llms/25-reset-when.md +14 -7
  217. package/docs/llms/26-transform-value.md +4 -4
  218. package/docs/llms/27-revalidate-when.md +16 -15
  219. package/docs/llms/28-submit-and-reset.md +278 -128
  220. package/docs/llms/29-async-preload.md +41 -31
  221. package/docs/llms/30-type-safety-recipes.md +105 -51
  222. package/docs/llms/31-async-validator-debounce.md +96 -53
  223. package/docs/llms/33-validation-strategy.md +178 -66
  224. package/docs/llms/34-i18n.md +298 -0
  225. package/docs/llms/35-unified-contract.md +309 -0
  226. package/docs/llms/36-issue-messages.md +284 -0
  227. package/docs/llms/37-compiled-schema.md +332 -0
  228. package/docs/llms/38-form-flow.md +396 -0
  229. package/llms-index.json +1 -0
  230. package/llms.txt +10690 -3089
  231. package/package.json +48 -9
  232. package/dist/behaviors-node-DGEFMTQu.js +0 -18
  233. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  234. package/dist/date-utils-dTT_x_Be.js +0 -31
  235. package/dist/derived-registry-CluZCQ7U.js +0 -16
  236. package/dist/form/behaviors.d.ts +0 -133
  237. package/dist/form/error-handler.d.ts +0 -166
  238. package/dist/form/factories/index.d.ts +0 -6
  239. package/dist/form/factories/node-factory.d.ts +0 -188
  240. package/dist/form/form-submitter.d.ts +0 -121
  241. package/dist/form/nodes/array-node.d.ts +0 -276
  242. package/dist/form/nodes/field-node.d.ts +0 -294
  243. package/dist/form/nodes/form-node.d.ts +0 -326
  244. package/dist/form/nodes/group-node.d.ts +0 -284
  245. package/dist/form/nodes/model-array-node.d.ts +0 -121
  246. package/dist/form/status-machine.d.ts +0 -115
  247. package/dist/form/types/deep-schema.d.ts +0 -107
  248. package/dist/form/types/form-proxy.d.ts +0 -141
  249. package/dist/form/unique-id.d.ts +0 -45
  250. package/dist/form/validation/validators/date-utils.d.ts +0 -30
  251. package/dist/form/validation/validators/email.d.ts +0 -23
  252. package/dist/form/validation/validators/future-date.d.ts +0 -23
  253. package/dist/form/validation/validators/integer.d.ts +0 -22
  254. package/dist/form/validation/validators/is-date.d.ts +0 -23
  255. package/dist/form/validation/validators/is-number.d.ts +0 -24
  256. package/dist/form/validation/validators/max-age.d.ts +0 -25
  257. package/dist/form/validation/validators/max-date.d.ts +0 -25
  258. package/dist/form/validation/validators/max-files.d.ts +0 -26
  259. package/dist/form/validation/validators/max-length.d.ts +0 -27
  260. package/dist/form/validation/validators/max-total-file-size.d.ts +0 -26
  261. package/dist/form/validation/validators/max.d.ts +0 -25
  262. package/dist/form/validation/validators/min-age.d.ts +0 -25
  263. package/dist/form/validation/validators/min-date.d.ts +0 -25
  264. package/dist/form/validation/validators/min-files.d.ts +0 -26
  265. package/dist/form/validation/validators/min-length.d.ts +0 -27
  266. package/dist/form/validation/validators/min.d.ts +0 -25
  267. package/dist/form/validation/validators/multiple-of.d.ts +0 -24
  268. package/dist/form/validation/validators/non-negative.d.ts +0 -22
  269. package/dist/form/validation/validators/non-zero.d.ts +0 -22
  270. package/dist/form/validation/validators/past-date.d.ts +0 -23
  271. package/dist/form/validation/validators/pattern.d.ts +0 -33
  272. package/dist/form/validation/validators/required.d.ts +0 -32
  273. package/dist/form/validation/validators/url.d.ts +0 -43
  274. package/dist/form/validation-schema.d.ts +0 -106
  275. package/dist/form/validation-strategy.d.ts +0 -55
  276. package/dist/form/validators.d.ts +0 -11
  277. package/dist/form-model-DYCzGGKF.js +0 -286
  278. package/dist/state/index.d.ts +0 -31
  279. package/dist/state/subscription-manager.d.ts +0 -180
  280. package/dist/state/types.d.ts +0 -144
  281. package/dist/state.d.ts +0 -2
  282. package/dist/state.js +0 -21
  283. package/dist/subscription-manager-705bls97.js +0 -171
@@ -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,51 +30,232 @@
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
+ | `createReactForm`, `useReactForm` (`@reformer/renderer-react`); `createJsonForm`, `useJsonForm` (`@reformer/renderer-json`) | `createForm` + `useFormBundle`; документ JSON — `createForm({ schema: document, registry })` | Фабрики слоёв удалены — сборка одна |
160
+ | `JsonFormRenderer`, `JsonRendererProvider` | `<FormRenderer form={bundle} />` из `@reformer/renderer-react` | Реестр уходит в сборку, а не в контекст |
161
+ | `renderBehavior`, `RenderBehaviorFn` | одно поведение `defineFormBehavior(({ model, form, schema, flow }) => …)` | Правила узлов — в поведении формы |
162
+ | `CoreForm`, `CreateFormConfigBase` | `FormBundle`, `CreateFormConfig` | Общие типы удалённых фабрик |
163
+ | `FormArrayProxy<T>` | `FormArray<T>` | Псевдоним типа удалён |
164
+ | `FormSubmitter`, `SubmittableForm`, `SubscriptionManager`, `createSchemaOverrideMaps`, `ModelArrayControl`, `FormControlsProxy` | — | Внутренние детали, из пакета не экспортируются |
165
+
166
+ ### Прежние ключи узла схемы
167
+
168
+ | Wrong | Correct | Notes |
169
+ | -------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
170
+ | `{ value: model.$.email, component }` | `{ model: model.$.email, component }` | Привязка поля — ключ `model` |
171
+ | `{ array: model.items, item }` | `{ model: model.$.items, item }` | Привязка массива под-форм — ручка, а не фасад |
172
+ | `{ email: { model, component } }` (запись «имя → узел») | `{ children: [{ model, component }] }` | Вложенные узлы читаются только из `children` |
173
+ | узел внутри `componentProps` | узел в `children` | В пропсы обход схемы не заглядывает |
174
+ | `testId` ключом узла | `componentProps: { testId }` | Ключа узла `testId` нет |
175
+
176
+ ### Операторы над сигналами — не в корне
177
+
178
+ `computeFrom`, `copyFrom`, `watchField`, `transformValue`, `resetWhen`, `syncFields`,
179
+ `revalidateWhen`, `enableWhen`, `disableWhen` из `@reformer/core` не импортируются.
180
+
181
+ | Wrong | Correct |
182
+ | -------------------------------- | ------------------------------------------------ |
183
+ | `import { enableWhen, copyFrom } from '@reformer/core'` | `import { enableWhen, copyFrom } from '@reformer/core/behaviors'` — внутри `defineFormBehavior` |
184
+ | `import { watchField } from '@reformer/core'` | `onChange` из `@reformer/core/behaviors`; сам примитив — `@reformer/core/model` |
185
+ | `import { computeFrom } from '@reformer/core'` | `compute` / `computeFrom` из `@reformer/core/behaviors` |
186
+
187
+ ### UI: удалённые field-версии компонентов (`@reformer/ui-kit`)
188
+
189
+ Отдельных «field-версий» у компонентов кита больше нет: в `component` поля кладётся сам
190
+ компонент, диалект он объявляет статикой `reformerAdapter`, связывает поле обёртка
191
+ (`FormField.Control` / рендерер).
192
+
193
+ | Wrong | Correct |
194
+ | ---------------------------------------------- | --------------------------------------------------------- |
195
+ | `InputField`, `SelectField`, `CheckboxField`, … (любой `*Field`) | `Input`, `SelectAsync`, `CheckboxWithLabel`, … — таблица в ui-kit `01-overview.md` |
196
+ | `InputField` + `type: 'number'` | `InputNumber` |
197
+ | `InputField` + `suggestions` | `InputSuggest` |
198
+ | `FileUploadField` + `variant: 'dropzone' \| 'input'` | `FileUploadDropzone` / `FileUploadInput` (без `variant`) |
199
+ | `withFormControl(MyControl, adapter)` | `defineFieldControl(MyControl, { adapter })` из `@reformer/ui-kit/fields` |
200
+
29
201
  ### Common Import Errors
30
202
 
31
203
  ```typescript
32
204
  // WRONG - этих символов / путей НЕ существует
33
205
  import { useForm, validateForm, validateFormModel } from '@reformer/core'; // NO!
206
+ import { useFormControl, useFormBundle } from '@reformer/core'; // NO! хуки — из /react
34
207
  import { validate, applyWhen, equalTo } from '@reformer/core/validators'; // NO! операторов тут нет
208
+ import { defineValidationSchema, type ValidationSchema } from '@reformer/core/validation'; // NO!
209
+ import { resolveValidationError, useValidationMessage } from '@reformer/core/i18n'; // NO!
210
+ import { FieldNode, GroupNode, isFieldNode, type FormProxy } from '@reformer/core'; // NO!
211
+ import { defineSteps, useWizardStepValidation } from '@reformer/cdk/form-wizard'; // NO!
212
+ import { FormArray, useFormArray } from '@reformer/cdk/form-array'; // NO! — field-array
35
213
  import { transformers } from '@reformer/core/behaviors'; // NO!
36
- import type { FieldPath, ValidationSchemaFn } from '@reformer/core'; // NO!
214
+ import type { FieldPath, ValidationSchemaFn, ValidationError } from '@reformer/core'; // NO!
37
215
 
38
216
  // CORRECT
39
- import { createModel, createForm, useFormControl } from '@reformer/core';
40
- import type { FormModel, ValidationError, FieldConfig } from '@reformer/core';
41
- // Операторы валидации + раннер + defineValidationSchema — отдельный сабпуть:
217
+ import { createModel, createForm, isFormControl } from '@reformer/core';
218
+ import { useFormBundle, useFormControl, useIssueMessage } from '@reformer/core/react';
219
+ import type { FormModel, FormGroup, FormSchemaNode, ValidationIssue } from '@reformer/core';
220
+ // Операторы валидации + раннер + defineValidation — отдельный сабпуть:
42
221
  import {
43
- validate, validateAsync, validateWhen, cross, each, apply,
44
- defineValidationSchema, validateModel,
45
- type Rule, type AsyncRule, type ValidationSchema,
222
+ validate, validateAsync, validateWhen, apply, applyEach,
223
+ defineValidation, validateModel, runValidation, applyValidationResult,
224
+ type Rule, type AsyncRule, type ValidationDefinition, type ValidationResult,
46
225
  } from '@reformer/core/validation';
47
- // Фабрики-валидаторы (value-only, принимают nullable):
226
+ // Фабрики-валидаторы — правила значения `Rule<T>`, пустое значение пропускают:
48
227
  import { required, email, min, minLength, pattern } from '@reformer/core/validators';
49
- // Поведение (отдельный слой, контракт не менялся):
228
+ // Тексты замечаний — словарь формы:
229
+ import { defineIssueMessages } from '@reformer/core/i18n';
230
+ // Поведение (отдельный слой):
50
231
  import { defineFormBehavior, compute, onChange, revalidateWhen } from '@reformer/core/behaviors';
232
+ // Компонент массива — FieldArray:
233
+ import { FieldArray, useFieldArray } from '@reformer/cdk/field-array';
51
234
  ```
52
235
 
53
236
  ### Schema Common Mistakes
54
237
 
55
- Валидация и layout — **разные** контракты. Layout-узел привязывает поле к сигналу и
56
- НЕ несёт `validators`; правила живут в отдельной `defineValidationSchema`.
238
+ Валидация и разметка — **разные** контракты. Узел схемы привязывает поле к ручке модели и
239
+ правил не несёт; правила живут в отдельной `defineValidation`.
57
240
 
58
241
  ```typescript
59
- // WRONG - примитив/литерал вместо сигнала, и validators прямо в layout-узле
242
+ // WRONG - запись «имя → узел», литерал вместо ручки и validators прямо в узле
60
243
  const schema = {
61
- name: '', // нет привязки к сигналу
62
- email: { value: '', validators: [required()] }, // value — литерал, а validators в layout больше нет
244
+ name: '', // нет привязки к модели
245
+ email: { value: '', validators: [required()] }, // ни ключа value, ни validators у узла нет
63
246
  };
64
247
 
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' } },
248
+ // CORRECT - схема только связывает поле с ручкой модели...
249
+ const schema: FormSchemaNode = {
250
+ children: [
251
+ { model: model.$.name, component: Input, componentProps: { label: 'Name' } },
252
+ { model: model.$.email, component: Input, componentProps: { label: 'Email' } },
253
+ ],
69
254
  };
70
255
 
71
- // ...а правила — отдельная ambient-схема (прогоняется раннером validateModel):
72
- const validation = defineValidationSchema<Form>(({ model }) => {
73
- validate(model.$.email, [required({ message: 'Email обязателен' }), email()]);
256
+ // ...а правила — отдельная схема (уходит в createForm({ validation }) либо в validateModel):
257
+ const validation = defineValidation<Form>(({ model }) => {
258
+ validate(model.$.email, [required({ code: 'email.required' }), email()]);
74
259
  });
75
260
  ```
76
261
 
@@ -85,7 +270,7 @@ enableWhen(model.$.city, () => Boolean(model.country), { resetOnDisable: true })
85
270
  ```
86
271
 
87
272
  Поведение **не владеет** валидацией. Чтобы поведение инициировало прогон схемы — мост
88
- `revalidateWhen` (validate/submit сами схему не запускают):
273
+ `revalidateWhen` (или живая стратегия сборки `validation: { strategy: 'change' }`):
89
274
 
90
275
  ```typescript
91
276
  // 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,56 @@ export const creditBehavior = defineFormBehavior<CreditForm>(({ model }) => {
56
59
  - **Все поля должны быть материализованы в модели** (`createModel`), даже те, что показываются
57
60
  только под условием. `enableWhen`/`compute`/`copyFrom` принимают сигналы `model.$.field`, а не
58
61
  строковые пути; сигнала для несуществующего поля нет.
59
- - Поведение подключается к форме через `createForm({ 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»).
86
+
87
+ **Скрытие — только видимость.** Поля скрытого узла (`hideWhen` или
88
+ `schema.node(sel).setHidden(true)`) остаются включёнными, и правила их проверяют: шаг с
89
+ незаполненным скрытым полем не пройдёт, а замечаний на экране не будет. Поэтому узел с полями
90
+ скрывают вместе с `enableWhen` по тому же условию — как в примере выше.
69
91
 
70
92
  ## Условная валидация
71
93
 
72
94
  Правила, действующие только при выполнении условия, оборачиваются оператором
73
- `validateWhen(() => cond, () => {…})` внутри схемы `defineValidationSchema<T>(({ model }) => …)`.
95
+ `validateWhen(() => cond, () => {…})` внутри схемы `defineValidation<T>(({ model }) => …)`.
74
96
  Условие `cond` читает модель напрямую (`model.field` — снимок на момент прогона): при истинном условии
75
97
  раннер применяет вложенные `validate`/`cross`/…, при ложном — правила ветки **гасятся** (поля,
76
- затронутые ранее, получают `setErrors([])`). Прогон — внешним раннером
77
- `validateModel(model, schema): Promise<boolean>` (ошибки сами роутятся в ноды формы).
98
+ затронутые ранее, получают `setIssues([])`). Прогон — уровнем формы (`createForm({ validation })`,
99
+ затем `bundle.validate()` / `bundle.submission.submit(handler)`) либо раннером
100
+ `validateModel(model, schema): Promise<boolean>`; замечания сами доезжают до нод формы.
78
101
 
79
102
  ```typescript
80
103
  import {
81
104
  validate,
82
105
  validateWhen,
83
- defineValidationSchema,
106
+ defineValidation,
84
107
  validateModel,
85
108
  } from '@reformer/core/validation';
86
109
  import { required, min } from '@reformer/core/validators';
87
110
 
88
- const schema = defineValidationSchema<CreditForm>(({ model }) => {
111
+ const schema = defineValidation<CreditForm>(({ model }) => {
89
112
  validate(model.$.loanType, [required()]);
90
113
 
91
114
  // ветка применяется только для ипотеки
@@ -102,19 +125,28 @@ const ok: boolean = await validateModel(model, schema);
102
125
  ```
103
126
 
104
127
  Переключение `loanType` с `mortgage` на другой тип автоматически снимает ошибки `required` с
105
- `propertyValue`/`initialPayment` — при ложном условии ветка `validateWhen` гасит их (`setErrors([])`).
128
+ `propertyValue`/`initialPayment` — при ложном условии ветка `validateWhen` гасит их (`setIssues([])`).
106
129
  `validateWhen` управляет только **валидацией**; чтобы поля ещё и выпадали из состояния (сброс
107
130
  значения), держи их под `enableWhen` из раздела выше — слои независимы.
108
131
 
132
+ Выключенное поле (`enableWhen`, `disable()`) в проверке **уровня формы** не участвует:
133
+ `bundle.validate`, `validateStep`, `submission.submit`, `flow.next()` и живая проверка гасят его
134
+ замечания, и статус прогона совпадает с `valid` формы. Значение выключенного поля при этом остаётся
135
+ в модели и уходит в обработчик отправки — модель передаётся целиком; убрать его из отправки —
136
+ забота обработчика либо `enableWhen(…, { resetOnDisable: true })`. `validateWhen` нужен, когда
137
+ поле остаётся доступным, но правило по условию не действует, — и в независимом раннере
138
+ `validateModel(model, rules)`, который о нодах не знает.
139
+
109
140
  ## Скрытие в JSX
110
141
 
111
142
  Чтобы условный блок вообще не рендерился, читай значение-триггер хуком `useFormControlValue(form.field)`
112
143
  и делай ранний `return null`. Хук возвращает **только значение** (без деструктуризации `{ value }`).
113
144
 
114
145
  ```tsx
115
- import { useFormControlValue } from '@reformer/core';
146
+ import type { FormGroup } from '@reformer/core';
147
+ import { useFormControlValue } from '@reformer/core/react';
116
148
 
117
- function MortgageFields({ form }: { form: CreditForm }) {
149
+ function MortgageFields({ form }: { form: FormGroup<CreditForm> }) {
118
150
  const loanType = useFormControlValue(form.loanType);
119
151
  if (loanType !== 'mortgage') return null;
120
152
 
@@ -137,7 +169,7 @@ function MortgageFields({ form }: { form: CreditForm }) {
137
169
 
138
170
  - **Нет branch-узла `{ when, children }` в дереве схемы.** Старая схема была деревом
139
171
  `{ value, validators: [...] }` с ветками `{ when: (scope, root) => boolean, children: [...] }`,
140
- которое обходил `validateFormModel`. Теперь схема — функция `({ model }) => void`, а условие —
172
+ которое обходил `validateFormModel`. Теперь схема — функция `({ model, cross }) => void`, а условие —
141
173
  оператор `validateWhen(() => cond, () => {…})`; отдельного объекта-узла нет.
142
174
 
143
175
  ```typescript
@@ -154,25 +186,32 @@ function MortgageFields({ form }: { form: CreditForm }) {
154
186
  - **`applyWhen` не существует** — ни как экспорт, ни как локальный сахар: он эмитил branch-узел,
155
187
  которого больше нет. Оборачивай условные правила оператором `validateWhen`.
156
188
 
157
- - **`ValidationSchemaFn` удалён** (тип старой path-схемы). Тип схемы — `ValidationSchema<T>`
158
- (`(ctx: { model: FormModel<T> }) => void`), фабрика-обёртка — `defineValidationSchema<T>(fn)`.
189
+ - **`ValidationSchemaFn` удалён** (тип старой path-схемы). Тип схемы — `ValidationDefinition<T>`
190
+ (`(scope: { model: FormModel<T>; cross }) => void`), фабрика-обёртка — `defineValidation<T>(fn)`.
159
191
 
160
- - **Cross-field — обычная функция над снапшотом, а не `ModelValidator (value, scope, root)`.**
161
- Соседние поля читаются из снимка модели (`f: Root`, эквивалент `model.get()`), а правило навешивается
162
- оператором `cross(sig, fn)`. Каноничное использование — `complex-multy-step-form/schemas/validation.ts`.
192
+ - **Cross-field — обычная функция над снимком, а не `ModelValidator (value, scope, root)`.**
193
+ Правило значения — `Rule<T> = (value: T) => error | null`, параметров `scope` / `root` у него нет.
194
+ Соседние поля читаются из снимка модели, а правило вешается оператором `cross(sig, check)` из
195
+ аргумента схемы. Каноничное использование — каталог `validation/` формы `complex-multy-step-form`.
163
196
 
164
197
  ```typescript
165
- import { cross } from '@reformer/core/validation';
166
- import type { ValidationError } from '@reformer/core';
198
+ import { defineValidation } from '@reformer/core/validation';
199
+ import type { ValidationIssue } from '@reformer/core';
167
200
 
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: 'Пароли не совпадают' }
201
+ type Passwords = { password: string; confirmPassword: string };
202
+
203
+ // (snapshot) => error — сравнение с соседним полем
204
+ const passwordsMatch = (passwords: Passwords): ValidationIssue | null =>
205
+ passwords.confirmPassword &&
206
+ passwords.password &&
207
+ passwords.confirmPassword !== passwords.password
208
+ ? { code: 'confirmPassword.mismatch' } // текст кода — в словаре формы
172
209
  : null;
173
210
 
174
- // внутри defineValidationSchema — fn получает снапшот текущего scope (model.get()):
175
- cross(model.$.confirmPassword, passwordsMatch);
211
+ // cross — из аргумента схемы; check получает снимок модели области:
212
+ const passwordRules = defineValidation<Passwords>(({ model, cross }) => {
213
+ cross(model.$.confirmPassword, passwordsMatch);
214
+ });
176
215
  ```
177
216
 
178
217
  ## 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
@@ -29,9 +41,38 @@ model.$.email.peek(); // нереактивный снимок
29
41
  model.$.address.city.value; // сигнал вложенного поля (≡ model.address.$.city у под-модели)
30
42
 
31
43
  // весь объект
32
- model.get(); // снимок { email, address: { city }, ... } — для submit
44
+ model.get(); // НЕреактивный снимок { email, address: { city }, ... } — для submit
33
45
  ```
34
46
 
47
+ > ⚠️ `model.get()` и `model.isDirty()` читают через `peek()` — внутри `effect`/`computed` они НЕ
48
+ > создают зависимостей. `computed(() => model.get())` никогда не пересчитается. Для реактивного
49
+ > чтения всего объекта — узел дерева `$` (ниже).
50
+
51
+ ### Подписка на группу и на модель целиком
52
+
53
+ Каждый узел дерева `$` — сигнал: лист отдаёт `PathAwareSignal`, а корень, вложенные объекты-группы
54
+ и массивы — `ReadonlySignal` агрегированного значения поддерева. Поэтому подписаться можно на любом
55
+ уровне, а не только на конкретном поле.
56
+
57
+ ```typescript
58
+ model.$.subscribe((all) => autosave(all)); // любое изменение модели; all: T целиком
59
+ model.$.address.subscribe((addr) => ...); // только поддерево address
60
+ model.$.address.value; // реактивный снимок группы
61
+ model.$.address.peek(); // нереактивный снимок группы
62
+ model.$.items.subscribe((rows) => ...); // массив: и правка элемента, и push/removeAt/move
63
+
64
+ // узел — обычный ReadonlySignal, поэтому принимается операциями слоя данных
65
+ watchField(model.$.address, (addr) => geocode(addr));
66
+ ```
67
+
68
+ Дети узла доступны как раньше — `model.$.address.city` по-прежнему сигнал поля. Подписчик вызывается
69
+ сразу с текущим значением (семантика `Signal.subscribe`), а `model.set(...)`/`model.reset()`
70
+ уведомляют один раз, а не по разу на поле.
71
+
72
+ > ⚠️ Доступ к полю выигрывает у свойства сигнала: если в форме есть поле с именем `value`, `peek`,
73
+ > `subscribe`, `valueOf`, `toString`, `toJSON` или `brand`, то `model.$.<группа>.<это имя>` вернёт
74
+ > сигнал поля. `subscribe` при этом продолжает работать.
75
+
35
76
  ### В behaviors — читаем model напрямую
36
77
 
37
78
  `compute`/`onChange`/условия `when` читают значения из value-модели (`model.field`) —