@reformer/core 1.1.0 → 2.0.0-beta.3

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