@reformer/core 1.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 (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/behaviors.d.ts +2 -0
  4. package/dist/behaviors.js +230 -0
  5. package/dist/core/behavior/behavior-applicator.d.ts +71 -0
  6. package/dist/core/behavior/behavior-applicator.js +92 -0
  7. package/dist/core/behavior/behavior-context.d.ts +29 -0
  8. package/dist/core/behavior/behavior-context.js +38 -0
  9. package/dist/core/behavior/behavior-registry.d.ts +97 -0
  10. package/dist/core/behavior/behavior-registry.js +198 -0
  11. package/dist/core/behavior/behaviors/compute-from.d.ts +41 -0
  12. package/dist/core/behavior/behaviors/compute-from.js +84 -0
  13. package/dist/core/behavior/behaviors/copy-from.d.ts +31 -0
  14. package/dist/core/behavior/behaviors/copy-from.js +64 -0
  15. package/dist/core/behavior/behaviors/enable-when.d.ts +49 -0
  16. package/dist/core/behavior/behaviors/enable-when.js +81 -0
  17. package/dist/core/behavior/behaviors/index.d.ts +11 -0
  18. package/dist/core/behavior/behaviors/index.js +11 -0
  19. package/dist/core/behavior/behaviors/reset-when.d.ts +51 -0
  20. package/dist/core/behavior/behaviors/reset-when.js +63 -0
  21. package/dist/core/behavior/behaviors/revalidate-when.d.ts +30 -0
  22. package/dist/core/behavior/behaviors/revalidate-when.js +51 -0
  23. package/dist/core/behavior/behaviors/sync-fields.d.ts +28 -0
  24. package/dist/core/behavior/behaviors/sync-fields.js +66 -0
  25. package/dist/core/behavior/behaviors/transform-value.d.ts +120 -0
  26. package/dist/core/behavior/behaviors/transform-value.js +110 -0
  27. package/dist/core/behavior/behaviors/watch-field.d.ts +35 -0
  28. package/dist/core/behavior/behaviors/watch-field.js +56 -0
  29. package/dist/core/behavior/compose-behavior.d.ts +106 -0
  30. package/dist/core/behavior/compose-behavior.js +166 -0
  31. package/dist/core/behavior/create-field-path.d.ts +20 -0
  32. package/dist/core/behavior/create-field-path.js +69 -0
  33. package/dist/core/behavior/index.d.ts +12 -0
  34. package/dist/core/behavior/index.js +17 -0
  35. package/dist/core/behavior/types.d.ts +152 -0
  36. package/dist/core/behavior/types.js +7 -0
  37. package/dist/core/context/form-context-impl.d.ts +29 -0
  38. package/dist/core/context/form-context-impl.js +37 -0
  39. package/dist/core/factories/index.d.ts +6 -0
  40. package/dist/core/factories/index.js +6 -0
  41. package/dist/core/factories/node-factory.d.ts +209 -0
  42. package/dist/core/factories/node-factory.js +281 -0
  43. package/dist/core/nodes/array-node.d.ts +308 -0
  44. package/dist/core/nodes/array-node.js +534 -0
  45. package/dist/core/nodes/field-node.d.ts +269 -0
  46. package/dist/core/nodes/field-node.js +510 -0
  47. package/dist/core/nodes/form-node.d.ts +342 -0
  48. package/dist/core/nodes/form-node.js +343 -0
  49. package/dist/core/nodes/group-node/field-registry.d.ts +191 -0
  50. package/dist/core/nodes/group-node/field-registry.js +215 -0
  51. package/dist/core/nodes/group-node/index.d.ts +11 -0
  52. package/dist/core/nodes/group-node/index.js +11 -0
  53. package/dist/core/nodes/group-node/proxy-builder.d.ts +71 -0
  54. package/dist/core/nodes/group-node/proxy-builder.js +161 -0
  55. package/dist/core/nodes/group-node/state-manager.d.ts +184 -0
  56. package/dist/core/nodes/group-node/state-manager.js +265 -0
  57. package/dist/core/nodes/group-node.d.ts +494 -0
  58. package/dist/core/nodes/group-node.js +770 -0
  59. package/dist/core/types/deep-schema.d.ts +78 -0
  60. package/dist/core/types/deep-schema.js +11 -0
  61. package/dist/core/types/field-path.d.ts +42 -0
  62. package/dist/core/types/field-path.js +4 -0
  63. package/dist/core/types/form-context.d.ts +83 -0
  64. package/dist/core/types/form-context.js +25 -0
  65. package/dist/core/types/group-node-proxy.d.ts +135 -0
  66. package/dist/core/types/group-node-proxy.js +31 -0
  67. package/dist/core/types/index.d.ts +163 -0
  68. package/dist/core/types/index.js +4 -0
  69. package/dist/core/types/validation-schema.d.ts +104 -0
  70. package/dist/core/types/validation-schema.js +10 -0
  71. package/dist/core/utils/create-form.d.ts +61 -0
  72. package/dist/core/utils/create-form.js +24 -0
  73. package/dist/core/utils/debounce.d.ts +160 -0
  74. package/dist/core/utils/debounce.js +197 -0
  75. package/dist/core/utils/error-handler.d.ts +180 -0
  76. package/dist/core/utils/error-handler.js +226 -0
  77. package/dist/core/utils/field-path-navigator.d.ts +240 -0
  78. package/dist/core/utils/field-path-navigator.js +374 -0
  79. package/dist/core/utils/index.d.ts +14 -0
  80. package/dist/core/utils/index.js +14 -0
  81. package/dist/core/utils/registry-helpers.d.ts +50 -0
  82. package/dist/core/utils/registry-helpers.js +79 -0
  83. package/dist/core/utils/registry-stack.d.ts +69 -0
  84. package/dist/core/utils/registry-stack.js +86 -0
  85. package/dist/core/utils/resources.d.ts +41 -0
  86. package/dist/core/utils/resources.js +69 -0
  87. package/dist/core/utils/subscription-manager.d.ts +180 -0
  88. package/dist/core/utils/subscription-manager.js +214 -0
  89. package/dist/core/utils/type-guards.d.ts +116 -0
  90. package/dist/core/utils/type-guards.js +169 -0
  91. package/dist/core/validation/core/apply-when.d.ts +28 -0
  92. package/dist/core/validation/core/apply-when.js +41 -0
  93. package/dist/core/validation/core/apply.d.ts +63 -0
  94. package/dist/core/validation/core/apply.js +38 -0
  95. package/dist/core/validation/core/index.d.ts +8 -0
  96. package/dist/core/validation/core/index.js +8 -0
  97. package/dist/core/validation/core/validate-async.d.ts +42 -0
  98. package/dist/core/validation/core/validate-async.js +45 -0
  99. package/dist/core/validation/core/validate-tree.d.ts +35 -0
  100. package/dist/core/validation/core/validate-tree.js +37 -0
  101. package/dist/core/validation/core/validate.d.ts +32 -0
  102. package/dist/core/validation/core/validate.js +38 -0
  103. package/dist/core/validation/field-path.d.ts +43 -0
  104. package/dist/core/validation/field-path.js +147 -0
  105. package/dist/core/validation/index.d.ts +21 -0
  106. package/dist/core/validation/index.js +33 -0
  107. package/dist/core/validation/validate-form.d.ts +85 -0
  108. package/dist/core/validation/validate-form.js +152 -0
  109. package/dist/core/validation/validation-applicator.d.ts +89 -0
  110. package/dist/core/validation/validation-applicator.js +217 -0
  111. package/dist/core/validation/validation-context.d.ts +47 -0
  112. package/dist/core/validation/validation-context.js +75 -0
  113. package/dist/core/validation/validation-registry.d.ts +156 -0
  114. package/dist/core/validation/validation-registry.js +298 -0
  115. package/dist/core/validation/validators/array-validators.d.ts +63 -0
  116. package/dist/core/validation/validators/array-validators.js +86 -0
  117. package/dist/core/validation/validators/date.d.ts +38 -0
  118. package/dist/core/validation/validators/date.js +117 -0
  119. package/dist/core/validation/validators/email.d.ts +44 -0
  120. package/dist/core/validation/validators/email.js +60 -0
  121. package/dist/core/validation/validators/index.d.ts +14 -0
  122. package/dist/core/validation/validators/index.js +14 -0
  123. package/dist/core/validation/validators/max-length.d.ts +45 -0
  124. package/dist/core/validation/validators/max-length.js +60 -0
  125. package/dist/core/validation/validators/max.d.ts +45 -0
  126. package/dist/core/validation/validators/max.js +60 -0
  127. package/dist/core/validation/validators/min-length.d.ts +45 -0
  128. package/dist/core/validation/validators/min-length.js +60 -0
  129. package/dist/core/validation/validators/min.d.ts +45 -0
  130. package/dist/core/validation/validators/min.js +60 -0
  131. package/dist/core/validation/validators/number.d.ts +38 -0
  132. package/dist/core/validation/validators/number.js +90 -0
  133. package/dist/core/validation/validators/pattern.d.ts +47 -0
  134. package/dist/core/validation/validators/pattern.js +62 -0
  135. package/dist/core/validation/validators/phone.d.ts +34 -0
  136. package/dist/core/validation/validators/phone.js +58 -0
  137. package/dist/core/validation/validators/required.d.ts +48 -0
  138. package/dist/core/validation/validators/required.js +69 -0
  139. package/dist/core/validation/validators/url.d.ts +29 -0
  140. package/dist/core/validation/validators/url.js +55 -0
  141. package/dist/create-field-path-CdPF3lIK.js +704 -0
  142. package/dist/hooks/useFormControl.d.ts +48 -0
  143. package/dist/hooks/useFormControl.js +298 -0
  144. package/dist/index.d.ts +10 -0
  145. package/dist/index.js +8 -0
  146. package/dist/node-factory-D7DOnSSN.js +3200 -0
  147. package/dist/validators.d.ts +2 -0
  148. package/dist/validators.js +298 -0
  149. package/llms.txt +847 -0
  150. package/package.json +86 -0
@@ -0,0 +1,342 @@
1
+ /**
2
+ * FormNode - абстрактный базовый класс для всех узлов формы
3
+ *
4
+ * Аналог AbstractControl из Angular Forms
5
+ * Унифицирует работу с полями (FieldNode), группами (GroupNode) и массивами (ArrayNode)
6
+ *
7
+ * Использует Template Method паттерн для управления состоянием:
8
+ * - Публичные методы (markAsTouched, disable и т.д.) реализованы в базовом классе
9
+ * - Protected hooks (onMarkAsTouched, onDisable и т.д.) переопределяются в наследниках
10
+ *
11
+ * @group Nodes
12
+ */
13
+ import { type ReadonlySignal, type Signal } from '@preact/signals-core';
14
+ import type { FieldStatus, ValidationError, ErrorFilterOptions } from '../types';
15
+ /**
16
+ * Опции для setValue
17
+ * @group Nodes
18
+ */
19
+ export interface SetValueOptions {
20
+ /** Не вызывать событие изменения (не триггерить валидацию) */
21
+ emitEvent?: boolean;
22
+ /** Обновить только этот узел, не распространять на родителей */
23
+ onlySelf?: boolean;
24
+ }
25
+ /**
26
+ * Абстрактный базовый класс для всех узлов формы
27
+ *
28
+ * Все узлы (поля, группы, массивы) наследуют от этого класса
29
+ * и реализуют единый интерфейс для работы с состоянием и валидацией
30
+ *
31
+ * Template Method паттерн используется для управления состоянием:
32
+ * - Общие signals (_touched, _dirty, _status) определены в базовом классе
33
+ * - Публичные методы (markAsTouched, disable и т.д.) реализованы здесь
34
+ * - Protected hooks (onMarkAsTouched, onDisable и т.д.) переопределяются в наследниках
35
+ *
36
+ * @group Nodes
37
+ */
38
+ export declare abstract class FormNode<T> {
39
+ /**
40
+ * Пользователь взаимодействовал с узлом (touched)
41
+ * Protected: наследники могут читать/изменять через методы
42
+ */
43
+ protected _touched: Signal<boolean>;
44
+ /**
45
+ * Значение узла было изменено (dirty)
46
+ * Protected: наследники могут читать/изменять через методы
47
+ */
48
+ protected _dirty: Signal<boolean>;
49
+ /**
50
+ * Текущий статус узла
51
+ * Protected: наследники могут читать/изменять через методы
52
+ */
53
+ protected _status: Signal<FieldStatus>;
54
+ /**
55
+ * Пользователь взаимодействовал с узлом (touched)
56
+ * Computed из _touched для предоставления readonly интерфейса
57
+ */
58
+ readonly touched: ReadonlySignal<boolean>;
59
+ /**
60
+ * Пользователь не взаимодействовал с узлом (untouched)
61
+ */
62
+ readonly untouched: ReadonlySignal<boolean>;
63
+ /**
64
+ * Значение узла было изменено (dirty)
65
+ * Computed из _dirty для предоставления readonly интерфейса
66
+ */
67
+ readonly dirty: ReadonlySignal<boolean>;
68
+ /**
69
+ * Значение узла не было изменено (pristine)
70
+ */
71
+ readonly pristine: ReadonlySignal<boolean>;
72
+ /**
73
+ * Текущий статус узла
74
+ * Computed из _status для предоставления readonly интерфейса
75
+ */
76
+ readonly status: ReadonlySignal<FieldStatus>;
77
+ /**
78
+ * Узел отключен (disabled)
79
+ */
80
+ readonly disabled: ReadonlySignal<boolean>;
81
+ /**
82
+ * Узел включен (enabled)
83
+ */
84
+ readonly enabled: ReadonlySignal<boolean>;
85
+ /**
86
+ * Текущее значение узла
87
+ * - Для FieldNode: значение поля
88
+ * - Для GroupNode: объект со значениями всех полей
89
+ * - Для ArrayNode: массив значений элементов
90
+ */
91
+ abstract readonly value: ReadonlySignal<T>;
92
+ /**
93
+ * Узел валиден (все валидаторы прошли успешно)
94
+ */
95
+ abstract readonly valid: ReadonlySignal<boolean>;
96
+ /**
97
+ * Узел невалиден (есть ошибки валидации)
98
+ */
99
+ abstract readonly invalid: ReadonlySignal<boolean>;
100
+ /**
101
+ * Выполняется асинхронная валидация
102
+ */
103
+ abstract readonly pending: ReadonlySignal<boolean>;
104
+ /**
105
+ * Массив ошибок валидации
106
+ */
107
+ abstract readonly errors: ReadonlySignal<ValidationError[]>;
108
+ /**
109
+ * Получить значение узла (non-reactive)
110
+ * Использует .peek() для получения значения без создания зависимости
111
+ */
112
+ abstract getValue(): T;
113
+ /**
114
+ * Установить значение узла
115
+ * @param value - новое значение
116
+ * @param options - опции установки значения
117
+ */
118
+ abstract setValue(value: T, options?: SetValueOptions): void;
119
+ /**
120
+ * Частично обновить значение узла
121
+ * Для FieldNode: работает как setValue
122
+ * Для GroupNode: обновляет только указанные поля
123
+ * Для ArrayNode: обновляет только указанные элементы
124
+ *
125
+ * @param value - частичное значение для обновления
126
+ */
127
+ abstract patchValue(value: Partial<T>): void;
128
+ /**
129
+ * Сбросить узел к начальному состоянию
130
+ * @param value - опциональное новое начальное значение
131
+ */
132
+ abstract reset(value?: T): void;
133
+ /**
134
+ * Запустить валидацию узла
135
+ * @returns `Promise<boolean>` - true если валидация успешна
136
+ */
137
+ abstract validate(): Promise<boolean>;
138
+ /**
139
+ * Установить ошибки валидации извне
140
+ * @param errors - массив ошибок
141
+ */
142
+ abstract setErrors(errors: ValidationError[]): void;
143
+ /**
144
+ * Очистить ошибки валидации
145
+ */
146
+ abstract clearErrors(): void;
147
+ /**
148
+ * Получить ошибки валидации с фильтрацией
149
+ *
150
+ * Позволяет фильтровать ошибки по различным критериям:
151
+ * - По коду ошибки
152
+ * - По сообщению (частичное совпадение)
153
+ * - По параметрам
154
+ * - Через кастомный предикат
155
+ *
156
+ * Без параметров возвращает все ошибки (эквивалент errors.value)
157
+ *
158
+ * @param options - Опции фильтрации ошибок
159
+ * @returns Отфильтрованный массив ошибок валидации
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * // Все ошибки
164
+ * const allErrors = form.getErrors();
165
+ *
166
+ * // Ошибки с конкретным кодом
167
+ * const requiredErrors = form.getErrors({ code: 'required' });
168
+ *
169
+ * // Ошибки с несколькими кодами
170
+ * const errors = form.getErrors({ code: ['required', 'email'] });
171
+ *
172
+ * // Ошибки по сообщению
173
+ * const passwordErrors = form.getErrors({ message: 'Password' });
174
+ *
175
+ * // Ошибки по параметрам
176
+ * const minLengthErrors = form.getErrors({
177
+ * params: { minLength: 8 }
178
+ * });
179
+ *
180
+ * // Кастомная фильтрация
181
+ * const customErrors = form.getErrors({
182
+ * predicate: (err) => err.code.startsWith('custom_')
183
+ * });
184
+ * ```
185
+ */
186
+ getErrors(options?: ErrorFilterOptions): ValidationError[];
187
+ /**
188
+ * Отметить узел как touched (пользователь взаимодействовал)
189
+ *
190
+ * Template Method: обновляет signal в базовом классе,
191
+ * вызывает hook для кастомной логики в наследниках
192
+ */
193
+ markAsTouched(): void;
194
+ /**
195
+ * Отметить узел как untouched
196
+ *
197
+ * Template Method: обновляет signal в базовом классе,
198
+ * вызывает hook для кастомной логики в наследниках
199
+ */
200
+ markAsUntouched(): void;
201
+ /**
202
+ * Отметить узел как dirty (значение изменено)
203
+ *
204
+ * Template Method: обновляет signal в базовом классе,
205
+ * вызывает hook для кастомной логики в наследниках
206
+ */
207
+ markAsDirty(): void;
208
+ /**
209
+ * Отметить узел как pristine (значение не изменено)
210
+ *
211
+ * Template Method: обновляет signal в базовом классе,
212
+ * вызывает hook для кастомной логики в наследниках
213
+ */
214
+ markAsPristine(): void;
215
+ /**
216
+ * Пометить все поля (включая вложенные) как touched
217
+ * Алиас для markAsTouched(), но более явно показывает намерение
218
+ * пометить ВСЕ поля рекурсивно
219
+ *
220
+ * Полезно для:
221
+ * - Показа всех ошибок валидации перед submit
222
+ * - Принудительного отображения ошибок при нажатии "Validate All"
223
+ * - Отображения невалидных полей в wizard/step form
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * // Показать все ошибки перед submit
228
+ * form.touchAll();
229
+ * const isValid = await form.validate();
230
+ * if (!isValid) {
231
+ * // Все ошибки теперь видны пользователю
232
+ * }
233
+ *
234
+ * // Или использовать submit() который уже вызывает touchAll
235
+ * await form.submit(async (values) => {
236
+ * await api.save(values);
237
+ * });
238
+ * ```
239
+ */
240
+ touchAll(): void;
241
+ /**
242
+ * Отключить узел
243
+ *
244
+ * Template Method: обновляет статус в базовом классе,
245
+ * вызывает hook для кастомной логики в наследниках
246
+ *
247
+ * Отключенные узлы не проходят валидацию и не включаются в getValue()
248
+ */
249
+ disable(): void;
250
+ /**
251
+ * Включить узел
252
+ *
253
+ * Template Method: обновляет статус в базовом классе,
254
+ * вызывает hook для кастомной логики в наследниках
255
+ */
256
+ enable(): void;
257
+ /**
258
+ * Очистить все ресурсы узла
259
+ * Должен вызываться при unmount компонента для предотвращения memory leaks
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * // React component
264
+ * useEffect(() => {
265
+ * return () => {
266
+ * form.dispose(); // Cleanup при unmount
267
+ * };
268
+ * }, []);
269
+ * ```
270
+ */
271
+ dispose?(): void;
272
+ /**
273
+ * Hook: вызывается после markAsTouched()
274
+ *
275
+ * Переопределите в наследниках для дополнительной логики:
276
+ * - GroupNode: пометить все дочерние узлы как touched
277
+ * - ArrayNode: пометить все элементы массива как touched
278
+ * - FieldNode: пустая реализация (нет дочерних узлов)
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * // GroupNode
283
+ * protected onMarkAsTouched(): void {
284
+ * this.fields.forEach(field => field.markAsTouched());
285
+ * }
286
+ * ```
287
+ */
288
+ protected onMarkAsTouched(): void;
289
+ /**
290
+ * Hook: вызывается после markAsUntouched()
291
+ *
292
+ * Переопределите в наследниках для дополнительной логики:
293
+ * - GroupNode: пометить все дочерние узлы как untouched
294
+ * - ArrayNode: пометить все элементы массива как untouched
295
+ * - FieldNode: пустая реализация (нет дочерних узлов)
296
+ */
297
+ protected onMarkAsUntouched(): void;
298
+ /**
299
+ * Hook: вызывается после markAsDirty()
300
+ *
301
+ * Переопределите в наследниках для дополнительной логики:
302
+ * - GroupNode: может обновить родительскую форму
303
+ * - ArrayNode: может обновить родительскую форму
304
+ * - FieldNode: пустая реализация
305
+ */
306
+ protected onMarkAsDirty(): void;
307
+ /**
308
+ * Hook: вызывается после markAsPristine()
309
+ *
310
+ * Переопределите в наследниках для дополнительной логики:
311
+ * - GroupNode: пометить все дочерние узлы как pristine
312
+ * - ArrayNode: пометить все элементы массива как pristine
313
+ * - FieldNode: пустая реализация
314
+ */
315
+ protected onMarkAsPristine(): void;
316
+ /**
317
+ * Hook: вызывается после disable()
318
+ *
319
+ * Переопределите в наследниках для дополнительной логики:
320
+ * - GroupNode: отключить все дочерние узлы
321
+ * - ArrayNode: отключить все элементы массива
322
+ * - FieldNode: очистить ошибки валидации
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * // GroupNode
327
+ * protected onDisable(): void {
328
+ * this.fields.forEach(field => field.disable());
329
+ * }
330
+ * ```
331
+ */
332
+ protected onDisable(): void;
333
+ /**
334
+ * Hook: вызывается после enable()
335
+ *
336
+ * Переопределите в наследниках для дополнительной логики:
337
+ * - GroupNode: включить все дочерние узлы
338
+ * - ArrayNode: включить все элементы массива
339
+ * - FieldNode: пустая реализация
340
+ */
341
+ protected onEnable(): void;
342
+ }
@@ -0,0 +1,343 @@
1
+ /**
2
+ * FormNode - абстрактный базовый класс для всех узлов формы
3
+ *
4
+ * Аналог AbstractControl из Angular Forms
5
+ * Унифицирует работу с полями (FieldNode), группами (GroupNode) и массивами (ArrayNode)
6
+ *
7
+ * Использует Template Method паттерн для управления состоянием:
8
+ * - Публичные методы (markAsTouched, disable и т.д.) реализованы в базовом классе
9
+ * - Protected hooks (onMarkAsTouched, onDisable и т.д.) переопределяются в наследниках
10
+ *
11
+ * @group Nodes
12
+ */
13
+ import { signal, computed } from '@preact/signals-core';
14
+ /**
15
+ * Абстрактный базовый класс для всех узлов формы
16
+ *
17
+ * Все узлы (поля, группы, массивы) наследуют от этого класса
18
+ * и реализуют единый интерфейс для работы с состоянием и валидацией
19
+ *
20
+ * Template Method паттерн используется для управления состоянием:
21
+ * - Общие signals (_touched, _dirty, _status) определены в базовом классе
22
+ * - Публичные методы (markAsTouched, disable и т.д.) реализованы здесь
23
+ * - Protected hooks (onMarkAsTouched, onDisable и т.д.) переопределяются в наследниках
24
+ *
25
+ * @group Nodes
26
+ */
27
+ export class FormNode {
28
+ // ============================================================================
29
+ // Protected состояние (для Template Method паттерна)
30
+ // ============================================================================
31
+ /**
32
+ * Пользователь взаимодействовал с узлом (touched)
33
+ * Protected: наследники могут читать/изменять через методы
34
+ */
35
+ _touched = signal(false);
36
+ /**
37
+ * Значение узла было изменено (dirty)
38
+ * Protected: наследники могут читать/изменять через методы
39
+ */
40
+ _dirty = signal(false);
41
+ /**
42
+ * Текущий статус узла
43
+ * Protected: наследники могут читать/изменять через методы
44
+ */
45
+ _status = signal('valid');
46
+ // ============================================================================
47
+ // Публичные computed signals (readonly для внешнего мира)
48
+ // ============================================================================
49
+ /**
50
+ * Пользователь взаимодействовал с узлом (touched)
51
+ * Computed из _touched для предоставления readonly интерфейса
52
+ */
53
+ touched = computed(() => this._touched.value);
54
+ /**
55
+ * Пользователь не взаимодействовал с узлом (untouched)
56
+ */
57
+ untouched = computed(() => !this._touched.value);
58
+ /**
59
+ * Значение узла было изменено (dirty)
60
+ * Computed из _dirty для предоставления readonly интерфейса
61
+ */
62
+ dirty = computed(() => this._dirty.value);
63
+ /**
64
+ * Значение узла не было изменено (pristine)
65
+ */
66
+ pristine = computed(() => !this._dirty.value);
67
+ /**
68
+ * Текущий статус узла
69
+ * Computed из _status для предоставления readonly интерфейса
70
+ */
71
+ status = computed(() => this._status.value);
72
+ /**
73
+ * Узел отключен (disabled)
74
+ */
75
+ disabled = computed(() => this._status.value === 'disabled');
76
+ /**
77
+ * Узел включен (enabled)
78
+ */
79
+ enabled = computed(() => this._status.value !== 'disabled');
80
+ /**
81
+ * Получить ошибки валидации с фильтрацией
82
+ *
83
+ * Позволяет фильтровать ошибки по различным критериям:
84
+ * - По коду ошибки
85
+ * - По сообщению (частичное совпадение)
86
+ * - По параметрам
87
+ * - Через кастомный предикат
88
+ *
89
+ * Без параметров возвращает все ошибки (эквивалент errors.value)
90
+ *
91
+ * @param options - Опции фильтрации ошибок
92
+ * @returns Отфильтрованный массив ошибок валидации
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // Все ошибки
97
+ * const allErrors = form.getErrors();
98
+ *
99
+ * // Ошибки с конкретным кодом
100
+ * const requiredErrors = form.getErrors({ code: 'required' });
101
+ *
102
+ * // Ошибки с несколькими кодами
103
+ * const errors = form.getErrors({ code: ['required', 'email'] });
104
+ *
105
+ * // Ошибки по сообщению
106
+ * const passwordErrors = form.getErrors({ message: 'Password' });
107
+ *
108
+ * // Ошибки по параметрам
109
+ * const minLengthErrors = form.getErrors({
110
+ * params: { minLength: 8 }
111
+ * });
112
+ *
113
+ * // Кастомная фильтрация
114
+ * const customErrors = form.getErrors({
115
+ * predicate: (err) => err.code.startsWith('custom_')
116
+ * });
117
+ * ```
118
+ */
119
+ getErrors(options) {
120
+ const allErrors = this.errors.value;
121
+ // Без фильтрации - вернуть все ошибки
122
+ if (!options) {
123
+ return allErrors;
124
+ }
125
+ return allErrors.filter((error) => {
126
+ // Фильтр по коду
127
+ if (options.code !== undefined) {
128
+ const codes = Array.isArray(options.code) ? options.code : [options.code];
129
+ if (!codes.includes(error.code)) {
130
+ return false;
131
+ }
132
+ }
133
+ // Фильтр по сообщению (частичное совпадение, регистронезависимый)
134
+ if (options.message !== undefined) {
135
+ if (!error.message.toLowerCase().includes(options.message.toLowerCase())) {
136
+ return false;
137
+ }
138
+ }
139
+ // Фильтр по параметрам
140
+ if (options.params !== undefined) {
141
+ if (!error.params) {
142
+ return false;
143
+ }
144
+ // Проверяем, что все ключи из options.params присутствуют в error.params
145
+ // и имеют те же значения
146
+ for (const [key, value] of Object.entries(options.params)) {
147
+ if (error.params[key] !== value) {
148
+ return false;
149
+ }
150
+ }
151
+ }
152
+ // Кастомный предикат
153
+ if (options.predicate !== undefined) {
154
+ if (!options.predicate(error)) {
155
+ return false;
156
+ }
157
+ }
158
+ return true;
159
+ });
160
+ }
161
+ // ============================================================================
162
+ // Методы управления состоянием (Template Method)
163
+ // ============================================================================
164
+ /**
165
+ * Отметить узел как touched (пользователь взаимодействовал)
166
+ *
167
+ * Template Method: обновляет signal в базовом классе,
168
+ * вызывает hook для кастомной логики в наследниках
169
+ */
170
+ markAsTouched() {
171
+ this._touched.value = true;
172
+ this.onMarkAsTouched();
173
+ }
174
+ /**
175
+ * Отметить узел как untouched
176
+ *
177
+ * Template Method: обновляет signal в базовом классе,
178
+ * вызывает hook для кастомной логики в наследниках
179
+ */
180
+ markAsUntouched() {
181
+ this._touched.value = false;
182
+ this.onMarkAsUntouched();
183
+ }
184
+ /**
185
+ * Отметить узел как dirty (значение изменено)
186
+ *
187
+ * Template Method: обновляет signal в базовом классе,
188
+ * вызывает hook для кастомной логики в наследниках
189
+ */
190
+ markAsDirty() {
191
+ this._dirty.value = true;
192
+ this.onMarkAsDirty();
193
+ }
194
+ /**
195
+ * Отметить узел как pristine (значение не изменено)
196
+ *
197
+ * Template Method: обновляет signal в базовом классе,
198
+ * вызывает hook для кастомной логики в наследниках
199
+ */
200
+ markAsPristine() {
201
+ this._dirty.value = false;
202
+ this.onMarkAsPristine();
203
+ }
204
+ /**
205
+ * Пометить все поля (включая вложенные) как touched
206
+ * Алиас для markAsTouched(), но более явно показывает намерение
207
+ * пометить ВСЕ поля рекурсивно
208
+ *
209
+ * Полезно для:
210
+ * - Показа всех ошибок валидации перед submit
211
+ * - Принудительного отображения ошибок при нажатии "Validate All"
212
+ * - Отображения невалидных полей в wizard/step form
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * // Показать все ошибки перед submit
217
+ * form.touchAll();
218
+ * const isValid = await form.validate();
219
+ * if (!isValid) {
220
+ * // Все ошибки теперь видны пользователю
221
+ * }
222
+ *
223
+ * // Или использовать submit() который уже вызывает touchAll
224
+ * await form.submit(async (values) => {
225
+ * await api.save(values);
226
+ * });
227
+ * ```
228
+ */
229
+ touchAll() {
230
+ this.markAsTouched();
231
+ }
232
+ // ============================================================================
233
+ // Методы управления доступностью (Template Method)
234
+ // ============================================================================
235
+ /**
236
+ * Отключить узел
237
+ *
238
+ * Template Method: обновляет статус в базовом классе,
239
+ * вызывает hook для кастомной логики в наследниках
240
+ *
241
+ * Отключенные узлы не проходят валидацию и не включаются в getValue()
242
+ */
243
+ disable() {
244
+ this._status.value = 'disabled';
245
+ this.onDisable();
246
+ }
247
+ /**
248
+ * Включить узел
249
+ *
250
+ * Template Method: обновляет статус в базовом классе,
251
+ * вызывает hook для кастомной логики в наследниках
252
+ */
253
+ enable() {
254
+ this._status.value = 'valid';
255
+ this.onEnable();
256
+ }
257
+ // ============================================================================
258
+ // Protected hooks (для переопределения в наследниках)
259
+ // ============================================================================
260
+ /**
261
+ * Hook: вызывается после markAsTouched()
262
+ *
263
+ * Переопределите в наследниках для дополнительной логики:
264
+ * - GroupNode: пометить все дочерние узлы как touched
265
+ * - ArrayNode: пометить все элементы массива как touched
266
+ * - FieldNode: пустая реализация (нет дочерних узлов)
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * // GroupNode
271
+ * protected onMarkAsTouched(): void {
272
+ * this.fields.forEach(field => field.markAsTouched());
273
+ * }
274
+ * ```
275
+ */
276
+ onMarkAsTouched() {
277
+ // Пустая реализация по умолчанию
278
+ // Наследники переопределяют при необходимости
279
+ }
280
+ /**
281
+ * Hook: вызывается после markAsUntouched()
282
+ *
283
+ * Переопределите в наследниках для дополнительной логики:
284
+ * - GroupNode: пометить все дочерние узлы как untouched
285
+ * - ArrayNode: пометить все элементы массива как untouched
286
+ * - FieldNode: пустая реализация (нет дочерних узлов)
287
+ */
288
+ onMarkAsUntouched() {
289
+ // Пустая реализация по умолчанию
290
+ }
291
+ /**
292
+ * Hook: вызывается после markAsDirty()
293
+ *
294
+ * Переопределите в наследниках для дополнительной логики:
295
+ * - GroupNode: может обновить родительскую форму
296
+ * - ArrayNode: может обновить родительскую форму
297
+ * - FieldNode: пустая реализация
298
+ */
299
+ onMarkAsDirty() {
300
+ // Пустая реализация по умолчанию
301
+ }
302
+ /**
303
+ * Hook: вызывается после markAsPristine()
304
+ *
305
+ * Переопределите в наследниках для дополнительной логики:
306
+ * - GroupNode: пометить все дочерние узлы как pristine
307
+ * - ArrayNode: пометить все элементы массива как pristine
308
+ * - FieldNode: пустая реализация
309
+ */
310
+ onMarkAsPristine() {
311
+ // Пустая реализация по умолчанию
312
+ }
313
+ /**
314
+ * Hook: вызывается после disable()
315
+ *
316
+ * Переопределите в наследниках для дополнительной логики:
317
+ * - GroupNode: отключить все дочерние узлы
318
+ * - ArrayNode: отключить все элементы массива
319
+ * - FieldNode: очистить ошибки валидации
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * // GroupNode
324
+ * protected onDisable(): void {
325
+ * this.fields.forEach(field => field.disable());
326
+ * }
327
+ * ```
328
+ */
329
+ onDisable() {
330
+ // Пустая реализация по умолчанию
331
+ }
332
+ /**
333
+ * Hook: вызывается после enable()
334
+ *
335
+ * Переопределите в наследниках для дополнительной логики:
336
+ * - GroupNode: включить все дочерние узлы
337
+ * - ArrayNode: включить все элементы массива
338
+ * - FieldNode: пустая реализация
339
+ */
340
+ onEnable() {
341
+ // Пустая реализация по умолчанию
342
+ }
343
+ }