@reformer/core 1.1.0 → 2.0.0-beta.2
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.
- package/dist/behaviors-DzYL8kY_.js +499 -0
- package/dist/behaviors.d.ts +6 -2
- package/dist/behaviors.js +19 -227
- package/dist/core/behavior/behavior-context.d.ts +6 -2
- package/dist/core/behavior/create-field-path.d.ts +3 -16
- package/dist/core/nodes/group-node.d.ts +14 -193
- package/dist/core/types/form-context.d.ts +10 -4
- package/dist/core/utils/field-path.d.ts +48 -0
- package/dist/core/utils/index.d.ts +1 -0
- package/dist/core/validation/core/validate-tree.d.ts +10 -4
- package/dist/core/validation/field-path.d.ts +3 -39
- package/dist/core/validation/validation-context.d.ts +23 -0
- package/dist/hooks/types.d.ts +328 -0
- package/dist/hooks/useFormControl.d.ts +13 -37
- package/dist/hooks/useFormControlValue.d.ts +167 -0
- package/dist/hooks/useSignalSubscription.d.ts +17 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2886 -8
- package/dist/{create-field-path-CdPF3lIK.js → registry-helpers-BRxAr6nG.js} +133 -347
- package/dist/validators-gXoHPdqM.js +418 -0
- package/dist/validators.d.ts +6 -2
- package/dist/validators.js +29 -296
- package/llms.txt +1283 -22
- package/package.json +8 -4
- package/dist/core/behavior/behavior-applicator.d.ts +0 -71
- package/dist/core/behavior/behavior-applicator.js +0 -92
- package/dist/core/behavior/behavior-context.js +0 -38
- package/dist/core/behavior/behavior-registry.js +0 -198
- package/dist/core/behavior/behaviors/compute-from.js +0 -84
- package/dist/core/behavior/behaviors/copy-from.js +0 -64
- package/dist/core/behavior/behaviors/enable-when.js +0 -81
- package/dist/core/behavior/behaviors/index.js +0 -11
- package/dist/core/behavior/behaviors/reset-when.js +0 -63
- package/dist/core/behavior/behaviors/revalidate-when.js +0 -51
- package/dist/core/behavior/behaviors/sync-fields.js +0 -66
- package/dist/core/behavior/behaviors/transform-value.js +0 -110
- package/dist/core/behavior/behaviors/watch-field.js +0 -56
- package/dist/core/behavior/compose-behavior.js +0 -166
- package/dist/core/behavior/create-field-path.js +0 -69
- package/dist/core/behavior/index.js +0 -17
- package/dist/core/behavior/types.js +0 -7
- package/dist/core/context/form-context-impl.js +0 -37
- package/dist/core/factories/index.js +0 -6
- package/dist/core/factories/node-factory.js +0 -281
- package/dist/core/nodes/array-node.js +0 -534
- package/dist/core/nodes/field-node.js +0 -510
- package/dist/core/nodes/form-node.js +0 -343
- package/dist/core/nodes/group-node/field-registry.d.ts +0 -191
- package/dist/core/nodes/group-node/field-registry.js +0 -215
- package/dist/core/nodes/group-node/index.d.ts +0 -11
- package/dist/core/nodes/group-node/index.js +0 -11
- package/dist/core/nodes/group-node/proxy-builder.d.ts +0 -71
- package/dist/core/nodes/group-node/proxy-builder.js +0 -161
- package/dist/core/nodes/group-node/state-manager.d.ts +0 -184
- package/dist/core/nodes/group-node/state-manager.js +0 -265
- package/dist/core/nodes/group-node.js +0 -770
- package/dist/core/types/deep-schema.js +0 -11
- package/dist/core/types/field-path.js +0 -4
- package/dist/core/types/form-context.js +0 -25
- package/dist/core/types/group-node-proxy.js +0 -31
- package/dist/core/types/index.js +0 -4
- package/dist/core/types/validation-schema.js +0 -10
- package/dist/core/utils/create-form.js +0 -24
- package/dist/core/utils/debounce.js +0 -197
- package/dist/core/utils/error-handler.js +0 -226
- package/dist/core/utils/field-path-navigator.js +0 -374
- package/dist/core/utils/index.js +0 -14
- package/dist/core/utils/registry-helpers.js +0 -79
- package/dist/core/utils/registry-stack.js +0 -86
- package/dist/core/utils/resources.js +0 -69
- package/dist/core/utils/subscription-manager.js +0 -214
- package/dist/core/utils/type-guards.js +0 -169
- package/dist/core/validation/core/apply-when.js +0 -41
- package/dist/core/validation/core/apply.js +0 -38
- package/dist/core/validation/core/index.js +0 -8
- package/dist/core/validation/core/validate-async.js +0 -45
- package/dist/core/validation/core/validate-tree.js +0 -37
- package/dist/core/validation/core/validate.js +0 -38
- package/dist/core/validation/field-path.js +0 -147
- package/dist/core/validation/index.js +0 -33
- package/dist/core/validation/validate-form.js +0 -152
- package/dist/core/validation/validation-applicator.js +0 -217
- package/dist/core/validation/validation-context.js +0 -75
- package/dist/core/validation/validation-registry.js +0 -298
- package/dist/core/validation/validators/array-validators.js +0 -86
- package/dist/core/validation/validators/date.js +0 -117
- package/dist/core/validation/validators/email.js +0 -60
- package/dist/core/validation/validators/index.js +0 -14
- package/dist/core/validation/validators/max-length.js +0 -60
- package/dist/core/validation/validators/max.js +0 -60
- package/dist/core/validation/validators/min-length.js +0 -60
- package/dist/core/validation/validators/min.js +0 -60
- package/dist/core/validation/validators/number.js +0 -90
- package/dist/core/validation/validators/pattern.js +0 -62
- package/dist/core/validation/validators/phone.js +0 -58
- package/dist/core/validation/validators/required.js +0 -69
- package/dist/core/validation/validators/url.js +0 -55
- package/dist/hooks/useFormControl.js +0 -298
- package/dist/node-factory-D7DOnSSN.js +0 -3200
|
@@ -1,534 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ArrayNode - узел формы для работы с массивами
|
|
3
|
-
*
|
|
4
|
-
* Управляет массивом форм с поддержкой:
|
|
5
|
-
* - Динамического добавления/удаления элементов
|
|
6
|
-
* - Валидации всех элементов
|
|
7
|
-
* - Реактивного состояния через signals
|
|
8
|
-
*
|
|
9
|
-
* @group Nodes
|
|
10
|
-
*/
|
|
11
|
-
import { signal, computed, effect } from '@preact/signals-core';
|
|
12
|
-
import { FormNode } from './form-node';
|
|
13
|
-
import { GroupNode } from './group-node';
|
|
14
|
-
import { SubscriptionManager } from '../utils/subscription-manager';
|
|
15
|
-
/**
|
|
16
|
-
* ArrayNode - массив форм с реактивным состоянием
|
|
17
|
-
*
|
|
18
|
-
* @group Nodes
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```typescript
|
|
22
|
-
* const array = new ArrayNode({
|
|
23
|
-
* title: { value: '', component: Input },
|
|
24
|
-
* price: { value: 0, component: Input },
|
|
25
|
-
* });
|
|
26
|
-
*
|
|
27
|
-
* array.push({ title: 'Item 1', price: 100 });
|
|
28
|
-
* array.at(0)?.title.setValue('Updated');
|
|
29
|
-
* console.log(array.length.value); // 1
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export class ArrayNode extends FormNode {
|
|
33
|
-
// ============================================================================
|
|
34
|
-
// Приватные поля
|
|
35
|
-
// ============================================================================
|
|
36
|
-
items;
|
|
37
|
-
itemSchema;
|
|
38
|
-
initialItems;
|
|
39
|
-
/**
|
|
40
|
-
* Менеджер подписок для централизованного cleanup
|
|
41
|
-
* Использует SubscriptionManager вместо массива для управления подписками
|
|
42
|
-
*/
|
|
43
|
-
disposers = new SubscriptionManager();
|
|
44
|
-
// ============================================================================
|
|
45
|
-
// Приватные поля для сохранения схем
|
|
46
|
-
// ============================================================================
|
|
47
|
-
validationSchemaFn;
|
|
48
|
-
behaviorSchemaFn;
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// Публичные computed signals
|
|
51
|
-
// ============================================================================
|
|
52
|
-
value;
|
|
53
|
-
valid;
|
|
54
|
-
invalid;
|
|
55
|
-
touched;
|
|
56
|
-
dirty;
|
|
57
|
-
pending;
|
|
58
|
-
errors;
|
|
59
|
-
status;
|
|
60
|
-
length;
|
|
61
|
-
// ============================================================================
|
|
62
|
-
// Конструктор
|
|
63
|
-
// ============================================================================
|
|
64
|
-
constructor(schema, initialItems = []) {
|
|
65
|
-
super();
|
|
66
|
-
this.itemSchema = schema;
|
|
67
|
-
this.initialItems = initialItems;
|
|
68
|
-
this.items = signal([]);
|
|
69
|
-
// Создать начальные элементы
|
|
70
|
-
for (const initialValue of initialItems) {
|
|
71
|
-
this.push(initialValue);
|
|
72
|
-
}
|
|
73
|
-
// ============================================================================
|
|
74
|
-
// Computed signals
|
|
75
|
-
// ============================================================================
|
|
76
|
-
this.length = computed(() => this.items.value.length);
|
|
77
|
-
this.value = computed(() => this.items.value.map((item) => item.value.value));
|
|
78
|
-
this.valid = computed(() => this.items.value.every((item) => item.valid.value));
|
|
79
|
-
this.invalid = computed(() => !this.valid.value);
|
|
80
|
-
this.pending = computed(() => this.items.value.some((item) => item.pending.value));
|
|
81
|
-
this.touched = computed(() => this.items.value.some((item) => item.touched.value));
|
|
82
|
-
this.dirty = computed(() => this.items.value.some((item) => item.dirty.value));
|
|
83
|
-
this.errors = computed(() => {
|
|
84
|
-
const allErrors = [];
|
|
85
|
-
this.items.value.forEach((item) => {
|
|
86
|
-
allErrors.push(...item.errors.value);
|
|
87
|
-
});
|
|
88
|
-
return allErrors;
|
|
89
|
-
});
|
|
90
|
-
this.status = computed(() => {
|
|
91
|
-
if (this.pending.value)
|
|
92
|
-
return 'pending';
|
|
93
|
-
if (this.invalid.value)
|
|
94
|
-
return 'invalid';
|
|
95
|
-
return 'valid';
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
// ============================================================================
|
|
99
|
-
// CRUD операции
|
|
100
|
-
// ============================================================================
|
|
101
|
-
/**
|
|
102
|
-
* Добавить элемент в конец массива
|
|
103
|
-
* @param initialValue - Начальные значения для нового элемента
|
|
104
|
-
*/
|
|
105
|
-
push(initialValue) {
|
|
106
|
-
const newItem = this.createItem(initialValue);
|
|
107
|
-
this.items.value = [...this.items.value, newItem];
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Удалить элемент по индексу
|
|
111
|
-
* @param index - Индекс элемента для удаления
|
|
112
|
-
*/
|
|
113
|
-
removeAt(index) {
|
|
114
|
-
if (index < 0 || index >= this.items.value.length) {
|
|
115
|
-
if (import.meta.env.DEV) {
|
|
116
|
-
console.warn(`ArrayNode: index ${index} out of bounds (length: ${this.items.value.length})`);
|
|
117
|
-
}
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
this.items.value = this.items.value.filter((_, i) => i !== index);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Вставить элемент в массив
|
|
124
|
-
* @param index - Индекс для вставки
|
|
125
|
-
* @param initialValue - Начальные значения для нового элемента
|
|
126
|
-
*/
|
|
127
|
-
insert(index, initialValue) {
|
|
128
|
-
if (index < 0 || index > this.items.value.length) {
|
|
129
|
-
if (import.meta.env.DEV) {
|
|
130
|
-
console.warn(`ArrayNode: index ${index} out of bounds (length: ${this.items.value.length})`);
|
|
131
|
-
}
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const newItem = this.createItem(initialValue);
|
|
135
|
-
const newItems = [...this.items.value];
|
|
136
|
-
newItems.splice(index, 0, newItem);
|
|
137
|
-
this.items.value = newItems;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Удалить все элементы массива
|
|
141
|
-
*/
|
|
142
|
-
clear() {
|
|
143
|
-
this.items.value = [];
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Получить элемент по индексу
|
|
147
|
-
* @param index - Индекс элемента
|
|
148
|
-
* @returns Типизированный GroupNode или undefined если индекс вне границ
|
|
149
|
-
*/
|
|
150
|
-
at(index) {
|
|
151
|
-
return this.items.value[index];
|
|
152
|
-
}
|
|
153
|
-
// ============================================================================
|
|
154
|
-
// Реализация абстрактных методов
|
|
155
|
-
// ============================================================================
|
|
156
|
-
getValue() {
|
|
157
|
-
return this.items.value.map((item) => item.getValue());
|
|
158
|
-
}
|
|
159
|
-
setValue(values, options) {
|
|
160
|
-
this.clear();
|
|
161
|
-
values.forEach((value) => this.push(value));
|
|
162
|
-
// Запускаем валидацию если emitEvent !== false
|
|
163
|
-
// Fire-and-forget (не ждем результат, как в FieldNode)
|
|
164
|
-
if (options?.emitEvent !== false) {
|
|
165
|
-
this.validate().catch(() => {
|
|
166
|
-
// Игнорируем ошибки валидации (они будут в errors signal)
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
patchValue(values) {
|
|
171
|
-
values.forEach((value, index) => {
|
|
172
|
-
if (this.items.value[index] && value !== undefined) {
|
|
173
|
-
this.items.value[index].patchValue(value);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Сбросить массив к указанным значениям (или очистить)
|
|
179
|
-
*
|
|
180
|
-
* @param values - опциональный массив значений для сброса
|
|
181
|
-
*
|
|
182
|
-
* @remarks
|
|
183
|
-
* Очищает текущий массив и заполняет новыми элементами
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
* ```typescript
|
|
187
|
-
* // Очистить массив
|
|
188
|
-
* arrayNode.reset();
|
|
189
|
-
*
|
|
190
|
-
* // Сбросить к новым значениям
|
|
191
|
-
* arrayNode.reset([{ name: 'Item 1' }, { name: 'Item 2' }]);
|
|
192
|
-
* ```
|
|
193
|
-
*/
|
|
194
|
-
reset(values) {
|
|
195
|
-
this.clear();
|
|
196
|
-
if (values) {
|
|
197
|
-
values.forEach((value) => this.push(value));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Сбросить массив к исходным значениям (initialItems)
|
|
202
|
-
*
|
|
203
|
-
* @remarks
|
|
204
|
-
* Восстанавливает массив в состояние, которое было при создании ArrayNode.
|
|
205
|
-
* Более явный способ сброса к начальным значениям по сравнению с reset()
|
|
206
|
-
*
|
|
207
|
-
* Полезно когда:
|
|
208
|
-
* - Пользователь нажал "Cancel" - вернуть массив к исходным элементам
|
|
209
|
-
* - Массив был изменен через reset(newValues), но нужно вернуться к началу
|
|
210
|
-
* - Явное намерение показать "отмена всех изменений"
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* ```typescript
|
|
214
|
-
* const arrayNode = new ArrayNode(
|
|
215
|
-
* { name: { value: '', component: Input } },
|
|
216
|
-
* [{ name: 'Initial 1' }, { name: 'Initial 2' }]
|
|
217
|
-
* );
|
|
218
|
-
*
|
|
219
|
-
* arrayNode.push({ name: 'New Item' });
|
|
220
|
-
* arrayNode.reset([{ name: 'Temp' }]);
|
|
221
|
-
* console.log(arrayNode.length.value); // 1
|
|
222
|
-
*
|
|
223
|
-
* arrayNode.resetToInitial();
|
|
224
|
-
* console.log(arrayNode.length.value); // 2
|
|
225
|
-
* console.log(arrayNode.at(0)?.name.value.value); // 'Initial 1'
|
|
226
|
-
* ```
|
|
227
|
-
*/
|
|
228
|
-
resetToInitial() {
|
|
229
|
-
this.clear();
|
|
230
|
-
this.initialItems.forEach((value) => this.push(value));
|
|
231
|
-
}
|
|
232
|
-
async validate() {
|
|
233
|
-
const results = await Promise.all(this.items.value.map((item) => item.validate()));
|
|
234
|
-
return results.every(Boolean);
|
|
235
|
-
}
|
|
236
|
-
setErrors(_errors) {
|
|
237
|
-
// ArrayNode level errors - можно реализовать позже
|
|
238
|
-
// Пока просто игнорируем
|
|
239
|
-
}
|
|
240
|
-
clearErrors() {
|
|
241
|
-
this.items.value.forEach((item) => item.clearErrors());
|
|
242
|
-
}
|
|
243
|
-
// ============================================================================
|
|
244
|
-
// Protected hooks (Template Method pattern)
|
|
245
|
-
// ============================================================================
|
|
246
|
-
/**
|
|
247
|
-
* Hook: вызывается после markAsTouched()
|
|
248
|
-
*
|
|
249
|
-
* Для ArrayNode: рекурсивно помечаем все элементы массива как touched
|
|
250
|
-
*/
|
|
251
|
-
onMarkAsTouched() {
|
|
252
|
-
this.items.value.forEach((item) => item.markAsTouched());
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Hook: вызывается после markAsUntouched()
|
|
256
|
-
*
|
|
257
|
-
* Для ArrayNode: рекурсивно помечаем все элементы массива как untouched
|
|
258
|
-
*/
|
|
259
|
-
onMarkAsUntouched() {
|
|
260
|
-
this.items.value.forEach((item) => item.markAsUntouched());
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Hook: вызывается после markAsDirty()
|
|
264
|
-
*
|
|
265
|
-
* Для ArrayNode: рекурсивно помечаем все элементы массива как dirty
|
|
266
|
-
*/
|
|
267
|
-
onMarkAsDirty() {
|
|
268
|
-
this.items.value.forEach((item) => item.markAsDirty());
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Hook: вызывается после markAsPristine()
|
|
272
|
-
*
|
|
273
|
-
* Для ArrayNode: рекурсивно помечаем все элементы массива как pristine
|
|
274
|
-
*/
|
|
275
|
-
onMarkAsPristine() {
|
|
276
|
-
this.items.value.forEach((item) => item.markAsPristine());
|
|
277
|
-
}
|
|
278
|
-
// ============================================================================
|
|
279
|
-
// Итерация
|
|
280
|
-
// ============================================================================
|
|
281
|
-
/**
|
|
282
|
-
* Итерировать по элементам массива
|
|
283
|
-
* @param callback - Функция, вызываемая для каждого элемента с типизированным GroupNode
|
|
284
|
-
*/
|
|
285
|
-
forEach(callback) {
|
|
286
|
-
this.items.value.forEach((item, index) => {
|
|
287
|
-
callback(item, index);
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Маппинг элементов массива
|
|
292
|
-
* @param callback - Функция преобразования с типизированным GroupNode
|
|
293
|
-
* @returns Новый массив результатов
|
|
294
|
-
*/
|
|
295
|
-
map(callback) {
|
|
296
|
-
return this.items.value.map((item, index) => {
|
|
297
|
-
return callback(item, index);
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
// ============================================================================
|
|
301
|
-
// Private методы
|
|
302
|
-
// ============================================================================
|
|
303
|
-
/**
|
|
304
|
-
* Создать новый элемент массива на основе схемы
|
|
305
|
-
* @param initialValue - Начальные значения
|
|
306
|
-
*/
|
|
307
|
-
createItem(initialValue) {
|
|
308
|
-
// Определить тип узла на основе схемы
|
|
309
|
-
if (this.isGroupSchema(this.itemSchema)) {
|
|
310
|
-
const node = new GroupNode(this.itemSchema);
|
|
311
|
-
if (initialValue) {
|
|
312
|
-
node.patchValue(initialValue);
|
|
313
|
-
}
|
|
314
|
-
// Применяем validation schema к новому элементу, если она была установлена
|
|
315
|
-
if (this.validationSchemaFn && 'applyValidationSchema' in node) {
|
|
316
|
-
node.applyValidationSchema(this.validationSchemaFn);
|
|
317
|
-
}
|
|
318
|
-
// Применяем behavior schema к новому элементу, если она была установлена
|
|
319
|
-
if (this.behaviorSchemaFn && 'applyBehaviorSchema' in node) {
|
|
320
|
-
node.applyBehaviorSchema(this.behaviorSchemaFn);
|
|
321
|
-
}
|
|
322
|
-
return node;
|
|
323
|
-
}
|
|
324
|
-
// Если схема - FieldConfig, ArrayNode не поддерживает примитивные массивы
|
|
325
|
-
throw new Error('ArrayNode поддерживает только GroupNode элементы. ' +
|
|
326
|
-
'Для массива примитивов используйте обычное поле с типом массива.');
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Проверить, является ли схема групповой (объект полей)
|
|
330
|
-
* @param schema - Схема для проверки
|
|
331
|
-
*/
|
|
332
|
-
isGroupSchema(schema) {
|
|
333
|
-
return (typeof schema === 'object' &&
|
|
334
|
-
schema !== null &&
|
|
335
|
-
!('component' in schema) &&
|
|
336
|
-
!Array.isArray(schema));
|
|
337
|
-
}
|
|
338
|
-
// ============================================================================
|
|
339
|
-
// Validation Schema
|
|
340
|
-
// ============================================================================
|
|
341
|
-
/**
|
|
342
|
-
* Применить validation schema ко всем элементам массива
|
|
343
|
-
*
|
|
344
|
-
* Validation schema будет применена к:
|
|
345
|
-
* - Всем существующим элементам
|
|
346
|
-
* - Всем новым элементам, добавляемым через push/insert
|
|
347
|
-
*
|
|
348
|
-
* @param schemaFn - Функция валидации для элемента массива
|
|
349
|
-
*
|
|
350
|
-
* @example
|
|
351
|
-
* ```typescript
|
|
352
|
-
* import { propertyValidation } from './validation/property-validation';
|
|
353
|
-
*
|
|
354
|
-
* form.properties.applyValidationSchema(propertyValidation);
|
|
355
|
-
* ```
|
|
356
|
-
*/
|
|
357
|
-
applyValidationSchema(schemaFn) {
|
|
358
|
-
// Сохраняем validation schema для применения к новым элементам
|
|
359
|
-
this.validationSchemaFn = schemaFn;
|
|
360
|
-
// Применяем validation schema ко всем существующим элементам
|
|
361
|
-
this.items.value.forEach((item) => {
|
|
362
|
-
if ('applyValidationSchema' in item &&
|
|
363
|
-
typeof item.applyValidationSchema === 'function') {
|
|
364
|
-
item.applyValidationSchema(schemaFn);
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Применить behavior schema ко всем элементам ArrayNode
|
|
370
|
-
*
|
|
371
|
-
* Автоматически применяется к новым элементам при push/insert.
|
|
372
|
-
*
|
|
373
|
-
* @param schemaFn - Behavior schema функция
|
|
374
|
-
*
|
|
375
|
-
* @example
|
|
376
|
-
* ```typescript
|
|
377
|
-
* import { addressBehavior } from './behaviors/address-behavior';
|
|
378
|
-
*
|
|
379
|
-
* form.addresses.applyBehaviorSchema(addressBehavior);
|
|
380
|
-
* ```
|
|
381
|
-
*/
|
|
382
|
-
applyBehaviorSchema(schemaFn) {
|
|
383
|
-
// Сохраняем behavior schema для применения к новым элементам
|
|
384
|
-
this.behaviorSchemaFn = schemaFn;
|
|
385
|
-
// Применяем behavior schema ко всем существующим элементам
|
|
386
|
-
this.items.value.forEach((item) => {
|
|
387
|
-
if ('applyBehaviorSchema' in item &&
|
|
388
|
-
typeof item.applyBehaviorSchema === 'function') {
|
|
389
|
-
item.applyBehaviorSchema(schemaFn);
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
// ============================================================================
|
|
394
|
-
// Методы-помощники для реактивности (Фаза 1)
|
|
395
|
-
// ============================================================================
|
|
396
|
-
/**
|
|
397
|
-
* Подписка на изменения конкретного поля во всех элементах массива
|
|
398
|
-
* Срабатывает при изменении значения поля в любом элементе
|
|
399
|
-
*
|
|
400
|
-
* @param fieldKey - Ключ поля для отслеживания
|
|
401
|
-
* @param callback - Функция, вызываемая при изменении, получает массив всех значений и индекс измененного элемента
|
|
402
|
-
* @returns Функция отписки для cleanup
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* ```typescript
|
|
406
|
-
* // Автоматический пересчет общей стоимости при изменении цен
|
|
407
|
-
* const dispose = form.existingLoans.watchItems(
|
|
408
|
-
* 'remainingAmount',
|
|
409
|
-
* (amounts) => {
|
|
410
|
-
* const totalDebt = amounts.reduce((sum, amount) => sum + (amount || 0), 0);
|
|
411
|
-
* form.totalDebt.setValue(totalDebt);
|
|
412
|
-
* }
|
|
413
|
-
* );
|
|
414
|
-
*
|
|
415
|
-
* // При изменении любого remainingAmount → пересчитается totalDebt
|
|
416
|
-
* form.existingLoans.at(0)?.remainingAmount.setValue(500000);
|
|
417
|
-
*
|
|
418
|
-
* // Cleanup
|
|
419
|
-
* useEffect(() => dispose, []);
|
|
420
|
-
* ```
|
|
421
|
-
*/
|
|
422
|
-
watchItems(fieldKey, callback) {
|
|
423
|
-
const dispose = effect(() => {
|
|
424
|
-
// Отслеживаем изменения всех элементов массива
|
|
425
|
-
const values = this.items.value.map((item) => {
|
|
426
|
-
if (item instanceof GroupNode) {
|
|
427
|
-
const field = item.getFieldByPath(fieldKey);
|
|
428
|
-
return field?.value.value;
|
|
429
|
-
}
|
|
430
|
-
return undefined;
|
|
431
|
-
});
|
|
432
|
-
callback(values);
|
|
433
|
-
});
|
|
434
|
-
// Регистрируем через SubscriptionManager и возвращаем unsubscribe
|
|
435
|
-
const key = `watchItems-${Date.now()}-${Math.random()}`;
|
|
436
|
-
return this.disposers.add(key, dispose);
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Подписка на изменение длины массива
|
|
440
|
-
* Срабатывает при добавлении/удалении элементов
|
|
441
|
-
*
|
|
442
|
-
* @param callback - Функция, вызываемая при изменении длины, получает новую длину
|
|
443
|
-
* @returns Функция отписки для cleanup
|
|
444
|
-
*
|
|
445
|
-
* @example
|
|
446
|
-
* ```typescript
|
|
447
|
-
* // Обновление счетчика элементов в UI
|
|
448
|
-
* const dispose = form.properties.watchLength((length) => {
|
|
449
|
-
* console.log(`Количество объектов недвижимости: ${length}`);
|
|
450
|
-
* form.propertyCount.setValue(length);
|
|
451
|
-
* });
|
|
452
|
-
*
|
|
453
|
-
* form.properties.push({ title: 'Квартира', value: 5000000 });
|
|
454
|
-
* // Выведет: "Количество объектов недвижимости: 1"
|
|
455
|
-
*
|
|
456
|
-
* // Cleanup
|
|
457
|
-
* useEffect(() => dispose, []);
|
|
458
|
-
* ```
|
|
459
|
-
*/
|
|
460
|
-
watchLength(callback) {
|
|
461
|
-
const dispose = effect(() => {
|
|
462
|
-
const currentLength = this.length.value;
|
|
463
|
-
callback(currentLength);
|
|
464
|
-
});
|
|
465
|
-
// Регистрируем через SubscriptionManager и возвращаем unsubscribe
|
|
466
|
-
const key = `watchLength-${Date.now()}-${Math.random()}`;
|
|
467
|
-
return this.disposers.add(key, dispose);
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* Очистить все ресурсы узла
|
|
471
|
-
* Рекурсивно очищает все subscriptions и элементы массива
|
|
472
|
-
*
|
|
473
|
-
* @example
|
|
474
|
-
* ```typescript
|
|
475
|
-
* useEffect(() => {
|
|
476
|
-
* return () => {
|
|
477
|
-
* arrayNode.dispose();
|
|
478
|
-
* };
|
|
479
|
-
* }, []);
|
|
480
|
-
* ```
|
|
481
|
-
*/
|
|
482
|
-
dispose() {
|
|
483
|
-
// Очищаем все subscriptions через SubscriptionManager
|
|
484
|
-
this.disposers.dispose();
|
|
485
|
-
// Очищаем элементы массива
|
|
486
|
-
this.items.value.forEach((item) => {
|
|
487
|
-
if ('dispose' in item && typeof item.dispose === 'function') {
|
|
488
|
-
item.dispose();
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Hook: вызывается после disable()
|
|
494
|
-
*
|
|
495
|
-
* Для ArrayNode: рекурсивно отключаем все элементы массива
|
|
496
|
-
*
|
|
497
|
-
* @example
|
|
498
|
-
* ```typescript
|
|
499
|
-
* // Отключить весь массив полей
|
|
500
|
-
* form.items.disable();
|
|
501
|
-
*
|
|
502
|
-
* // Все элементы становятся disabled
|
|
503
|
-
* form.items.forEach(item => {
|
|
504
|
-
* console.log(item.status.value); // 'disabled'
|
|
505
|
-
* });
|
|
506
|
-
* ```
|
|
507
|
-
*/
|
|
508
|
-
onDisable() {
|
|
509
|
-
this.items.value.forEach((item) => {
|
|
510
|
-
item.disable();
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Hook: вызывается после enable()
|
|
515
|
-
*
|
|
516
|
-
* Для ArrayNode: рекурсивно включаем все элементы массива
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* ```typescript
|
|
520
|
-
* // Включить весь массив полей
|
|
521
|
-
* form.items.enable();
|
|
522
|
-
*
|
|
523
|
-
* // Все элементы становятся enabled
|
|
524
|
-
* form.items.forEach(item => {
|
|
525
|
-
* console.log(item.status.value); // 'valid' или 'invalid'
|
|
526
|
-
* });
|
|
527
|
-
* ```
|
|
528
|
-
*/
|
|
529
|
-
onEnable() {
|
|
530
|
-
this.items.value.forEach((item) => {
|
|
531
|
-
item.enable();
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
}
|