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

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