@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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldPath proxy - типобезопасный доступ к путям полей формы
|
|
3
|
+
*
|
|
4
|
+
* Единый модуль для validation и behavior схем.
|
|
5
|
+
* Предоставляет типизированную навигацию по структуре формы.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/field-path
|
|
8
|
+
*/
|
|
9
|
+
import type { FieldPath, FieldPathNode } from '../types';
|
|
10
|
+
/**
|
|
11
|
+
* Создать FieldPath proxy для формы
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const path = createFieldPath<MyForm>();
|
|
16
|
+
* console.log(path.email.__path); // 'email'
|
|
17
|
+
* console.log(path.personalData.firstName.__path); // 'personalData.firstName'
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function createFieldPath<T>(): FieldPath<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Извлечь путь из FieldPathNode
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractPath(node: FieldPathNode<unknown, unknown> | unknown): string;
|
|
25
|
+
/**
|
|
26
|
+
* Преобразовать FieldPathNode в FieldPath для переиспользования схем
|
|
27
|
+
*
|
|
28
|
+
* Позволяет композировать validation schemas:
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const personalDataValidation = (path: FieldPath<PersonalData>) => {
|
|
33
|
+
* required(path.firstName, { message: 'Имя обязательно' });
|
|
34
|
+
* required(path.lastName, { message: 'Фамилия обязательна' });
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* const mainValidation = (path: FieldPath<MyForm>) => {
|
|
38
|
+
* // Переиспользуем схему
|
|
39
|
+
* personalDataValidation(toFieldPath(path.personalData));
|
|
40
|
+
* required(path.email);
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function toFieldPath<T>(node: FieldPathNode<unknown, T, never> | FieldPathNode<any, T, any>): FieldPath<T>;
|
|
45
|
+
/**
|
|
46
|
+
* Извлечь ключ поля из FieldPathNode
|
|
47
|
+
*/
|
|
48
|
+
export declare function extractKey(node: FieldPathNode<unknown, unknown> | unknown): string;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Централизованные вспомогательные классы и функции.
|
|
5
5
|
*/
|
|
6
6
|
export { FieldPathNavigator, type PathSegment } from './field-path-navigator';
|
|
7
|
+
export { createFieldPath, extractPath, extractKey, toFieldPath } from './field-path';
|
|
7
8
|
export { SubscriptionManager } from './subscription-manager';
|
|
8
9
|
export { getCurrentValidationRegistry, getCurrentBehaviorRegistry } from './registry-helpers';
|
|
9
10
|
export { RegistryStack } from './registry-stack';
|
|
@@ -13,13 +13,19 @@ import { TreeValidatorFn, ValidateTreeOptions } from '../../types';
|
|
|
13
13
|
* @group Validation
|
|
14
14
|
* @category Core Functions
|
|
15
15
|
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Параметр `ctx` в callback требует явной типизации для корректного вывода типов:
|
|
18
|
+
* ```typescript
|
|
19
|
+
* validateTree((ctx: { form: MyFormType }) => { ... });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
16
22
|
* @example
|
|
17
23
|
* ```typescript
|
|
24
|
+
* // Явная типизация ctx для избежания implicit any
|
|
18
25
|
* validateTree(
|
|
19
|
-
* (ctx:
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* if (form.initialPayment > form.propertyValue) {
|
|
26
|
+
* (ctx: { form: MyForm }) => {
|
|
27
|
+
* if (ctx.form.initialPayment && ctx.form.propertyValue) {
|
|
28
|
+
* if (ctx.form.initialPayment > ctx.form.propertyValue) {
|
|
23
29
|
* return {
|
|
24
30
|
* code: 'initialPaymentTooHigh',
|
|
25
31
|
* message: 'Первоначальный взнос не может превышать стоимость',
|
|
@@ -1,43 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FieldPath proxy - типобезопасный доступ к путям полей формы
|
|
3
|
-
*/
|
|
4
|
-
import type { FieldPath, FieldPathNode } from '../types';
|
|
5
|
-
/**
|
|
6
|
-
* Создать FieldPath proxy для формы
|
|
7
3
|
*
|
|
8
|
-
* @
|
|
9
|
-
*
|
|
10
|
-
* const path = createFieldPath<MyForm>();
|
|
11
|
-
* console.log(path.email.__path); // 'email'
|
|
12
|
-
* console.log(path.personalData.firstName.__path); // 'personalData.firstName'
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
export declare function createFieldPath<T>(): FieldPath<T>;
|
|
16
|
-
/**
|
|
17
|
-
* Извлечь путь из FieldPathNode
|
|
18
|
-
*/
|
|
19
|
-
export declare function extractPath(node: FieldPathNode<unknown, unknown> | unknown): string;
|
|
20
|
-
/**
|
|
21
|
-
* Преобразовать FieldPathNode в FieldPath для переиспользования схем
|
|
22
|
-
*
|
|
23
|
-
* Позволяет композировать validation schemas:
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```typescript
|
|
27
|
-
* const personalDataValidation = (path: FieldPath<PersonalData>) => {
|
|
28
|
-
* required(path.firstName, { message: 'Имя обязательно' });
|
|
29
|
-
* required(path.lastName, { message: 'Фамилия обязательна' });
|
|
30
|
-
* };
|
|
31
|
-
*
|
|
32
|
-
* const mainValidation = (path: FieldPath<MyForm>) => {
|
|
33
|
-
* // Переиспользуем схему
|
|
34
|
-
* personalDataValidation(toFieldPath(path.personalData));
|
|
35
|
-
* required(path.email);
|
|
36
|
-
* };
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare function toFieldPath<T>(node: FieldPathNode<unknown, T, never> | FieldPathNode<any, T, any>): FieldPath<T>;
|
|
40
|
-
/**
|
|
41
|
-
* Извлечь ключ поля из FieldPathNode
|
|
4
|
+
* @deprecated Импортируйте из '../utils/field-path' напрямую.
|
|
5
|
+
* Этот файл оставлен для обратной совместимости.
|
|
42
6
|
*/
|
|
43
|
-
export
|
|
7
|
+
export { createFieldPath, extractPath, extractKey, toFieldPath } from '../utils/field-path';
|
|
@@ -45,3 +45,26 @@ export declare class TreeValidationContextImpl<TForm> implements FormContext<TFo
|
|
|
45
45
|
*/
|
|
46
46
|
setFieldValue(path: string, value: unknown): void;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Реализация контекста валидации для ArrayNode
|
|
50
|
+
* Позволяет валидаторам типа notEmpty работать с массивами
|
|
51
|
+
*/
|
|
52
|
+
export declare class ArrayValidationContextImpl<TForm, TItem> implements FormContext<TForm> {
|
|
53
|
+
private _form;
|
|
54
|
+
private arrayValue;
|
|
55
|
+
/**
|
|
56
|
+
* Форма с типизированным Proxy-доступом к полям
|
|
57
|
+
*/
|
|
58
|
+
readonly form: GroupNodeWithControls<TForm>;
|
|
59
|
+
constructor(form: GroupNode<TForm>, _fieldKey: keyof TForm, arrayValue: TItem[]);
|
|
60
|
+
/**
|
|
61
|
+
* Получить текущее значение массива (для валидатора)
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
value(): TItem[];
|
|
65
|
+
/**
|
|
66
|
+
* Безопасно установить значение поля по строковому пути
|
|
67
|
+
* Автоматически использует emitEvent: false для предотвращения циклов
|
|
68
|
+
*/
|
|
69
|
+
setFieldValue(path: string, value: unknown): void;
|
|
70
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import type { ValidationError } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* Состояние поля формы, возвращаемое хуком {@link useFormControl} для {@link FieldNode}.
|
|
4
|
+
*
|
|
5
|
+
* Содержит реактивные данные поля: значение, состояние валидации, флаги взаимодействия
|
|
6
|
+
* и пользовательские props для компонентов.
|
|
7
|
+
*
|
|
8
|
+
* @typeParam T - Тип значения поля (string, number, boolean и т.д.)
|
|
9
|
+
*
|
|
10
|
+
* @example Базовое использование
|
|
11
|
+
* ```tsx
|
|
12
|
+
* interface Props {
|
|
13
|
+
* control: FieldNode<string>;
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* function TextField({ control }: Props) {
|
|
17
|
+
* const state = useFormControl(control);
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <div>
|
|
21
|
+
* <input
|
|
22
|
+
* value={state.value}
|
|
23
|
+
* disabled={state.disabled}
|
|
24
|
+
* onChange={e => control.setValue(e.target.value)}
|
|
25
|
+
* />
|
|
26
|
+
* {state.shouldShowError && state.errors[0] && (
|
|
27
|
+
* <span className="error">{state.errors[0].message}</span>
|
|
28
|
+
* )}
|
|
29
|
+
* </div>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @see {@link useFormControl} - хук для получения состояния
|
|
35
|
+
* @see {@link ArrayControlState} - состояние для массивов
|
|
36
|
+
*
|
|
37
|
+
* @group Types
|
|
38
|
+
*/
|
|
39
|
+
export interface FieldControlState<T> {
|
|
40
|
+
/**
|
|
41
|
+
* Текущее значение поля.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* const { value } = useFormControl(emailField);
|
|
46
|
+
* console.log(value); // "user@example.com"
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
value: T;
|
|
50
|
+
/**
|
|
51
|
+
* Флаг асинхронной валидации или загрузки.
|
|
52
|
+
* `true` когда выполняется асинхронный валидатор.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const { pending } = useFormControl(usernameField);
|
|
57
|
+
*
|
|
58
|
+
* return (
|
|
59
|
+
* <div>
|
|
60
|
+
* <input {...props} />
|
|
61
|
+
* {pending && <Spinner size="small" />}
|
|
62
|
+
* </div>
|
|
63
|
+
* );
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
pending: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Флаг отключения поля.
|
|
69
|
+
* `true` когда поле недоступно для редактирования.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* const { disabled, value } = useFormControl(field);
|
|
74
|
+
*
|
|
75
|
+
* return (
|
|
76
|
+
* <input
|
|
77
|
+
* value={value}
|
|
78
|
+
* disabled={disabled}
|
|
79
|
+
* className={disabled ? 'opacity-50' : ''}
|
|
80
|
+
* />
|
|
81
|
+
* );
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
disabled: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Массив ошибок валидации.
|
|
87
|
+
* Пустой массив означает отсутствие ошибок.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const { errors } = useFormControl(field);
|
|
92
|
+
*
|
|
93
|
+
* return (
|
|
94
|
+
* <ul className="error-list">
|
|
95
|
+
* {errors.map((error, i) => (
|
|
96
|
+
* <li key={i}>{error.message}</li>
|
|
97
|
+
* ))}
|
|
98
|
+
* </ul>
|
|
99
|
+
* );
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
errors: ValidationError[];
|
|
103
|
+
/**
|
|
104
|
+
* Флаг валидности поля.
|
|
105
|
+
* `true` когда поле прошло все валидации (errors.length === 0).
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* const { valid } = useFormControl(field);
|
|
110
|
+
*
|
|
111
|
+
* return (
|
|
112
|
+
* <input className={valid ? 'border-green' : 'border-gray'} />
|
|
113
|
+
* );
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
valid: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Флаг невалидности поля.
|
|
119
|
+
* `true` когда есть ошибки валидации (errors.length > 0).
|
|
120
|
+
* Противоположность {@link valid}.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```tsx
|
|
124
|
+
* const { invalid } = useFormControl(field);
|
|
125
|
+
*
|
|
126
|
+
* return (
|
|
127
|
+
* <input
|
|
128
|
+
* aria-invalid={invalid}
|
|
129
|
+
* className={invalid ? 'border-red' : ''}
|
|
130
|
+
* />
|
|
131
|
+
* );
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
invalid: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Флаг взаимодействия с полем.
|
|
137
|
+
* `true` после того как поле потеряло фокус (blur) хотя бы один раз.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```tsx
|
|
141
|
+
* const { touched, invalid } = useFormControl(field);
|
|
142
|
+
*
|
|
143
|
+
* // Показываем ошибку только после взаимодействия
|
|
144
|
+
* const showError = touched && invalid;
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
touched: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Флаг для отображения ошибки.
|
|
150
|
+
* Комбинация touched && invalid - удобный shortcut для UI.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```tsx
|
|
154
|
+
* const { shouldShowError, errors } = useFormControl(field);
|
|
155
|
+
*
|
|
156
|
+
* return (
|
|
157
|
+
* <div>
|
|
158
|
+
* <input {...props} />
|
|
159
|
+
* {shouldShowError && (
|
|
160
|
+
* <span className="error">{errors[0]?.message}</span>
|
|
161
|
+
* )}
|
|
162
|
+
* </div>
|
|
163
|
+
* );
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
shouldShowError: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Пользовательские props для передачи в UI-компоненты.
|
|
169
|
+
* Устанавливаются через {@link FieldNode.setComponentProps}.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```tsx
|
|
173
|
+
* // Установка props
|
|
174
|
+
* field.setComponentProps({
|
|
175
|
+
* placeholder: 'Enter email...',
|
|
176
|
+
* maxLength: 100,
|
|
177
|
+
* autoComplete: 'email'
|
|
178
|
+
* });
|
|
179
|
+
*
|
|
180
|
+
* // Использование в компоненте
|
|
181
|
+
* const { componentProps, value } = useFormControl(field);
|
|
182
|
+
*
|
|
183
|
+
* return (
|
|
184
|
+
* <input
|
|
185
|
+
* value={value}
|
|
186
|
+
* placeholder={componentProps.placeholder}
|
|
187
|
+
* maxLength={componentProps.maxLength}
|
|
188
|
+
* autoComplete={componentProps.autoComplete}
|
|
189
|
+
* />
|
|
190
|
+
* );
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
componentProps: Record<string, any>;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Состояние массива формы, возвращаемое хуком {@link useFormControl} для {@link ArrayNode}.
|
|
197
|
+
*
|
|
198
|
+
* Содержит реактивные данные массива: значения элементов, длину, состояние валидации
|
|
199
|
+
* и флаги взаимодействия.
|
|
200
|
+
*
|
|
201
|
+
* @typeParam T - Тип элемента массива (обычно объект с полями формы)
|
|
202
|
+
*
|
|
203
|
+
* @example Список с динамическим добавлением
|
|
204
|
+
* ```tsx
|
|
205
|
+
* interface Phone {
|
|
206
|
+
* type: string;
|
|
207
|
+
* number: string;
|
|
208
|
+
* }
|
|
209
|
+
*
|
|
210
|
+
* interface Props {
|
|
211
|
+
* control: ArrayNode<Phone>;
|
|
212
|
+
* }
|
|
213
|
+
*
|
|
214
|
+
* function PhoneList({ control }: Props) {
|
|
215
|
+
* const { length, valid } = useFormControl(control);
|
|
216
|
+
*
|
|
217
|
+
* return (
|
|
218
|
+
* <div>
|
|
219
|
+
* {control.map((item, index) => (
|
|
220
|
+
* <PhoneItem
|
|
221
|
+
* key={item.id}
|
|
222
|
+
* control={item}
|
|
223
|
+
* onRemove={() => control.remove(index)}
|
|
224
|
+
* />
|
|
225
|
+
* ))}
|
|
226
|
+
*
|
|
227
|
+
* {length === 0 && <p>No phones added</p>}
|
|
228
|
+
*
|
|
229
|
+
* <button onClick={() => control.push({ type: 'mobile', number: '' })}>
|
|
230
|
+
* Add Phone
|
|
231
|
+
* </button>
|
|
232
|
+
*
|
|
233
|
+
* {!valid && <p className="error">Please fix phone errors</p>}
|
|
234
|
+
* </div>
|
|
235
|
+
* );
|
|
236
|
+
* }
|
|
237
|
+
* ```
|
|
238
|
+
*
|
|
239
|
+
* @see {@link useFormControl} - хук для получения состояния
|
|
240
|
+
* @see {@link FieldControlState} - состояние для полей
|
|
241
|
+
*
|
|
242
|
+
* @group Types
|
|
243
|
+
*/
|
|
244
|
+
export interface ArrayControlState<T> {
|
|
245
|
+
/**
|
|
246
|
+
* Массив текущих значений всех элементов.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```tsx
|
|
250
|
+
* const { value } = useFormControl(phonesArray);
|
|
251
|
+
* console.log(value);
|
|
252
|
+
* // [{ type: 'mobile', number: '+1234567890' }, { type: 'home', number: '+0987654321' }]
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
value: T[];
|
|
256
|
+
/**
|
|
257
|
+
* Количество элементов в массиве.
|
|
258
|
+
* Эквивалентно value.length, но оптимизировано для реактивности.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```tsx
|
|
262
|
+
* const { length } = useFormControl(itemsArray);
|
|
263
|
+
*
|
|
264
|
+
* return (
|
|
265
|
+
* <div>
|
|
266
|
+
* <span>Items: {length}</span>
|
|
267
|
+
* {length >= 10 && <span>Maximum reached</span>}
|
|
268
|
+
* </div>
|
|
269
|
+
* );
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
length: number;
|
|
273
|
+
/**
|
|
274
|
+
* Флаг асинхронной валидации.
|
|
275
|
+
* `true` когда выполняется асинхронный валидатор массива или любого элемента.
|
|
276
|
+
*/
|
|
277
|
+
pending: boolean;
|
|
278
|
+
/**
|
|
279
|
+
* Массив ошибок валидации уровня массива.
|
|
280
|
+
* Не включает ошибки отдельных элементов.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```tsx
|
|
284
|
+
* // Валидатор массива
|
|
285
|
+
* validators.apply(phonesArray, {
|
|
286
|
+
* validator: (phones) => phones.length >= 1,
|
|
287
|
+
* message: 'At least one phone required'
|
|
288
|
+
* });
|
|
289
|
+
*
|
|
290
|
+
* // В компоненте
|
|
291
|
+
* const { errors } = useFormControl(phonesArray);
|
|
292
|
+
* // errors содержит ошибку "At least one phone required" если массив пуст
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
errors: ValidationError[];
|
|
296
|
+
/**
|
|
297
|
+
* Флаг валидности массива и всех его элементов.
|
|
298
|
+
* `true` только когда массив и все вложенные элементы валидны.
|
|
299
|
+
*/
|
|
300
|
+
valid: boolean;
|
|
301
|
+
/**
|
|
302
|
+
* Флаг невалидности.
|
|
303
|
+
* `true` когда есть ошибки в массиве или любом элементе.
|
|
304
|
+
*/
|
|
305
|
+
invalid: boolean;
|
|
306
|
+
/**
|
|
307
|
+
* Флаг взаимодействия.
|
|
308
|
+
* `true` после взаимодействия с любым элементом массива.
|
|
309
|
+
*/
|
|
310
|
+
touched: boolean;
|
|
311
|
+
/**
|
|
312
|
+
* Флаг изменения.
|
|
313
|
+
* `true` когда значение массива отличается от начального.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```tsx
|
|
317
|
+
* const { dirty } = useFormControl(itemsArray);
|
|
318
|
+
*
|
|
319
|
+
* return (
|
|
320
|
+
* <div>
|
|
321
|
+
* {dirty && <span>* Unsaved changes</span>}
|
|
322
|
+
* <button disabled={!dirty}>Save</button>
|
|
323
|
+
* </div>
|
|
324
|
+
* );
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
dirty: boolean;
|
|
328
|
+
}
|
|
@@ -1,48 +1,24 @@
|
|
|
1
1
|
import type { FieldNode } from '../core/nodes/field-node';
|
|
2
2
|
import type { ArrayNode } from '../core/nodes/array-node';
|
|
3
|
-
import type { FormValue,
|
|
3
|
+
import type { FormValue, FormFields } from '../core/types';
|
|
4
|
+
import type { FieldControlState, ArrayControlState } from './types';
|
|
4
5
|
/**
|
|
5
|
-
* @
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
disabled: boolean;
|
|
11
|
-
errors: ValidationError[];
|
|
12
|
-
valid: boolean;
|
|
13
|
-
invalid: boolean;
|
|
14
|
-
touched: boolean;
|
|
15
|
-
shouldShowError: boolean;
|
|
16
|
-
componentProps: Record<string, any>;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* @internal
|
|
20
|
-
*/
|
|
21
|
-
interface ArrayControlState<T> {
|
|
22
|
-
value: T[];
|
|
23
|
-
length: number;
|
|
24
|
-
pending: boolean;
|
|
25
|
-
errors: ValidationError[];
|
|
26
|
-
valid: boolean;
|
|
27
|
-
invalid: boolean;
|
|
28
|
-
touched: boolean;
|
|
29
|
-
dirty: boolean;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Хук для получения только значения поля без подписки на errors, valid и т.д.
|
|
33
|
-
* Используйте когда нужно только значение для условного рендеринга.
|
|
6
|
+
* React-хук для подписки на состояние {@link ArrayNode}.
|
|
7
|
+
*
|
|
8
|
+
* @typeParam T - Тип элемента массива
|
|
9
|
+
* @param control - ArrayNode или undefined
|
|
10
|
+
* @returns Состояние массива {@link ArrayControlState}
|
|
34
11
|
*
|
|
35
|
-
* @group React Hooks
|
|
36
|
-
*/
|
|
37
|
-
export declare function useFormControlValue<T extends FormValue>(control: FieldNode<T>): T;
|
|
38
|
-
/**
|
|
39
|
-
* Хук для работы с ArrayNode - возвращает состояние массива с подписками на сигналы
|
|
40
12
|
* @group React Hooks
|
|
41
13
|
*/
|
|
42
14
|
export declare function useFormControl<T extends FormFields>(control: ArrayNode<T> | undefined): ArrayControlState<T>;
|
|
43
15
|
/**
|
|
44
|
-
*
|
|
16
|
+
* React-хук для подписки на состояние {@link FieldNode}.
|
|
17
|
+
*
|
|
18
|
+
* @typeParam T - Тип значения поля
|
|
19
|
+
* @param control - FieldNode для подписки
|
|
20
|
+
* @returns Состояние поля {@link FieldControlState}
|
|
21
|
+
*
|
|
45
22
|
* @group React Hooks
|
|
46
23
|
*/
|
|
47
24
|
export declare function useFormControl<T extends FormValue>(control: FieldNode<T>): FieldControlState<T>;
|
|
48
|
-
export {};
|