@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
@@ -4,29 +4,52 @@
4
4
 
5
5
  Одна декларативная точка выбора «когда прогонять схему валидации» вместо ручной разводки
6
6
  `markAsTouched` + `validateModel` + `revalidateWhen` + `useState(pending)`. Аддитивный слой над
7
- функциональной схемой (`@reformer/core/validation`): переиспользует `validateModel` как
8
- ЕДИНСТВЕННЫЙ движок (роутинг ошибок в ноды, отмена устаревших прогонов, дедуп по `(model, schema)`),
9
- второго движка не заводит. Node-level `updateOn` / `debounce` на самой ноде поля (legacy-триггеры)
10
- этот API НЕ трогает.
7
+ функциональной схемой (`@reformer/core/validation`): переиспользует раннер схемы как
8
+ ЕДИНСТВЕННЫЙ движок (разнос ошибок по нодам, отмена устаревших прогонов, дедуп по `(model, schema)`),
9
+ второго движка не заводит. Других триггеров валидации нет: у ноды поля собственных валидаторов,
10
+ `updateOn` и `debounce` не бывает — когда запускать проверку, решает только стратегия.
11
11
 
12
12
  Стратегия определяет только момент запуска и раскрытие ошибок — сами правила остаются в отдельной
13
- `defineValidationSchema<T>(({ model }) => …)`.
13
+ `defineValidation<T>(({ model }) => …)`.
14
14
 
15
15
  ## API
16
16
 
17
- Основной способ — React-хук `useFormValidation` из `@reformer/core` (рядом с `useFormControl`).
18
- Под ним — headless-фабрика `createFormValidation` из `@reformer/core/validation` (для SSR / тестов /
19
- не-React потребителей).
17
+ Три точки входа к одному контроллеру:
18
+
19
+ - **сборка `createForm`** — `validation: { rules?, extras?, strategy, debounce, liveAfterSubmit }`.
20
+ Контроллер собирает уровень формы, армирует `useFormBundle` (в эффекте, не при SSR);
21
+ `bundle.validate()` и `bundle.submission.submit(handler)` — полный прогон с раскрытием замечаний.
22
+ Это путь по умолчанию для формы со сборкой;
23
+ - **React-хук `useFormValidation`** из `@reformer/core/react` — для формы без сборки
24
+ (`createFormFromModel` + своя схема);
25
+ - **headless-фабрика `createFormValidation`** из `@reformer/core/validation` — для SSR / тестов /
26
+ не-React потребителей.
20
27
 
21
28
  ```typescript
22
- // React (@reformer/core)
29
+ // Сборка (@reformer/core)
30
+ createForm<T>({
31
+ …,
32
+ validation: {
33
+ rules?: ValidationDefinition<T>; // полный набор; без него — правила шагов flow + extras
34
+ extras?: ValidationDefinition<T>;
35
+ strategy?: ValidationStrategyKind; // default 'submit'
36
+ debounce?: number;
37
+ liveAfterSubmit?: 'change' | 'blur';
38
+ },
39
+ });
40
+
41
+ // React (@reformer/core/react)
23
42
  function useFormValidation<T>(args: {
24
43
  model: FormModel<T>;
25
- schema: ValidationSchema<T>; // СТАБИЛЬНАЯ ссылка (module-level const / useMemo)
44
+ schema: ValidationDefinition<T>; // СТАБИЛЬНАЯ ссылка (module-level const / useMemo)
26
45
  strategy?: ValidationStrategyKind; // default 'submit'
27
46
  debounce?: number; // мс, для live-фаз (change / live-часть afterFirstSubmit)
28
47
  liveAfterSubmit?: 'change' | 'blur'; // default 'change'
29
- }): { submit: () => Promise<boolean>; validate: () => Promise<boolean>; isValidating: boolean };
48
+ }): {
49
+ submit: () => Promise<boolean>; // попытка отправки: при отказе раскрыта вся форма, afterFirstSubmit → живая фаза
50
+ validate: () => Promise<boolean>; // проверка без отправки: раскрыты проверенные поля, фаза не меняется
51
+ isValidating: boolean;
52
+ };
30
53
 
31
54
  // headless (@reformer/core/validation)
32
55
  type ValidationStrategyKind = 'submit' | 'blur' | 'change' | 'afterFirstSubmit';
@@ -35,51 +58,101 @@ interface ValidationStrategyOptions {
35
58
  debounce?: number;
36
59
  liveAfterSubmit?: 'change' | 'blur';
37
60
  }
61
+ interface FormValidationRunOptions {
62
+ touch?: true | 'all'; // true (default) — проверенные поля; 'all' — при отказе ещё и вся форма
63
+ submit?: boolean; // прогон — попытка отправки: afterFirstSubmit → живая фаза
64
+ }
38
65
  interface FormValidationController {
39
- validate(): Promise<boolean>; // полный прогон, touch:true (раскрыть ВСЕ ошибки); переводит afterFirstSubmit в live-фазу
66
+ validate(): Promise<boolean>; // нейтральная проверка: touch:true, фазу afterFirstSubmit не трогает; true — только valid
67
+ run(options?: FormValidationRunOptions): Promise<ValidationResult>; // valid | invalid | error | cancelled
40
68
  start(): () => void; // армит реактивные подписки (ТОЛЬКО на клиенте) → dispose
41
- dispose(): void;
69
+ dispose(): void; // снять подписки и таймеры, отменить идущий прогон (он получит cancelled)
42
70
  readonly isValidating: boolean;
43
71
  readonly validating: ReadonlySignal<boolean>;
44
72
  }
45
73
  function createFormValidation<T>(
46
74
  model: FormModel<T>,
47
- schema: ValidationSchema<T>,
75
+ schema: ValidationDefinition<T>,
48
76
  options?: ValidationStrategyOptions,
49
77
  ): FormValidationController;
50
78
  ```
51
79
 
52
- - `useFormValidation` мемоизирует контроллер по `[model, schema, strategy, debounce, liveAfterSubmit]`,
53
- армит стратегию в `useEffect` (SSR-safe), а `submit()` = `controller.validate()`.
80
+ - `useFormValidation` мемоизирует контроллер по
81
+ `[model, schema, strategy, debounce, liveAfterSubmit]` и армит стратегию в `useEffect`
82
+ (SSR-safe). `submit()` = `controller.run({ touch: 'all', submit: true })`, `validate()` =
83
+ `controller.validate()`; обе — стабильные ссылки, отвечают `true` только при `valid`.
54
84
  - `createFormValidation` ЧИСТА до `start()` (никаких подписок) → SSR-safe; `start()` возвращает
55
85
  `dispose` и арминует триггеры только на клиенте.
56
86
 
87
+ ### Живая фаза `afterFirstSubmit` — одно правило
88
+
89
+ Живую фазу включает **только попытка отправки**:
90
+
91
+ | Вызов | Попытка отправки | Касание при отказе |
92
+ | --------------------------------------------------------------- | ---------------- | ------------------ |
93
+ | `bundle.submission.submit(handler)` | да | вся форма |
94
+ | `submit()` хука `useFormValidation` | да | вся форма |
95
+ | `controller.run({ touch: 'all', submit: true })` | да | вся форма |
96
+ | `bundle.validate()`, `validate()` хука, `controller.validate()` | нет | проверенные поля |
97
+ | `bundle.validateStep(id)`, `flow.next()` | нет | поля шага |
98
+
99
+ Кнопка «Проверить» — `validate()`: показывает замечания, но форма остаётся в тихой фазе. Отменённая
100
+ отправка (`cancelled`) форму тронутой не помечает.
101
+
57
102
  ### Стратегии
58
103
 
59
- | strategy | когда прогон | touch | раскрытие ошибок |
60
- | --- | --- | --- | --- |
61
- | `submit` (default) | только `validate()` / `submit()` | `true` | всё на submit |
62
- | `blur` | смена `touched` поля (потеря фокуса) | `false` | только сблюренные (по `touched`) |
63
- | `change` | на каждый ввод (+ `debounce`) | `false` | только редактированные (по `dirty`) |
64
- | `afterFirstSubmit` | тихо до 1-го submit, затем live (`liveAfterSubmit: 'change'` \| `'blur'`, default `'change'`) | `true` один раз на 1-м submit, дальше `false` | тихо → раскрыть всё на submit → далее live |
104
+ | strategy | когда прогон | touch | раскрытие замечаний |
105
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------- |
106
+ | `submit` (default) | только `validate()` / `submit()` (в сборке — `bundle.validate()` / `bundle.submission.submit()`) | `true` | всё на submit |
107
+ | `blur` | каждый уход из поля (потеря фокуса), и повторный тоже | `false` | только сблюренные (по `touched`) |
108
+ | `change` | на каждый ввод (+ `debounce`) | `false` | только редактированные (по `dirty`) |
109
+ | `afterFirstSubmit` | тихо до первой попытки отправки, затем live (`liveAfterSubmit: 'change'` \| `'blur'`, default `'change'`); `validate()` фазу не переключает | при отправке — вся форма (при отказе), дальше live — `false` | тихо → раскрыть всё на отправке → далее live |
65
110
 
66
111
  ## Examples
67
112
 
68
- ### Простая форма — afterFirstSubmit + debounce
113
+ ### Сборка — стратегия полем `validation`
114
+
115
+ ```tsx
116
+ import { createForm } from '@reformer/core';
117
+ import { useFormBundle } from '@reformer/core/react';
118
+
119
+ const createRegistration = () =>
120
+ createForm<RegistrationData>({
121
+ initial: { username: '', email: '', password: '' },
122
+ schema: registrationSchema,
123
+ validation: { rules: registrationValidation, strategy: 'afterFirstSubmit', debounce: 400 },
124
+ });
125
+
126
+ function RegistrationForm() {
127
+ const registration = useFormBundle(createRegistration); // армирует живую стратегию в эффекте
128
+ const onSubmit = async (event: React.FormEvent) => {
129
+ event.preventDefault();
130
+ // первая отправка раскрывает все замечания и переводит форму в живую фазу;
131
+ // registration.validate() фазу не переключает
132
+ await registration.submission.submit(api.save);
133
+ };
134
+ /* … */
135
+ }
136
+ ```
137
+
138
+ ### Без сборки — useFormValidation, afterFirstSubmit + debounce
69
139
 
70
- Тихо до первой отправки, `submit()` раскрывает все ошибки и переводит форму в живую фазу
71
- (дальше проверка на ввод с задержкой 400 мс). `submit()` сам метит `touched` — ручной
72
- `markAsTouched` не нужен.
140
+ Тихо до первой отправки, `submit()` раскрывает все замечания и переводит форму в живую фазу
141
+ (дальше проверка на ввод с задержкой 400 мс). `submit()` сам метит `touched` (при отказе — всю
142
+ форму) — ручной `markAsTouched` не нужен. `validate()` — кнопка «Проверить»: раскрывает замечания
143
+ проверенных полей, а форма остаётся в тихой фазе.
73
144
 
74
145
  ```tsx
75
- import { createModel, createForm, useFormValidation } from '@reformer/core';
76
- import { defineValidationSchema, validate } from '@reformer/core/validation';
146
+ import { useMemo } from 'react';
147
+ import { createModel, createFormFromModel } from '@reformer/core';
148
+ import { useFormValidation } from '@reformer/core/react';
149
+ import { defineValidation, validate } from '@reformer/core/validation';
77
150
  import { required, email, minLength } from '@reformer/core/validators';
78
151
 
79
152
  type RegistrationData = { username: string; email: string; password: string };
80
153
 
81
154
  // Схема — стабильный module-level const (иначе ломается дедуп раннера).
82
- const registrationValidation = defineValidationSchema<RegistrationData>(({ model }) => {
155
+ const registrationValidation = defineValidation<RegistrationData>(({ model }) => {
83
156
  validate(model.$.username, [required(), minLength(3)]);
84
157
  validate(model.$.email, [required(), email()]);
85
158
  validate(model.$.password, [required(), minLength(8)]);
@@ -87,17 +160,17 @@ const registrationValidation = defineValidationSchema<RegistrationData>(({ model
87
160
 
88
161
  function RegistrationForm() {
89
162
  const model = useMemo(() => createModel<RegistrationData>({ username: '', email: '', password: '' }), []);
90
- const form = useMemo(() => createForm({ model }), [model]);
163
+ const form = useMemo(() => createFormFromModel({ model }), [model]);
91
164
 
92
- const { submit, isValidating } = useFormValidation({
165
+ const { submit, validate, isValidating } = useFormValidation({
93
166
  model,
94
167
  schema: registrationValidation,
95
168
  strategy: 'afterFirstSubmit',
96
169
  debounce: 400,
97
170
  });
98
171
 
99
- const onSubmit = async (e: React.FormEvent) => {
100
- e.preventDefault();
172
+ const onSubmit = async (event: React.FormEvent) => {
173
+ event.preventDefault();
101
174
  if (await submit()) await api.save(model.get()); // true = нет блокирующих ошибок
102
175
  };
103
176
 
@@ -106,49 +179,69 @@ function RegistrationForm() {
106
179
  <FormField control={form.username} />
107
180
  <FormField control={form.email} />
108
181
  <FormField control={form.password} />
109
- <button type="submit" disabled={isValidating}>Отправить</button>
182
+ <button type="button" onClick={() => void validate()}>
183
+ Проверить
184
+ </button>
185
+ <button type="submit" disabled={isValidating}>
186
+ Отправить
187
+ </button>
110
188
  </form>
111
189
  );
112
190
  }
113
191
  ```
114
192
 
115
- ### Wizard — live-стратегия внутри активного шага
193
+ ### Визард — стратегия формы и проверка шага потоком
116
194
 
117
- `defineSteps(..., { strategy })` задаёт ЖИВУЮ стратегию ВНУТРИ шага; `useWizardStepValidation`
118
- арминует её под текущий шаг (`currentStep` из `FormWizardContext`) и снимает при смене шага / unmount.
119
- Per-step gate (`validateStep` на «Далее») и `validateAll` (submit) при этом НЕ меняются — стратегия
120
- добавляет живой слой ПОВЕРХ них. No-op, если `strategy` не задана / `'submit'` / у шага нет правил.
195
+ Живая стратегия — свойство формы, а не шага: она задаётся полем `validation` сборки и армируется
196
+ `useFormBundle`. Проверку шага делает поток: «Далее» визарда — `flow.next()`, он прогоняет правила
197
+ текущего шага и не пускает дальше при замечаниях. Отправка — `bundle.submission.submit` (все
198
+ шаги + `extras`). Живую фазу `afterFirstSubmit` включает только она: `flow.next()` и
199
+ `validateStep` фазу не переключают.
121
200
 
122
201
  ```tsx
123
- import { defineSteps, useWizardStepValidation, FormWizard } from '@reformer/cdk/form-wizard';
124
-
125
- const config = defineSteps<'loan' | 'applicant' | 'confirm', CreditForm>(model, {
126
- steps: { loan: loanStep, applicant: applicantStep, confirm: null }, // confirm — без правил, ЯВНО
127
- extras: crossFieldRules, // применяются только в validateAll (submit)
128
- strategy: 'blur', // живая валидация полей текущего шага при потере фокуса
129
- });
202
+ const createCredit = () =>
203
+ createForm<CreditForm>({
204
+ model: createCreditModel(),
205
+ schema: creditSchema,
206
+ validation: { extras: crossFieldRules, strategy: 'blur' }, // живая проверка при потере фокуса
207
+ flow: {
208
+ steps: [
209
+ { id: 'loan', rules: loanStep },
210
+ { id: 'applicant', rules: applicantStep },
211
+ { id: 'confirm' }, // без правил
212
+ ],
213
+ },
214
+ });
130
215
 
131
- function LoanStepBody() {
132
- useWizardStepValidation(config); // армит 'blur' под активный шаг, снимает при переходе
133
- return <>{/* поля шага */}</>;
216
+ function CreditWizard() {
217
+ const credit = useFormBundle(createCredit);
218
+ return (
219
+ <FormWizard
220
+ form={credit.form}
221
+ flow={credit.flow}
222
+ steps={STEPS}
223
+ onSubmit={() => void credit.submission.submit(api.save)}
224
+ />
225
+ );
134
226
  }
135
-
136
- <FormWizard form={form} config={config}>
137
- <LoanStepBody />
138
- </FormWizard>;
139
227
  ```
140
228
 
229
+ Живая фаза прогоняет **полный** набор правил (все шаги + `extras`), а показ замечаний гейтится
230
+ `touched` / `dirty` полей: поля следующих шагов пользователь ещё не трогал — их замечания не видны.
231
+ Прежний per-step механизм (`defineSteps(…, { strategy })` + `useWizardStepValidation`) удалён.
232
+
141
233
  ### Headless / SSR / тесты — createFormValidation
142
234
 
143
235
  ```typescript
144
236
  import { createFormValidation } from '@reformer/core/validation';
145
237
 
146
- const ctrl = createFormValidation(model, registrationValidation, {
238
+ const controller = createFormValidation(model, registrationValidation, {
147
239
  strategy: 'afterFirstSubmit',
148
240
  debounce: 300,
149
241
  });
150
- const dispose = ctrl.start(); // ТОЛЬКО на клиенте (в React — из useEffect)
151
- const ok = await ctrl.validate(); // submit → раскрыть все ошибки
242
+ const dispose = controller.start(); // ТОЛЬКО на клиенте (в React — из useEffect)
243
+ const checked = await controller.validate(); // проверка: замечания проверенных полей, фаза тихая
244
+ const sent = await controller.run({ touch: 'all', submit: true }); // попытка отправки → живая фаза
152
245
  dispose();
153
246
  ```
154
247
 
@@ -157,32 +250,50 @@ dispose();
157
250
  ```typescript
158
251
  // ❌ schema пересоздаётся каждый рендер → контроллер и дедуп раннера ломаются
159
252
  function MyForm() {
160
- const schema = defineValidationSchema<Form>(({ model }) => { … }); // новая ссылка на каждый рендер
253
+ const schema = defineValidation<Form>(({ model }) => { … }); // новая ссылка на каждый рендер
161
254
  useFormValidation({ model, schema, strategy: 'change' });
162
255
  }
163
256
 
164
257
  // ✅ Стабильная ссылка: module-level const (или useMemo с пустыми deps)
165
- const formValidation = defineValidationSchema<Form>(({ model }) => { … });
258
+ const formValidation = defineValidation<Form>(({ model }) => { … });
166
259
  function MyForm() {
167
260
  useFormValidation({ model, schema: formValidation, strategy: 'change' });
168
261
  }
169
262
  ```
170
263
 
171
264
  ```typescript
172
- // ❌ Node-level updateOn (Слой B) И активная schema-стратегия на ОДНОМ поле → оба пишут ошибки в ноду → мерцание
173
- // узел поля: { updateOn: 'blur', … } + useFormValidation({ strategy: 'blur', schema: включает это же поле })
265
+ // ❌ updateOn / debounce / validators в узле схемы — таких ключей у узла нет, сборка их не читает
266
+ // { model: model.$.email, component: Input, updateOn: 'blur', validators: [required()] }
174
267
 
175
- // ✅ Одно поле обслуживает ОДИН слой: либо node-level updateOn, либо schema-стратегия — не оба сразу
268
+ // ✅ Момент запуска — стратегия формы; правила — схема валидации
269
+ // createForm({ …, validation: { rules: formValidation, strategy: 'blur', debounce: 300 } })
270
+ ```
271
+
272
+ ```typescript
273
+ // ❌ Своя стратегия поверх сборки: второй контроллер на ту же модель и правила
274
+ const bundle = useFormBundle(() => createForm({ …, validation: rules }));
275
+ useFormValidation({ model: bundle.model, schema: rules, strategy: 'change' });
276
+
277
+ // ✅ Стратегия — полем validation сборки; армирует useFormBundle
278
+ const bundle = useFormBundle(() => createForm({ …, validation: { rules, strategy: 'change' } }));
176
279
  ```
177
280
 
178
281
  ```typescript
179
282
  // ❌ start() на сервере — арминует реактивные подписки при SSR
180
- const ctrl = createFormValidation(model, schema, { strategy: 'change' });
181
- ctrl.start(); // на сервере подписок быть не должно
283
+ const controller = createFormValidation(model, schema, { strategy: 'change' });
284
+ controller.start(); // на сервере подписок быть не должно
182
285
 
183
286
  // ✅ Фабрика чиста до start(); армить только на клиенте (useFormValidation делает это в useEffect)
184
- const ctrl = createFormValidation(model, schema, { strategy: 'change' });
185
- if (typeof window !== 'undefined') ctrl.start();
287
+ const controller = createFormValidation(model, schema, { strategy: 'change' });
288
+ if (typeof window !== 'undefined') controller.start();
289
+ ```
290
+
291
+ ```typescript
292
+ // ❌ afterFirstSubmit: ждать живой фазы после проверки — validate() фазу не переключает
293
+ await bundle.validate(); // форма в тихой фазе, ввод не перепроверяется
294
+
295
+ // ✅ Живую фазу включает попытка отправки
296
+ await bundle.submission.submit(api.save); // без сборки — submit() хука useFormValidation
186
297
  ```
187
298
 
188
299
  ## See also
@@ -190,4 +301,5 @@ if (typeof window !== 'undefined') ctrl.start();
190
301
  - [28-submit-and-reset.md](./28-submit-and-reset.md) — submit-флоу и `validateModel` (движок под стратегией)
191
302
  - [27-revalidate-when.md](./27-revalidate-when.md) — ручные триггеры перевалидации (низкоуровневый примитив)
192
303
  - [31-async-validator-debounce.md](./31-async-validator-debounce.md) — async-правила и отмена устаревших прогонов
193
- - [13-multi-step.md](./13-multi-step.md) — per-step / полная валидация wizard (`validateStep` / `validateAll`)
304
+ - [13-multi-step.md](./13-multi-step.md) — многошаговая форма: поток `flow`, `bundle.validateStep(id)`
305
+ - [38-form-flow.md](./38-form-flow.md) — уровень формы: `validate`, `submission`, поток шагов