@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.
- 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,374 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Навигация по путям к полям формы
|
|
3
|
-
*
|
|
4
|
-
* Централизует логику парсинга и навигации по путям к полям формы.
|
|
5
|
-
* Используется в ValidationContext, BehaviorContext, GroupNode для единообразной
|
|
6
|
-
* обработки путей вида "address.city" или "items[0].name".
|
|
7
|
-
*
|
|
8
|
-
* Устраняет дублирование логики парсинга путей в 4 местах кодовой базы.
|
|
9
|
-
*
|
|
10
|
-
* @internal
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* const navigator = new FieldPathNavigator();
|
|
15
|
-
*
|
|
16
|
-
* // Парсинг пути
|
|
17
|
-
* const segments = navigator.parsePath('items[0].title');
|
|
18
|
-
* // [{ key: 'items', index: 0 }, { key: 'title' }]
|
|
19
|
-
*
|
|
20
|
-
* // Получение значения из объекта
|
|
21
|
-
* const obj = { items: [{ title: 'Item 1' }] };
|
|
22
|
-
* const value = navigator.getValueByPath(obj, 'items[0].title');
|
|
23
|
-
* // 'Item 1'
|
|
24
|
-
*
|
|
25
|
-
* // Получение узла формы
|
|
26
|
-
* const titleNode = navigator.getNodeByPath(form, 'items[0].title');
|
|
27
|
-
* titleNode?.setValue('New Title');
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export class FieldPathNavigator {
|
|
31
|
-
/**
|
|
32
|
-
* Парсит путь в массив сегментов
|
|
33
|
-
*
|
|
34
|
-
* Поддерживаемые форматы:
|
|
35
|
-
* - Простые пути: "name", "email"
|
|
36
|
-
* - Вложенные пути: "address.city", "user.profile.avatar"
|
|
37
|
-
* - Массивы: "items[0]", "items[0].name", "tags[1][0]"
|
|
38
|
-
* - Комбинации: "orders[0].items[1].price"
|
|
39
|
-
*
|
|
40
|
-
* @param path Путь к полю (строка с точками и квадратными скобками)
|
|
41
|
-
* @returns Массив сегментов пути
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```typescript
|
|
45
|
-
* navigator.parsePath('email');
|
|
46
|
-
* // [{ key: 'email' }]
|
|
47
|
-
*
|
|
48
|
-
* navigator.parsePath('address.city');
|
|
49
|
-
* // [{ key: 'address' }, { key: 'city' }]
|
|
50
|
-
*
|
|
51
|
-
* navigator.parsePath('items[0].name');
|
|
52
|
-
* // [{ key: 'items', index: 0 }, { key: 'name' }]
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
parsePath(path) {
|
|
56
|
-
const segments = [];
|
|
57
|
-
let currentPart = '';
|
|
58
|
-
let inBrackets = false;
|
|
59
|
-
for (let i = 0; i < path.length; i++) {
|
|
60
|
-
const char = path[i];
|
|
61
|
-
if (char === '[') {
|
|
62
|
-
inBrackets = true;
|
|
63
|
-
currentPart += char;
|
|
64
|
-
}
|
|
65
|
-
else if (char === ']') {
|
|
66
|
-
inBrackets = false;
|
|
67
|
-
currentPart += char;
|
|
68
|
-
}
|
|
69
|
-
else if (char === '.' && !inBrackets) {
|
|
70
|
-
// Разделитель найден, обрабатываем накопленную часть
|
|
71
|
-
if (currentPart) {
|
|
72
|
-
this.addSegment(segments, currentPart);
|
|
73
|
-
currentPart = '';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
currentPart += char;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// Добавляем последнюю часть
|
|
81
|
-
if (currentPart) {
|
|
82
|
-
this.addSegment(segments, currentPart);
|
|
83
|
-
}
|
|
84
|
-
return segments;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Добавляет сегмент в массив, обрабатывая массивы
|
|
88
|
-
* @private
|
|
89
|
-
*/
|
|
90
|
-
addSegment(segments, part) {
|
|
91
|
-
// Проверка на массив: items[0]
|
|
92
|
-
const arrayMatch = part.match(/^(.+)\[(\d+)\]$/);
|
|
93
|
-
if (arrayMatch) {
|
|
94
|
-
segments.push({
|
|
95
|
-
key: arrayMatch[1],
|
|
96
|
-
index: parseInt(arrayMatch[2], 10),
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
segments.push({ key: part });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Получает значение по пути из объекта
|
|
105
|
-
*
|
|
106
|
-
* Проходит по всем сегментам пути и возвращает конечное значение.
|
|
107
|
-
* Если путь не найден, возвращает undefined.
|
|
108
|
-
*
|
|
109
|
-
* @param obj Объект для навигации
|
|
110
|
-
* @param path Путь к значению
|
|
111
|
-
* @returns Значение или undefined, если путь не найден
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```typescript
|
|
115
|
-
* const obj = {
|
|
116
|
-
* email: 'test@mail.com',
|
|
117
|
-
* address: { city: 'Moscow' },
|
|
118
|
-
* items: [{ title: 'Item 1' }]
|
|
119
|
-
* };
|
|
120
|
-
*
|
|
121
|
-
* navigator.getValueByPath(obj, 'email');
|
|
122
|
-
* // 'test@mail.com'
|
|
123
|
-
*
|
|
124
|
-
* navigator.getValueByPath(obj, 'address.city');
|
|
125
|
-
* // 'Moscow'
|
|
126
|
-
*
|
|
127
|
-
* navigator.getValueByPath(obj, 'items[0].title');
|
|
128
|
-
* // 'Item 1'
|
|
129
|
-
*
|
|
130
|
-
* navigator.getValueByPath(obj, 'invalid.path');
|
|
131
|
-
* // undefined
|
|
132
|
-
* ```
|
|
133
|
-
*/
|
|
134
|
-
getValueByPath(obj, path) {
|
|
135
|
-
const segments = this.parsePath(path);
|
|
136
|
-
let current = obj;
|
|
137
|
-
for (const segment of segments) {
|
|
138
|
-
if (current == null)
|
|
139
|
-
return undefined;
|
|
140
|
-
current = current[segment.key];
|
|
141
|
-
if (segment.index !== undefined) {
|
|
142
|
-
if (!Array.isArray(current))
|
|
143
|
-
return undefined;
|
|
144
|
-
current = current[segment.index];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return current;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Устанавливает значение по пути в объекте (мутирует объект)
|
|
151
|
-
*
|
|
152
|
-
* Создает промежуточные объекты, если они не существуют.
|
|
153
|
-
* Выбрасывает ошибку, если ожидается массив, но его нет.
|
|
154
|
-
*
|
|
155
|
-
* @param obj Объект для модификации
|
|
156
|
-
* @param path Путь к значению
|
|
157
|
-
* @param value Новое значение
|
|
158
|
-
*
|
|
159
|
-
* @throws {Error} Если ожидается массив по пути, но его нет
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* ```typescript
|
|
163
|
-
* const obj = { address: { city: '' } };
|
|
164
|
-
* navigator.setValueByPath(obj, 'address.city', 'Moscow');
|
|
165
|
-
* // obj.address.city === 'Moscow'
|
|
166
|
-
*
|
|
167
|
-
* const obj2: UnknownRecord = {};
|
|
168
|
-
* navigator.setValueByPath(obj2, 'address.city', 'Moscow');
|
|
169
|
-
* // Создаст { address: { city: 'Moscow' } }
|
|
170
|
-
*
|
|
171
|
-
* const obj3 = { items: [{ title: 'Old' }] };
|
|
172
|
-
* navigator.setValueByPath(obj3, 'items[0].title', 'New');
|
|
173
|
-
* // obj3.items[0].title === 'New'
|
|
174
|
-
* ```
|
|
175
|
-
*/
|
|
176
|
-
setValueByPath(obj, path, value) {
|
|
177
|
-
const segments = this.parsePath(path);
|
|
178
|
-
if (segments.length === 0) {
|
|
179
|
-
throw new Error('Cannot set value: empty path');
|
|
180
|
-
}
|
|
181
|
-
let current = obj;
|
|
182
|
-
// Проходим до предпоследнего сегмента
|
|
183
|
-
for (let i = 0; i < segments.length - 1; i++) {
|
|
184
|
-
const segment = segments[i];
|
|
185
|
-
let next = current[segment.key];
|
|
186
|
-
if (segment.index !== undefined) {
|
|
187
|
-
// Доступ к массиву: items[0]
|
|
188
|
-
if (!Array.isArray(next)) {
|
|
189
|
-
throw new Error(`Expected array at path segment: ${segment.key}, but got ${typeof next}`);
|
|
190
|
-
}
|
|
191
|
-
current = next[segment.index];
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
// Доступ к объекту: address
|
|
195
|
-
if (next == null) {
|
|
196
|
-
// Создаем объект, если его нет
|
|
197
|
-
current[segment.key] = {};
|
|
198
|
-
next = current[segment.key];
|
|
199
|
-
}
|
|
200
|
-
current = next;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
// Устанавливаем значение в последнем сегменте
|
|
204
|
-
const lastSegment = segments[segments.length - 1];
|
|
205
|
-
if (lastSegment.index !== undefined) {
|
|
206
|
-
const arr = current[lastSegment.key];
|
|
207
|
-
if (!Array.isArray(arr)) {
|
|
208
|
-
throw new Error(`Expected array at path segment: ${lastSegment.key}, but got ${typeof arr}`);
|
|
209
|
-
}
|
|
210
|
-
arr[lastSegment.index] = value;
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
current[lastSegment.key] = value;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Получить значение из FormNode по пути
|
|
218
|
-
*
|
|
219
|
-
* Автоматически извлекает значение из FormNode (через .value.value).
|
|
220
|
-
* Используется в ValidationContext и BehaviorContext для единообразного
|
|
221
|
-
* доступа к значениям полей формы.
|
|
222
|
-
*
|
|
223
|
-
* @param form Корневой узел формы (обычно GroupNode)
|
|
224
|
-
* @param path Путь к полю
|
|
225
|
-
* @returns Значение поля или undefined, если путь не найден
|
|
226
|
-
*
|
|
227
|
-
* @example
|
|
228
|
-
* ```typescript
|
|
229
|
-
* const form = new GroupNode({
|
|
230
|
-
* email: { value: 'test@mail.com', component: Input },
|
|
231
|
-
* address: {
|
|
232
|
-
* city: { value: 'Moscow', component: Input }
|
|
233
|
-
* },
|
|
234
|
-
* items: [{ title: { value: 'Item 1', component: Input } }]
|
|
235
|
-
* });
|
|
236
|
-
*
|
|
237
|
-
* navigator.getFormNodeValue(form, 'email');
|
|
238
|
-
* // 'test@mail.com'
|
|
239
|
-
*
|
|
240
|
-
* navigator.getFormNodeValue(form, 'address.city');
|
|
241
|
-
* // 'Moscow'
|
|
242
|
-
*
|
|
243
|
-
* navigator.getFormNodeValue(form, 'items[0].title');
|
|
244
|
-
* // 'Item 1'
|
|
245
|
-
*
|
|
246
|
-
* navigator.getFormNodeValue(form, 'invalid.path');
|
|
247
|
-
* // undefined
|
|
248
|
-
* ```
|
|
249
|
-
*/
|
|
250
|
-
getFormNodeValue(form, path) {
|
|
251
|
-
const node = this.getNodeByPath(form, path);
|
|
252
|
-
if (node == null) {
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
// FormNode возвращает .value.value
|
|
256
|
-
if (this.isFormNode(node)) {
|
|
257
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
|
-
return node.value.value;
|
|
259
|
-
}
|
|
260
|
-
// Для обычных объектов возвращаем как есть
|
|
261
|
-
return node;
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Type guard для проверки, является ли объект FormNode
|
|
265
|
-
*
|
|
266
|
-
* Проверяет наличие характерных свойств FormNode:
|
|
267
|
-
* - value (Signal)
|
|
268
|
-
* - value.value (значение Signal)
|
|
269
|
-
*
|
|
270
|
-
* @param obj Объект для проверки
|
|
271
|
-
* @returns true, если объект является FormNode
|
|
272
|
-
* @private
|
|
273
|
-
*/
|
|
274
|
-
isFormNode(obj) {
|
|
275
|
-
return (obj != null &&
|
|
276
|
-
typeof obj === 'object' &&
|
|
277
|
-
'value' in obj &&
|
|
278
|
-
typeof obj.value === 'object' &&
|
|
279
|
-
obj.value != null &&
|
|
280
|
-
'value' in obj.value);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Получает узел формы по пути
|
|
284
|
-
*
|
|
285
|
-
* Навигирует по структуре FormNode (GroupNode/FieldNode/ArrayNode)
|
|
286
|
-
* и возвращает узел по указанному пути.
|
|
287
|
-
*
|
|
288
|
-
* Поддерживает:
|
|
289
|
-
* - Доступ к полям GroupNode через fields Map
|
|
290
|
-
* - Доступ к элементам ArrayNode через индекс
|
|
291
|
-
* - Proxy-доступ к полям (для обратной совместимости)
|
|
292
|
-
*
|
|
293
|
-
* @param form Корневой узел формы (обычно GroupNode)
|
|
294
|
-
* @param path Путь к узлу
|
|
295
|
-
* @returns Узел формы или null, если путь не найден
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* ```typescript
|
|
299
|
-
* const form = new GroupNode({
|
|
300
|
-
* email: { value: '', component: Input },
|
|
301
|
-
* address: {
|
|
302
|
-
* city: { value: '', component: Input }
|
|
303
|
-
* },
|
|
304
|
-
* items: [{ title: { value: '', component: Input } }]
|
|
305
|
-
* });
|
|
306
|
-
*
|
|
307
|
-
* const emailNode = navigator.getNodeByPath(form, 'email');
|
|
308
|
-
* // FieldNode
|
|
309
|
-
*
|
|
310
|
-
* const cityNode = navigator.getNodeByPath(form, 'address.city');
|
|
311
|
-
* // FieldNode
|
|
312
|
-
*
|
|
313
|
-
* const itemNode = navigator.getNodeByPath(form, 'items[0]');
|
|
314
|
-
* // GroupNode
|
|
315
|
-
*
|
|
316
|
-
* const titleNode = navigator.getNodeByPath(form, 'items[0].title');
|
|
317
|
-
* // FieldNode
|
|
318
|
-
*
|
|
319
|
-
* const invalidNode = navigator.getNodeByPath(form, 'invalid.path');
|
|
320
|
-
* // null
|
|
321
|
-
* ```
|
|
322
|
-
*/
|
|
323
|
-
getNodeByPath(form, path) {
|
|
324
|
-
const segments = this.parsePath(path);
|
|
325
|
-
let current = form;
|
|
326
|
-
for (const segment of segments) {
|
|
327
|
-
if (current == null)
|
|
328
|
-
return null;
|
|
329
|
-
const currentRecord = current;
|
|
330
|
-
// Для GroupNode: доступ через fields Map
|
|
331
|
-
if (currentRecord.fields && currentRecord.fields instanceof Map) {
|
|
332
|
-
current = currentRecord.fields.get(segment.key);
|
|
333
|
-
// Если это не ArrayNode или нет индекса, продолжаем
|
|
334
|
-
if (segment.index === undefined) {
|
|
335
|
-
if (current == null)
|
|
336
|
-
return null;
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
// Для ArrayNode: доступ через items и индекс
|
|
341
|
-
else if (segment.index !== undefined && currentRecord.items) {
|
|
342
|
-
const items = currentRecord.items.value || currentRecord.items;
|
|
343
|
-
if (!Array.isArray(items))
|
|
344
|
-
return null;
|
|
345
|
-
current = items[segment.index];
|
|
346
|
-
if (current == null)
|
|
347
|
-
return null;
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
// Proxy-доступ (для обратной совместимости)
|
|
351
|
-
else if (segment.index === undefined) {
|
|
352
|
-
current = currentRecord[segment.key];
|
|
353
|
-
if (current == null)
|
|
354
|
-
return null;
|
|
355
|
-
continue;
|
|
356
|
-
}
|
|
357
|
-
// Если нашли ArrayNode и есть индекс, получаем элемент массива
|
|
358
|
-
if (current && segment.index !== undefined && current.items) {
|
|
359
|
-
const items = current.items.value ||
|
|
360
|
-
current.items;
|
|
361
|
-
if (!Array.isArray(items))
|
|
362
|
-
return null;
|
|
363
|
-
current = items[segment.index];
|
|
364
|
-
}
|
|
365
|
-
// Если запрашивается индекс, но узел не является ArrayNode, возвращаем null
|
|
366
|
-
else if (current && segment.index !== undefined && !current.items) {
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
if (current == null)
|
|
370
|
-
return null;
|
|
371
|
-
}
|
|
372
|
-
return current;
|
|
373
|
-
}
|
|
374
|
-
}
|
package/dist/core/utils/index.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Утилиты для работы с формами
|
|
3
|
-
*
|
|
4
|
-
* Централизованные вспомогательные классы и функции.
|
|
5
|
-
*/
|
|
6
|
-
export { FieldPathNavigator } from './field-path-navigator';
|
|
7
|
-
export { SubscriptionManager } from './subscription-manager';
|
|
8
|
-
export { getCurrentValidationRegistry, getCurrentBehaviorRegistry } from './registry-helpers';
|
|
9
|
-
export { RegistryStack } from './registry-stack';
|
|
10
|
-
export { isFormNode, isFieldNode, isGroupNode, isArrayNode, getNodeType } from './type-guards';
|
|
11
|
-
export { Debouncer } from './debounce';
|
|
12
|
-
export { FormErrorHandler, ErrorStrategy } from './error-handler';
|
|
13
|
-
export { createForm } from './create-form';
|
|
14
|
-
export * from './resources';
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Вспомогательные функции для работы с реестрами
|
|
3
|
-
*
|
|
4
|
-
* Централизует логику получения текущего активного реестра валидации/поведения
|
|
5
|
-
* из context stack, избегая дублирования кода в schema-validators.ts,
|
|
6
|
-
* array-validators.ts и schema-behaviors.ts
|
|
7
|
-
*/
|
|
8
|
-
import { ValidationRegistry } from '../validation/validation-registry';
|
|
9
|
-
import { BehaviorRegistry } from '../behavior/behavior-registry';
|
|
10
|
-
/**
|
|
11
|
-
* Получить текущий активный ValidationRegistry из context stack
|
|
12
|
-
*
|
|
13
|
-
* Используется внутри validation schema функций (validate, validateAsync, required и т.д.)
|
|
14
|
-
* для регистрации валидаторов в активном контексте GroupNode
|
|
15
|
-
*
|
|
16
|
-
* @returns Текущий ValidationRegistry или заглушка в production
|
|
17
|
-
* @throws Error если нет активного контекста (только в DEV режиме)
|
|
18
|
-
*
|
|
19
|
-
* @internal
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```typescript
|
|
23
|
-
* function required(fieldPath, options) {
|
|
24
|
-
* const registry = getCurrentValidationRegistry();
|
|
25
|
-
* registry.registerSync(path, validator, options);
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export function getCurrentValidationRegistry() {
|
|
30
|
-
const registry = ValidationRegistry.getCurrent();
|
|
31
|
-
if (!registry) {
|
|
32
|
-
if (import.meta.env.DEV) {
|
|
33
|
-
throw new Error('No active ValidationRegistry context. Make sure to call applyValidationSchema() within a GroupNode context.');
|
|
34
|
-
}
|
|
35
|
-
// В production возвращаем заглушку чтобы не ломать приложение
|
|
36
|
-
return {
|
|
37
|
-
registerSync: () => { },
|
|
38
|
-
registerAsync: () => { },
|
|
39
|
-
registerTree: () => { },
|
|
40
|
-
enterCondition: () => { },
|
|
41
|
-
exitCondition: () => { },
|
|
42
|
-
registerArrayItemValidation: () => { },
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
return registry;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Получить текущий активный BehaviorRegistry из context stack
|
|
49
|
-
*
|
|
50
|
-
* Используется внутри behavior schema функций (copyFrom, enableWhen, computeFrom и т.д.)
|
|
51
|
-
* для регистрации поведений в активном контексте GroupNode
|
|
52
|
-
*
|
|
53
|
-
* @returns Текущий BehaviorRegistry или заглушка в production
|
|
54
|
-
* @throws Error если нет активного контекста (только в DEV режиме)
|
|
55
|
-
*
|
|
56
|
-
* @internal
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```typescript
|
|
60
|
-
* function copyFrom(target, source, options) {
|
|
61
|
-
* const registry = getCurrentBehaviorRegistry();
|
|
62
|
-
* const handler = createCopyBehavior(target, source, options);
|
|
63
|
-
* registry.register(handler, { debounce: options?.debounce });
|
|
64
|
-
* }
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
export function getCurrentBehaviorRegistry() {
|
|
68
|
-
const registry = BehaviorRegistry.getCurrent();
|
|
69
|
-
if (!registry) {
|
|
70
|
-
if (import.meta.env.DEV) {
|
|
71
|
-
throw new Error('No active BehaviorRegistry context. Make sure to call applyBehaviorSchema() within a GroupNode context.');
|
|
72
|
-
}
|
|
73
|
-
// В production возвращаем заглушку чтобы не ломать приложение
|
|
74
|
-
return {
|
|
75
|
-
register: () => { },
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
return registry;
|
|
79
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic Registry Stack - утилита для управления стеком регистрации
|
|
3
|
-
*
|
|
4
|
-
* Используется ValidationRegistry и BehaviorRegistry для tracking активного контекста.
|
|
5
|
-
* Устраняет дублирование кода между параллельными системами.
|
|
6
|
-
*
|
|
7
|
-
* @template T - Тип элементов в стеке (ValidationRegistry или BehaviorRegistry)
|
|
8
|
-
*
|
|
9
|
-
* @internal
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```typescript
|
|
13
|
-
* class ValidationRegistry {
|
|
14
|
-
* private static registryStack = new RegistryStack<ValidationRegistry>();
|
|
15
|
-
*
|
|
16
|
-
* static getCurrent() {
|
|
17
|
-
* return ValidationRegistry.registryStack.getCurrent();
|
|
18
|
-
* }
|
|
19
|
-
*
|
|
20
|
-
* beginRegistration() {
|
|
21
|
-
* ValidationRegistry.registryStack.push(this);
|
|
22
|
-
* }
|
|
23
|
-
*
|
|
24
|
-
* endRegistration() {
|
|
25
|
-
* ValidationRegistry.registryStack.verify(this, 'ValidationRegistry');
|
|
26
|
-
* }
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export class RegistryStack {
|
|
31
|
-
stack = [];
|
|
32
|
-
/**
|
|
33
|
-
* Добавить элемент в стек
|
|
34
|
-
* @param item - Элемент для добавления
|
|
35
|
-
*/
|
|
36
|
-
push(item) {
|
|
37
|
-
this.stack.push(item);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Извлечь элемент из стека
|
|
41
|
-
* @returns Извлеченный элемент или undefined если стек пуст
|
|
42
|
-
*/
|
|
43
|
-
pop() {
|
|
44
|
-
return this.stack.pop();
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Получить текущий элемент (вершину стека) без извлечения
|
|
48
|
-
* @returns Текущий элемент или null если стек пуст
|
|
49
|
-
*/
|
|
50
|
-
getCurrent() {
|
|
51
|
-
return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Извлечь элемент из стека с проверкой
|
|
55
|
-
* Выводит предупреждение в DEV режиме если извлеченный элемент не совпадает с ожидаемым
|
|
56
|
-
*
|
|
57
|
-
* @param expected - Ожидаемый элемент
|
|
58
|
-
* @param name - Имя реестра для отладки (например, 'ValidationRegistry')
|
|
59
|
-
*/
|
|
60
|
-
verify(expected, name) {
|
|
61
|
-
const popped = this.pop();
|
|
62
|
-
if (popped !== expected && import.meta.env.DEV) {
|
|
63
|
-
console.warn(`${name}: Stack mismatch. Expected ${expected}, got ${popped}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Получить длину стека
|
|
68
|
-
* @returns Количество элементов в стеке
|
|
69
|
-
*/
|
|
70
|
-
get length() {
|
|
71
|
-
return this.stack.length;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Проверить, пуст ли стек
|
|
75
|
-
* @returns true если стек пуст
|
|
76
|
-
*/
|
|
77
|
-
isEmpty() {
|
|
78
|
-
return this.stack.length === 0;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Очистить стек
|
|
82
|
-
*/
|
|
83
|
-
clear() {
|
|
84
|
-
this.stack = [];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Типы ресурсов
|
|
3
|
-
// ============================================================================
|
|
4
|
-
// ============================================================================
|
|
5
|
-
// 1. Статическая стратегия
|
|
6
|
-
// ============================================================================
|
|
7
|
-
/**
|
|
8
|
-
* Статический ресурс - данные загружаются один раз
|
|
9
|
-
* @param items - массив элементов
|
|
10
|
-
*/
|
|
11
|
-
export function staticResource(items) {
|
|
12
|
-
return {
|
|
13
|
-
type: 'static',
|
|
14
|
-
load: async () => ({
|
|
15
|
-
items,
|
|
16
|
-
totalCount: items.length,
|
|
17
|
-
}),
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// 2. Предзагрузка
|
|
22
|
-
// ============================================================================
|
|
23
|
-
/**
|
|
24
|
-
* Предзагрузка - данные загружаются один раз при первом обращении через функцию
|
|
25
|
-
* @param loader - функция загрузки, принимает параметры и возвращает массив
|
|
26
|
-
*/
|
|
27
|
-
export function preloadResource(loader) {
|
|
28
|
-
let cache = null;
|
|
29
|
-
return {
|
|
30
|
-
type: 'preload',
|
|
31
|
-
load: async (params) => {
|
|
32
|
-
if (!cache) {
|
|
33
|
-
const items = await loader(params);
|
|
34
|
-
cache = {
|
|
35
|
-
items,
|
|
36
|
-
totalCount: items.length,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
return cache;
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
// ============================================================================
|
|
44
|
-
// 3. Парциональная загрузка
|
|
45
|
-
// ============================================================================
|
|
46
|
-
/**
|
|
47
|
-
* Парциональная загрузка - данные загружаются порциями с учетом поиска/пагинации
|
|
48
|
-
* @param loader - функция загрузки, принимает параметры и возвращает массив
|
|
49
|
-
*/
|
|
50
|
-
export function partialResource(loader) {
|
|
51
|
-
return {
|
|
52
|
-
type: 'partial',
|
|
53
|
-
load: async (params) => {
|
|
54
|
-
const items = await loader(params);
|
|
55
|
-
return {
|
|
56
|
-
items,
|
|
57
|
-
totalCount: items.length, // Можно расширить для поддержки серверной пагинации
|
|
58
|
-
};
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
// ============================================================================
|
|
63
|
-
// Экспорт всех стратегий
|
|
64
|
-
// ============================================================================
|
|
65
|
-
export const Resources = {
|
|
66
|
-
static: staticResource,
|
|
67
|
-
preload: preloadResource,
|
|
68
|
-
partial: partialResource,
|
|
69
|
-
};
|