@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,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FieldPath proxy - типобезопасный доступ к путям полей формы
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Создать FieldPath proxy для формы
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* const path = createFieldPath<MyForm>();
|
|
10
|
-
* console.log(path.email.__path); // 'email'
|
|
11
|
-
* console.log(path.personalData.firstName.__path); // 'personalData.firstName'
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
14
|
-
export function createFieldPath() {
|
|
15
|
-
return createFieldPathProxy('');
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Внутренняя функция для создания прокси с отслеживанием пути
|
|
19
|
-
* @private
|
|
20
|
-
*/
|
|
21
|
-
function createFieldPathProxy(basePath) {
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
-
return new Proxy({}, {
|
|
24
|
-
get(_target, prop) {
|
|
25
|
-
if (typeof prop === 'symbol') {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
// Специальные свойства для метаинформации
|
|
29
|
-
if (prop === '__path') {
|
|
30
|
-
return basePath || prop;
|
|
31
|
-
}
|
|
32
|
-
if (prop === '__key') {
|
|
33
|
-
const parts = basePath.split('.');
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
return (parts[parts.length - 1] || prop);
|
|
36
|
-
}
|
|
37
|
-
// Игнорируем некоторые служебные свойства
|
|
38
|
-
if (prop === 'then' ||
|
|
39
|
-
prop === 'catch' ||
|
|
40
|
-
prop === 'finally' ||
|
|
41
|
-
prop === 'constructor' ||
|
|
42
|
-
prop === 'toString' ||
|
|
43
|
-
prop === 'valueOf' ||
|
|
44
|
-
prop === 'toJSON') {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
// Создаем новый proxy для вложенного свойства
|
|
48
|
-
const newPath = basePath ? `${basePath}.${prop}` : prop;
|
|
49
|
-
// Возвращаем объект, который ведет себя и как значение, и как прокси
|
|
50
|
-
const node = {
|
|
51
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
-
__key: prop,
|
|
53
|
-
__path: newPath,
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
-
__formType: undefined,
|
|
56
|
-
__fieldType: undefined,
|
|
57
|
-
};
|
|
58
|
-
// Создаем прокси, который содержит и метаинформацию, и позволяет дальнейшую навигацию
|
|
59
|
-
return new Proxy(node, {
|
|
60
|
-
get(_nodeTarget, nodeProp) {
|
|
61
|
-
if (typeof nodeProp === 'symbol') {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
// Если запрашивают метаинформацию, возвращаем её
|
|
65
|
-
if (nodeProp === '__path')
|
|
66
|
-
return newPath;
|
|
67
|
-
if (nodeProp === '__key')
|
|
68
|
-
return prop;
|
|
69
|
-
if (nodeProp === '__formType')
|
|
70
|
-
return undefined;
|
|
71
|
-
if (nodeProp === '__fieldType')
|
|
72
|
-
return undefined;
|
|
73
|
-
// Игнорируем служебные свойства
|
|
74
|
-
if (nodeProp === 'then' ||
|
|
75
|
-
nodeProp === 'catch' ||
|
|
76
|
-
nodeProp === 'finally' ||
|
|
77
|
-
nodeProp === 'constructor' ||
|
|
78
|
-
nodeProp === 'toString' ||
|
|
79
|
-
nodeProp === 'valueOf' ||
|
|
80
|
-
nodeProp === 'toJSON') {
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
// Иначе создаем еще более вложенный proxy
|
|
84
|
-
return createFieldPathProxy(`${newPath}.${nodeProp}`);
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Извлечь путь из FieldPathNode
|
|
92
|
-
*/
|
|
93
|
-
export function extractPath(node) {
|
|
94
|
-
// Fallback для строк
|
|
95
|
-
if (typeof node === 'string') {
|
|
96
|
-
return node;
|
|
97
|
-
}
|
|
98
|
-
// Проверка для Proxy и обычных объектов
|
|
99
|
-
if (node && typeof node === 'object') {
|
|
100
|
-
// Пытаемся получить __path напрямую (работает для Proxy)
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
-
const path = node.__path;
|
|
103
|
-
if (typeof path === 'string') {
|
|
104
|
-
return path;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
throw new Error('Invalid field path node: ' + JSON.stringify(node));
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Преобразовать FieldPathNode в FieldPath для переиспользования схем
|
|
111
|
-
*
|
|
112
|
-
* Позволяет композировать validation schemas:
|
|
113
|
-
*
|
|
114
|
-
* @example
|
|
115
|
-
* ```typescript
|
|
116
|
-
* const personalDataValidation = (path: FieldPath<PersonalData>) => {
|
|
117
|
-
* required(path.firstName, { message: 'Имя обязательно' });
|
|
118
|
-
* required(path.lastName, { message: 'Фамилия обязательна' });
|
|
119
|
-
* };
|
|
120
|
-
*
|
|
121
|
-
* const mainValidation = (path: FieldPath<MyForm>) => {
|
|
122
|
-
* // Переиспользуем схему
|
|
123
|
-
* personalDataValidation(toFieldPath(path.personalData));
|
|
124
|
-
* required(path.email);
|
|
125
|
-
* };
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
128
|
-
export function toFieldPath(
|
|
129
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
-
node) {
|
|
131
|
-
const basePath = extractPath(node);
|
|
132
|
-
return createFieldPathProxy(basePath);
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Извлечь ключ поля из FieldPathNode
|
|
136
|
-
*/
|
|
137
|
-
export function extractKey(node) {
|
|
138
|
-
if (node && typeof node === 'object' && '__key' in node) {
|
|
139
|
-
return node.__key;
|
|
140
|
-
}
|
|
141
|
-
// Fallback для строк
|
|
142
|
-
if (typeof node === 'string') {
|
|
143
|
-
const parts = node.split('.');
|
|
144
|
-
return parts[parts.length - 1];
|
|
145
|
-
}
|
|
146
|
-
throw new Error('Invalid field path node');
|
|
147
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Validation Schema API
|
|
3
|
-
// ============================================================================
|
|
4
|
-
// Core validation functions
|
|
5
|
-
export { validate } from './core/validate';
|
|
6
|
-
export { validateAsync } from './core/validate-async';
|
|
7
|
-
export { validateTree } from './core/validate-tree';
|
|
8
|
-
export { apply } from './core/apply';
|
|
9
|
-
export { applyWhen } from './core/apply-when';
|
|
10
|
-
// Reusable validators
|
|
11
|
-
export { required } from './validators/required';
|
|
12
|
-
export { min } from './validators/min';
|
|
13
|
-
export { max } from './validators/max';
|
|
14
|
-
export { minLength } from './validators/min-length';
|
|
15
|
-
export { maxLength } from './validators/max-length';
|
|
16
|
-
export { email } from './validators/email';
|
|
17
|
-
export { pattern } from './validators/pattern';
|
|
18
|
-
export { url } from './validators/url';
|
|
19
|
-
export { phone } from './validators/phone';
|
|
20
|
-
export { number } from './validators/number';
|
|
21
|
-
export { date } from './validators/date';
|
|
22
|
-
// Валидаторы для массивов
|
|
23
|
-
export { notEmpty, validateItems } from './validators/array-validators';
|
|
24
|
-
// Утилиты для FieldPath
|
|
25
|
-
export { createFieldPath, extractPath, extractKey, toFieldPath } from './field-path';
|
|
26
|
-
// Утилита для валидации формы по схеме
|
|
27
|
-
export { validateForm } from './validate-form';
|
|
28
|
-
// ValidationRegistry (для внутреннего использования)
|
|
29
|
-
// Примечание: ValidationRegistry (глобальный singleton) был удален в пользу
|
|
30
|
-
// локальных экземпляров ValidationRegistry в каждом GroupNode
|
|
31
|
-
export { ValidationRegistry } from './validation-registry';
|
|
32
|
-
// Контексты валидации
|
|
33
|
-
export { ValidationContextImpl, TreeValidationContextImpl } from './validation-context';
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* validateForm - утилита для валидации формы в соответствии со схемой
|
|
3
|
-
*
|
|
4
|
-
* Позволяет применить validation schema к форме без изменения
|
|
5
|
-
* зарегистрированной схемы в ValidationRegistry.
|
|
6
|
-
*
|
|
7
|
-
* ## Использование
|
|
8
|
-
*
|
|
9
|
-
* - **Multi-step формы**: валидация только полей текущего шага
|
|
10
|
-
* - **Условная валидация**: применение разных схем в зависимости от состояния
|
|
11
|
-
* - **Временная валидация**: проверка без сохранения в реестр
|
|
12
|
-
*
|
|
13
|
-
* ## Как это работает
|
|
14
|
-
*
|
|
15
|
-
* 1. Создаётся временный ValidationRegistry для схемы
|
|
16
|
-
* 2. Валидируются все FieldNode (field-level валидаторы из FieldConfig)
|
|
17
|
-
* 3. Применяются ТОЛЬКО contextual validators из переданной схемы
|
|
18
|
-
* 4. Временный реестр отменяется (не сохраняется в форму)
|
|
19
|
-
*
|
|
20
|
-
* ## Важно для multi-step форм
|
|
21
|
-
*
|
|
22
|
-
* Форма может быть создана С полной схемой валидации:
|
|
23
|
-
* ```typescript
|
|
24
|
-
* createForm({
|
|
25
|
-
* form: schema,
|
|
26
|
-
* validation: fullValidation, // Полная схема
|
|
27
|
-
* });
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
* При этом `validateForm(form, stepSchema)` будет применять
|
|
31
|
-
* только валидаторы из `stepSchema`, игнорируя валидаторы
|
|
32
|
-
* из других шагов.
|
|
33
|
-
*
|
|
34
|
-
* @see docs/multi-step-validation.md для подробной документации
|
|
35
|
-
*
|
|
36
|
-
* @module validation
|
|
37
|
-
*/
|
|
38
|
-
import { GroupNode } from '../nodes/group-node';
|
|
39
|
-
import { FieldNode } from '../nodes/field-node';
|
|
40
|
-
import { ArrayNode } from '../nodes/array-node';
|
|
41
|
-
import { ValidationRegistry } from './validation-registry';
|
|
42
|
-
import { createFieldPath } from './field-path';
|
|
43
|
-
/**
|
|
44
|
-
* Рекурсивно собирает все FieldNode из дерева формы
|
|
45
|
-
*
|
|
46
|
-
* Пропускает GroupNode и ArrayNode как узлы, но обходит их детей.
|
|
47
|
-
* Это необходимо для validateForm, чтобы избежать триггера
|
|
48
|
-
* contextual validators из validationRegistry вложенных групп.
|
|
49
|
-
*
|
|
50
|
-
* @param node - Корневой узел для обхода
|
|
51
|
-
* @returns Массив всех FieldNode в дереве
|
|
52
|
-
*/
|
|
53
|
-
function collectAllFieldNodes(node) {
|
|
54
|
-
if (node instanceof FieldNode) {
|
|
55
|
-
return [node];
|
|
56
|
-
}
|
|
57
|
-
if (node instanceof GroupNode) {
|
|
58
|
-
return Array.from(node.getAllFields()).flatMap(collectAllFieldNodes);
|
|
59
|
-
}
|
|
60
|
-
if (node instanceof ArrayNode) {
|
|
61
|
-
// items приватный, используем публичный метод map()
|
|
62
|
-
return node.map((item) => collectAllFieldNodes(item)).flat();
|
|
63
|
-
}
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Валидировать форму в соответствии с указанной схемой
|
|
68
|
-
*
|
|
69
|
-
* Функция создает временный контекст валидации, применяет валидаторы
|
|
70
|
-
* из схемы и очищает контекст без сохранения в реестр.
|
|
71
|
-
*
|
|
72
|
-
* @param form - GroupNode для валидации
|
|
73
|
-
* @param schema - Схема валидации (ValidationSchemaFn)
|
|
74
|
-
* @returns Promise<boolean> - `true` если форма валидна, `false` если есть ошибки
|
|
75
|
-
*
|
|
76
|
-
* @example Multi-step форма: валидация текущего шага
|
|
77
|
-
* ```typescript
|
|
78
|
-
* const goToNextStep = async () => {
|
|
79
|
-
* const isValid = await validateForm(form, step1LoanValidation);
|
|
80
|
-
*
|
|
81
|
-
* if (!isValid) {
|
|
82
|
-
* form.markAsTouched(); // Показать ошибки
|
|
83
|
-
* return false;
|
|
84
|
-
* }
|
|
85
|
-
*
|
|
86
|
-
* setCurrentStep(2);
|
|
87
|
-
* return true;
|
|
88
|
-
* };
|
|
89
|
-
* ```
|
|
90
|
-
*
|
|
91
|
-
* @example Полная валидация перед submit
|
|
92
|
-
* ```typescript
|
|
93
|
-
* const handleSubmit = async () => {
|
|
94
|
-
* const isValid = await validateForm(form, fullValidationSchema);
|
|
95
|
-
*
|
|
96
|
-
* if (isValid) {
|
|
97
|
-
* await form.submit(onSubmit);
|
|
98
|
-
* }
|
|
99
|
-
* };
|
|
100
|
-
* ```
|
|
101
|
-
*
|
|
102
|
-
* @example Условная валидация
|
|
103
|
-
* ```typescript
|
|
104
|
-
* const schema = isBusinessAccount
|
|
105
|
-
* ? businessValidation
|
|
106
|
-
* : personalValidation;
|
|
107
|
-
*
|
|
108
|
-
* const isValid = await validateForm(form, schema);
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
export async function validateForm(form, schema) {
|
|
112
|
-
// Создаем временный реестр для этой валидации
|
|
113
|
-
// Это изолирует валидацию от других форм и не затрагивает постоянный реестр формы
|
|
114
|
-
const tempRegistry = new ValidationRegistry();
|
|
115
|
-
// Начинаем регистрацию валидаторов в временном реестре
|
|
116
|
-
tempRegistry.beginRegistration();
|
|
117
|
-
let tempValidators = [];
|
|
118
|
-
let cancelled = false;
|
|
119
|
-
try {
|
|
120
|
-
// Регистрируем валидаторы из схемы
|
|
121
|
-
// schema() будет использовать tempRegistry через getCurrent() из context stack
|
|
122
|
-
const path = createFieldPath();
|
|
123
|
-
schema(path);
|
|
124
|
-
// Получаем валидаторы БЕЗ сохранения в реестр
|
|
125
|
-
const context = tempRegistry.getCurrentContext();
|
|
126
|
-
tempValidators = context?.getValidators() || [];
|
|
127
|
-
// Отменяем регистрацию (не сохраняем в реестр формы)
|
|
128
|
-
tempRegistry.cancelRegistration();
|
|
129
|
-
cancelled = true;
|
|
130
|
-
// Очищаем текущие ошибки полей
|
|
131
|
-
form.clearErrors();
|
|
132
|
-
// Валидируем все FieldNode (field-level валидация)
|
|
133
|
-
// ВАЖНО: Используем collectAllFieldNodes вместо getAllFields,
|
|
134
|
-
// чтобы избежать вызова validate() на вложенных GroupNode/ArrayNode,
|
|
135
|
-
// которые триггерят свой validationRegistry с полной схемой валидации
|
|
136
|
-
const allFieldNodes = collectAllFieldNodes(form);
|
|
137
|
-
await Promise.all(allFieldNodes.map((field) => field.validate()));
|
|
138
|
-
// Применяем contextual validators
|
|
139
|
-
if (tempValidators.length > 0) {
|
|
140
|
-
await form.applyContextualValidators(tempValidators);
|
|
141
|
-
}
|
|
142
|
-
// Проверяем результат
|
|
143
|
-
return form.valid.value;
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
// В случае ошибки отменяем регистрацию только если еще не отменили
|
|
147
|
-
if (!cancelled) {
|
|
148
|
-
tempRegistry.cancelRegistration();
|
|
149
|
-
}
|
|
150
|
-
throw error;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Применение валидаторов к форме
|
|
3
|
-
*
|
|
4
|
-
* Извлечено из GroupNode для соблюдения SRP (Single Responsibility Principle).
|
|
5
|
-
* Отвечает только за логику применения валидаторов к полям формы.
|
|
6
|
-
*
|
|
7
|
-
* @template T Тип формы
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* class GroupNode {
|
|
12
|
-
* private readonly validationApplicator = new ValidationApplicator(this);
|
|
13
|
-
*
|
|
14
|
-
* async applyContextualValidators(validators: ValidatorRegistration[]) {
|
|
15
|
-
* await this.validationApplicator.apply(validators);
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
import { ValidationContextImpl, TreeValidationContextImpl } from './validation-context';
|
|
21
|
-
import { isFieldNode } from '../utils/type-guards';
|
|
22
|
-
import { FormErrorHandler, ErrorStrategy } from '../utils/error-handler';
|
|
23
|
-
/**
|
|
24
|
-
* Класс для применения валидаторов к форме
|
|
25
|
-
*
|
|
26
|
-
* Выполняет:
|
|
27
|
-
* - Группировку валидаторов по полям
|
|
28
|
-
* - Фильтрацию по условиям (condition)
|
|
29
|
-
* - Применение sync/async валидаторов к FieldNode
|
|
30
|
-
* - Применение tree валидаторов (кросс-полевая валидация)
|
|
31
|
-
*
|
|
32
|
-
* @template T Тип формы (объект)
|
|
33
|
-
*/
|
|
34
|
-
export class ValidationApplicator {
|
|
35
|
-
form;
|
|
36
|
-
constructor(form) {
|
|
37
|
-
this.form = form;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Применить валидаторы к полям формы
|
|
41
|
-
*
|
|
42
|
-
* Этапы применения:
|
|
43
|
-
* 1. Разделение валидаторов на field и tree
|
|
44
|
-
* 2. Применение field валидаторов (sync/async)
|
|
45
|
-
* 3. Применение tree валидаторов (кросс-полевая валидация)
|
|
46
|
-
*
|
|
47
|
-
* @param validators Зарегистрированные валидаторы
|
|
48
|
-
*/
|
|
49
|
-
async apply(validators) {
|
|
50
|
-
// 1. Группировка валидаторов
|
|
51
|
-
const { validatorsByField, treeValidators } = this.groupValidators(validators);
|
|
52
|
-
// 2. Применение field валидаторов
|
|
53
|
-
await this.applyFieldValidators(validatorsByField);
|
|
54
|
-
// 3. Применение tree валидаторов
|
|
55
|
-
this.applyTreeValidators(treeValidators);
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Группировка валидаторов по типам
|
|
59
|
-
*
|
|
60
|
-
* Разделяет валидаторы на:
|
|
61
|
-
* - Field validators (sync/async) - группируются по fieldPath
|
|
62
|
-
* - Tree validators - применяются ко всей форме
|
|
63
|
-
*
|
|
64
|
-
* @param validators Все зарегистрированные валидаторы
|
|
65
|
-
* @returns Сгруппированные валидаторы
|
|
66
|
-
*/
|
|
67
|
-
groupValidators(validators) {
|
|
68
|
-
const validatorsByField = new Map();
|
|
69
|
-
const treeValidators = [];
|
|
70
|
-
for (const registration of validators) {
|
|
71
|
-
if (registration.type === 'tree') {
|
|
72
|
-
treeValidators.push(registration);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
const existing = validatorsByField.get(registration.fieldPath) || [];
|
|
76
|
-
existing.push(registration);
|
|
77
|
-
validatorsByField.set(registration.fieldPath, existing);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return { validatorsByField, treeValidators };
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Применение field валидаторов к полям
|
|
84
|
-
*
|
|
85
|
-
* Для каждого поля:
|
|
86
|
-
* 1. Найти FieldNode по пути
|
|
87
|
-
* 2. Проверить условия (condition)
|
|
88
|
-
* 3. Выполнить sync/async валидаторы
|
|
89
|
-
* 4. Установить ошибки на поле
|
|
90
|
-
*
|
|
91
|
-
* @param validatorsByField Валидаторы, сгруппированные по полям
|
|
92
|
-
*/
|
|
93
|
-
async applyFieldValidators(validatorsByField) {
|
|
94
|
-
for (const [fieldPath, fieldValidators] of validatorsByField) {
|
|
95
|
-
// Поддержка вложенных путей (например, "personalData.lastName")
|
|
96
|
-
const control = this.form.getFieldByPath(fieldPath);
|
|
97
|
-
if (!control) {
|
|
98
|
-
if (import.meta.env.DEV) {
|
|
99
|
-
throw new Error(`Field "${fieldPath}" not found in GroupNode`);
|
|
100
|
-
}
|
|
101
|
-
console.warn(`Field ${fieldPath} not found in GroupNode`);
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
// Валидация работает только с FieldNode
|
|
105
|
-
if (!isFieldNode(control)) {
|
|
106
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
107
|
-
console.warn(`Validation can only run on FieldNode, skipping ${fieldPath}`);
|
|
108
|
-
}
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
const errors = [];
|
|
112
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
-
const context = new ValidationContextImpl(this.form, fieldPath, control);
|
|
114
|
-
// Выполнение валидаторов с учетом условий
|
|
115
|
-
for (const registration of fieldValidators) {
|
|
116
|
-
// Проверка условия (condition)
|
|
117
|
-
if (registration.condition) {
|
|
118
|
-
const shouldApply = this.checkCondition(registration.condition);
|
|
119
|
-
if (!shouldApply) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// Выполнение валидатора
|
|
124
|
-
// Новый паттерн: (value, ctx) => ValidationError | null
|
|
125
|
-
try {
|
|
126
|
-
let error = null;
|
|
127
|
-
const value = context.value();
|
|
128
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
-
const validator = registration.validator;
|
|
130
|
-
if (registration.type === 'sync') {
|
|
131
|
-
error = validator(value, context);
|
|
132
|
-
}
|
|
133
|
-
else if (registration.type === 'async') {
|
|
134
|
-
error = await validator(value, context);
|
|
135
|
-
}
|
|
136
|
-
if (error) {
|
|
137
|
-
errors.push(error);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch (e) {
|
|
141
|
-
FormErrorHandler.handle(e, `ValidationApplicator: validator for ${fieldPath}`, ErrorStrategy.LOG);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// Установка ошибок на поле
|
|
145
|
-
if (errors.length > 0) {
|
|
146
|
-
control.setErrors(errors);
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// Очистить ошибки, если они были contextual
|
|
150
|
-
if (control.errors.value.length > 0 &&
|
|
151
|
-
!control.errors.value.some((e) => e.code !== 'contextual')) {
|
|
152
|
-
control.clearErrors();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Применение tree валидаторов (кросс-полевая валидация)
|
|
159
|
-
*
|
|
160
|
-
* Tree валидаторы имеют доступ ко всей форме через TreeValidationContext.
|
|
161
|
-
* Ошибки устанавливаются на targetField (если указано).
|
|
162
|
-
*
|
|
163
|
-
* @param treeValidators Список tree валидаторов
|
|
164
|
-
*/
|
|
165
|
-
applyTreeValidators(treeValidators) {
|
|
166
|
-
for (const registration of treeValidators) {
|
|
167
|
-
const context = new TreeValidationContextImpl(this.form);
|
|
168
|
-
// Проверка условия (condition)
|
|
169
|
-
if (registration.condition) {
|
|
170
|
-
const shouldApply = this.checkCondition(registration.condition);
|
|
171
|
-
if (!shouldApply) {
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Выполнение tree валидатора
|
|
176
|
-
try {
|
|
177
|
-
// Tree валидаторы должны использовать TreeValidatorFn
|
|
178
|
-
if (registration.type !== 'tree') {
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
-
const error = registration.validator(context);
|
|
183
|
-
if (error && registration.options && 'targetField' in registration.options) {
|
|
184
|
-
const targetField = registration.options.targetField;
|
|
185
|
-
if (targetField) {
|
|
186
|
-
const targetControl = this.form.getFieldByPath(String(targetField));
|
|
187
|
-
if (targetControl && isFieldNode(targetControl)) {
|
|
188
|
-
const existingErrors = targetControl.errors.value;
|
|
189
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
|
-
targetControl.setErrors([...existingErrors, error]);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
catch (e) {
|
|
196
|
-
FormErrorHandler.handle(e, 'ValidationApplicator: tree validator', ErrorStrategy.LOG);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Проверка условия (condition) для валидатора
|
|
202
|
-
*
|
|
203
|
-
* Условие определяет, должен ли валидатор выполняться.
|
|
204
|
-
* Использует getFieldByPath для поддержки вложенных путей.
|
|
205
|
-
*
|
|
206
|
-
* @param condition Условие валидатора
|
|
207
|
-
* @returns true, если условие выполнено
|
|
208
|
-
*/
|
|
209
|
-
checkCondition(condition) {
|
|
210
|
-
const conditionField = this.form.getFieldByPath(condition.fieldPath);
|
|
211
|
-
if (!conditionField) {
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
const conditionValue = conditionField.value.value;
|
|
215
|
-
return condition.conditionFn(conditionValue);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Реализация контекста валидации
|
|
3
|
-
*/
|
|
4
|
-
import { isFormNode } from '../utils/type-guards';
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// Field Validation Context (для обычных валидаторов)
|
|
7
|
-
// ============================================================================
|
|
8
|
-
/**
|
|
9
|
-
* Реализация контекста валидации для отдельного поля
|
|
10
|
-
* Реализует FormContext
|
|
11
|
-
*/
|
|
12
|
-
export class ValidationContextImpl {
|
|
13
|
-
_form;
|
|
14
|
-
control;
|
|
15
|
-
/**
|
|
16
|
-
* Форма с типизированным Proxy-доступом к полям
|
|
17
|
-
*/
|
|
18
|
-
form;
|
|
19
|
-
constructor(form, _fieldKey, control) {
|
|
20
|
-
this._form = form;
|
|
21
|
-
this.control = control;
|
|
22
|
-
// Получаем Proxy для типизированного доступа
|
|
23
|
-
this.form = (form
|
|
24
|
-
._proxyInstance || form.getProxy());
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Получить текущее значение поля (внутренний метод для validation-applicator)
|
|
28
|
-
* @internal
|
|
29
|
-
*/
|
|
30
|
-
value() {
|
|
31
|
-
return this.control.value.value;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Безопасно установить значение поля по строковому пути
|
|
35
|
-
* Автоматически использует emitEvent: false для предотвращения циклов
|
|
36
|
-
*/
|
|
37
|
-
setFieldValue(path, value) {
|
|
38
|
-
const node = this._form.getFieldByPath(path);
|
|
39
|
-
if (node && isFormNode(node)) {
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
-
node.setValue(value, { emitEvent: false });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
// ============================================================================
|
|
46
|
-
// Tree Validation Context (для cross-field валидаторов)
|
|
47
|
-
// ============================================================================
|
|
48
|
-
/**
|
|
49
|
-
* Реализация контекста для cross-field валидации
|
|
50
|
-
* Реализует FormContext
|
|
51
|
-
*/
|
|
52
|
-
export class TreeValidationContextImpl {
|
|
53
|
-
_form;
|
|
54
|
-
/**
|
|
55
|
-
* Форма с типизированным Proxy-доступом к полям
|
|
56
|
-
*/
|
|
57
|
-
form;
|
|
58
|
-
constructor(form) {
|
|
59
|
-
this._form = form;
|
|
60
|
-
// Получаем Proxy для типизированного доступа
|
|
61
|
-
this.form = (form
|
|
62
|
-
._proxyInstance || form.getProxy());
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Безопасно установить значение поля по строковому пути
|
|
66
|
-
* Автоматически использует emitEvent: false для предотвращения циклов
|
|
67
|
-
*/
|
|
68
|
-
setFieldValue(path, value) {
|
|
69
|
-
const node = this._form.getFieldByPath(path);
|
|
70
|
-
if (node && isFormNode(node)) {
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
-
node.setValue(value, { emitEvent: false });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|