@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.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/behaviors.d.ts +2 -0
- package/dist/behaviors.js +230 -0
- package/dist/core/behavior/behavior-applicator.d.ts +71 -0
- package/dist/core/behavior/behavior-applicator.js +92 -0
- package/dist/core/behavior/behavior-context.d.ts +29 -0
- package/dist/core/behavior/behavior-context.js +38 -0
- package/dist/core/behavior/behavior-registry.d.ts +97 -0
- package/dist/core/behavior/behavior-registry.js +198 -0
- package/dist/core/behavior/behaviors/compute-from.d.ts +41 -0
- package/dist/core/behavior/behaviors/compute-from.js +84 -0
- package/dist/core/behavior/behaviors/copy-from.d.ts +31 -0
- package/dist/core/behavior/behaviors/copy-from.js +64 -0
- package/dist/core/behavior/behaviors/enable-when.d.ts +49 -0
- package/dist/core/behavior/behaviors/enable-when.js +81 -0
- package/dist/core/behavior/behaviors/index.d.ts +11 -0
- package/dist/core/behavior/behaviors/index.js +11 -0
- package/dist/core/behavior/behaviors/reset-when.d.ts +51 -0
- package/dist/core/behavior/behaviors/reset-when.js +63 -0
- package/dist/core/behavior/behaviors/revalidate-when.d.ts +30 -0
- package/dist/core/behavior/behaviors/revalidate-when.js +51 -0
- package/dist/core/behavior/behaviors/sync-fields.d.ts +28 -0
- package/dist/core/behavior/behaviors/sync-fields.js +66 -0
- package/dist/core/behavior/behaviors/transform-value.d.ts +120 -0
- package/dist/core/behavior/behaviors/transform-value.js +110 -0
- package/dist/core/behavior/behaviors/watch-field.d.ts +35 -0
- package/dist/core/behavior/behaviors/watch-field.js +56 -0
- package/dist/core/behavior/compose-behavior.d.ts +106 -0
- package/dist/core/behavior/compose-behavior.js +166 -0
- package/dist/core/behavior/create-field-path.d.ts +20 -0
- package/dist/core/behavior/create-field-path.js +69 -0
- package/dist/core/behavior/index.d.ts +12 -0
- package/dist/core/behavior/index.js +17 -0
- package/dist/core/behavior/types.d.ts +152 -0
- package/dist/core/behavior/types.js +7 -0
- package/dist/core/context/form-context-impl.d.ts +29 -0
- package/dist/core/context/form-context-impl.js +37 -0
- package/dist/core/factories/index.d.ts +6 -0
- package/dist/core/factories/index.js +6 -0
- package/dist/core/factories/node-factory.d.ts +209 -0
- package/dist/core/factories/node-factory.js +281 -0
- package/dist/core/nodes/array-node.d.ts +308 -0
- package/dist/core/nodes/array-node.js +534 -0
- package/dist/core/nodes/field-node.d.ts +269 -0
- package/dist/core/nodes/field-node.js +510 -0
- package/dist/core/nodes/form-node.d.ts +342 -0
- package/dist/core/nodes/form-node.js +343 -0
- package/dist/core/nodes/group-node/field-registry.d.ts +191 -0
- package/dist/core/nodes/group-node/field-registry.js +215 -0
- package/dist/core/nodes/group-node/index.d.ts +11 -0
- package/dist/core/nodes/group-node/index.js +11 -0
- package/dist/core/nodes/group-node/proxy-builder.d.ts +71 -0
- package/dist/core/nodes/group-node/proxy-builder.js +161 -0
- package/dist/core/nodes/group-node/state-manager.d.ts +184 -0
- package/dist/core/nodes/group-node/state-manager.js +265 -0
- package/dist/core/nodes/group-node.d.ts +494 -0
- package/dist/core/nodes/group-node.js +770 -0
- package/dist/core/types/deep-schema.d.ts +78 -0
- package/dist/core/types/deep-schema.js +11 -0
- package/dist/core/types/field-path.d.ts +42 -0
- package/dist/core/types/field-path.js +4 -0
- package/dist/core/types/form-context.d.ts +83 -0
- package/dist/core/types/form-context.js +25 -0
- package/dist/core/types/group-node-proxy.d.ts +135 -0
- package/dist/core/types/group-node-proxy.js +31 -0
- package/dist/core/types/index.d.ts +163 -0
- package/dist/core/types/index.js +4 -0
- package/dist/core/types/validation-schema.d.ts +104 -0
- package/dist/core/types/validation-schema.js +10 -0
- package/dist/core/utils/create-form.d.ts +61 -0
- package/dist/core/utils/create-form.js +24 -0
- package/dist/core/utils/debounce.d.ts +160 -0
- package/dist/core/utils/debounce.js +197 -0
- package/dist/core/utils/error-handler.d.ts +180 -0
- package/dist/core/utils/error-handler.js +226 -0
- package/dist/core/utils/field-path-navigator.d.ts +240 -0
- package/dist/core/utils/field-path-navigator.js +374 -0
- package/dist/core/utils/index.d.ts +14 -0
- package/dist/core/utils/index.js +14 -0
- package/dist/core/utils/registry-helpers.d.ts +50 -0
- package/dist/core/utils/registry-helpers.js +79 -0
- package/dist/core/utils/registry-stack.d.ts +69 -0
- package/dist/core/utils/registry-stack.js +86 -0
- package/dist/core/utils/resources.d.ts +41 -0
- package/dist/core/utils/resources.js +69 -0
- package/dist/core/utils/subscription-manager.d.ts +180 -0
- package/dist/core/utils/subscription-manager.js +214 -0
- package/dist/core/utils/type-guards.d.ts +116 -0
- package/dist/core/utils/type-guards.js +169 -0
- package/dist/core/validation/core/apply-when.d.ts +28 -0
- package/dist/core/validation/core/apply-when.js +41 -0
- package/dist/core/validation/core/apply.d.ts +63 -0
- package/dist/core/validation/core/apply.js +38 -0
- package/dist/core/validation/core/index.d.ts +8 -0
- package/dist/core/validation/core/index.js +8 -0
- package/dist/core/validation/core/validate-async.d.ts +42 -0
- package/dist/core/validation/core/validate-async.js +45 -0
- package/dist/core/validation/core/validate-tree.d.ts +35 -0
- package/dist/core/validation/core/validate-tree.js +37 -0
- package/dist/core/validation/core/validate.d.ts +32 -0
- package/dist/core/validation/core/validate.js +38 -0
- package/dist/core/validation/field-path.d.ts +43 -0
- package/dist/core/validation/field-path.js +147 -0
- package/dist/core/validation/index.d.ts +21 -0
- package/dist/core/validation/index.js +33 -0
- package/dist/core/validation/validate-form.d.ts +85 -0
- package/dist/core/validation/validate-form.js +152 -0
- package/dist/core/validation/validation-applicator.d.ts +89 -0
- package/dist/core/validation/validation-applicator.js +217 -0
- package/dist/core/validation/validation-context.d.ts +47 -0
- package/dist/core/validation/validation-context.js +75 -0
- package/dist/core/validation/validation-registry.d.ts +156 -0
- package/dist/core/validation/validation-registry.js +298 -0
- package/dist/core/validation/validators/array-validators.d.ts +63 -0
- package/dist/core/validation/validators/array-validators.js +86 -0
- package/dist/core/validation/validators/date.d.ts +38 -0
- package/dist/core/validation/validators/date.js +117 -0
- package/dist/core/validation/validators/email.d.ts +44 -0
- package/dist/core/validation/validators/email.js +60 -0
- package/dist/core/validation/validators/index.d.ts +14 -0
- package/dist/core/validation/validators/index.js +14 -0
- package/dist/core/validation/validators/max-length.d.ts +45 -0
- package/dist/core/validation/validators/max-length.js +60 -0
- package/dist/core/validation/validators/max.d.ts +45 -0
- package/dist/core/validation/validators/max.js +60 -0
- package/dist/core/validation/validators/min-length.d.ts +45 -0
- package/dist/core/validation/validators/min-length.js +60 -0
- package/dist/core/validation/validators/min.d.ts +45 -0
- package/dist/core/validation/validators/min.js +60 -0
- package/dist/core/validation/validators/number.d.ts +38 -0
- package/dist/core/validation/validators/number.js +90 -0
- package/dist/core/validation/validators/pattern.d.ts +47 -0
- package/dist/core/validation/validators/pattern.js +62 -0
- package/dist/core/validation/validators/phone.d.ts +34 -0
- package/dist/core/validation/validators/phone.js +58 -0
- package/dist/core/validation/validators/required.d.ts +48 -0
- package/dist/core/validation/validators/required.js +69 -0
- package/dist/core/validation/validators/url.d.ts +29 -0
- package/dist/core/validation/validators/url.js +55 -0
- package/dist/create-field-path-CdPF3lIK.js +704 -0
- package/dist/hooks/useFormControl.d.ts +48 -0
- package/dist/hooks/useFormControl.js +298 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/node-factory-D7DOnSSN.js +3200 -0
- package/dist/validators.d.ts +2 -0
- package/dist/validators.js +298 -0
- package/llms.txt +847 -0
- package/package.json +86 -0
|
@@ -0,0 +1,147 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { validate } from './core/validate';
|
|
2
|
+
export { validateAsync } from './core/validate-async';
|
|
3
|
+
export { validateTree } from './core/validate-tree';
|
|
4
|
+
export { apply } from './core/apply';
|
|
5
|
+
export { applyWhen } from './core/apply-when';
|
|
6
|
+
export { required } from './validators/required';
|
|
7
|
+
export { min } from './validators/min';
|
|
8
|
+
export { max } from './validators/max';
|
|
9
|
+
export { minLength } from './validators/min-length';
|
|
10
|
+
export { maxLength } from './validators/max-length';
|
|
11
|
+
export { email } from './validators/email';
|
|
12
|
+
export { pattern } from './validators/pattern';
|
|
13
|
+
export { url } from './validators/url';
|
|
14
|
+
export { phone, type PhoneFormat } from './validators/phone';
|
|
15
|
+
export { number } from './validators/number';
|
|
16
|
+
export { date } from './validators/date';
|
|
17
|
+
export { notEmpty, validateItems } from './validators/array-validators';
|
|
18
|
+
export { createFieldPath, extractPath, extractKey, toFieldPath } from './field-path';
|
|
19
|
+
export { validateForm } from './validate-form';
|
|
20
|
+
export { ValidationRegistry } from './validation-registry';
|
|
21
|
+
export { ValidationContextImpl, TreeValidationContextImpl } from './validation-context';
|
|
@@ -0,0 +1,33 @@
|
|
|
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';
|
|
@@ -0,0 +1,85 @@
|
|
|
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 type { ValidationSchemaFn, FormFields } from '../types';
|
|
40
|
+
/**
|
|
41
|
+
* Валидировать форму в соответствии с указанной схемой
|
|
42
|
+
*
|
|
43
|
+
* Функция создает временный контекст валидации, применяет валидаторы
|
|
44
|
+
* из схемы и очищает контекст без сохранения в реестр.
|
|
45
|
+
*
|
|
46
|
+
* @param form - GroupNode для валидации
|
|
47
|
+
* @param schema - Схема валидации (ValidationSchemaFn)
|
|
48
|
+
* @returns Promise<boolean> - `true` если форма валидна, `false` если есть ошибки
|
|
49
|
+
*
|
|
50
|
+
* @example Multi-step форма: валидация текущего шага
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const goToNextStep = async () => {
|
|
53
|
+
* const isValid = await validateForm(form, step1LoanValidation);
|
|
54
|
+
*
|
|
55
|
+
* if (!isValid) {
|
|
56
|
+
* form.markAsTouched(); // Показать ошибки
|
|
57
|
+
* return false;
|
|
58
|
+
* }
|
|
59
|
+
*
|
|
60
|
+
* setCurrentStep(2);
|
|
61
|
+
* return true;
|
|
62
|
+
* };
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @example Полная валидация перед submit
|
|
66
|
+
* ```typescript
|
|
67
|
+
* const handleSubmit = async () => {
|
|
68
|
+
* const isValid = await validateForm(form, fullValidationSchema);
|
|
69
|
+
*
|
|
70
|
+
* if (isValid) {
|
|
71
|
+
* await form.submit(onSubmit);
|
|
72
|
+
* }
|
|
73
|
+
* };
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example Условная валидация
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const schema = isBusinessAccount
|
|
79
|
+
* ? businessValidation
|
|
80
|
+
* : personalValidation;
|
|
81
|
+
*
|
|
82
|
+
* const isValid = await validateForm(form, schema);
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function validateForm<T extends FormFields>(form: GroupNode<T>, schema: ValidationSchemaFn<T>): Promise<boolean>;
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
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 type { GroupNode } from '../nodes/group-node';
|
|
21
|
+
import type { ValidatorRegistration } from '../types';
|
|
22
|
+
/**
|
|
23
|
+
* Класс для применения валидаторов к форме
|
|
24
|
+
*
|
|
25
|
+
* Выполняет:
|
|
26
|
+
* - Группировку валидаторов по полям
|
|
27
|
+
* - Фильтрацию по условиям (condition)
|
|
28
|
+
* - Применение sync/async валидаторов к FieldNode
|
|
29
|
+
* - Применение tree валидаторов (кросс-полевая валидация)
|
|
30
|
+
*
|
|
31
|
+
* @template T Тип формы (объект)
|
|
32
|
+
*/
|
|
33
|
+
export declare class ValidationApplicator<T> {
|
|
34
|
+
private readonly form;
|
|
35
|
+
constructor(form: GroupNode<T>);
|
|
36
|
+
/**
|
|
37
|
+
* Применить валидаторы к полям формы
|
|
38
|
+
*
|
|
39
|
+
* Этапы применения:
|
|
40
|
+
* 1. Разделение валидаторов на field и tree
|
|
41
|
+
* 2. Применение field валидаторов (sync/async)
|
|
42
|
+
* 3. Применение tree валидаторов (кросс-полевая валидация)
|
|
43
|
+
*
|
|
44
|
+
* @param validators Зарегистрированные валидаторы
|
|
45
|
+
*/
|
|
46
|
+
apply(validators: ValidatorRegistration[]): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Группировка валидаторов по типам
|
|
49
|
+
*
|
|
50
|
+
* Разделяет валидаторы на:
|
|
51
|
+
* - Field validators (sync/async) - группируются по fieldPath
|
|
52
|
+
* - Tree validators - применяются ко всей форме
|
|
53
|
+
*
|
|
54
|
+
* @param validators Все зарегистрированные валидаторы
|
|
55
|
+
* @returns Сгруппированные валидаторы
|
|
56
|
+
*/
|
|
57
|
+
private groupValidators;
|
|
58
|
+
/**
|
|
59
|
+
* Применение field валидаторов к полям
|
|
60
|
+
*
|
|
61
|
+
* Для каждого поля:
|
|
62
|
+
* 1. Найти FieldNode по пути
|
|
63
|
+
* 2. Проверить условия (condition)
|
|
64
|
+
* 3. Выполнить sync/async валидаторы
|
|
65
|
+
* 4. Установить ошибки на поле
|
|
66
|
+
*
|
|
67
|
+
* @param validatorsByField Валидаторы, сгруппированные по полям
|
|
68
|
+
*/
|
|
69
|
+
private applyFieldValidators;
|
|
70
|
+
/**
|
|
71
|
+
* Применение tree валидаторов (кросс-полевая валидация)
|
|
72
|
+
*
|
|
73
|
+
* Tree валидаторы имеют доступ ко всей форме через TreeValidationContext.
|
|
74
|
+
* Ошибки устанавливаются на targetField (если указано).
|
|
75
|
+
*
|
|
76
|
+
* @param treeValidators Список tree валидаторов
|
|
77
|
+
*/
|
|
78
|
+
private applyTreeValidators;
|
|
79
|
+
/**
|
|
80
|
+
* Проверка условия (condition) для валидатора
|
|
81
|
+
*
|
|
82
|
+
* Условие определяет, должен ли валидатор выполняться.
|
|
83
|
+
* Использует getFieldByPath для поддержки вложенных путей.
|
|
84
|
+
*
|
|
85
|
+
* @param condition Условие валидатора
|
|
86
|
+
* @returns true, если условие выполнено
|
|
87
|
+
*/
|
|
88
|
+
private checkCondition;
|
|
89
|
+
}
|