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