@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
package/dist/index.js CHANGED
@@ -1,2743 +1,1084 @@
1
- import { c as Le } from "./form-model-DYCzGGKF.js";
2
- import { c as $e, a as Ge, r as je, b as Ue, d as Be, e as We, f as qe, s as Ke, t as He, w as ze } from "./behaviors-value-C7dSEhCc.js";
3
- import { i as $ } from "./derived-registry-CluZCQ7U.js";
4
- import { m as Ye, u as Qe } from "./derived-registry-CluZCQ7U.js";
5
- import { S as I } from "./subscription-manager-705bls97.js";
6
- import { signal as g, computed as f, effect as _, batch as X, Signal as G } from "@preact/signals-core";
7
- import { r as Z } from "./signal-node-registry-tiwel-TV.js";
8
- import { g as Ze } from "./signal-node-registry-tiwel-TV.js";
9
- import { d as tt, e as st } from "./behaviors-node-DGEFMTQu.js";
10
- import H, { useRef as V, useCallback as b, useMemo as ee, useEffect as te } from "react";
11
- import { createFormValidation as se } from "./validation.js";
12
- class M {
13
- // ============================================================================
14
- // Protected состояние (для Template Method паттерна)
15
- // ============================================================================
16
- /**
17
- * Пользователь взаимодействовал с узлом (touched)
18
- * Protected: наследники могут читать/изменять через методы
19
- */
20
- _touched = g(!1);
21
- /**
22
- * Значение узла было изменено (dirty)
23
- * Protected: наследники могут читать/изменять через методы
24
- */
25
- _dirty = g(!1);
26
- /**
27
- * Текущий статус узла
28
- * Protected: наследники могут читать/изменять через методы
29
- */
30
- _status = g("valid");
31
- // ============================================================================
32
- // Публичные computed signals (readonly для внешнего мира)
33
- // ============================================================================
34
- /**
35
- * Пользователь взаимодействовал с узлом (touched)
36
- * Computed из _touched для предоставления readonly интерфейса
37
- */
38
- touched = f(() => this._touched.value);
39
- /**
40
- * Значение узла было изменено (dirty)
41
- * Computed из _dirty для предоставления readonly интерфейса
42
- */
43
- dirty = f(() => this._dirty.value);
44
- /**
45
- * Текущий статус узла
46
- * Computed из _status для предоставления readonly интерфейса
47
- */
48
- status = f(() => this._status.value);
49
- /**
50
- * Узел отключен (disabled)
51
- */
52
- disabled = f(() => this._status.value === "disabled");
53
- /**
54
- * Получить ошибки валидации с фильтрацией
55
- *
56
- * Позволяет фильтровать ошибки по различным критериям:
57
- * - По коду ошибки
58
- * - По сообщению (частичное совпадение)
59
- * - По параметрам
60
- * - Через кастомный предикат
61
- *
62
- * Без параметров возвращает все ошибки (эквивалент errors.value)
63
- *
64
- * @param options - Опции фильтрации ошибок
65
- * @returns Отфильтрованный массив ошибок валидации
66
- *
67
- * @example
68
- * ```typescript
69
- * // Все ошибки
70
- * const allErrors = form.getErrors();
71
- *
72
- * // Ошибки с конкретным кодом
73
- * const requiredErrors = form.getErrors({ code: 'required' });
74
- *
75
- * // Ошибки с несколькими кодами
76
- * const errors = form.getErrors({ code: ['required', 'email'] });
77
- *
78
- * // Ошибки по сообщению
79
- * const passwordErrors = form.getErrors({ message: 'Password' });
80
- *
81
- * // Ошибки по параметрам
82
- * const minLengthErrors = form.getErrors({
83
- * params: { minLength: 8 }
84
- * });
85
- *
86
- * // Кастомная фильтрация
87
- * const customErrors = form.getErrors({
88
- * predicate: (err) => err.code.startsWith('custom_')
89
- * });
90
- * ```
91
- */
92
- getErrors(e) {
93
- const t = this.errors.value;
94
- return e ? t.filter((s) => {
95
- if (e.code !== void 0 && !(Array.isArray(e.code) ? e.code : [e.code]).includes(s.code) || e.message !== void 0 && !s.message.toLowerCase().includes(e.message.toLowerCase()))
96
- return !1;
97
- if (e.params !== void 0) {
98
- if (!s.params)
99
- return !1;
100
- for (const [r, n] of Object.entries(e.params))
101
- if (s.params[r] !== n)
102
- return !1;
103
- }
104
- return !(e.predicate !== void 0 && !e.predicate(s));
105
- }) : t;
106
- }
107
- // ============================================================================
108
- // Методы управления состоянием (Template Method)
109
- // ============================================================================
110
- /**
111
- * Отметить узел как touched (пользователь взаимодействовал)
112
- *
113
- * Template Method: обновляет signal в базовом классе,
114
- * вызывает hook для кастомной логики в наследниках
115
- */
116
- markAsTouched() {
117
- this._touched.value = !0, this.onMarkAsTouched();
118
- }
119
- /**
120
- * Отметить узел как untouched
121
- *
122
- * Template Method: обновляет signal в базовом классе,
123
- * вызывает hook для кастомной логики в наследниках
124
- */
125
- markAsUntouched() {
126
- this._touched.value = !1, this.onMarkAsUntouched();
127
- }
128
- /**
129
- * Отметить узел как dirty (значение изменено)
130
- *
131
- * Template Method: обновляет signal в базовом классе,
132
- * вызывает hook для кастомной логики в наследниках
133
- */
134
- markAsDirty() {
135
- this._dirty.value = !0, this.onMarkAsDirty();
136
- }
137
- /**
138
- * Отметить узел как pristine (значение не изменено)
139
- *
140
- * Template Method: обновляет signal в базовом классе,
141
- * вызывает hook для кастомной логики в наследниках
142
- */
143
- markAsPristine() {
144
- this._dirty.value = !1, this.onMarkAsPristine();
145
- }
146
- /**
147
- * Пометить все поля (включая вложенные) как touched
148
- * Алиас для markAsTouched(), но более явно показывает намерение
149
- * пометить ВСЕ поля рекурсивно
150
- *
151
- * Полезно для:
152
- * - Показа всех ошибок валидации перед submit
153
- * - Принудительного отображения ошибок при нажатии "Validate All"
154
- * - Отображения невалидных полей в wizard/step form
155
- *
156
- * @example
157
- * ```typescript
158
- * // Показать все ошибки перед submit
159
- * form.touchAll();
160
- * const isValid = await form.validate();
161
- * if (!isValid) {
162
- * // Все ошибки теперь видны пользователю
163
- * }
164
- *
165
- * // Или использовать submit() который уже вызывает touchAll
166
- * await form.submit(async (values) => {
167
- * await api.save(values);
168
- * });
169
- * ```
170
- */
171
- touchAll() {
172
- this.markAsTouched();
173
- }
174
- // ============================================================================
175
- // Методы управления доступностью (Template Method)
176
- // ============================================================================
177
- /**
178
- * Отключить узел
179
- *
180
- * Template Method: обновляет статус в базовом классе,
181
- * вызывает hook для кастомной логики в наследниках
182
- *
183
- * Отключенные узлы не проходят валидацию и не включаются в getValue()
184
- */
185
- disable() {
186
- this._status.value = "disabled", this.onDisable();
187
- }
188
- /**
189
- * Включить узел
190
- *
191
- * Template Method: обновляет статус в базовом классе,
192
- * вызывает hook для кастомной логики в наследниках
193
- */
194
- enable() {
195
- this._status.value = "valid", this.onEnable();
196
- }
197
- // ============================================================================
198
- // Protected hooks (для переопределения в наследниках)
199
- // ============================================================================
200
- /**
201
- * Hook: вызывается после markAsTouched()
202
- *
203
- * Переопределите в наследниках для дополнительной логики:
204
- * - GroupNode: пометить все дочерние узлы как touched
205
- * - ArrayNode: пометить все элементы массива как touched
206
- * - FieldNode: пустая реализация (нет дочерних узлов)
207
- *
208
- * @example
209
- * ```typescript
210
- * // GroupNode
211
- * protected onMarkAsTouched(): void {
212
- * this.fields.forEach(field => field.markAsTouched());
213
- * }
214
- * ```
215
- */
216
- onMarkAsTouched() {
217
- }
218
- /**
219
- * Hook: вызывается после markAsUntouched()
220
- *
221
- * Переопределите в наследниках для дополнительной логики:
222
- * - GroupNode: пометить все дочерние узлы как untouched
223
- * - ArrayNode: пометить все элементы массива как untouched
224
- * - FieldNode: пустая реализация (нет дочерних узлов)
225
- */
226
- onMarkAsUntouched() {
227
- }
228
- /**
229
- * Hook: вызывается после markAsDirty()
230
- *
231
- * Переопределите в наследниках для дополнительной логики:
232
- * - GroupNode: может обновить родительскую форму
233
- * - ArrayNode: может обновить родительскую форму
234
- * - FieldNode: пустая реализация
235
- */
236
- onMarkAsDirty() {
237
- }
238
- /**
239
- * Hook: вызывается после markAsPristine()
240
- *
241
- * Переопределите в наследниках для дополнительной логики:
242
- * - GroupNode: пометить все дочерние узлы как pristine
243
- * - ArrayNode: пометить все элементы массива как pristine
244
- * - FieldNode: пустая реализация
245
- */
246
- onMarkAsPristine() {
247
- }
248
- /**
249
- * Hook: вызывается после disable()
250
- *
251
- * Переопределите в наследниках для дополнительной логики:
252
- * - GroupNode: отключить все дочерние узлы
253
- * - ArrayNode: отключить все элементы массива
254
- * - FieldNode: очистить ошибки валидации
255
- *
256
- * @example
257
- * ```typescript
258
- * // GroupNode
259
- * protected onDisable(): void {
260
- * this.fields.forEach(field => field.disable());
261
- * }
262
- * ```
263
- */
264
- onDisable() {
265
- }
266
- /**
267
- * Hook: вызывается после enable()
268
- *
269
- * Переопределите в наследниках для дополнительной логики:
270
- * - GroupNode: включить все дочерние узлы
271
- * - ArrayNode: включить все элементы массива
272
- * - FieldNode: пустая реализация
273
- */
274
- onEnable() {
275
- }
276
- }
277
- const O = {
278
- /** FieldNode.watch() */
279
- Watch: "watch",
280
- /** FieldNode.computeFrom() */
281
- ComputeFrom: "computeFrom",
282
- /** GroupNode.linkFields() */
283
- LinkFields: "linkFields",
284
- /** GroupNode.watchField() */
285
- WatchField: "watchField"
286
- };
287
- let re = 0;
288
- function N(i) {
289
- return `${i}-${++re}`;
290
- }
291
- var A = /* @__PURE__ */ ((i) => (i.THROW = "throw", i.LOG = "log", i.CONVERT = "convert", i))(A || {});
292
- class k {
293
- static handle(e, t, s = "throw") {
294
- const r = this.extractMessage(e);
295
- switch (process.env.NODE_ENV !== "production" && console.error(`[${t}]`, e), s) {
296
- case "throw":
297
- throw e;
298
- case "log":
299
- return;
300
- case "convert":
301
- return {
302
- code: "validator_error",
303
- message: r,
304
- params: { field: t }
305
- };
306
- }
307
- }
308
- /**
309
- * Извлечь сообщение из ошибки
310
- *
311
- * Обрабатывает различные типы ошибок:
312
- * - Error объекты → error.message
313
- * - Строки → возвращает как есть
314
- * - Объекты с message → извлекает message
315
- * - Другое → String(error)
316
- *
317
- * @param error Ошибка для извлечения сообщения
318
- * @returns Сообщение ошибки
319
- * @private
320
- *
321
- * @example
322
- * ```typescript
323
- * FormErrorHandler.extractMessage(new Error('Test'));
324
- * // 'Test'
325
- *
326
- * FormErrorHandler.extractMessage('String error');
327
- * // 'String error'
328
- *
329
- * FormErrorHandler.extractMessage({ message: 'Object error' });
330
- * // 'Object error'
331
- *
332
- * FormErrorHandler.extractMessage(null);
333
- * // 'null'
334
- * ```
335
- */
336
- static extractMessage(e) {
337
- return e instanceof Error ? e.message : typeof e == "string" ? e : typeof e == "object" && e !== null && "message" in e ? String(e.message) : String(e);
338
- }
339
- /**
340
- * Создать ValidationError с заданными параметрами
341
- *
342
- * Утилитная функция для создания ValidationError объектов
343
- *
344
- * @param code Код ошибки
345
- * @param message Сообщение ошибки
346
- * @param field Поле (опционально)
347
- * @returns ValidationError объект
348
- *
349
- * @example
350
- * ```typescript
351
- * const error = FormErrorHandler.createValidationError(
352
- * 'required',
353
- * 'This field is required',
354
- * 'email'
355
- * );
356
- * // { code: 'required', message: 'This field is required', field: 'email' }
357
- * ```
358
- */
359
- static createValidationError(e, t, s) {
360
- return {
361
- code: e,
362
- message: t,
363
- params: s ? { field: s } : void 0
364
- };
365
- }
366
- /**
367
- * Проверить, является ли объект ValidationError
368
- *
369
- * Type guard для ValidationError
370
- *
371
- * @param value Значение для проверки
372
- * @returns true если value является ValidationError
373
- *
374
- * @example
375
- * ```typescript
376
- * if (FormErrorHandler.isValidationError(result)) {
377
- * console.log(result.code); // OK, типобезопасно
378
- * }
379
- * ```
380
- */
381
- static isValidationError(e) {
382
- return typeof e == "object" && e !== null && "code" in e && "message" in e && typeof e.code == "string" && typeof e.message == "string";
383
- }
384
- }
385
- class ie {
386
- /** Внутренний сигнал статуса */
387
- _status;
388
- /** Публичный read-only сигнал статуса */
389
- status;
390
- /** Поле валидно */
391
- valid;
392
- /** Поле невалидно */
393
- invalid;
394
- /** Идет валидация */
395
- pending;
396
- /** Поле отключено */
397
- disabled;
398
- /**
399
- * @param initial - Начальный статус (по умолчанию 'valid')
400
- */
401
- constructor(e = "valid") {
402
- this._status = g(e), this.status = f(() => this._status.value), this.valid = f(() => this._status.value === "valid"), this.invalid = f(() => this._status.value === "invalid"), this.pending = f(() => this._status.value === "pending"), this.disabled = f(() => this._status.value === "disabled");
403
- }
404
- /**
405
- * Начать валидацию
406
- *
407
- * Переводит статус в 'pending' если поле не отключено
408
- *
409
- * @example
410
- * ```typescript
411
- * statusMachine.startValidation();
412
- * // status: 'pending'
413
- * ```
414
- */
415
- startValidation() {
416
- this._status.value !== "disabled" && (this._status.value = "pending");
417
- }
418
- /**
419
- * Завершить валидацию
420
- *
421
- * @param hasErrors - Есть ли ошибки валидации
422
- *
423
- * @example
424
- * ```typescript
425
- * // Валидация успешна
426
- * statusMachine.completeValidation(false);
427
- * // status: 'valid'
428
- *
429
- * // Есть ошибки
430
- * statusMachine.completeValidation(true);
431
- * // status: 'invalid'
432
- * ```
433
- */
434
- completeValidation(e) {
435
- this._status.value !== "disabled" && (this._status.value = e ? "invalid" : "valid");
436
- }
437
- /**
438
- * Установить ошибки напрямую (без перехода через pending)
439
- *
440
- * Используется для синхронной валидации или установки ошибок извне
441
- *
442
- * @param hasErrors - Есть ли ошибки
443
- *
444
- * @example
445
- * ```typescript
446
- * statusMachine.setErrors(true);
447
- * // status: 'invalid'
448
- * ```
449
- */
450
- setErrors(e) {
451
- this._status.value !== "disabled" && (this._status.value = e ? "invalid" : "valid");
452
- }
453
- /**
454
- * Отключить поле
455
- *
456
- * Переводит статус в 'disabled'
457
- *
458
- * @example
459
- * ```typescript
460
- * statusMachine.disable();
461
- * // status: 'disabled'
462
- * ```
463
- */
464
- disable() {
465
- this._status.value = "disabled";
466
- }
467
- /**
468
- * Включить поле
469
- *
470
- * @param hasErrors - Есть ли ошибки (определяет valid/invalid)
471
- *
472
- * @example
473
- * ```typescript
474
- * statusMachine.enable(false);
475
- * // status: 'valid'
476
- *
477
- * statusMachine.enable(true);
478
- * // status: 'invalid'
479
- * ```
480
- */
481
- enable(e = !1) {
482
- this._status.value = e ? "invalid" : "valid";
483
- }
484
- }
485
- class ne extends M {
486
- // ============================================================================
487
- // Приватные сигналы
488
- // ============================================================================
489
- _value;
490
- _errors;
491
- // _touched, _dirty наследуются от FormNode (protected)
492
- // _status управляется через statusMachine
493
- _componentProps;
494
- /**
495
- * State machine для управления статусом поля
496
- * Централизует логику переходов между valid/invalid/pending/disabled
497
- */
498
- statusMachine;
499
- // ============================================================================
500
- // Публичные computed signals
501
- // ============================================================================
502
- value;
503
- // valid, invalid, pending, status, disabled берутся из statusMachine
504
- valid;
505
- invalid;
506
- pending;
507
- // Override status и disabled из базового класса
508
- status;
509
- disabled;
510
- // touched, dirty наследуются от FormNode
511
- errors;
512
- componentProps;
513
- /**
514
- * Вычисляемое свойство: нужно ли показывать ошибку
515
- * Ошибка показывается если поле невалидно И (touched ИЛИ dirty)
516
- */
517
- shouldShowError;
518
- // ============================================================================
519
- // Конфигурация
520
- // ============================================================================
521
- validators;
522
- asyncValidators;
523
- updateOn;
524
- initialValue;
525
- currentAbortController;
526
- debounceMs;
527
- validateDebounceTimer;
528
- /**
529
- * Pending debounced validation state
530
- * Contains resolve function and AbortController for cancellation
531
- */
532
- pendingValidation;
533
- /**
534
- * Менеджер подписок для централизованного cleanup
535
- * Использует SubscriptionManager вместо массива для управления подписками
536
- */
537
- disposers = new I();
538
- component;
539
- // ============================================================================
540
- // Конструктор
541
- // ============================================================================
542
- constructor(e) {
543
- super(), this.validators = e.validators || [], this.asyncValidators = e.asyncValidators || [], this.updateOn = e.updateOn || "blur", this.debounceMs = e.debounce || 0, this.component = e.component, this._value = e.valueSignal ?? g(e.value), this.initialValue = this._value.peek(), this._errors = g([]), this._componentProps = g(e.componentProps || {}), this.statusMachine = new ie(e.disabled ? "disabled" : "valid"), this.value = f(() => this._value.value), this.valid = f(() => this.statusMachine.valid.value), this.invalid = f(() => this.statusMachine.invalid.value), this.pending = f(() => this.statusMachine.pending.value), this.status = f(() => this.statusMachine.status.value), this.disabled = f(() => this.statusMachine.disabled.value), this.errors = f(() => this._errors.value), this.componentProps = f(() => this._componentProps.value), this.shouldShowError = f(
544
- () => this.statusMachine.invalid.value && (this._touched.value || this._dirty.value)
545
- );
546
- }
547
- // ============================================================================
548
- // Реализация абстрактных методов FormNode
549
- // ============================================================================
550
- getValue() {
551
- return this._value.peek();
552
- }
553
- setValue(e, t) {
554
- if (this._value.value = e, this._dirty.value = !0, t?.emitEvent === !1)
555
- return;
556
- const s = this.validators.length > 0 || this.asyncValidators.length > 0, r = this._errors.value.length > 0;
557
- if (this.updateOn === "change") {
558
- this.validate();
559
- return;
560
- }
561
- r && s && this.validate();
562
- }
563
- patchValue(e) {
564
- this.setValue(e);
565
- }
566
- /**
567
- * Сбросить поле к указанному значению (или к initialValue)
568
- *
569
- * @param value - опциональное значение для сброса. Если не указано, используется initialValue
570
- *
571
- * @remarks
572
- * Этот метод:
573
- * - Устанавливает значение в value или initialValue
574
- * - Очищает ошибки валидации
575
- * - Сбрасывает touched/dirty флаги
576
- * - Устанавливает статус в 'valid'
577
- *
578
- * Если вам нужно сбросить к исходному значению, используйте resetToInitial()
579
- *
580
- * @example
581
- * ```typescript
582
- * // Сброс к initialValue
583
- * field.reset();
584
- *
585
- * // Сброс к новому значению
586
- * field.reset('new value');
587
- * ```
588
- */
589
- reset(e) {
590
- this._value.value = e !== void 0 ? e : this.initialValue, this._errors.value = [], this._touched.value = !1, this._dirty.value = !1, this.statusMachine.setErrors(!1);
591
- }
592
- /**
593
- * Сбросить поле к исходному значению (initialValue)
594
- *
595
- * @remarks
596
- * Алиас для reset() без параметров, но более явный:
597
- * - resetToInitial() - явно показывает намерение вернуться к начальному значению
598
- * - reset() - может принимать новое значение
599
- *
600
- * Полезно когда:
601
- * - Пользователь нажал "Cancel" - вернуть форму в исходное состояние
602
- * - Форма была изменена через reset(newValue), но нужно вернуться к самому началу
603
- * - Явное намерение показать "отмену всех изменений"
604
- *
605
- * @example
606
- * ```typescript
607
- * const field = new FieldNode({ value: 'initial', component: Input });
608
- *
609
- * field.setValue('changed');
610
- * field.reset('temp value');
611
- * console.log(field.value.value); // 'temp value'
612
- *
613
- * field.resetToInitial();
614
- * console.log(field.value.value); // 'initial'
615
- * ```
616
- */
617
- resetToInitial() {
618
- this.reset(this.initialValue);
619
- }
620
- /**
621
- * Cancel any pending validation (debounced or running)
622
- * @private
623
- * @remarks
624
- * Centralizes all cancellation logic:
625
- * - Aborts pending debounced validation and resolves its promise
626
- * - Clears debounce timer
627
- * - Aborts currently running async validation
628
- */
629
- cancelPendingValidation() {
630
- this.pendingValidation && (this.pendingValidation.abortController.abort(), this.pendingValidation.resolve(!1), this.pendingValidation = void 0), this.validateDebounceTimer && (clearTimeout(this.validateDebounceTimer), this.validateDebounceTimer = void 0), this.currentAbortController && (this.currentAbortController.abort(), this.currentAbortController = void 0);
631
- }
632
- /**
633
- * Запустить валидацию поля
634
- * @param options - опции валидации
635
- * @returns `Promise<boolean>` - true если поле валидно
636
- *
637
- * @remarks
638
- * Метод защищен от race conditions через AbortController.
639
- * При быстром вводе только последняя валидация применяет результаты.
640
- *
641
- * @example
642
- * ```typescript
643
- * // Обычная валидация
644
- * await field.validate();
645
- *
646
- * // С debounce
647
- * await field.validate({ debounce: 300 });
648
- * ```
649
- */
650
- async validate(e) {
651
- const t = e?.debounce ?? this.debounceMs;
652
- if (this.cancelPendingValidation(), t <= 0 || this.asyncValidators.length === 0)
653
- return this.validateImmediate();
654
- const s = new AbortController();
655
- return new Promise((r) => {
656
- this.pendingValidation = { resolve: r, abortController: s }, this.validateDebounceTimer = setTimeout(async () => {
657
- if (this.validateDebounceTimer = void 0, s.signal.aborted) {
658
- r(!1);
659
- return;
660
- }
661
- this.pendingValidation = void 0;
662
- const n = await this.validateImmediate(s);
663
- r(n);
664
- }, t), s.signal.addEventListener(
665
- "abort",
666
- () => {
667
- this.validateDebounceTimer && (clearTimeout(this.validateDebounceTimer), this.validateDebounceTimer = void 0), r(!1);
668
- },
669
- { once: !0 }
670
- );
671
- });
672
- }
673
- /**
674
- * Немедленная валидация без debounce
675
- * @private
676
- * @param providedController - AbortController from debounced validate()
677
- * @remarks
678
- * Защищена от race conditions через AbortController:
679
- * - Отменяет предыдущую валидацию при запуске новой (if no controller provided)
680
- * - Передаёт AbortSignal в async валидаторы для отмены операций (например, fetch)
681
- * - Проверяет signal.aborted в ключевых точках
682
- */
683
- async validateImmediate(e) {
684
- const t = e ?? new AbortController();
685
- e || this.currentAbortController?.abort(), this.currentAbortController = t;
686
- const { signal: s } = t, r = [];
687
- for (const a of this.validators) {
688
- const l = a(this._value.value);
689
- l && r.push(l);
690
- }
691
- if (s.aborted)
692
- return !1;
693
- if (r.length > 0) {
694
- this._errors.value = r;
695
- const a = r.some((l) => l.severity !== "warning");
696
- if (this.statusMachine.setErrors(a), a)
697
- return !1;
698
- }
699
- if (this.asyncValidators.length > 0) {
700
- if (s.aborted)
701
- return !1;
702
- this.statusMachine.startValidation();
703
- try {
704
- const a = await Promise.all(
705
- this.asyncValidators.map(async (d) => {
706
- if (s.aborted)
707
- throw new DOMException("Validation aborted", "AbortError");
708
- try {
709
- const u = await d(this._value.value, { signal: s });
710
- if (s.aborted)
711
- throw new DOMException("Validation aborted", "AbortError");
712
- return u;
713
- } catch (u) {
714
- if (u instanceof DOMException && u.name === "AbortError")
715
- throw u;
716
- return k.handle(
717
- u,
718
- "FieldNode AsyncValidator",
719
- A.CONVERT
720
- );
721
- }
722
- })
723
- );
724
- if (s.aborted)
725
- return !1;
726
- const l = a.filter((d) => d !== null);
727
- if (l.length > 0) {
728
- this._errors.value = l;
729
- const d = l.some((u) => u.severity !== "warning");
730
- if (this.statusMachine.completeValidation(d), d)
731
- return !1;
732
- }
733
- } catch (a) {
734
- if (a instanceof DOMException && a.name === "AbortError")
735
- return !1;
736
- throw a;
737
- }
738
- }
739
- return s.aborted ? !1 : ((this.validators.length > 0 || this.asyncValidators.length > 0) && (this._errors.value = [], this.statusMachine.completeValidation(!1)), !this._errors.value.some((a) => a.severity !== "warning"));
740
- }
741
- setErrors(e) {
742
- this._errors.value = e;
743
- const t = e.some((s) => s.severity !== "warning");
744
- this.statusMachine.setErrors(t);
745
- }
746
- clearErrors() {
747
- this._errors.value = [], this.statusMachine.setErrors(!1);
748
- }
749
- // ============================================================================
750
- // Protected hooks (Template Method pattern)
751
- // ============================================================================
752
- /**
753
- * Hook: вызывается после markAsTouched()
754
- *
755
- * Для FieldNode: если updateOn === 'blur', запускаем валидацию
756
- */
757
- onMarkAsTouched() {
758
- this.updateOn === "blur" && this.validate();
759
- }
760
- /**
761
- * Hook: вызывается после disable()
762
- *
763
- * Для FieldNode: синхронизируем statusMachine и очищаем ошибки
764
- */
765
- onDisable() {
766
- this.statusMachine.disable(), this._errors.value = [];
767
- }
768
- /**
769
- * Hook: вызывается после enable()
770
- *
771
- * Для FieldNode: синхронизируем statusMachine и запускаем валидацию
772
- */
773
- onEnable() {
774
- this.statusMachine.enable(this._errors.value.length > 0), this.validate();
775
- }
776
- /**
777
- * Обновляет свойства компонента (например, опции для Select)
778
- *
779
- * @example
780
- * ```typescript
781
- * // Обновление опций для Select после загрузки справочников
782
- * form.registrationAddress.city.updateComponentProps({
783
- * options: cities
784
- * });
785
- * ```
786
- */
787
- updateComponentProps(e) {
788
- this._componentProps.value = {
789
- ...this._componentProps.value,
790
- ...e
791
- };
792
- }
793
- /**
794
- * Динамически изменяет триггер валидации (updateOn)
795
- * Полезно для адаптивной валидации - например, переключиться на instant feedback после первого submit
796
- *
797
- * @param updateOn - новый триггер валидации: 'change' | 'blur' | 'submit'
798
- *
799
- * @example
800
- * ```typescript
801
- * // Сценарий 1: Instant feedback после submit
802
- * const form = createForm({
803
- * email: {
804
- * value: '',
805
- * component: Input,
806
- * updateOn: 'submit', // Изначально валидация только при submit
807
- * validators: [required, email],
808
- * },
809
- * });
810
- *
811
- * await form.submit(async (values) => {
812
- * // После submit переключаем на instant feedback
813
- * form.email.setUpdateOn('change');
814
- * await api.save(values);
815
- * });
816
- *
817
- * // Теперь валидация происходит при каждом изменении
818
- *
819
- * // Сценарий 2: Прогрессивное улучшение
820
- * form.email.setUpdateOn('blur'); // Сначала только при blur
821
- * // ... пользователь начинает вводить ...
822
- * form.email.setUpdateOn('change'); // Переключаем на change для real-time feedback
823
- * ```
824
- */
825
- setUpdateOn(e) {
826
- this.updateOn = e;
827
- }
828
- getUpdateOn() {
829
- return this.updateOn;
830
- }
831
- // ============================================================================
832
- // Методы-помощники для реактивности (Фаза 1)
833
- // ============================================================================
834
- /**
835
- * Подписка на изменения значения поля
836
- * Автоматически отслеживает изменения через @preact/signals effect
837
- *
838
- * @param callback - Функция, вызываемая при изменении значения.
839
- * Для async операций передается AbortSignal во втором параметре.
840
- * @returns Функция отписки для cleanup
841
- *
842
- * @example
843
- * ```typescript
844
- * // Синхронный callback
845
- * const unsubscribe = form.email.watch((value) => {
846
- * console.log('Email changed:', value);
847
- * });
848
- *
849
- * // Асинхронный callback с поддержкой отмены
850
- * const unsubscribe = form.email.watch(async (value, signal) => {
851
- * const result = await fetch('/api/validate', { signal });
852
- * // ...
853
- * });
854
- *
855
- * // Cleanup
856
- * useEffect(() => unsubscribe, []);
857
- * ```
858
- */
859
- watch(e) {
860
- const t = new AbortController(), s = _(() => {
861
- const n = this.value.value;
862
- e(n, t.signal);
863
- }), r = N(O.Watch);
864
- return this.disposers.add(r, () => {
865
- t.abort(), s();
866
- });
867
- }
868
- /**
869
- * Вычисляемое значение из других полей
870
- * Автоматически обновляет текущее поле при изменении источников
871
- *
872
- * @param sources - Массив ReadonlySignal для отслеживания
873
- * @param computeFn - Функция вычисления нового значения
874
- * @returns Функция отписки для cleanup
875
- *
876
- * @example
877
- * ```typescript
878
- * // Автоматический расчет первоначального взноса (20% от стоимости)
879
- * const dispose = form.initialPayment.computeFrom(
880
- * [form.propertyValue.value],
881
- * (propertyValue) => {
882
- * return propertyValue ? propertyValue * 0.2 : null;
883
- * }
884
- * );
885
- *
886
- * // Cleanup
887
- * useEffect(() => dispose, []);
888
- * ```
889
- */
890
- computeFrom(e, t) {
891
- const s = _(() => {
892
- const n = e.map((l) => l.value), a = t(...n);
893
- this.setValue(a, { emitEvent: !1 });
894
- }), r = N(O.ComputeFrom);
895
- return this.disposers.add(r, s);
896
- }
897
- /**
898
- * Очистить все ресурсы и таймеры
899
- * Должен вызываться при unmount компонента
900
- *
901
- * @remarks
902
- * Освобождает все ресурсы:
903
- * - Отписывает все subscriptions через SubscriptionManager
904
- * - Отменяет pending/running валидации через cancelPendingValidation()
905
- *
906
- * Использует try-finally для гарантированного cleanup даже при ошибках.
907
- *
908
- * @example
909
- * ```typescript
910
- * useEffect(() => {
911
- * return () => {
912
- * field.dispose();
913
- * };
914
- * }, []);
915
- * ```
916
- */
917
- dispose() {
918
- try {
919
- this.disposers.dispose();
920
- } finally {
921
- this.cancelPendingValidation();
1
+ import { c as ue } from "./create-model-DPSG3I0O.js";
2
+ import { e as vt, a as pt } from "./create-model-DPSG3I0O.js";
3
+ import { d as T, q as H, t as de, o as he, n as fe, b as J, m as _, i as X, v as ve, w as pe, p as me, f as we, g as ye, x as be } from "./safe-effect-BazLOv1g.js";
4
+ import { a as wt, c as yt, r as bt, s as gt, e as kt, u as St } from "./safe-effect-BazLOv1g.js";
5
+ import { signal as w, computed as d, batch as D, effect as ge, untracked as A } from "@preact/signals-core";
6
+ import { N as j, R as ke, k as Se, r as k } from "./node-kind-C-r6ZMdo.js";
7
+ import { r as O } from "./signal-node-registry-tiwel-TV.js";
8
+ import { g as Et } from "./signal-node-registry-tiwel-TV.js";
9
+ import { c as $e, u as Ee } from "./schema-controller-CpiO2JoK.js";
10
+ import { a as At } from "./schema-controller-CpiO2JoK.js";
11
+ import { e as Pe, h as Ae, i as Oe, c as Fe, j as Ie } from "./strategy-DzvnsWLh.js";
12
+ import { c as Ft, a as It, b as Nt, i as Vt } from "./type-guards-D-N_5Ddh.js";
13
+ function Ne() {
14
+ const t = w(!1), e = w(!1), s = w(0);
15
+ return {
16
+ touched: d(() => t.value),
17
+ dirty: d(() => e.value),
18
+ touches: d(() => s.value),
19
+ setTouched: (i) => {
20
+ D(() => {
21
+ t.value = i, i && (s.value = s.peek() + 1);
22
+ });
23
+ },
24
+ setDirty: (i) => {
25
+ e.value = i;
922
26
  }
923
- }
27
+ };
924
28
  }
925
- function L(i) {
926
- const { getChildren: e, ownErrors: t, disabled: s } = i, r = f(() => t.value.length > 0 ? !1 : e().every((o) => o.disabled.value || o.valid.value)), n = f(() => !r.value), a = f(() => e().some((o) => o.pending.value)), l = f(() => e().some((o) => o.touched.value)), d = f(() => e().some((o) => o.dirty.value)), u = f(() => {
927
- const o = [...t.value];
928
- for (const m of e())
929
- o.push(...m.errors.value);
930
- return o;
931
- }), v = f(() => s?.value ? "disabled" : a.value ? "pending" : n.value ? "invalid" : "valid");
29
+ function R() {
30
+ const t = w([]);
932
31
  return {
933
- valid: r,
934
- invalid: n,
935
- pending: a,
936
- touched: l,
937
- dirty: d,
938
- errors: u,
939
- status: v
32
+ list: d(() => t.value),
33
+ blocking: d(() => t.value.some((e) => e.severity !== "warning")),
34
+ set: (e) => {
35
+ t.value = e;
36
+ },
37
+ clear: () => {
38
+ t.value = [];
39
+ }
940
40
  };
941
41
  }
942
- function j(i) {
943
- return typeof i == "object" && i !== null && "getProxy" in i && typeof i.getProxy == "function";
944
- }
945
- const U = "$";
946
- function ae(i, e) {
947
- let t;
948
- const s = () => {
949
- if (!t) {
950
- const r = {};
951
- e.forEach((n, a) => {
952
- r[a] = n && j(n) ? n.getProxy() : n;
953
- }), t = r;
42
+ function K(t = !1) {
43
+ const e = w(t);
44
+ return {
45
+ disabled: d(() => e.value),
46
+ setDisabled: (s) => {
47
+ e.value = s;
954
48
  }
955
- return t;
956
49
  };
957
- return new Proxy(i, {
958
- get: (r, n) => {
959
- if (n === U)
960
- return s();
961
- if (n in r)
962
- return r[n];
963
- if (typeof n == "string" && e.has(n)) {
964
- const a = e.get(n);
965
- return a && j(a) ? a.getProxy() : a;
966
- }
967
- },
968
- set: (r, n, a) => typeof n == "string" && e.has(n) ? !1 : (r[n] = a, !0),
969
- has: (r, n) => n === U || typeof n == "string" && e.has(n) ? !0 : n in r,
970
- ownKeys: (r) => {
971
- const n = Reflect.ownKeys(r), a = Array.from(e.keys());
972
- return [.../* @__PURE__ */ new Set([...n, ...a])];
973
- },
974
- getOwnPropertyDescriptor: (r, n) => typeof n == "string" && e.has(n) ? { enumerable: !0, configurable: !0 } : Reflect.getOwnPropertyDescriptor(r, n)
975
- });
976
50
  }
977
- class oe {
978
- /**
979
- * @param form - Форма для отправки
980
- */
981
- constructor(e) {
982
- this.form = e;
983
- }
984
- /** Внутренний сигнал состояния отправки */
985
- _submitting = g(!1);
986
- /** Публичный read-only сигнал состояния отправки */
987
- submitting = f(() => this._submitting.value);
988
- /**
989
- * Отправить форму
990
- *
991
- * Процесс:
992
- * 1. Помечает все поля как touched (для отображения ошибок)
993
- * 2. Валидирует форму
994
- * 3. Если валидация успешна - вызывает onSubmit
995
- * 4. Управляет состоянием submitting
996
- *
997
- * @param onSubmit - Callback для отправки данных
998
- * @param options - Опции submit
999
- * @returns Результат от onSubmit или `null` если валидация не пройдена
1000
- *
1001
- * @remarks
1002
- * Возвращаемый `null` неоднозначен: он сигналит и провал валидации, и легитимный `null`,
1003
- * который вернул `onSubmit` (или void-обработчик). Чтобы различать эти случаи, используйте
1004
- * {@link submitWithResult} — он отдаёт явный флаг `success`.
1005
- *
1006
- * @example
1007
- * ```typescript
1008
- * const result = await submitter.submit(async (values) => {
1009
- * const response = await fetch('/api/form', {
1010
- * method: 'POST',
1011
- * body: JSON.stringify(values)
1012
- * });
1013
- * return response.json();
1014
- * });
1015
- *
1016
- * if (result === null) {
1017
- * console.log('Форма не прошла валидацию');
1018
- * }
1019
- * ```
1020
- */
1021
- async submit(e, t) {
1022
- const { skipValidation: s = !1, skipTouch: r = !1 } = t || {};
1023
- if (r || this.form.markAsTouched(), !s && !await this.form.validate())
1024
- return null;
1025
- this._submitting.value = !0;
1026
- try {
1027
- return await e(this.form.getValue());
1028
- } finally {
1029
- this._submitting.value = !1;
1030
- }
1031
- }
1032
- /**
1033
- * Отправить форму с расширенным результатом
1034
- *
1035
- * В отличие от submit(), возвращает объект с информацией об успехе/ошибке
1036
- *
1037
- * @param onSubmit - Callback для отправки данных
1038
- * @param options - Опции submit
1039
- * @returns Объект SubmitResult с данными и статусом
1040
- *
1041
- * @example
1042
- * ```typescript
1043
- * const result = await submitter.submitWithResult(async (values) => {
1044
- * return await api.saveForm(values);
1045
- * });
1046
- *
1047
- * if (result.success) {
1048
- * console.log('Сохранено:', result.data);
1049
- * } else if (result.error) {
1050
- * console.error('Ошибка:', result.error.message);
1051
- * } else {
1052
- * console.log('Валидация не пройдена');
1053
- * }
1054
- * ```
1055
- */
1056
- async submitWithResult(e, t) {
1057
- const { skipValidation: s = !1, skipTouch: r = !1 } = t || {};
1058
- if (r || this.form.markAsTouched(), !s && !await this.form.validate())
1059
- return { success: !1, data: null };
1060
- this._submitting.value = !0;
1061
- try {
1062
- return { success: !0, data: await e(this.form.getValue()) };
1063
- } catch (n) {
1064
- return {
1065
- success: !1,
1066
- data: null,
1067
- error: n instanceof Error ? n : new Error(String(n))
1068
- };
1069
- } finally {
1070
- this._submitting.value = !1;
51
+ function W() {
52
+ const t = w(0);
53
+ return {
54
+ active: d(() => t.value > 0),
55
+ mark: (e) => {
56
+ t.value = Math.max(0, t.peek() + (e ? 1 : -1));
1071
57
  }
1072
- }
1073
- }
1074
- function le(i) {
1075
- const e = [];
1076
- for (const t of i.split(".")) {
1077
- const s = /^([^[\]]+)(?:\[(\d+)\])?$/.exec(t);
1078
- if (!s) return [];
1079
- e.push(s[2] !== void 0 ? { key: s[1], index: Number(s[2]) } : { key: s[1] });
1080
- }
1081
- return e;
58
+ };
1082
59
  }
1083
- class S extends M {
1084
- // ============================================================================
1085
- // Приватные поля
1086
- // ============================================================================
1087
- id = crypto.randomUUID();
1088
- /**
1089
- * Коллекция полей формы (упрощённый Map вместо FieldRegistry)
1090
- */
1091
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1092
- _fields = /* @__PURE__ */ new Map();
1093
- /**
1094
- * Менеджер подписок для централизованного cleanup
1095
- */
1096
- disposers = new I();
1097
- /**
1098
- * Ссылка на Proxy-инстанс
1099
- */
1100
- _proxyInstance;
1101
- /**
1102
- * Cleanup декларативной схемы поведения (createForm({ behavior })). Вызывается в dispose().
1103
- */
1104
- _behaviorCleanup;
1105
- /**
1106
- * Фабрика для создания узлов формы
1107
- */
1108
- nodeFactory = new ue();
1109
- // ============================================================================
1110
- // Приватные сигналы состояния (inline из StateManager)
1111
- // ============================================================================
1112
- /** Управление отправкой формы */
1113
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1114
- formSubmitter;
1115
- /** Флаг disabled состояния */
1116
- _disabled = g(!1);
1117
- /** Form-level validation errors */
1118
- _formErrors = g([]);
1119
- /**
1120
- * M1: связь листовой ноды-ребёнка → её сигнал модели (тот, что помечает `markDerived`).
1121
- * Нужна bulk-сеттерам (`setValue`/`patchValue`), чтобы корректно определять derived-поля:
1122
- * `field.value` — computed-обёртка, отличная от записываемого сигнала модели. @internal
1123
- */
1124
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1125
- _fieldSignals = /* @__PURE__ */ new WeakMap();
1126
- // ============================================================================
1127
- // Публичные computed signals
1128
- // ============================================================================
1129
- value;
1130
- valid;
1131
- invalid;
1132
- touched;
1133
- dirty;
1134
- pending;
1135
- errors;
1136
- status;
1137
- submitting;
1138
- constructor(e) {
1139
- super(), this.formSubmitter = new oe(this);
1140
- const t = e.form, s = t != null && typeof t == "object" && ("valueSignal" in t || "value" in t), n = "form" in e && !s ? e.form : e;
1141
- for (const [l, d] of Object.entries(n)) {
1142
- const u = this.createNode(d);
1143
- this._fields.set(l, u);
1144
- }
1145
- this.value = f(() => {
1146
- const l = {};
1147
- return this._fields.forEach((d, u) => {
1148
- l[u] = d.value.value;
1149
- }), l;
1150
- });
1151
- const a = L({
1152
- getChildren: () => Array.from(this._fields.values()),
1153
- ownErrors: this._formErrors,
1154
- disabled: this._disabled
1155
- });
1156
- this.valid = a.valid, this.invalid = a.invalid, this.pending = a.pending, this.touched = a.touched, this.dirty = a.dirty, this.errors = a.errors, this.status = a.status, this.submitting = this.formSubmitter.submitting;
1157
- }
1158
- // ============================================================================
1159
- // Приватный метод для создания Proxy
1160
- // ============================================================================
1161
- /**
1162
- * Создать Proxy для типобезопасного доступа к полям
1163
- * @see buildFormProxy
1164
- */
1165
- buildProxy() {
1166
- return ae(this, this._fields);
1167
- }
1168
- // ============================================================================
1169
- // Реализация абстрактных методов FormNode
1170
- // ============================================================================
1171
- getValue() {
1172
- const e = {};
1173
- return this._fields.forEach((t, s) => {
1174
- e[s] = t.getValue();
1175
- }), e;
1176
- }
1177
- setValue(e, t) {
1178
- for (const [s, r] of Object.entries(e)) {
1179
- const n = this._fields.get(s);
1180
- if (n) {
1181
- const a = this._fieldSignals.get(n) ?? n.value;
1182
- if ($(a)) continue;
1183
- n.setValue(r, t);
1184
- }
1185
- }
1186
- }
1187
- patchValue(e) {
1188
- X(() => {
1189
- for (const [t, s] of Object.entries(e)) {
1190
- const r = this._fields.get(t);
1191
- if (r && s !== void 0) {
1192
- const n = this._fieldSignals.get(r) ?? r.value;
1193
- if ($(n)) continue;
1194
- r.setValue(s, { emitEvent: !1 });
1195
- }
1196
- }
1197
- });
1198
- }
1199
- /**
1200
- * Сбросить форму к указанным значениям (или к initialValues)
1201
- *
1202
- * @param value - опциональный объект со значениями для сброса
1203
- *
1204
- * @remarks
1205
- * Рекурсивно вызывает reset() для всех полей формы
1206
- *
1207
- * @example
1208
- * ```typescript
1209
- * // Сброс к initialValues
1210
- * form.reset();
1211
- *
1212
- * // Сброс к новым значениям
1213
- * form.reset({ email: 'new@mail.com', password: '' });
1214
- * ```
1215
- */
1216
- reset(e) {
1217
- this._formErrors.value = [], this._fields.forEach((t, s) => {
1218
- const r = e?.[s];
1219
- t.reset(r);
1220
- });
1221
- }
1222
- /**
1223
- * Сбросить форму к исходным значениям (initialValues)
1224
- */
1225
- resetToInitial() {
1226
- this._formErrors.value = [], this._fields.forEach((e) => {
1227
- "resetToInitial" in e && typeof e.resetToInitial == "function" ? e.resetToInitial() : e.reset();
1228
- });
1229
- }
1230
- async validate() {
1231
- return this.clearErrors(), await Promise.all(Array.from(this._fields.values()).map((t) => t.validate())), Array.from(this._fields.values()).every(
1232
- (t) => t.valid.value || t.disabled.value
1233
- );
1234
- }
1235
- /**
1236
- * Установить form-level validation errors
1237
- */
1238
- setErrors(e) {
1239
- this._formErrors.value = e;
1240
- }
1241
- /**
1242
- * Очистить все errors (form-level + field-level)
1243
- */
1244
- clearErrors() {
1245
- this._formErrors.value = [], this._fields.forEach((e) => e.clearErrors());
1246
- }
1247
- /**
1248
- * Получить поле по ключу
1249
- */
1250
- getField(e) {
1251
- return this._fields.get(e);
1252
- }
1253
- /**
1254
- * Получить Map всех полей формы (для совместимости)
1255
- */
1256
- get fields() {
1257
- return this._fields;
1258
- }
1259
- /**
1260
- * Получить Proxy-инстанс для прямого доступа к полям
1261
- *
1262
- * Proxy позволяет обращаться к полям формы напрямую через точечную нотацию:
1263
- * - form.email вместо form.fields.get('email')
1264
- * - form.address.city вместо form.fields.get('address').fields.get('city')
1265
- *
1266
- * @returns Proxy-инстанс с типобезопасным доступом к полям или сама форма, если proxy не доступен
1267
- *
1268
- * @example
1269
- * ```typescript
1270
- * const form = new GroupNode({
1271
- * email: { value: '', component: Input },
1272
- * name: { value: '', component: Input },
1273
- * });
1274
- *
1275
- * const proxy = form.getProxy();
1276
- * console.log(proxy.email.value.value); // Прямой доступ к полю
1277
- * ```
1278
- */
1279
- getProxy() {
1280
- return this._proxyInstance || (this._proxyInstance = this.buildProxy()), this._proxyInstance;
1281
- }
1282
- /**
1283
- * Получить все поля формы как итератор
1284
- */
1285
- getAllFields() {
1286
- return this._fields.values();
1287
- }
1288
- // ============================================================================
1289
- // Protected hooks (Template Method pattern)
1290
- // ============================================================================
1291
- onMarkAsTouched() {
1292
- this._fields.forEach((e) => e.markAsTouched());
1293
- }
1294
- onMarkAsUntouched() {
1295
- this._fields.forEach((e) => e.markAsUntouched());
1296
- }
1297
- onMarkAsDirty() {
1298
- this._fields.forEach((e) => e.markAsDirty());
1299
- }
1300
- onMarkAsPristine() {
1301
- this._fields.forEach((e) => e.markAsPristine());
1302
- }
1303
- // ============================================================================
1304
- // Дополнительные методы (из FormStore)
1305
- // ============================================================================
1306
- /**
1307
- * Отправить форму
1308
- *
1309
- * @param onSubmit - Callback для отправки данных
1310
- * @param options - Опции submit (skipValidation, skipTouch)
1311
- * @returns Результат от onSubmit или `null` если валидация не пройдена
1312
- *
1313
- * @remarks
1314
- * `null` перегружен: он означает и «валидация не пройдена», и легитимный `null`-результат
1315
- * `onSubmit` (или void-обработчик). Если вызывающей стороне нужно различать эти случаи —
1316
- * используйте {@link submitWithResult}, который возвращает явный флаг `success`.
1317
- */
1318
- async submit(e, t) {
1319
- return this.formSubmitter.submit(e, t);
1320
- }
1321
- /**
1322
- * Отправить форму с расширенным результатом
1323
- *
1324
- * @param onSubmit - Callback для отправки данных
1325
- * @param options - Опции submit
1326
- * @returns Объект SubmitResult с данными, статусом и возможной ошибкой
1327
- */
1328
- async submitWithResult(e, t) {
1329
- return this.formSubmitter.submitWithResult(e, t);
1330
- }
1331
- /**
1332
- * Получить вложенное поле по пути
1333
- *
1334
- * Поддерживаемые форматы путей:
1335
- * - Simple: "email" - получить поле верхнего уровня
1336
- * - Nested: "address.city" - получить вложенное поле
1337
- * - Array index: "items[0]" - получить элемент массива по индексу
1338
- * - Combined: "items[0].name" - получить поле элемента массива
1339
- *
1340
- * @param path - Путь к полю
1341
- * @returns FormNode если найдено, undefined если путь не существует
1342
- *
1343
- * @example
1344
- * ```typescript
1345
- * const form = new GroupNode({
1346
- * email: { value: '', component: Input },
1347
- * address: {
1348
- * city: { value: '', component: Input }
1349
- * },
1350
- * items: [{ name: { value: '', component: Input } }]
1351
- * });
1352
- *
1353
- * form.getFieldByPath('email'); // FieldNode
1354
- * form.getFieldByPath('address.city'); // FieldNode
1355
- * form.getFieldByPath('items[0]'); // GroupNode
1356
- * form.getFieldByPath('items[0].name'); // FieldNode
1357
- * form.getFieldByPath('invalid.path'); // undefined
1358
- * ```
1359
- */
1360
- getFieldByPath(e) {
1361
- if (e.startsWith(".") || e.endsWith("."))
1362
- return;
1363
- const t = le(e);
1364
- if (t.length === 0)
1365
- return;
1366
- let s = this;
1367
- for (const r of t) {
1368
- if (!(s instanceof S) || (s = s.getField(r.key), !s)) return;
1369
- if (r.index !== void 0)
1370
- if ("at" in s && "length" in s && typeof s.at == "function") {
1371
- const n = s.at(r.index);
1372
- if (!n) return;
1373
- s = n;
1374
- } else
1375
- return;
1376
- }
1377
- return s;
1378
- }
1379
- // ============================================================================
1380
- // Private методы для создания узлов
1381
- // ============================================================================
1382
- /**
1383
- * Создать узел на основе конфигурации
1384
- *
1385
- * ✅ РЕФАКТОРИНГ: Полное делегирование NodeFactory
1386
- *
1387
- * NodeFactory теперь обрабатывает:
1388
- * - Массивы [schema, ...items]
1389
- * - FieldConfig
1390
- * - GroupConfig
1391
- * - ArrayConfig
1392
- *
1393
- * @param config Конфигурация узла
1394
- * @returns Созданный узел формы
1395
- * @private
1396
- */
1397
- createNode(e) {
1398
- return this.nodeFactory.createNode(e);
1399
- }
1400
- // ============================================================================
1401
- // Методы-помощники для реактивности (Фаза 1)
1402
- // ============================================================================
1403
- /**
1404
- * Связывает два поля: при изменении source автоматически обновляется target
1405
- */
1406
- linkFields(e, t, s) {
1407
- const r = this._fields.get(e), n = this._fields.get(t);
1408
- if (!r || !n) {
1409
- const d = r ? t : e;
1410
- throw new Error(`GroupNode.linkFields: field "${String(d)}" not found`);
60
+ function Ve(t = {}) {
61
+ const e = w(t);
62
+ return {
63
+ props: d(() => e.value),
64
+ update: (s) => {
65
+ e.value = { ...e.peek(), ...s };
1411
66
  }
1412
- const a = _(() => {
1413
- const d = r.value.value, u = s ? s(d) : d;
1414
- n.setValue(u, { emitEvent: !1 });
1415
- }), l = N(O.LinkFields);
1416
- return this.disposers.add(l, a);
1417
- }
1418
- /**
1419
- * Подписка на изменения вложенного поля по строковому пути
1420
- * Поддерживает вложенные пути типа "address.city"
1421
- *
1422
- * @param fieldPath - Строковый путь к полю (например, "address.city")
1423
- * @param callback - Функция, вызываемая при изменении поля
1424
- * @returns Функция отписки для cleanup
1425
- *
1426
- * @example
1427
- * ```typescript
1428
- * // Подписка на изменение страны для загрузки городов
1429
- * const dispose = form.watchField(
1430
- * 'registrationAddress.country',
1431
- * async (countryCode) => {
1432
- * if (countryCode) {
1433
- * const cities = await fetchCitiesByCountry(countryCode);
1434
- * form.registrationAddress.city.updateComponentProps({
1435
- * options: cities
1436
- * });
1437
- * }
1438
- * }
1439
- * );
1440
- *
1441
- * // Cleanup
1442
- * useEffect(() => dispose, []);
1443
- * ```
1444
- */
1445
- /** Подписка на top-level поле — value типизирован как `T[K]`. */
1446
- watchField(e, t) {
1447
- const s = this.getFieldByPath(e);
1448
- if (!s)
1449
- throw new Error(`GroupNode.watchField: field "${e}" not found`);
1450
- const r = _(() => {
1451
- t(s.value.value);
1452
- }), n = N(O.WatchField);
1453
- return this.disposers.add(n, r);
1454
- }
1455
- /**
1456
- * Подписка на вложенное поле по строковому пути ("address.city").
1457
- * Путь нельзя выразить в типах узла → value честно `unknown`, потребитель сужает.
1458
- */
1459
- watchFieldByPath(e, t) {
1460
- const s = this.getFieldByPath(e);
1461
- if (!s)
1462
- throw new Error(`GroupNode.watchFieldByPath: field "${e}" not found`);
1463
- const r = _(() => {
1464
- t(s.value.value);
1465
- }), n = N(O.WatchField);
1466
- return this.disposers.add(n, r);
1467
- }
1468
- /**
1469
- * Hook: вызывается после disable()
1470
- */
1471
- onDisable() {
1472
- this._disabled.value = !0, this._fields.forEach((e) => e.disable());
1473
- }
1474
- /**
1475
- * Hook: вызывается после enable()
1476
- */
1477
- onEnable() {
1478
- this._disabled.value = !1, this._fields.forEach((e) => e.enable());
1479
- }
1480
- /**
1481
- * Прикрепить cleanup декларативной схемы поведения (вызывается createForm).
1482
- * @internal
1483
- */
1484
- attachBehaviorCleanup(e) {
1485
- this._behaviorCleanup = e;
1486
- }
1487
- /**
1488
- * Связать листовую ноду-ребёнка с её сигналом модели (M1). Вызывается `createForm` для каждого
1489
- * листового поля на его владеющей группе — используется bulk-сеттерами для derived-guard (F9).
1490
- * @internal
1491
- */
1492
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1493
- registerFieldSignal(e, t) {
1494
- this._fieldSignals.set(e, t);
1495
- }
1496
- /**
1497
- * Очистить все ресурсы узла
1498
- */
1499
- dispose() {
1500
- this._behaviorCleanup?.(), this._behaviorCleanup = void 0, this.disposers.dispose(), this._fields.forEach((e) => {
1501
- "dispose" in e && typeof e.dispose == "function" && e.dispose();
1502
- });
1503
- }
67
+ };
1504
68
  }
1505
- class B extends M {
1506
- // ============================================================================
1507
- // Приватные поля
1508
- // ============================================================================
1509
- items;
1510
- itemSchema;
1511
- initialItems;
1512
- /**
1513
- * Менеджер подписок для централизованного cleanup
1514
- * Использует SubscriptionManager вместо массива для управления подписками
1515
- */
1516
- disposers = new I();
1517
- /** Array-level validation errors (e.g., "минимум 1 элемент") */
1518
- _arrayErrors = g([]);
1519
- // ============================================================================
1520
- // Публичные computed signals
1521
- // ============================================================================
69
+ class Ce {
70
+ [j] = "control";
1522
71
  value;
1523
72
  valid;
1524
73
  invalid;
74
+ pending;
75
+ disabled;
1525
76
  touched;
77
+ /** Уходы из поля, включая повторные, — триггер стратегии `blur`. @internal */
78
+ touches;
1526
79
  dirty;
1527
- pending;
1528
- errors;
1529
- status;
1530
- length;
1531
- // ============================================================================
1532
- // Конструктор
1533
- // ============================================================================
1534
- constructor(e, t = []) {
1535
- super(), this.itemSchema = e, this.initialItems = t, this.items = g([]);
1536
- for (const r of t)
1537
- this.push(r);
1538
- this.length = f(() => this.items.value.length), this.value = f(() => this.items.value.map((r) => r.value.value));
1539
- const s = L({
1540
- getChildren: () => this.items.value,
1541
- ownErrors: this._arrayErrors
1542
- });
1543
- this.valid = s.valid, this.invalid = s.invalid, this.pending = s.pending, this.touched = s.touched, this.dirty = s.dirty, this.errors = s.errors, this.status = s.status;
1544
- }
1545
- // ============================================================================
1546
- // CRUD операции
1547
- // ============================================================================
1548
- /**
1549
- * Добавить элемент в конец массива
1550
- * @param initialValue - Начальные значения для нового элемента
1551
- */
1552
- push(e) {
1553
- const t = this.createItem(e);
1554
- this.items.value = [...this.items.value, t];
1555
- }
80
+ issues;
81
+ ownIssues;
82
+ component;
83
+ componentProps;
84
+ shouldShowError;
85
+ shouldShowIssues;
86
+ source;
1556
87
  /**
1557
- * Удалить элемент по индексу
1558
- * @param index - Индекс элемента для удаления
1559
- *
1560
- * @remarks
1561
- * Вызывает dispose() на удаляемом элементе для очистки подписок
88
+ * Значение на момент построения. К нему возвращает `resetToCreation` (`resetOnDisable`), а
89
+ * {@link reset} — только поле над собственным сигналом, без модели за ним.
1562
90
  */
1563
- removeAt(e) {
1564
- if (e < 0 || e >= this.items.value.length) {
1565
- k.handle(
1566
- new Error(
1567
- `ArrayNode.removeAt: index ${e} out of bounds (length: ${this.items.value.length})`
1568
- ),
1569
- "ArrayNode.removeAt",
1570
- A.LOG
1571
- );
1572
- return;
1573
- }
1574
- const t = this.items.value[e];
1575
- this.items.value = this.items.value.filter((s, r) => r !== e), t && "dispose" in t && typeof t.dispose == "function" && t.dispose();
1576
- }
91
+ initialValue;
92
+ interaction = Ne();
93
+ issueState = R();
94
+ availability;
1577
95
  /**
1578
- * Вставить элемент в массив
1579
- * @param index - Индекс для вставки
1580
- * @param initialValue - Начальные значения для нового элемента
96
+ * Блокировка из схемы (`disabled: true` узла — вычисляемые `total`, `fullName`). Постоянная:
97
+ * `enable()` контейнера её не снимает, иначе «выключить форму на время отправки → включить»
98
+ * разблокировало бы такие поля. Снимает её только явный `enable()` этого поля.
1581
99
  */
1582
- insert(e, t) {
1583
- if (e < 0 || e > this.items.value.length) {
1584
- k.handle(
1585
- new Error(
1586
- `ArrayNode.insert: index ${e} out of bounds (length: ${this.items.value.length})`
1587
- ),
1588
- "ArrayNode.insert",
1589
- A.LOG
1590
- );
1591
- return;
1592
- }
1593
- const s = this.createItem(t), r = [...this.items.value];
1594
- r.splice(e, 0, s), this.items.value = r;
100
+ locked;
101
+ pendingState = W();
102
+ componentState;
103
+ constructor(e) {
104
+ this.source = e.valueSignal, this.initialValue = this.source.peek(), this.component = e.component, this.availability = K(e.disabled === !0), this.locked = e.disabled === !0, this.componentState = Ve(e.componentProps ?? {});
105
+ const { disabled: s } = this.availability, i = this.issueState.blocking;
106
+ this.value = d(() => this.source.value), this.disabled = s, this.pending = d(() => !s.value && this.pendingState.active.value), this.invalid = d(() => !s.value && !this.pending.value && i.value), this.valid = d(() => !s.value && !this.pending.value && !i.value), this.touched = this.interaction.touched, this.touches = this.interaction.touches, this.dirty = this.interaction.dirty, this.issues = this.issueState.list, this.ownIssues = this.issueState.list, this.componentProps = this.componentState.props;
107
+ const n = d(() => this.touched.value || this.dirty.value);
108
+ this.shouldShowError = d(() => this.invalid.value && n.value), this.shouldShowIssues = d(
109
+ () => !s.value && !this.pending.value && this.issues.value.length > 0 && n.value
110
+ );
1595
111
  }
1596
- /**
1597
- * Переместить элемент массива с позиции `from` на позицию `to`
1598
- *
1599
- * @param from - Индекс перемещаемого элемента
1600
- * @param to - Целевой индекс
1601
- *
1602
- * @remarks
1603
- * Реордер не уничтожает элемент (dispose НЕ вызывается) — тот же инстанс под-формы
1604
- * переезжает на новую позицию, состояние/валидация сохраняются. Длина не меняется.
1605
- *
1606
- * @example
1607
- * ```typescript
1608
- * // Поднять элемент #2 на одну позицию вверх
1609
- * form.items.move(2, 1);
1610
- * ```
1611
- */
1612
- move(e, t) {
1613
- const s = this.items.value.length;
1614
- if (e < 0 || e >= s || t < 0 || t >= s) {
1615
- k.handle(
1616
- new Error(`ArrayNode.move: index out of bounds (from=${e}, to=${t}, length=${s})`),
1617
- "ArrayNode.move",
1618
- A.LOG
1619
- );
1620
- return;
1621
- }
1622
- if (e === t) return;
1623
- const r = [...this.items.value], [n] = r.splice(e, 1);
1624
- r.splice(t, 0, n), this.items.value = r;
112
+ getValue() {
113
+ return this.source.peek();
1625
114
  }
1626
- /**
1627
- * Поменять местами два элемента массива
1628
- *
1629
- * @param a - Индекс первого элемента
1630
- * @param b - Индекс второго элемента
1631
- *
1632
- * @remarks
1633
- * Как и {@link move}, не уничтожает элементы — меняет только их порядок.
1634
- *
1635
- * @example
1636
- * ```typescript
1637
- * form.items.swap(0, 2);
1638
- * ```
1639
- */
1640
- swap(e, t) {
1641
- const s = this.items.value.length;
1642
- if (e < 0 || e >= s || t < 0 || t >= s) {
1643
- k.handle(
1644
- new Error(`ArrayNode.swap: index out of bounds (a=${e}, b=${t}, length=${s})`),
1645
- "ArrayNode.swap",
1646
- A.LOG
1647
- );
1648
- return;
1649
- }
1650
- if (e === t) return;
1651
- const r = [...this.items.value];
1652
- [r[e], r[t]] = [r[t], r[e]], this.items.value = r;
115
+ setValue(e) {
116
+ this.source.value = e, this.interaction.setDirty(!0);
1653
117
  }
1654
118
  /**
1655
- * Удалить все элементы массива
119
+ * Сбросить поле: значение, замечания, `touched` и `dirty`. Без аргумента — к точке отсчёта модели,
120
+ * той же, к которой возвращает `model.reset()`: значению создания модели либо последнему
121
+ * `captureInitial()` (загруженные данные). Поле над собственным сигналом, без модели, — к значению,
122
+ * с которым нода создана.
1656
123
  *
1657
- * @remarks
1658
- * Вызывает dispose() на всех элементах для очистки подписок
124
+ * У производного поля (цель `compute`) без аргумента сбрасывается только состояние: значением
125
+ * владеет поведение, а значение создания ноды снято до первого расчёта — запись вернула бы
126
+ * «сырое» значение, которое `compute` пересчитает лишь при смене источников.
1659
127
  */
1660
- clear() {
1661
- const e = [...this.items.value];
1662
- this.items.value = [], e.forEach((t) => {
1663
- "dispose" in t && typeof t.dispose == "function" && t.dispose();
1664
- });
128
+ reset(e) {
129
+ e !== void 0 ? this.source.value = e : T(this.source) || (this.source.value = this.referenceValue()), this.resetState();
1665
130
  }
1666
- /**
1667
- * Получить элемент по индексу
1668
- * @param index - Индекс элемента
1669
- * @returns Типизированный GroupNode proxy или undefined если индекс вне границ
1670
- */
1671
- at(e) {
1672
- const t = this.items.value[e];
1673
- if (t)
1674
- return t.getProxy();
131
+ /** Точка отсчёта модели за полем; у поля без модели — значение создания. */
132
+ referenceValue() {
133
+ const e = H(this.source);
134
+ return e === void 0 ? this.initialValue : e.value;
1675
135
  }
1676
- // ============================================================================
1677
- // Реализация абстрактных методов
1678
- // ============================================================================
1679
- getValue() {
1680
- return this.items.value.map((e) => e.getValue());
136
+ /** Значение создания и чистое состояние, у цели `compute` тоже — для `resetOnDisable`. @internal */
137
+ resetToCreation() {
138
+ this.source.value = this.initialValue, this.resetState();
1681
139
  }
1682
- setValue(e, t) {
1683
- this.clear(), e.forEach((s) => this.push(s)), t?.emitEvent !== !1 && this.validate().catch((s) => {
1684
- k.handle(s, "ArrayNode.setValue", A.LOG);
1685
- });
140
+ resetState() {
141
+ this.issueState.clear(), this.interaction.setTouched(!1), this.interaction.setDirty(!1);
1686
142
  }
1687
- patchValue(e) {
1688
- e.forEach((t, s) => {
1689
- this.items.value[s] && t !== void 0 && this.items.value[s].patchValue(t);
1690
- });
143
+ markAsTouched() {
144
+ this.interaction.setTouched(!0);
1691
145
  }
1692
- /**
1693
- * Сбросить массив к указанным значениям (или очистить)
1694
- *
1695
- * @param values - опциональный массив значений для сброса
1696
- *
1697
- * @remarks
1698
- * Очищает текущий массив и заполняет новыми элементами
1699
- *
1700
- * @example
1701
- * ```typescript
1702
- * // Очистить массив
1703
- * arrayNode.reset();
1704
- *
1705
- * // Сбросить к новым значениям
1706
- * arrayNode.reset([{ name: 'Item 1' }, { name: 'Item 2' }]);
1707
- * ```
1708
- */
1709
- reset(e) {
1710
- this._arrayErrors.value = [], this.clear(), e && e.forEach((t) => this.push(t));
146
+ markAsUntouched() {
147
+ this.interaction.setTouched(!1);
1711
148
  }
1712
- /**
1713
- * Сбросить массив к исходным значениям (initialItems)
1714
- *
1715
- * @remarks
1716
- * Восстанавливает массив в состояние, которое было при создании ArrayNode.
1717
- * Более явный способ сброса к начальным значениям по сравнению с reset()
1718
- *
1719
- * Полезно когда:
1720
- * - Пользователь нажал "Cancel" - вернуть массив к исходным элементам
1721
- * - Массив был изменен через reset(newValues), но нужно вернуться к началу
1722
- * - Явное намерение показать "отмена всех изменений"
1723
- *
1724
- * @example
1725
- * ```typescript
1726
- * const arrayNode = new ArrayNode(
1727
- * { name: { value: '', component: Input } },
1728
- * [{ name: 'Initial 1' }, { name: 'Initial 2' }]
1729
- * );
1730
- *
1731
- * arrayNode.push({ name: 'New Item' });
1732
- * arrayNode.reset([{ name: 'Temp' }]);
1733
- * console.log(arrayNode.length.value); // 1
1734
- *
1735
- * arrayNode.resetToInitial();
1736
- * console.log(arrayNode.length.value); // 2
1737
- * console.log(arrayNode.at(0)?.name.value.value); // 'Initial 1'
1738
- * ```
1739
- */
1740
- resetToInitial() {
1741
- this._arrayErrors.value = [], this.clear(), this.initialItems.forEach((e) => this.push(e));
149
+ disable() {
150
+ this.availability.setDisabled(!0), this.issueState.clear();
1742
151
  }
1743
- async validate() {
1744
- return (await Promise.all(this.items.value.map((t) => t.validate()))).every(Boolean) && this._arrayErrors.value.length === 0;
152
+ enable() {
153
+ this.locked = !1, this.availability.setDisabled(!1);
1745
154
  }
1746
- /**
1747
- * Установить array-level validation errors
1748
- *
1749
- * @param errors - Массив ошибок валидации уровня массива
1750
- *
1751
- * @example
1752
- * ```typescript
1753
- * arrayNode.setErrors([{
1754
- * code: 'minItems',
1755
- * message: 'Минимум 1 элемент обязателен',
1756
- * }]);
1757
- * ```
1758
- */
1759
- setErrors(e) {
1760
- this._arrayErrors.value = e;
155
+ /** Каскад от контейнера: блокировку из схемы не снимает. @internal */
156
+ enableFromParent() {
157
+ this.locked || this.availability.setDisabled(!1);
1761
158
  }
1762
- /**
1763
- * Очистить все errors (array-level + item-level)
1764
- *
1765
- * @example
1766
- * ```typescript
1767
- * arrayNode.clearErrors();
1768
- * console.log(arrayNode.errors.value); // []
1769
- * ```
1770
- */
1771
- clearErrors() {
1772
- this._arrayErrors.value = [], this.items.value.forEach((e) => e.clearErrors());
159
+ setIssues(e) {
160
+ this.issueState.set(e);
1773
161
  }
1774
- // ============================================================================
1775
- // Protected hooks (Template Method pattern)
1776
- // ============================================================================
1777
- /**
1778
- * Hook: вызывается после markAsTouched()
1779
- *
1780
- * Для ArrayNode: рекурсивно помечаем все элементы массива как touched
1781
- */
1782
- onMarkAsTouched() {
1783
- this.items.value.forEach((e) => e.markAsTouched());
162
+ clearIssues() {
163
+ this.issueState.clear();
1784
164
  }
1785
- /**
1786
- * Hook: вызывается после markAsUntouched()
1787
- *
1788
- * Для ArrayNode: рекурсивно помечаем все элементы массива как untouched
1789
- */
1790
- onMarkAsUntouched() {
1791
- this.items.value.forEach((e) => e.markAsUntouched());
165
+ updateComponentProps(e) {
166
+ this.componentState.update(e);
1792
167
  }
1793
- /**
1794
- * Hook: вызывается после markAsDirty()
1795
- *
1796
- * Для ArrayNode: рекурсивно помечаем все элементы массива как dirty
1797
- */
1798
- onMarkAsDirty() {
1799
- this.items.value.forEach((e) => e.markAsDirty());
168
+ /** Раннер валидации: начало (`true`) и конец (`false`) async-правил поля. @internal */
169
+ setPending(e) {
170
+ this.pendingState.mark(e);
1800
171
  }
1801
- /**
1802
- * Hook: вызывается после markAsPristine()
1803
- *
1804
- * Для ArrayNode: рекурсивно помечаем все элементы массива как pristine
1805
- */
1806
- onMarkAsPristine() {
1807
- this.items.value.forEach((e) => e.markAsPristine());
172
+ /** У поля подписок нет. @internal */
173
+ dispose() {
1808
174
  }
1809
- // ============================================================================
1810
- // Итерация
1811
- // ============================================================================
1812
- /**
1813
- * Итерировать по элементам массива
1814
- * @param callback - Функция, вызываемая для каждого элемента с типизированным GroupNode proxy
175
+ }
176
+ function Q(t) {
177
+ const { getChildren: e, ownIssues: s, ownPending: i } = t, n = () => t.disabled?.value ?? !1, a = d(() => s.value.some((l) => l.severity !== "warning")), r = d(
178
+ () => !n() && ((i?.value ?? !1) || e().some((l) => l.pending.value))
179
+ ), o = d(
180
+ () => !n() && !r.value && !a.value && e().every((l) => l.disabled.value || l.valid.value)
181
+ ), c = d(
182
+ () => !n() && !r.value && (a.value || e().some((l) => !l.disabled.value && l.invalid.value))
183
+ );
184
+ return {
185
+ valid: o,
186
+ invalid: c,
187
+ pending: r,
188
+ touched: d(() => e().some((l) => l.touched.value)),
189
+ dirty: d(() => e().some((l) => l.dirty.value)),
190
+ issues: d(() => [
191
+ ...s.value,
192
+ ...e().flatMap((l) => l.issues.value)
193
+ ])
194
+ };
195
+ }
196
+ const Me = [
197
+ "value",
198
+ "valid",
199
+ "invalid",
200
+ "pending",
201
+ "disabled",
202
+ "touched",
203
+ "dirty",
204
+ "issues",
205
+ "ownIssues",
206
+ "getValue",
207
+ "setValue",
208
+ "reset",
209
+ "markAsTouched",
210
+ "markAsUntouched",
211
+ "enable",
212
+ "disable",
213
+ "setIssues",
214
+ "clearIssues"
215
+ ], L = new Set(Me), N = "$";
216
+ function Te(t) {
217
+ return Se(t) === "group" ? t.proxy : t;
218
+ }
219
+ function De(t, e) {
220
+ let s;
221
+ const i = () => {
222
+ if (!s) {
223
+ const r = {};
224
+ e.forEach((o, c) => {
225
+ r[c] = Te(o);
226
+ }), s = Object.freeze(r);
227
+ }
228
+ return s;
229
+ }, n = /* @__PURE__ */ new Map(), a = (r) => {
230
+ const o = Reflect.get(t, r, t);
231
+ if (typeof o != "function") return o;
232
+ let c = n.get(r);
233
+ return c || (c = o.bind(t), n.set(r, c)), c;
234
+ };
235
+ return new Proxy(t, {
236
+ get: (r, o) => o === ke ? t : o === N ? i() : L.has(o) ? a(o) : typeof o == "string" && e.has(o) ? i()[o] : a(o),
237
+ set: (r, o, c) => o === N || L.has(o) || typeof o == "string" && e.has(o) ? !1 : Reflect.set(t, o, c, t),
238
+ has: (r, o) => o === N || typeof o == "string" && e.has(o) || o in t,
239
+ ownKeys: () => [.../* @__PURE__ */ new Set([...Reflect.ownKeys(t), ...e.keys()])],
240
+ getOwnPropertyDescriptor: (r, o) => typeof o == "string" && e.has(o) ? { enumerable: !0, configurable: !0, value: i()[o] } : Reflect.getOwnPropertyDescriptor(t, o)
241
+ });
242
+ }
243
+ class _e {
244
+ [j] = "group";
245
+ value;
246
+ valid;
247
+ invalid;
248
+ pending;
249
+ disabled;
250
+ touched;
251
+ dirty;
252
+ issues;
253
+ ownIssues;
254
+ /** Сырые ноды детей по именам полей данных. */
255
+ children;
256
+ /** Замечания самой группы (ответ сервера на форму целиком). */
257
+ own = R();
258
+ availability = K();
259
+ /** Ожидание async-правила на ручке самой группы. */
260
+ ownPending = W();
261
+ /**
262
+ * Лист → его записываемый сигнал модели: по нему `setValue` узнаёт цели `compute` (их
263
+ * помечает `markDerived`), а не по computed-обёртке `value` ноды.
264
+ */
265
+ leafSignals = /* @__PURE__ */ new WeakMap();
266
+ behaviorCleanup;
267
+ face;
268
+ /**
269
+ * @param children - Готовые ноды детей по именам полей данных. Вид ребёнка задан нодой: по
270
+ * именам и значениям группа ничего не угадывает.
1815
271
  */
1816
- forEach(e) {
1817
- this.items.value.forEach((t, s) => {
1818
- const r = t.getProxy();
1819
- e(r, s);
272
+ constructor(e) {
273
+ this.children = new Map(e);
274
+ const s = [...this.children.values()];
275
+ this.value = d(() => {
276
+ const a = {};
277
+ return this.children.forEach((r, o) => {
278
+ a[o] = r.value.value;
279
+ }), a;
1820
280
  });
1821
- }
1822
- /**
1823
- * Маппинг элементов массива
1824
- * @param callback - Функция преобразования с типизированным GroupNode proxy
1825
- * @returns Новый массив результатов
1826
- */
1827
- map(e) {
1828
- return this.items.value.map((t, s) => {
1829
- const r = t.getProxy();
1830
- return e(r, s);
281
+ const { disabled: i } = this.availability, n = Q({
282
+ getChildren: () => s,
283
+ ownIssues: this.own.list,
284
+ ownPending: d(() => !i.value && this.ownPending.active.value),
285
+ disabled: i
1831
286
  });
287
+ this.valid = n.valid, this.invalid = n.invalid, this.pending = n.pending, this.touched = n.touched, this.dirty = n.dirty, this.issues = n.issues, this.ownIssues = this.own.list, this.disabled = this.availability.disabled;
1832
288
  }
1833
- // ============================================================================
1834
- // Private методы
1835
- // ============================================================================
1836
- /**
1837
- * Создать новый элемент массива на основе схемы
1838
- * @param initialValue - Начальные значения
1839
- */
1840
- createItem(e) {
1841
- if (this.isGroupSchema(this.itemSchema)) {
1842
- const t = new S(this.itemSchema);
1843
- return e && t.patchValue(e), t;
1844
- }
1845
- throw new Error(
1846
- "ArrayNode поддерживает только GroupNode элементы. Для массива примитивов используйте обычное поле с типом массива."
1847
- );
289
+ /** Лицо группы: прокси с нодами полей. Строится один раз. */
290
+ get proxy() {
291
+ return this.face ??= De(this, this.children), this.face;
1848
292
  }
1849
- /**
1850
- * Проверить, является ли схема групповой (объект полей)
1851
- * @param schema - Схема для проверки
1852
- */
1853
- isGroupSchema(e) {
1854
- return typeof e == "object" && e !== null && !("component" in e) && !Array.isArray(e);
293
+ getValue() {
294
+ const e = {};
295
+ return this.children.forEach((s, i) => {
296
+ e[i] = s.getValue();
297
+ }), e;
1855
298
  }
1856
- // ============================================================================
1857
- // Методы-помощники для реактивности (Фаза 1)
1858
- // ============================================================================
1859
- /**
1860
- * Очистить все ресурсы узла
1861
- * Рекурсивно очищает все subscriptions и элементы массива
1862
- *
1863
- * @example
1864
- * ```typescript
1865
- * useEffect(() => {
1866
- * return () => {
1867
- * arrayNode.dispose();
1868
- * };
1869
- * }, []);
1870
- * ```
1871
- */
1872
- dispose() {
1873
- this.disposers.dispose(), this.items.value.forEach((e) => {
1874
- "dispose" in e && typeof e.dispose == "function" && e.dispose();
1875
- });
299
+ /** Записать значения полей; цели `compute` пропускаются — ими владеет поведение. */
300
+ setValue(e) {
301
+ if (!(e === null || typeof e != "object"))
302
+ for (const [s, i] of Object.entries(e)) {
303
+ const n = this.children.get(s);
304
+ if (!n) continue;
305
+ const a = this.leafSignals.get(n) ?? n.value;
306
+ T(a) || n.setValue(i);
307
+ }
1876
308
  }
1877
309
  /**
1878
- * Hook: вызывается после disable()
1879
- *
1880
- * Для ArrayNode: рекурсивно отключаем все элементы массива
1881
- *
1882
- * @example
1883
- * ```typescript
1884
- * // Отключить весь массив полей
1885
- * form.items.disable();
1886
- *
1887
- * // Все элементы становятся disabled
1888
- * form.items.forEach(item => {
1889
- * console.log(item.status.value); // 'disabled'
1890
- * });
1891
- * ```
310
+ * Сбросить группу: замечания группы и всех полей, `touched`, `dirty` и значения. Без аргумента —
311
+ * к точке отсчёта модели, той же, к которой возвращает `model.reset()` (значения создания модели
312
+ * либо последнего `captureInitial()`), — и форма после сброса не изменена.
313
+ * Цели `compute` значения не получают, как и в `setValue`: ими владеет поведение.
1892
314
  */
1893
- onDisable() {
1894
- this.items.value.forEach((e) => {
1895
- e.disable();
315
+ reset(e) {
316
+ this.own.clear(), this.children.forEach((s, i) => {
317
+ const n = this.leafSignals.get(s) ?? s.value, a = T(n) ? void 0 : e?.[i];
318
+ s.reset(a);
1896
319
  });
1897
320
  }
1898
- /**
1899
- * Hook: вызывается после enable()
1900
- *
1901
- * Для ArrayNode: рекурсивно включаем все элементы массива
1902
- *
1903
- * @example
1904
- * ```typescript
1905
- * // Включить весь массив полей
1906
- * form.items.enable();
1907
- *
1908
- * // Все элементы становятся enabled
1909
- * form.items.forEach(item => {
1910
- * console.log(item.status.value); // 'valid' или 'invalid'
1911
- * });
1912
- * ```
1913
- */
1914
- onEnable() {
1915
- this.items.value.forEach((e) => {
1916
- e.enable();
1917
- });
321
+ /** Сброс всех полей группы к значениям создания, у целей `compute` тоже. @internal */
322
+ resetToCreation() {
323
+ this.own.clear(), this.children.forEach((e) => k(e).resetToCreation());
1918
324
  }
1919
- }
1920
- class ue {
1921
- /**
1922
- * Создает узел формы на основе конфигурации
1923
- *
1924
- * ✅ ОБНОВЛЕНО: Теперь поддерживает массивы напрямую
1925
- *
1926
- * Автоматически определяет тип узла:
1927
- * - FieldNode: имеет value и component
1928
- * - ArrayNode: массив [schema, ...items] или { schema, initialItems }
1929
- * - GroupNode: объект без value, component, schema
1930
- *
1931
- * @param config Конфигурация узла
1932
- * @returns Экземпляр FieldNode, GroupNode или ArrayNode
1933
- * @throws Error если конфиг не соответствует ни одному типу
1934
- *
1935
- * @example
1936
- * ```typescript
1937
- * const factory = new NodeFactory();
1938
- *
1939
- * // FieldNode
1940
- * const field = factory.createNode({
1941
- * value: 'test@mail.com',
1942
- * component: Input,
1943
- * validators: [required, email]
1944
- * });
1945
- *
1946
- * // GroupNode
1947
- * const group = factory.createNode({
1948
- * email: { value: '', component: Input },
1949
- * password: { value: '', component: Input }
1950
- * });
1951
- *
1952
- * // ArrayNode (объект)
1953
- * const array = factory.createNode({
1954
- * schema: { title: { value: '', component: Input } },
1955
- * initialItems: [{ title: 'Item 1' }]
1956
- * });
1957
- *
1958
- * // ArrayNode (массив) - новый формат
1959
- * const array2 = factory.createNode([
1960
- * { title: { value: '', component: Input } }, // schema
1961
- * { title: 'Item 1' }, // initial item 1
1962
- * { title: 'Item 2' } // initial item 2
1963
- * ]);
1964
- * ```
1965
- */
1966
- createNode(e) {
1967
- if (Array.isArray(e) && e.length >= 1)
1968
- return this.createArrayNodeFromArray(e);
1969
- if (this.isFieldConfig(e))
1970
- return new ne(e);
1971
- if (this.isArrayConfig(e))
1972
- return new B(
1973
- e.schema,
1974
- e.initialItems
1975
- );
1976
- if (this.isGroupConfig(e))
1977
- return new S(e);
1978
- throw new Error(
1979
- `NodeFactory: Unknown node config. Expected FieldConfig, GroupConfig, or ArrayConfig, but got: ${JSON.stringify(
1980
- e
1981
- )}`
1982
- );
325
+ markAsTouched() {
326
+ this.children.forEach((e) => e.markAsTouched());
1983
327
  }
1984
- /**
1985
- * Создать ArrayNode из массива [schema, ...initialItems]
1986
- *
1987
- * ✅ НОВОЕ: Извлечено из GroupNode для централизации логики
1988
- *
1989
- * Формат: [itemSchema, ...initialItems]
1990
- * - Первый элемент - схема элемента массива
1991
- * - Остальные элементы - начальные значения
1992
- *
1993
- * @param config Массив с схемой и начальными элементами
1994
- * @returns ArrayNode
1995
- *
1996
- * @example
1997
- * ```typescript
1998
- * const factory = new NodeFactory();
1999
- *
2000
- * // Массив с начальными элементами
2001
- * const array = factory.createArrayNodeFromArray([
2002
- * { title: { value: '', component: Input } }, // schema
2003
- * { title: 'Item 1' }, // initial value
2004
- * { title: 'Item 2' } // initial value
2005
- * ]);
2006
- * ```
2007
- * @private
2008
- */
2009
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2010
- createArrayNodeFromArray(e) {
2011
- const [t, ...s] = e, r = [];
2012
- this.isGroupConfig(t) && r.push(this.extractValues(t));
2013
- for (const n of s)
2014
- this.isGroupConfig(n) ? r.push(this.extractValues(n)) : r.push(n);
2015
- return new B(t, r);
328
+ markAsUntouched() {
329
+ this.children.forEach((e) => e.markAsUntouched());
2016
330
  }
2017
- /**
2018
- * Извлечь значения из схемы (рекурсивно)
2019
- *
2020
- * ✅ НОВОЕ: Извлечено из GroupNode для централизации логики
2021
- *
2022
- * Преобразует схему формы в объект со значениями:
2023
- * - `{ name: { value: 'John', component: Input } } → { name: 'John' }`
2024
- * - Поддерживает вложенные группы
2025
- * - Поддерживает массивы
2026
- *
2027
- * @param schema Схема формы
2028
- * @returns Объект со значениями полей
2029
- *
2030
- * @example
2031
- * ```typescript
2032
- * const factory = new NodeFactory();
2033
- *
2034
- * const schema = {
2035
- * name: { value: 'John', component: Input },
2036
- * age: { value: 30, component: Input },
2037
- * address: {
2038
- * city: { value: 'Moscow', component: Input }
2039
- * }
2040
- * };
2041
- *
2042
- * factory.extractValues(schema);
2043
- * // { name: 'John', age: 30, address: { city: 'Moscow' } }
2044
- * ```
2045
- */
2046
- extractValues(e) {
2047
- if (this.isFieldConfig(e))
2048
- return e.value;
2049
- if (Array.isArray(e))
2050
- return e.map((t) => this.extractValues(t));
2051
- if (this.isGroupConfig(e)) {
2052
- const t = {};
2053
- for (const [s, r] of Object.entries(e))
2054
- t[s] = this.extractValues(r);
2055
- return t;
2056
- }
2057
- return e;
331
+ disable() {
332
+ this.availability.setDisabled(!0), this.own.clear(), this.children.forEach((e) => e.disable());
2058
333
  }
2059
- /**
2060
- * Проверяет, является ли конфиг конфигурацией поля (FieldConfig)
2061
- *
2062
- * FieldConfig имеет обязательные свойства:
2063
- * - value: начальное значение поля
2064
- * - component: React-компонент для отображения
2065
- *
2066
- * @param config Проверяемая конфигурация
2067
- * @returns true если config является FieldConfig
2068
- *
2069
- * @example
2070
- * ```typescript
2071
- * const factory = new NodeFactory();
2072
- *
2073
- * factory.isFieldConfig({ value: '', component: Input }); // true
2074
- * factory.isFieldConfig({ email: { value: '' } }); // false
2075
- * factory.isFieldConfig(null); // false
2076
- * ```
2077
- */
2078
- isFieldConfig(e) {
2079
- return e == null || typeof e != "object" ? !1 : "valueSignal" in e ? !0 : "value" in e ? "component" in e || "componentProps" in e || "validators" in e || "asyncValidators" in e || "updateOn" in e || "debounce" in e : !1;
334
+ /** Включить группу и детей; поля с блокировкой из схемы (`disabled: true`) остаются выключенными. */
335
+ enable() {
336
+ this.availability.setDisabled(!1), this.children.forEach((e) => k(e).enableFromParent());
2080
337
  }
2081
- /**
2082
- * Проверяет, является ли конфиг конфигурацией массива (ArrayConfig)
2083
- *
2084
- * ArrayConfig имеет обязательное свойство:
2085
- * - schema: схема для элементов массива
2086
- *
2087
- * И НЕ имеет:
2088
- * - value (отличие от FieldConfig)
2089
- *
2090
- * @param config Проверяемая конфигурация
2091
- * @returns true если config является ArrayConfig
2092
- *
2093
- * @example
2094
- * ```typescript
2095
- * const factory = new NodeFactory();
2096
- *
2097
- * factory.isArrayConfig({ schema: {}, initialItems: [] }); // true
2098
- * factory.isArrayConfig({ value: '', component: Input }); // false
2099
- * factory.isArrayConfig({ email: { value: '' } }); // false
2100
- * ```
2101
- */
2102
- isArrayConfig(e) {
2103
- return e != null && typeof e == "object" && "schema" in e && !("value" in e);
338
+ /** Каскад от контейнера — то же, что `enable()`: своей блокировки из схемы у группы нет. @internal */
339
+ enableFromParent() {
340
+ this.enable();
2104
341
  }
2105
- /**
2106
- * Проверяет, является ли конфиг конфигурацией группы (GroupConfig)
2107
- *
2108
- * GroupConfig - это объект, который:
2109
- * - НЕ является FieldConfig (нет value/component)
2110
- * - НЕ является ArrayConfig (нет schema)
2111
- * - Содержит вложенные конфиги полей/групп/массивов
2112
- *
2113
- * @param config Проверяемая конфигурация
2114
- * @returns true если config является GroupConfig
2115
- *
2116
- * @example
2117
- * ```typescript
2118
- * const factory = new NodeFactory();
2119
- *
2120
- * factory.isGroupConfig({
2121
- * email: { value: '', component: Input },
2122
- * password: { value: '', component: Input }
2123
- * }); // true
2124
- *
2125
- * factory.isGroupConfig({ value: '', component: Input }); // false
2126
- * factory.isGroupConfig({ schema: {} }); // false
2127
- * factory.isGroupConfig(null); // false
2128
- * ```
2129
- */
2130
- isGroupConfig(e) {
2131
- return e != null && typeof e == "object" && !this.isFieldConfig(e) && !this.isArrayConfig(e);
342
+ setIssues(e) {
343
+ this.own.set(e);
344
+ }
345
+ clearIssues() {
346
+ this.own.clear(), this.children.forEach((e) => e.clearIssues());
347
+ }
348
+ /** Своё ожидание async-правила на ручке группы; ожидание детей агрегат добавит сам. @internal */
349
+ setPending(e) {
350
+ this.ownPending.mark(e);
351
+ }
352
+ /** Прикрепить cleanup поведения формы (`createForm({ behavior })`). @internal */
353
+ attachBehaviorCleanup(e) {
354
+ this.behaviorCleanup = e;
355
+ }
356
+ /** Связать лист с его сигналом модели для `setValue`. @internal */
357
+ registerLeafSignal(e, s) {
358
+ this.leafSignals.set(e, s);
359
+ }
360
+ /** Снять поведение и подписки всех детей. @internal */
361
+ dispose() {
362
+ this.behaviorCleanup?.(), this.behaviorCleanup = void 0, this.children.forEach((e) => e.dispose());
2132
363
  }
2133
364
  }
2134
- class de extends M {
2135
- /**
2136
- * @param control Реактивный массив модели (`model.<path>`).
2137
- * @param buildItem Строитель формы элемента по его под-модели (инъекция, чтобы избежать цикла
2138
- * импорта с `create-form`). Обычно `(item) => createForm({ model: item, schema: itemSchema(item) })`.
2139
- */
2140
- constructor(e, t) {
2141
- super(), this.control = e, this.buildItem = t, this.initial = e.toArray(), this.disposeSync = _(() => {
2142
- const r = e.length, n = [], a = [];
2143
- for (let u = 0; u < r; u++) {
2144
- const v = e.at(u);
2145
- if (!v) continue;
2146
- let o = this.cache.get(v);
2147
- o || (o = t(v), this.cache.set(v, o)), n.push(v), a.push(o);
365
+ const z = /* @__PURE__ */ new WeakMap();
366
+ let je = 0;
367
+ function Z(t) {
368
+ let e = z.get(t);
369
+ return e === void 0 && (e = je++, z.set(t, e)), e;
370
+ }
371
+ class Re {
372
+ /**
373
+ * @param control - Массив модели (`model.<путь>`).
374
+ * @param buildRow - Форма строки по её под-модели; инъекция снимает цикл импорта со сборкой.
375
+ */
376
+ constructor(e, s) {
377
+ this.control = e, this.initial = e.toArray(), this.stopSync = ge(() => {
378
+ const a = e.length, r = [], o = [];
379
+ for (let h = 0; h < a; h++) {
380
+ const u = e.at(h);
381
+ if (!u) continue;
382
+ let f = this.cache.get(u);
383
+ f || (f = A(() => {
384
+ const p = s(u);
385
+ return this.availability.disabled.peek() && p.disable(), p;
386
+ }), this.cache.set(u, f)), r.push(u), o.push(f);
2148
387
  }
2149
- const l = new Set(n), d = [];
2150
- for (const u of this.mountedModels) {
2151
- if (l.has(u)) continue;
2152
- const v = this.cache.get(u);
2153
- this.cache.delete(u), v && d.push(v);
388
+ const c = new Set(r), l = [];
389
+ for (const h of this.mounted) {
390
+ if (c.has(h)) continue;
391
+ const u = this.cache.get(h);
392
+ this.cache.delete(h), u && l.push(u);
2154
393
  }
2155
- this.mountedModels = n, this.itemNodes.value = a, d.forEach((u) => this.disposeItemNode(u));
2156
- }), this.length = f(() => this.itemNodes.value.length), this.value = f(() => this.itemNodes.value.map((r) => r.value.value));
2157
- const s = L({
2158
- getChildren: () => this.itemNodes.value,
2159
- ownErrors: this._arrayErrors
394
+ this.mounted = r, this.rows.value = o, A(() => l.forEach((h) => k(h).dispose()));
395
+ }), this.value = d(() => this.rows.value.map((a) => a.value.value));
396
+ const { disabled: i } = this.availability, n = Q({
397
+ getChildren: () => this.rows.value,
398
+ ownIssues: this.own.list,
399
+ ownPending: d(() => !i.value && this.ownPending.active.value),
400
+ disabled: i
2160
401
  });
2161
- this.valid = s.valid, this.invalid = s.invalid, this.pending = s.pending, this.touched = s.touched, this.dirty = s.dirty, this.errors = s.errors, this.status = s.status;
2162
- }
2163
- itemNodes = g([]);
2164
- // Кэш per-item формы по идентичности фасада под-модели (фасады кэшируются в core).
2165
- cache = /* @__PURE__ */ new WeakMap();
2166
- // Фасады под-моделей, для которых сейчас смонтированы формы — «предыдущий» набор для
2167
- // реконсиляции в disposeSync. Выпавшие из массива элементы диспозятся и вытесняются из кэша.
2168
- mountedModels = [];
2169
- initial;
2170
- _arrayErrors = g([]);
2171
- disposeSync;
2172
- /**
2173
- * Сигнал per-item форм. Публичен в т.ч. чтобы `isArrayNode` распознавал узел как массив
2174
- * (duck-typing проверяет наличие `items`).
2175
- */
2176
- get items() {
2177
- return this.itemNodes;
402
+ this.valid = n.valid, this.invalid = n.invalid, this.pending = n.pending, this.touched = n.touched, this.dirty = d(() => this.reshaped.value || n.dirty.value), this.issues = n.issues, this.ownIssues = this.own.list, this.disabled = this.availability.disabled;
2178
403
  }
404
+ [j] = "array";
2179
405
  value;
2180
406
  valid;
2181
407
  invalid;
2182
408
  pending;
409
+ disabled;
2183
410
  touched;
2184
411
  dirty;
2185
- errors;
2186
- status;
2187
- length;
2188
- // ── Доступ к элементам ─────────────────────────────────────────────────────
412
+ issues;
413
+ ownIssues;
414
+ rows = w([]);
415
+ /**
416
+ * Состав массива меняли методами ноды после создания или сброса. Как `dirty` поля: ставит запись
417
+ * через форму (`push`, `removeAt`, `move`, `setValue`…), а не запись в модель — загрузка данных
418
+ * массив изменённым не делает. Операция, которая состав не поменяла (`clear` пустого массива,
419
+ * `move(i, i)`), — тоже: поведение «очистить список при снятом флаге» после сброса формы её не
420
+ * испачкает. Снимает `reset`.
421
+ */
422
+ reshaped = w(!1);
423
+ /** Форма строки по фасаду под-модели элемента. */
424
+ cache = /* @__PURE__ */ new WeakMap();
425
+ /** Под-модели строк, для которых сейчас есть формы, — «прежний» набор для сверки. */
426
+ mounted = [];
427
+ initial;
428
+ own = R();
429
+ availability = K();
430
+ /** Ожидание async-правила на ручке самого массива. */
431
+ ownPending = W();
432
+ stopSync;
433
+ get length() {
434
+ return this.rows.value.length;
435
+ }
2189
436
  at(e) {
2190
- return this.itemNodes.value[e];
437
+ return this.rows.value[e];
2191
438
  }
2192
439
  map(e) {
2193
- return this.itemNodes.value.map((t, s) => e(t, s));
440
+ return this.rows.value.map(e);
2194
441
  }
2195
442
  forEach(e) {
2196
- this.itemNodes.value.forEach((t, s) => e(t, s));
443
+ this.rows.value.forEach(e);
444
+ }
445
+ keyAt(e) {
446
+ const s = e < this.rows.value.length ? this.mounted[e] : void 0;
447
+ if (s === void 0)
448
+ throw new RangeError(`[@reformer/core] FormArray.keyAt: no row at index ${e}`);
449
+ return String(Z(s));
2197
450
  }
2198
- // ── Мутации (делегируют массиву модели; effect пересоберёт itemNodes) ──────
2199
451
  push(e) {
2200
- this.control.push(e ?? {});
452
+ this.reshape(() => this.control.push(e));
2201
453
  }
2202
- insert(e, t) {
2203
- this.control.insertAt(e, t ?? {});
454
+ insert(e, s) {
455
+ this.reshape(() => this.control.insertAt(this.modelIndex(e), s));
2204
456
  }
2205
457
  removeAt(e) {
2206
- this.control.removeAt(e);
458
+ this.reshape(() => this.control.removeAt(this.modelIndex(e)));
2207
459
  }
2208
- /**
2209
- * Переместить элемент модель-массива. Делегирует мутацию массиву модели; внутренний `effect`
2210
- * пересоберёт `itemNodes` в новом порядке (per-item формы сохраняются по кешу идентичности).
2211
- */
2212
- move(e, t) {
2213
- this.control.move(e, t);
460
+ move(e, s) {
461
+ this.reshape(() => this.control.move(this.modelIndex(e), this.modelIndex(s)));
2214
462
  }
2215
- /** Поменять местами два элемента модель-массива (см. {@link move}). */
2216
- swap(e, t) {
2217
- this.control.swap(e, t);
463
+ swap(e, s) {
464
+ this.reshape(() => this.control.swap(this.modelIndex(e), this.modelIndex(s)));
2218
465
  }
2219
466
  clear() {
2220
- this.control.clear();
467
+ this.reshape(() => this.control.clear());
2221
468
  }
2222
- // ── Абстрактные методы FormNode ────────────────────────────────────────────
2223
469
  getValue() {
2224
470
  return this.control.toArray();
2225
471
  }
2226
472
  setValue(e) {
2227
- this.control.clear(), (e ?? []).forEach((t) => this.control.push(t));
2228
- }
2229
- patchValue(e) {
2230
- this.setValue(e);
473
+ this.reshape(() => {
474
+ this.control.clear(), (e ?? []).forEach((s) => this.control.push(s));
475
+ });
2231
476
  }
477
+ /**
478
+ * Сбросить массив: строки, замечания и `dirty`. Без аргумента — к точке отсчёта модели, той же, к
479
+ * которой возвращает `model.reset()`: элементам создания модели либо последнего
480
+ * `captureInitial()`.
481
+ */
2232
482
  reset(e) {
2233
- this._arrayErrors.value = [], this.setValue(e ?? this.initial);
483
+ this.own.clear(), this.setValue(e ?? this.referenceItems()), this.reshaped.value = !1;
2234
484
  }
2235
- resetToInitial() {
2236
- this.reset(this.initial);
485
+ /** Строки пересобираются из значений создания — их поведение считает заново. @internal */
486
+ resetToCreation() {
487
+ this.own.clear(), this.setValue(this.initial), this.reshaped.value = !1;
2237
488
  }
2238
- async validate() {
2239
- return (await Promise.all(
2240
- this.itemNodes.value.map(
2241
- (t) => t.validate()
2242
- )
2243
- )).every(Boolean) && this._arrayErrors.value.length === 0;
489
+ /** Точка отсчёта модели за массивом; без модели — элементы создания. */
490
+ referenceItems() {
491
+ const e = H(this.control);
492
+ return e === void 0 ? this.initial : e.value;
2244
493
  }
2245
- setErrors(e) {
2246
- this._arrayErrors.value = e;
494
+ markAsTouched() {
495
+ this.rows.value.forEach((e) => e.markAsTouched());
2247
496
  }
2248
- clearErrors() {
2249
- this._arrayErrors.value = [], this.itemNodes.value.forEach(
2250
- (e) => e.clearErrors?.()
2251
- );
497
+ markAsUntouched() {
498
+ this.rows.value.forEach((e) => e.markAsUntouched());
2252
499
  }
2253
- // ── Hooks (forward к элементам) ────────────────────────────────────────────
2254
- // Агрегатное состояние (touched/dirty/status) выводится из детей (createAggregateSignals), поэтому
2255
- // базовые сигналы бесполезны для чтения — состояние меняется ТОЛЬКО через per-item формы. Форвардим
2256
- // ВСЕ hook'и (как ArrayNode), иначе markAsUntouched/Pristine/Dirty/disable/enable молча no-op.
2257
- onMarkAsTouched() {
2258
- this.itemNodes.value.forEach(
2259
- (e) => e.markAsTouched?.()
2260
- );
500
+ disable() {
501
+ this.availability.setDisabled(!0), this.own.clear(), this.rows.value.forEach((e) => e.disable());
2261
502
  }
2262
- onMarkAsUntouched() {
2263
- this.itemNodes.value.forEach(
2264
- (e) => e.markAsUntouched?.()
2265
- );
503
+ /** Включить массив и строки; поля с блокировкой из схемы (`disabled: true`) остаются выключенными. */
504
+ enable() {
505
+ this.availability.setDisabled(!1), this.rows.value.forEach((e) => k(e).enableFromParent());
2266
506
  }
2267
- onMarkAsDirty() {
2268
- this.itemNodes.value.forEach(
2269
- (e) => e.markAsDirty?.()
2270
- );
507
+ /** Каскад от контейнера — то же, что `enable()`: своей блокировки из схемы у массива нет. @internal */
508
+ enableFromParent() {
509
+ this.enable();
2271
510
  }
2272
- onMarkAsPristine() {
2273
- this.itemNodes.value.forEach(
2274
- (e) => e.markAsPristine?.()
2275
- );
511
+ setIssues(e) {
512
+ this.own.set(e);
2276
513
  }
2277
- onDisable() {
2278
- this.itemNodes.value.forEach((e) => e.disable?.());
514
+ clearIssues() {
515
+ this.own.clear(), this.rows.value.forEach((e) => e.clearIssues());
2279
516
  }
2280
- onEnable() {
2281
- this.itemNodes.value.forEach((e) => e.enable?.());
517
+ /** Своё ожидание async-правила на ручке массива; ожидание строк агрегат добавит сам. @internal */
518
+ setPending(e) {
519
+ this.ownPending.mark(e);
2282
520
  }
2283
- /** Диспозит форму элемента (эффекты/поведения/вложенные подписки); терпима к не-GroupNode. */
2284
- disposeItemNode(e) {
2285
- e.dispose?.();
521
+ /** Выполнить мутацию массива модели и отметить `dirty`, если состав строк изменился. */
522
+ reshape(e) {
523
+ const s = this.composition();
524
+ e();
525
+ const i = this.composition();
526
+ (i.length !== s.length || i.some((n, a) => n !== s[a])) && (this.reshaped.value = !0);
2286
527
  }
2287
528
  /**
2288
- * Очистка подписки синхронизации И детерминированный teardown всех форм элементов.
2289
- * Без пробрасывания dispose в itemNodes эффекты/поведения каждой формы элемента (в т.ч. disposeSync
2290
- * вложенных ModelArrayNode) остались бы жить после teardown формы — утечка подписок.
529
+ * Индекс модели для индекса строки формы. Без `null`-элементов они совпадают; с ними строка `i` —
530
+ * `i`-й непустой элемент, а индекс за последней строкой — конец массива модели. Читает модель, а не
531
+ * `mounted`: внутри `batch` синхронизация ещё не прошла.
2291
532
  */
533
+ modelIndex(e) {
534
+ return A(() => {
535
+ const s = [];
536
+ for (let i = 0; i < this.control.length; i++)
537
+ this.control.at(i) && s.push(i);
538
+ return s.length === this.control.length ? e : e >= 0 && e < s.length ? s[e] : e === s.length ? this.control.length : e;
539
+ });
540
+ }
541
+ /** Под-модели строк по порядку — без подписки: мутацию могут вызвать из effect'а поведения. */
542
+ composition() {
543
+ return A(
544
+ () => Array.from({ length: this.control.length }, (e, s) => this.control.at(s))
545
+ );
546
+ }
547
+ /** Остановить синхронизацию и диспозить формы всех строк. @internal */
2292
548
  dispose() {
2293
- this.disposeSync(), this.itemNodes.value.forEach((e) => this.disposeItemNode(e)), this.mountedModels = [];
549
+ this.stopSync(), this.rows.peek().forEach((e) => k(e).dispose()), this.mounted = [];
2294
550
  }
2295
551
  }
2296
- const z = (i) => i !== null && typeof i == "object" && !Array.isArray(i) && !(i instanceof Date) && !(typeof Blob < "u" && i instanceof Blob) && !(typeof File < "u" && i instanceof File);
2297
- function D(i, e, t) {
2298
- if (i == null || typeof i != "object" || i instanceof G) return;
2299
- if (Array.isArray(i)) {
2300
- for (const n of i) D(n, e, t);
2301
- return;
2302
- }
2303
- const s = i, r = s.array;
2304
- if (r && typeof r.__path == "string" && typeof s.item == "function") {
2305
- t.set(r.__path, s.item);
2306
- return;
2307
- }
2308
- s.value instanceof G ? e.set(s.value, {
2309
- component: s.component,
2310
- componentProps: s.componentProps,
2311
- updateOn: s.updateOn,
2312
- disabled: s.disabled,
2313
- debounce: s.debounce
2314
- }) : process.env.NODE_ENV !== "production" && s.component !== void 0 && ("valueSignal" in s || s.value != null) && console.warn(
2315
- "[reformer] createForm({ schema }): узел с `component` не распознан как поле — `value` не является сигналом модели. Ожидается `value: model.$.<path>`; проверьте, что не передан `value: model.<path>` (value-прокси) или `valueSignal:`."
2316
- );
2317
- for (const [n, a] of Object.entries(s))
2318
- n !== "value" && D(a, e, t);
552
+ const q = /* @__PURE__ */ new WeakMap();
553
+ function ee(t, e) {
554
+ let s = q.get(e);
555
+ return s || (s = /* @__PURE__ */ new Map(), q.set(e, s)), s.has(t) || s.set(t, t(e)), s.get(t);
2319
556
  }
2320
- function J(i, e, t, s) {
2321
- const r = {};
2322
- for (const [n, a] of Object.entries(e)) {
2323
- const l = t === "" ? n : `${t}.${n}`;
2324
- if (Array.isArray(a))
2325
- continue;
2326
- if (z(a)) {
2327
- r[n] = J(i, a, l, s);
2328
- continue;
557
+ const b = "[@reformer/core] compileSchema", Ke = /* @__PURE__ */ new Set([
558
+ "model",
559
+ "item",
560
+ "part",
561
+ "children",
562
+ "component",
563
+ "componentProps",
564
+ "selector",
565
+ "disabled",
566
+ "initialValue"
567
+ ]), x = {
568
+ value: "привязка поля — `model: model.$.<поле>`",
569
+ array: "привязка массива под-форм — `model: model.$.<массив>`"
570
+ }, We = Object.freeze({}), te = /* @__PURE__ */ new WeakSet();
571
+ function lt(t) {
572
+ return t !== null && typeof t == "object" && te.has(t);
573
+ }
574
+ function se(t) {
575
+ return de(t)?.path;
576
+ }
577
+ function ie(t, e, s) {
578
+ const i = se(t);
579
+ if (i === void 0) return s;
580
+ const n = e.basePath;
581
+ return n === void 0 || n === "" ? i : i.startsWith(`${n}.`) ? i.slice(n.length + 1) : i;
582
+ }
583
+ function Be(t) {
584
+ return typeof t == "string" || typeof t == "number" || J(t);
585
+ }
586
+ function Ue(t) {
587
+ return t == null || typeof t == "boolean";
588
+ }
589
+ function g(t) {
590
+ process.env.NODE_ENV !== "production" && typeof console < "u" && console.warn(t);
591
+ }
592
+ function F(t, e, s) {
593
+ const i = typeof t.selector == "string" && t.selector !== "" ? t.selector : void 0;
594
+ if (i !== void 0) {
595
+ const n = s.selectorPaths.get(i);
596
+ if (n !== void 0)
597
+ throw new Error(
598
+ `${b}: в области «${s.scope}» селектор «${i}» повторяется — узлы ${n} и ${e}. По \`selector\` узел адресуют поведение и шаги, поэтому в области он уникален. Дайте узлам разные селекторы; в строках массива и подформах селекторы живут в своих областях и с корнем не пересекаются.`
599
+ );
600
+ s.selectorPaths.set(i, e);
601
+ }
602
+ return {
603
+ id: i ?? e,
604
+ path: e,
605
+ selector: i,
606
+ scope: s.scope,
607
+ component: t.component,
608
+ props: t.componentProps ?? We,
609
+ source: t
610
+ };
611
+ }
612
+ function I(t, e) {
613
+ const s = e.nodes.get(t.id);
614
+ if (s !== void 0)
615
+ throw new Error(
616
+ `${b}: в области «${e.scope}» идентификатор «${t.id}» занят — узлы ${s.path} и ${t.path}. Селектор не может совпадать с позиционным путём узла (\`#/…\`): дайте узлу другой селектор.`
617
+ );
618
+ return te.add(t), e.nodes.set(t.id, t), t;
619
+ }
620
+ function oe(t, e, s) {
621
+ const i = t.get(e);
622
+ i !== void 0 && g(
623
+ `${b}: ручка «${s.handle.__path}» привязана к двум полям — узлы ${i.path} и ${s.path}. Нода формы у ручки одна и возьмёт конфиг последнего узла (component, componentProps, disabled). Значение второй раз показывают текстом — сигналом ручки в \`children\`.`
624
+ ), t.set(e, s);
625
+ }
626
+ function V(t, e, s) {
627
+ const i = F(t, e, s), n = [], a = t.children;
628
+ if (Array.isArray(a)) {
629
+ let o = null;
630
+ for (let c = 0; c < a.length; c++) {
631
+ const l = a[c], h = `${e}/${c}`;
632
+ if (!Ue(l)) {
633
+ if (Be(l)) {
634
+ o === null && (o = { id: h, parts: [] }, n.push({ kind: "text", id: o.id, parts: o.parts })), o.parts.push(l);
635
+ continue;
636
+ }
637
+ if (o = null, typeof l != "object" || X(l) || ve(l)) {
638
+ g(
639
+ `${b}: ребёнок ${h} — не узел и не текст (группа модели, под-модель либо значение другого типа); он пропущен. Текст — строка, число или сигнал; группу подключают узлом \`{ model: model.$.<группа>, part }\`.`
640
+ );
641
+ continue;
642
+ }
643
+ n.push(ne(l, h, s));
644
+ }
2329
645
  }
2330
- const d = i.signalAt(l);
2331
- if (!d) throw new Error(`createForm({ model }): не найден сигнал для пути "${l}"`);
2332
- const u = s.get(d) ?? {};
2333
- r[n] = { ...u, valueSignal: d };
2334
646
  }
2335
- return r;
647
+ const r = Object.keys(t).every((o) => o === "selector" || o === "componentProps");
648
+ return i.component === void 0 && a === void 0 && r && g(
649
+ `${b}: узел ${i.id} — без \`component\` и без \`children\`: он ничего не рисует, и \`componentProps\` ему некому отдать. Пустую секцию объявляют с \`children: []\`, узел с разметкой — с \`component\`.`
650
+ ), I({ ...i, kind: "container", children: n }, s);
2336
651
  }
2337
- function Y(i, e, t) {
2338
- for (const [s, r] of Object.entries(i)) {
2339
- const n = e === "" ? s : `${e}.${s}`;
2340
- Array.isArray(r) || (z(r) ? Y(r, n, t) : t.push(n));
652
+ function ne(t, e, s) {
653
+ if (s.ancestors.has(t))
654
+ throw new Error(`${b}: узел ${e} содержит сам себя — дерево схемы зациклено.`);
655
+ s.ancestors.add(t);
656
+ try {
657
+ return Le(t, e, s);
658
+ } finally {
659
+ s.ancestors.delete(t);
2341
660
  }
2342
661
  }
2343
- function Q(i) {
2344
- const { model: e, schema: t, behavior: s } = i, r = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
2345
- t !== void 0 && D(t, r, n);
2346
- const a = J(e, e.get(), "", r), l = new S(a), d = e.get();
2347
- for (const [o, m] of Object.entries(d)) {
2348
- if (!Array.isArray(m)) continue;
2349
- const c = n.get(o);
2350
- if (!c) continue;
2351
- const h = e[o], p = (y) => Q({ model: y, schema: c(y) }), E = new de(h, p);
2352
- l.fields.set(o, E);
2353
- }
2354
- const u = l.getProxy(), v = [];
2355
- Y(e.get(), "", v);
2356
- for (const o of v) {
2357
- const m = e.signalAt(o), c = l.getFieldByPath(o);
2358
- if (m && c) {
2359
- Z(m, c);
2360
- const h = o.lastIndexOf("."), p = h === -1 ? l : l.getFieldByPath(o.slice(0, h));
2361
- p instanceof S && p.registerFieldSignal(c, m);
2362
- }
662
+ function Le(t, e, s) {
663
+ if (process.env.NODE_ENV !== "production")
664
+ for (const i of Object.keys(t)) Ke.has(i) || s.unknownKeys.add(i);
665
+ if (typeof t.item == "function") {
666
+ const i = he(t.model);
667
+ return i ? ze(t, i, e, s) : (g(
668
+ `${b}: у узла с \`item\` привязка — не массив модели (узел ${e}); он стал контейнером без строк. Ожидается \`model: model.$.<массив>\`; начальное значение поля должно быть массивом, а не \`null\`.`
669
+ ), V(t, e, s));
670
+ }
671
+ if (typeof t.part == "function") {
672
+ const i = fe(t.model);
673
+ return i ? qe(t, i, e, s) : (g(
674
+ `${b}: у узла с \`part\` привязка — не группа модели (узел ${e}); он стал контейнером. Ожидается \`model: model.$.<группа>\`.`
675
+ ), V(t, e, s));
676
+ }
677
+ if (J(t.model)) {
678
+ const i = t.model, n = I(
679
+ { ...F(t, e, s), kind: "field", handle: i, disabled: t.disabled === !0 },
680
+ s
681
+ );
682
+ return oe(s.fields, i, n), n;
2363
683
  }
2364
- return s && l.attachBehaviorCleanup(s.__run(e, u)), u;
684
+ return t.model != null && g(
685
+ `${b}: привязка узла не распознана (узел ${e}) — он не стал полем. Ожидается \`model: model.$.<path>\` (лист или массив); проверьте, что не передан \`model: model.<path>\` (value-фасад) или группа без \`part\`.`
686
+ ), V(t, e, s);
2365
687
  }
2366
- const ce = (i) => i != null && typeof i == "object" && "model" in i && typeof i.model?.signalAt == "function";
2367
- function Ne(i) {
2368
- return ce(i) ? Q(i) : new S(i).getProxy();
688
+ function ze(t, e, s, i) {
689
+ const n = t.item, a = ie(e, i, s), r = `${i.scope}/${a}`, o = /* @__PURE__ */ new WeakMap(), c = /* @__PURE__ */ new Set(), l = I(
690
+ {
691
+ ...F(t, s, i),
692
+ kind: "array",
693
+ handle: e,
694
+ control: _(e),
695
+ initialValue: t.initialValue,
696
+ row(h) {
697
+ let u = o.get(h);
698
+ if (!u) {
699
+ const f = /* @__PURE__ */ new Set();
700
+ u = B(
701
+ ee(n, h),
702
+ h,
703
+ `${r}[${Z(h)}]`,
704
+ f
705
+ );
706
+ const p = [...f].filter((y) => !c.has(y));
707
+ if (p.length > 0) {
708
+ for (const y of p) c.add(y);
709
+ re(new Set(p));
710
+ }
711
+ o.set(h, u);
712
+ }
713
+ return u;
714
+ }
715
+ },
716
+ i
717
+ );
718
+ return i.arrays.set(e, l), l;
2369
719
  }
2370
- function x(i) {
2371
- return i == null ? !1 : typeof i == "object" && "value" in i && "setValue" in i && "getValue" in i && "validate" in i;
720
+ function qe(t, e, s, i) {
721
+ const n = F(t, s, i), a = _(e), r = `${i.scope}/${ie(e, i, s)}`, o = B(
722
+ ee(t.part, a),
723
+ a,
724
+ r,
725
+ i.unknownKeys,
726
+ i.ancestors
727
+ );
728
+ for (const [c, l] of o.fields) oe(i.fields, c, l);
729
+ for (const [c, l] of o.arrays) i.arrays.set(c, l);
730
+ return I({ ...n, kind: "part", handle: e, model: a, content: o }, i);
2372
731
  }
2373
- function he(i) {
2374
- return x(i) && "validators" in i && "asyncValidators" in i && // FieldNode имеет markAsTouched метод
2375
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2376
- typeof i.markAsTouched == "function" && // У FieldNode нет fields или items
2377
- !("fields" in i) && !("items" in i);
732
+ function B(t, e, s, i, n = /* @__PURE__ */ new Set()) {
733
+ if (t === null || typeof t != "object")
734
+ throw new Error(`${b}: корень области «${s}» — не узел схемы: ${String(t)}.`);
735
+ const a = e !== void 0 ? e.$ : void 0, r = {
736
+ scope: s,
737
+ basePath: se(a),
738
+ nodes: /* @__PURE__ */ new Map(),
739
+ fields: /* @__PURE__ */ new Map(),
740
+ arrays: /* @__PURE__ */ new Map(),
741
+ selectorPaths: /* @__PURE__ */ new Map(),
742
+ ancestors: n,
743
+ unknownKeys: i
744
+ }, o = ne(t, "#", r);
745
+ return {
746
+ scope: s,
747
+ model: e,
748
+ root: o,
749
+ nodes: r.nodes,
750
+ fields: r.fields,
751
+ arrays: r.arrays
752
+ };
2378
753
  }
2379
- function fe(i) {
2380
- return x(i) && "getFieldByPath" in i && "fields" in i && // GroupNode НЕ имеет items/push/removeAt (это ArrayNode/ModelArrayNode)
2381
- !("items" in i) && !("push" in i) && !("removeAt" in i);
754
+ function xe(t, e, s) {
755
+ const i = /* @__PURE__ */ new Set(), n = B(t, e, s, i);
756
+ return i.size > 0 && re(i), n;
2382
757
  }
2383
- function ve(i) {
2384
- return x(i) && "items" in i && "length" in i && "push" in i && "removeAt" in i && "at" in i && // eslint-disable-next-line @typescript-eslint/no-explicit-any
2385
- typeof i.push == "function" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
2386
- typeof i.removeAt == "function";
758
+ function re(t) {
759
+ const e = [...t], s = e.filter((i) => i in x).map((i) => `\`${i}\`: ${x[i]}`);
760
+ g(
761
+ `${b}: у узлов схемы есть ключи, которые сборка не читает: ${e.map((i) => `\`${i}\``).join(", ")}. Узел описывают \`model\`, \`item\`, \`part\`, \`children\`, \`component\`, \`componentProps\`, \`selector\`, \`disabled\` и \`initialValue\`; вложенные узлы читаются только из \`children\`.` + (s.length > 0 ? ` ${s.join("; ")}.` : "")
762
+ );
2387
763
  }
2388
- function Fe(i) {
2389
- return he(i) ? "FieldNode" : fe(i) ? "GroupNode" : ve(i) ? "ArrayNode" : x(i) ? "FormNode" : "Unknown";
764
+ function ae(t, e, s) {
765
+ return xe(t, e, s?.scope ?? "#");
2390
766
  }
2391
- var F = { exports: {} }, P = {};
2392
- var W;
2393
- function me() {
2394
- if (W) return P;
2395
- W = 1;
2396
- var i = H;
2397
- function e(o, m) {
2398
- return o === m && (o !== 0 || 1 / o === 1 / m) || o !== o && m !== m;
2399
- }
2400
- var t = typeof Object.is == "function" ? Object.is : e, s = i.useState, r = i.useEffect, n = i.useLayoutEffect, a = i.useDebugValue;
2401
- function l(o, m) {
2402
- var c = m(), h = s({ inst: { value: c, getSnapshot: m } }), p = h[0].inst, E = h[1];
2403
- return n(
2404
- function() {
2405
- p.value = c, p.getSnapshot = m, d(p) && E({ inst: p });
2406
- },
2407
- [o, c, m]
2408
- ), r(
2409
- function() {
2410
- return d(p) && E({ inst: p }), o(function() {
2411
- d(p) && E({ inst: p });
2412
- });
2413
- },
2414
- [o]
2415
- ), a(c), c;
2416
- }
2417
- function d(o) {
2418
- var m = o.getSnapshot;
2419
- o = o.value;
2420
- try {
2421
- var c = m();
2422
- return !t(o, c);
2423
- } catch {
2424
- return !0;
767
+ const Ge = { fields: /* @__PURE__ */ new Map(), arrays: /* @__PURE__ */ new Map() }, Ye = (t) => (
768
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
769
+ ((e) => U({ model: e, compiled: t.row(e) }))
770
+ );
771
+ function le(t, e) {
772
+ const s = /* @__PURE__ */ new Map(), i = [], n = (r, o) => {
773
+ const c = e.fields.get(o), l = c ? {
774
+ component: c.component,
775
+ componentProps: c.source.componentProps,
776
+ disabled: c.disabled
777
+ } : {}, h = new Ce({ ...l, valueSignal: o });
778
+ s.set(r, h), O(o, h), i.push([h, o]);
779
+ };
780
+ for (const r of Object.keys(t)) {
781
+ const o = t[r];
782
+ if (me(o)) {
783
+ const c = e.arrays.get(o);
784
+ if (c) {
785
+ const l = _(o), h = new Re(l, Ye(c));
786
+ s.set(r, h), O(o, h);
787
+ } else e.fields.has(o) && n(r, o);
788
+ continue;
2425
789
  }
790
+ if (X(o)) {
791
+ const c = le(o, e);
792
+ s.set(r, c), O(o, c.proxy);
793
+ continue;
794
+ }
795
+ n(r, o);
2426
796
  }
2427
- function u(o, m) {
2428
- return m();
2429
- }
2430
- var v = typeof window > "u" || typeof window.document > "u" || typeof window.document.createElement > "u" ? u : l;
2431
- return P.useSyncExternalStore = i.useSyncExternalStore !== void 0 ? i.useSyncExternalStore : v, P;
797
+ const a = new _e(s);
798
+ for (const [r, o] of i) a.registerLeafSignal(r, o);
799
+ return a;
800
+ }
801
+ function ct(t) {
802
+ const { schema: e, ...s } = t;
803
+ return U({
804
+ ...s,
805
+ ...e !== void 0 ? { compiled: ae(e, t.model) } : {}
806
+ });
2432
807
  }
2433
- var T = {};
2434
- var q;
2435
- function pe() {
2436
- return q || (q = 1, process.env.NODE_ENV !== "production" && (function() {
2437
- function i(c, h) {
2438
- return c === h && (c !== 0 || 1 / c === 1 / h) || c !== c && h !== h;
808
+ function He(t) {
809
+ k(t).dispose();
810
+ }
811
+ function U(t) {
812
+ const { model: e, compiled: s, behavior: i } = t, n = s ?? Ge;
813
+ for (const [o, c] of n.arrays)
814
+ c.initialValue !== void 0 && pe(o, c.initialValue);
815
+ const a = le(
816
+ e.$,
817
+ n
818
+ ), r = a.proxy;
819
+ if (O(e.$, r), i)
820
+ try {
821
+ a.attachBehaviorCleanup(
822
+ i.__run(e, r, t.schemaController, t.flow)
823
+ );
824
+ } catch (o) {
825
+ throw a.dispose(), o;
2439
826
  }
2440
- function e(c, h) {
2441
- v || r.startTransition === void 0 || (v = !0, console.error(
2442
- "You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."
2443
- ));
2444
- var p = h();
2445
- if (!o) {
2446
- var E = h();
2447
- n(p, E) || (console.error(
2448
- "The result of getSnapshot should be cached to avoid an infinite loop"
2449
- ), o = !0);
827
+ return r;
828
+ }
829
+ const C = "[@reformer/core] FormFlow";
830
+ function Je(t) {
831
+ const e = [...t.steps];
832
+ if (e.length === 0) throw new Error(`${C}: у потока нет шагов.`);
833
+ const s = e.filter((u, f) => e.indexOf(u) !== f);
834
+ if (s.length > 0)
835
+ throw new Error(`${C}: шаги повторяются: ${[...new Set(s)].join(", ")}.`);
836
+ const i = w(0), n = w(/* @__PURE__ */ new Set()), a = w(0);
837
+ let r = 0;
838
+ const o = (u) => e.indexOf(u), c = (u) => {
839
+ const f = o(u);
840
+ if (f < 0)
841
+ throw new Error(`${C}: шага «${u}» нет. Шаги потока: ${e.join(", ")}.`);
842
+ return f;
843
+ }, l = (u) => {
844
+ const f = e[i.peek()];
845
+ return r++, D(() => {
846
+ u < i.peek() && (n.value = new Set([...n.peek()].filter((p) => o(p) < u))), i.value = u;
847
+ }), { status: "moved", from: f, to: e[u] };
848
+ }, h = (u) => {
849
+ const f = o(u);
850
+ return f < 0 ? !1 : f === 0 || n.value.has(e[f - 1]);
851
+ };
852
+ return {
853
+ steps: e,
854
+ current: d(() => e[i.value]),
855
+ // Наружу — только чтение: шаг меняют переходы потока.
856
+ index: d(() => i.value),
857
+ completed: d(() => n.value),
858
+ isFirst: d(() => i.value === 0),
859
+ isLast: d(() => i.value === e.length - 1),
860
+ validating: d(() => a.value > 0),
861
+ canGoTo: h,
862
+ async next() {
863
+ const u = i.peek(), f = r, p = e[u];
864
+ let y;
865
+ if (t.validateStep) {
866
+ a.value++;
867
+ try {
868
+ y = await t.validateStep(p);
869
+ } finally {
870
+ a.value--;
871
+ }
2450
872
  }
2451
- E = a({
2452
- inst: { value: p, getSnapshot: h }
873
+ const S = y !== void 0 ? { validation: y } : {};
874
+ return r !== f ? { status: "stayed", step: e[i.peek()], reason: "cancelled", ...S } : y !== void 0 && y.status !== "valid" ? { status: "stayed", step: p, reason: y.status, ...S } : (n.value = /* @__PURE__ */ new Set([...n.peek(), p]), u === e.length - 1 ? { status: "stayed", step: p, reason: "edge", ...S } : (r++, i.value = u + 1, { status: "moved", from: p, to: e[u + 1] }));
875
+ },
876
+ previous() {
877
+ const u = i.peek();
878
+ return u === 0 ? { status: "stayed", step: e[0], reason: "edge" } : l(u - 1);
879
+ },
880
+ goTo(u) {
881
+ const f = c(u);
882
+ return f === i.peek() ? { status: "stayed", step: u, reason: "current" } : h(u) ? l(f) : { status: "stayed", step: e[i.peek()], reason: "locked" };
883
+ },
884
+ reset() {
885
+ r++, D(() => {
886
+ i.value = 0, n.value = /* @__PURE__ */ new Set();
2453
887
  });
2454
- var y = E[0].inst, w = E[1];
2455
- return d(
2456
- function() {
2457
- y.value = p, y.getSnapshot = h, t(y) && w({ inst: y });
2458
- },
2459
- [c, p, h]
2460
- ), l(
2461
- function() {
2462
- return t(y) && w({ inst: y }), c(function() {
2463
- t(y) && w({ inst: y });
2464
- });
2465
- },
2466
- [c]
2467
- ), u(p), p;
2468
888
  }
2469
- function t(c) {
2470
- var h = c.getSnapshot;
2471
- c = c.value;
889
+ };
890
+ }
891
+ function Xe(t) {
892
+ const e = w(0);
893
+ return {
894
+ submitting: d(() => e.value > 0),
895
+ async submit(s) {
896
+ const i = await t.check?.();
897
+ if (i) {
898
+ const { result: a } = i;
899
+ if (a.status !== "valid") return { status: a.status, validation: a };
900
+ if (i.stale)
901
+ return { status: "cancelled", validation: { ...a, status: "cancelled" } };
902
+ }
903
+ const n = i ? i.values : t.values();
904
+ e.value++;
2472
905
  try {
2473
- var p = h();
2474
- return !n(c, p);
2475
- } catch {
2476
- return !0;
906
+ return { status: "submitted", result: await s(n) };
907
+ } catch (a) {
908
+ return { status: "failed", error: a };
909
+ } finally {
910
+ e.value--;
2477
911
  }
2478
912
  }
2479
- function s(c, h) {
2480
- return h();
2481
- }
2482
- typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ < "u" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart == "function" && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
2483
- var r = H, n = typeof Object.is == "function" ? Object.is : i, a = r.useState, l = r.useEffect, d = r.useLayoutEffect, u = r.useDebugValue, v = !1, o = !1, m = typeof window > "u" || typeof window.document > "u" || typeof window.document.createElement > "u" ? s : e;
2484
- T.useSyncExternalStore = r.useSyncExternalStore !== void 0 ? r.useSyncExternalStore : m, typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ < "u" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop == "function" && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
2485
- })()), T;
2486
- }
2487
- var K;
2488
- function ye() {
2489
- return K || (K = 1, process.env.NODE_ENV === "production" ? F.exports = me() : F.exports = pe()), F.exports;
2490
- }
2491
- var C = ye();
2492
- function ge(i, e) {
2493
- if (i === e) return !0;
2494
- if (i.length !== e.length) return !1;
2495
- for (let t = 0; t < i.length; t++)
2496
- if (i[t] !== e[t]) return !1;
2497
- return !0;
913
+ };
2498
914
  }
2499
- function R(i, e, t, s) {
2500
- const r = V(null), n = V(i);
2501
- n.current = i;
2502
- const a = V(e);
2503
- a.current = e;
2504
- const l = V(t);
2505
- if (l.current = t, r.current === null) {
2506
- const v = {};
2507
- for (const o in i)
2508
- v[o] = i[o].value;
2509
- r.current = { ...v, __snapshot: null };
2510
- }
2511
- const d = b(
2512
- (v) => {
2513
- let o = !0;
2514
- return _(() => {
2515
- const c = n.current;
2516
- for (const h in c)
2517
- c[h].value;
2518
- if (o) {
2519
- o = !1;
2520
- return;
2521
- }
2522
- v();
2523
- });
2524
- },
2525
- [s]
2526
- ), u = b(() => {
2527
- const v = r.current, o = n.current, m = {};
2528
- for (const h in o)
2529
- m[h] = o[h].value;
2530
- let c = !1;
2531
- for (const h of a.current) {
2532
- const { key: p, useShallowArrayEqual: E } = h, y = m[p], w = v[p];
2533
- if (E) {
2534
- if (!ge(w, y)) {
2535
- c = !0;
2536
- break;
2537
- }
2538
- } else if (w !== y) {
2539
- c = !0;
2540
- break;
2541
- }
915
+ const M = Object.freeze({
916
+ status: "valid",
917
+ issues: /* @__PURE__ */ new Map(),
918
+ failures: []
919
+ }), G = "[@reformer/core] createForm";
920
+ function Qe(t, e) {
921
+ const s = typeof e.validation == "function" ? { rules: e.validation } : e.validation ?? {}, i = /* @__PURE__ */ new Map();
922
+ for (const v of e.flow?.steps ?? []) {
923
+ if (i.has(v.id)) throw new Error(`${G}: шаг потока «${v.id}» повторяется.`);
924
+ i.set(v.id, v.rules ?? null);
925
+ }
926
+ const n = [...i.values()].filter(
927
+ (v) => v != null
928
+ ), a = s.rules ?? (n.length > 0 || s.extras ? Pe(
929
+ () => Fe(...n, ...s.extras ? [s.extras] : [])
930
+ ) : void 0), r = a !== void 0 ? Ae(t, a, {
931
+ strategy: s.strategy,
932
+ debounce: s.debounce,
933
+ liveAfterSubmit: s.liveAfterSubmit
934
+ }) : void 0, o = w(0), c = (v) => {
935
+ const m = we();
936
+ return m ? (o.value++, m.then(v).finally(() => {
937
+ o.value--;
938
+ })) : v();
939
+ }, l = async (v) => {
940
+ let m;
941
+ const $ = await c(() => (m = t.get(), v())), P = t.get();
942
+ return { result: $, values: P, stale: $.status === "valid" && !ye(m, P) };
943
+ }, h = () => r ? c(() => r.run()) : Promise.resolve(M), u = (v) => {
944
+ if (!i.has(v)) {
945
+ const m = [...i.keys()];
946
+ throw new Error(
947
+ `${G}: шага «${v}» нет в потоке. ` + (m.length > 0 ? `Шаги потока: ${m.join(", ")}.` : "Поток не задан: `flow`.")
948
+ );
949
+ }
950
+ return i.get(v) ?? null;
951
+ }, f = async (v) => {
952
+ o.value++;
953
+ try {
954
+ return await Ie(t, v, { touch: !0, skipDisabled: !0 });
955
+ } finally {
956
+ o.value--;
957
+ }
958
+ }, p = async (v) => {
959
+ const m = u(v);
960
+ return m ? c(() => f(m)) : M;
961
+ }, y = async (v) => {
962
+ const m = u(v);
963
+ if (!m) return M;
964
+ const { result: $, stale: P } = await l(() => f(m));
965
+ return P ? { ...$, status: "cancelled" } : $;
966
+ }, S = e.flow !== void 0 ? Je({ steps: [...i.keys()], validateStep: y }) : void 0, ce = Xe({
967
+ check: r ? () => l(() => r.run({ touch: "all", submit: !0 })) : void 0,
968
+ values: () => t.get()
969
+ });
970
+ return {
971
+ flow: S,
972
+ validating: d(() => (r?.validating.value ?? !1) || o.value > 0),
973
+ validation: a !== void 0 && r ? { rules: a, controller: r } : void 0,
974
+ submission: ce,
975
+ validate: h,
976
+ validateStep: p,
977
+ dispose() {
978
+ r?.dispose();
979
+ for (const v of n) Oe(t, v);
2542
980
  }
2543
- if (!c && v.__snapshot)
2544
- return v.__snapshot;
2545
- for (const h in o)
2546
- v[h] = m[h];
2547
- return v.__snapshot = l.current(m), v.__snapshot;
2548
- }, [s]);
2549
- return C.useSyncExternalStore(d, u, u);
981
+ };
2550
982
  }
2551
- function Ee(i) {
2552
- const e = {
2553
- value: i.value,
2554
- disabled: i.disabled,
2555
- errors: i.errors,
2556
- pending: i.pending,
2557
- valid: i.valid,
2558
- invalid: i.invalid,
2559
- touched: i.touched,
2560
- dirty: i.dirty,
2561
- shouldShowError: i.shouldShowError,
2562
- componentProps: i.componentProps
2563
- }, t = [
2564
- { key: "value" },
2565
- { key: "disabled" },
2566
- { key: "errors", useShallowArrayEqual: !0 },
2567
- { key: "pending" },
2568
- { key: "valid" },
2569
- { key: "invalid" },
2570
- { key: "touched" },
2571
- { key: "dirty" },
2572
- { key: "shouldShowError" },
2573
- { key: "componentProps" }
2574
- ], s = b(
2575
- (r) => ({
2576
- value: r.value,
2577
- pending: r.pending,
2578
- disabled: r.disabled,
2579
- errors: r.errors,
2580
- valid: r.valid,
2581
- invalid: r.invalid,
2582
- touched: r.touched,
2583
- dirty: r.dirty,
2584
- shouldShowError: r.shouldShowError,
2585
- componentProps: r.componentProps
2586
- }),
2587
- []
2588
- );
2589
- return R(e, t, s, i);
983
+ const E = "[@reformer/core] createForm", Y = (t) => t.map((e) => `"${e}"`).join(", ");
984
+ function Ze(t, e, s) {
985
+ if (t === void 0) return;
986
+ const i = Ee(e, t);
987
+ if (i.length > 0 && console.warn(
988
+ `${E}: поведение обращается к узлам схемы, которых нет в корневом дереве: ${Y(i)}. Правило ничего не сделает. Узлы строки массива и подформы адресуются из их поведения — через \`applyEach\` / \`apply\`.`
989
+ ), s) {
990
+ const n = s.steps.map((r) => r.id), a = n.filter((r) => t.nodes.get(r)?.selector !== r);
991
+ a.length > 0 && a.length < n.length && console.warn(
992
+ `${E}: в \`flow.steps\` есть шаги, для которых в дереве схемы нет узла с таким \`selector\`: ${Y(a)}. Визард не свяжет их с разметкой шага.`
993
+ );
994
+ }
2590
995
  }
2591
- function be(i) {
2592
- const e = {
2593
- value: i.value,
2594
- length: i.length,
2595
- errors: i.errors,
2596
- pending: i.pending,
2597
- valid: i.valid,
2598
- invalid: i.invalid,
2599
- touched: i.touched,
2600
- dirty: i.dirty,
2601
- disabled: i.disabled
2602
- }, t = [
2603
- { key: "value" },
2604
- { key: "length" },
2605
- { key: "errors", useShallowArrayEqual: !0 },
2606
- { key: "pending" },
2607
- { key: "valid" },
2608
- { key: "invalid" },
2609
- { key: "touched" },
2610
- { key: "dirty" },
2611
- { key: "disabled" }
2612
- ], s = b(
2613
- (r) => ({
2614
- value: r.value,
2615
- length: r.length,
2616
- pending: r.pending,
2617
- errors: r.errors,
2618
- valid: r.valid,
2619
- invalid: r.invalid,
2620
- touched: r.touched,
2621
- dirty: r.dirty,
2622
- disabled: r.disabled
2623
- }),
2624
- []
2625
- );
2626
- return R(e, t, s, i);
996
+ function et(t, e) {
997
+ const { schema: s, registry: i } = t;
998
+ if (s == null) return {};
999
+ if (typeof s == "function")
1000
+ return { tree: s(e) };
1001
+ if (i == null)
1002
+ throw new Error(
1003
+ `${E}: \`schema\` — готовое дерево, а не билдер \`(model) => узел\`. Передайте билдер; документу схемы (JSON) нужен \`registry\`. Форму из модели и готового дерева без сборки собирает \`createFormFromModel({ model, schema })\`.`
1004
+ );
1005
+ if (typeof i.resolveSchema != "function")
1006
+ throw new Error(
1007
+ `${E}: \`registry\` не умеет собирать дерево из документа схемы — у него нет метода \`resolveSchema\`. Возьмите реестр \`@reformer/renderer-json\` (\`createComponentRegistry\`).`
1008
+ );
1009
+ return i.resolveSchema(s, e);
2627
1010
  }
2628
- function _e() {
2629
- const i = b(
2630
- () => ({
2631
- value: [],
2632
- length: 0,
2633
- pending: !1,
2634
- errors: [],
2635
- valid: !0,
2636
- invalid: !1,
2637
- touched: !1,
2638
- dirty: !1,
2639
- disabled: !1
2640
- }),
2641
- []
1011
+ function ut(t) {
1012
+ if (typeof t?.model?.signalAt != "function" && t?.initial === void 0)
1013
+ throw new Error(
1014
+ `${E}: нужна модель — \`model\` либо \`initial\`. Значения формы принадлежат модели: \`createForm({ initial: { email: '' }, schema })\`.`
1015
+ );
1016
+ const s = t.model ?? ue(t.initial);
1017
+ t.seed?.(s);
1018
+ const { tree: i, fieldWrapper: n, errorBoundary: a } = et(t, s), r = i !== void 0 ? ae(i, s) : void 0, o = $e(), c = o.scopeOf(s), l = Qe(s, { validation: t.validation, flow: t.flow }), h = be(
1019
+ () => U({
1020
+ model: s,
1021
+ ...r !== void 0 ? { compiled: r } : {},
1022
+ ...t.behavior ? { behavior: t.behavior, schemaController: o } : {},
1023
+ ...l.flow !== void 0 ? { flow: l.flow } : {}
1024
+ })
2642
1025
  );
2643
- return R({}, [], i);
2644
- }
2645
- function Me(i) {
2646
- const e = i && "length" in i && "map" in i;
2647
- return i ? e ? be(i) : Ee(i) : _e();
2648
- }
2649
- function xe(i) {
2650
- const e = V({ value: i.value.value }), t = b(
2651
- (r) => {
2652
- let n = !0;
2653
- return _(() => {
2654
- if (i.value.value, n) {
2655
- n = !1;
2656
- return;
2657
- }
2658
- r();
2659
- });
2660
- },
2661
- [i]
2662
- ), s = b(() => {
2663
- const r = i.value.value;
2664
- return e.current.value === r ? e.current.value : (e.current.value = r, r);
2665
- }, [i]);
2666
- return C.useSyncExternalStore(t, s, s);
2667
- }
2668
- function Ce(i) {
2669
- const e = V({ length: i.length.value }), t = b(
2670
- (r) => {
2671
- let n = !0;
2672
- return _(() => {
2673
- if (i.length.value, n) {
2674
- n = !1;
2675
- return;
2676
- }
2677
- r();
2678
- });
1026
+ t.model === void 0 && s.captureInitial(), process.env.NODE_ENV !== "production" && Ze(r, c.__overrideMaps, t.flow);
1027
+ const u = {
1028
+ ...l,
1029
+ model: s,
1030
+ form: h,
1031
+ render: {
1032
+ tree: r,
1033
+ controller: o,
1034
+ node: (f) => c.node(f),
1035
+ ...n !== void 0 ? { fieldWrapper: n } : {},
1036
+ ...a !== void 0 ? { errorBoundary: a } : {}
2679
1037
  },
2680
- [i]
2681
- ), s = b(() => {
2682
- const r = i.length.value;
2683
- return e.current.length === r ? e.current.length : (e.current.length = r, r);
2684
- }, [i]);
2685
- return C.useSyncExternalStore(t, s, s);
2686
- }
2687
- function Pe(i) {
2688
- const { model: e, schema: t, strategy: s, debounce: r, liveAfterSubmit: n } = i, a = ee(
2689
- () => se(e, t, { strategy: s, debounce: r, liveAfterSubmit: n }),
2690
- [e, t, s, r, n]
2691
- );
2692
- te(() => a.start(), [a]);
2693
- const l = C.useSyncExternalStore(
2694
- b((u) => a.validating.subscribe(u), [a]),
2695
- () => a.validating.peek(),
2696
- () => !1
2697
- // SSR: прогон не идёт
2698
- ), d = b(() => a.validate(), [a]);
2699
- return { submit: d, validate: d, isValidating: l };
1038
+ // После `...level`: уровень снимает только себя, сборка — ещё и форму.
1039
+ dispose() {
1040
+ He(h), l.dispose();
1041
+ }
1042
+ };
1043
+ try {
1044
+ t.setup?.(u);
1045
+ } catch (f) {
1046
+ throw u.dispose(), f;
1047
+ }
1048
+ return u;
2700
1049
  }
2701
- const Te = Object.freeze({ runtime: "reformer-core", v: 1 });
1050
+ const dt = Object.freeze({ runtime: "reformer-core", v: 1 });
2702
1051
  export {
2703
- B as ArrayNode,
2704
- Te as CORE_RUNTIME_TOKEN,
2705
- A as ErrorStrategy,
2706
- ne as FieldNode,
2707
- k as FormErrorHandler,
2708
- M as FormNode,
2709
- oe as FormSubmitter,
2710
- S as GroupNode,
2711
- de as ModelArrayNode,
2712
- ue as NodeFactory,
2713
- I as SubscriptionManager,
2714
- $e as computeFrom,
2715
- Ge as copyFrom,
2716
- Ne as createForm,
2717
- Q as createFormFromModel,
2718
- Le as createModel,
2719
- tt as disableWhen,
2720
- st as enableWhen,
2721
- Ze as getNodeForSignal,
2722
- Fe as getNodeType,
2723
- ve as isArrayNode,
2724
- $ as isDerived,
2725
- he as isFieldNode,
2726
- x as isFormNode,
2727
- fe as isGroupNode,
2728
- Ye as markDerived,
2729
- Z as registerSignalNode,
2730
- je as resetWhen,
2731
- Ue as revalidateWhen,
2732
- Be as runOutsideEffect,
2733
- We as safeCallback,
2734
- qe as safeDebouncedCallback,
2735
- Ke as syncFields,
2736
- He as transformValue,
2737
- Qe as unmarkDerived,
2738
- Ce as useArrayLength,
2739
- Me as useFormControl,
2740
- xe as useFormControlValue,
2741
- Pe as useFormValidation,
2742
- ze as watchField
1052
+ dt as CORE_RUNTIME_TOKEN,
1053
+ wt as arrayOf,
1054
+ ae as compileSchema,
1055
+ ut as createForm,
1056
+ Je as createFormFlow,
1057
+ ct as createFormFromModel,
1058
+ Qe as createFormLevel,
1059
+ ue as createModel,
1060
+ $e as createSchemaController,
1061
+ At as createSchemaScope,
1062
+ Xe as createSubmission,
1063
+ He as disposeForm,
1064
+ vt as eachLeafSignal,
1065
+ pt as eachValueSignal,
1066
+ Et as getNodeForSignal,
1067
+ lt as isCompiledNode,
1068
+ T as isDerived,
1069
+ Ft as isFormArray,
1070
+ It as isFormControl,
1071
+ Nt as isFormGroup,
1072
+ Vt as isFormNode,
1073
+ X as isModelContainerSignal,
1074
+ J as isValueSignal,
1075
+ yt as markDerived,
1076
+ _ as modelOf,
1077
+ O as registerSignalNode,
1078
+ bt as runOutsideEffect,
1079
+ gt as safeCallback,
1080
+ kt as safeDebouncedCallback,
1081
+ ee as schemaSubtree,
1082
+ Ee as unknownSchemaSelectors,
1083
+ St as unmarkDerived
2743
1084
  };