@reformer/core 3.0.0 → 4.0.0
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/compute-from.d.ts +2 -0
- package/dist/behaviors/compute-from.js +31 -0
- package/dist/behaviors/copy-from.d.ts +2 -0
- package/dist/behaviors/copy-from.js +29 -0
- package/dist/behaviors/enable-when.d.ts +2 -0
- package/dist/behaviors/enable-when.js +25 -0
- package/dist/behaviors/reset-when.d.ts +2 -0
- package/dist/behaviors/reset-when.js +24 -0
- package/dist/behaviors/revalidate-when.d.ts +2 -0
- package/dist/behaviors/revalidate-when.js +18 -0
- package/dist/behaviors/sync-fields.d.ts +2 -0
- package/dist/behaviors/sync-fields.js +41 -0
- package/dist/behaviors/transform-value.d.ts +2 -0
- package/dist/behaviors/transform-value.js +45 -0
- package/dist/behaviors/watch-field.d.ts +2 -0
- package/dist/behaviors/watch-field.js +21 -0
- package/dist/behaviors.js +26 -19
- package/dist/core/behavior/behavior-context.d.ts +26 -12
- package/dist/core/behavior/behavior-registry.d.ts +15 -27
- package/dist/core/behavior/behaviors/compute-from.d.ts +50 -21
- package/dist/core/behavior/behaviors/copy-from.d.ts +39 -14
- package/dist/core/behavior/behaviors/enable-when.d.ts +88 -19
- package/dist/core/behavior/behaviors/reset-when.d.ts +31 -18
- package/dist/core/behavior/behaviors/revalidate-when.d.ts +40 -17
- package/dist/core/behavior/behaviors/sync-fields.d.ts +34 -14
- package/dist/core/behavior/behaviors/transform-value.d.ts +116 -44
- package/dist/core/behavior/behaviors/watch-field.d.ts +66 -21
- package/dist/core/behavior/compose-behavior.d.ts +2 -12
- package/dist/core/behavior/index.d.ts +0 -1
- package/dist/core/behavior/types.d.ts +2 -8
- package/dist/core/factories/node-factory.d.ts +6 -29
- package/dist/core/nodes/array-node.d.ts +39 -19
- package/dist/core/nodes/field-node.d.ts +51 -26
- package/dist/core/nodes/form-node.d.ts +18 -20
- package/dist/core/nodes/group-node.d.ts +25 -21
- package/dist/core/types/deep-schema.d.ts +2 -12
- package/dist/core/types/field-path.d.ts +1 -1
- package/dist/core/types/form-context.d.ts +26 -26
- package/dist/core/types/form-proxy.d.ts +2 -32
- package/dist/core/types/index.d.ts +51 -5
- package/dist/core/types/validation-schema.d.ts +3 -12
- package/dist/core/utils/abstract-registry.d.ts +74 -0
- package/dist/core/utils/aggregate-signals.d.ts +71 -0
- package/dist/core/utils/create-form.d.ts +1 -18
- package/dist/core/utils/error-handler.d.ts +1 -18
- package/dist/core/utils/field-path-navigator.d.ts +1 -1
- package/dist/core/utils/field-path.d.ts +23 -11
- package/dist/core/utils/form-observer.d.ts +176 -0
- package/dist/core/utils/form-proxy-builder.d.ts +25 -0
- package/dist/core/utils/form-submitter.d.ts +121 -0
- package/dist/core/utils/index.d.ts +9 -2
- package/dist/core/utils/registry-helpers.d.ts +0 -7
- package/dist/core/utils/safe-effect.d.ts +73 -0
- package/dist/core/utils/status-machine.d.ts +153 -0
- package/dist/core/utils/type-guards.d.ts +5 -23
- package/dist/core/utils/unique-id.d.ts +53 -0
- package/dist/core/validation/core/apply-when.d.ts +3 -9
- package/dist/core/validation/core/apply.d.ts +2 -13
- package/dist/core/validation/core/validate-async.d.ts +2 -8
- package/dist/core/validation/core/validate-tree.d.ts +0 -6
- package/dist/core/validation/core/validate.d.ts +1 -7
- package/dist/core/validation/index.d.ts +8 -2
- package/dist/core/validation/validate-form.d.ts +1 -38
- package/dist/core/validation/validation-applicator.d.ts +2 -21
- package/dist/core/validation/validation-context.d.ts +58 -42
- package/dist/core/validation/validation-registry.d.ts +11 -25
- package/dist/core/validation/validators/array-validators.d.ts +2 -12
- package/dist/core/validation/validators/date-utils.d.ts +26 -0
- package/dist/core/validation/validators/email.d.ts +2 -9
- package/dist/core/validation/validators/future-date.d.ts +35 -0
- package/dist/core/validation/validators/index.d.ts +7 -1
- package/dist/core/validation/validators/is-date.d.ts +36 -0
- package/dist/core/validation/validators/max-age.d.ts +36 -0
- package/dist/core/validation/validators/max-date.d.ts +36 -0
- package/dist/core/validation/validators/max-length.d.ts +3 -10
- package/dist/core/validation/validators/max.d.ts +3 -10
- package/dist/core/validation/validators/min-age.d.ts +36 -0
- package/dist/core/validation/validators/min-date.d.ts +36 -0
- package/dist/core/validation/validators/min-length.d.ts +3 -10
- package/dist/core/validation/validators/min.d.ts +3 -10
- package/dist/core/validation/validators/number.d.ts +2 -9
- package/dist/core/validation/validators/past-date.d.ts +35 -0
- package/dist/core/validation/validators/pattern.d.ts +2 -9
- package/dist/core/validation/validators/phone.d.ts +2 -9
- package/dist/core/validation/validators/required.d.ts +2 -9
- package/dist/core/validation/validators/url.d.ts +2 -9
- package/dist/date-utils-xUWFslTj.js +29 -0
- package/dist/field-path-DuKdGcIE.js +66 -0
- package/dist/hooks/types.d.ts +1 -1
- package/dist/hooks/useArrayLength.d.ts +31 -0
- package/dist/hooks/useFormControl.d.ts +4 -4
- package/dist/hooks/useFormControlValue.d.ts +2 -2
- package/dist/hooks/useHiddenCondition.d.ts +25 -0
- package/dist/hooks/useSignalSubscription.d.ts +1 -1
- package/dist/index-D25LsbRm.js +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1171 -786
- package/dist/registry-helpers-Bv_BJ1s-.js +615 -0
- package/dist/safe-effect-Dh8uw81c.js +20 -0
- package/dist/validate-C3XiA_zf.js +10 -0
- package/dist/validators/email.d.ts +2 -0
- package/dist/validators/email.js +13 -0
- package/dist/validators/future-date.d.ts +2 -0
- package/dist/validators/future-date.js +20 -0
- package/dist/validators/is-date.d.ts +2 -0
- package/dist/validators/is-date.js +12 -0
- package/dist/validators/max-age.d.ts +2 -0
- package/dist/validators/max-age.js +20 -0
- package/dist/validators/max-date.d.ts +2 -0
- package/dist/validators/max-date.js +20 -0
- package/dist/validators/max-length.d.ts +2 -0
- package/dist/validators/max-length.js +11 -0
- package/dist/validators/max.d.ts +2 -0
- package/dist/validators/max.js +11 -0
- package/dist/validators/min-age.d.ts +2 -0
- package/dist/validators/min-age.js +20 -0
- package/dist/validators/min-date.d.ts +2 -0
- package/dist/validators/min-date.js +20 -0
- package/dist/validators/min-length.d.ts +2 -0
- package/dist/validators/min-length.js +11 -0
- package/dist/validators/min.d.ts +2 -0
- package/dist/validators/min.js +11 -0
- package/dist/validators/number.d.ts +2 -0
- package/dist/validators/number.js +35 -0
- package/dist/validators/past-date.d.ts +2 -0
- package/dist/validators/past-date.js +20 -0
- package/dist/validators/pattern.d.ts +2 -0
- package/dist/validators/pattern.js +11 -0
- package/dist/validators/phone.d.ts +2 -0
- package/dist/validators/phone.js +35 -0
- package/dist/validators/required.d.ts +2 -0
- package/dist/validators/required.js +15 -0
- package/dist/validators/url.d.ts +2 -0
- package/dist/validators/url.js +19 -0
- package/dist/validators-BGsNOgT1.js +207 -0
- package/dist/validators.js +54 -29
- package/llms.txt +7878 -311
- package/package.json +83 -9
- package/dist/behaviors-DzYL8kY_.js +0 -499
- package/dist/core/behavior/create-field-path.d.ts +0 -7
- package/dist/core/context/form-context-impl.d.ts +0 -29
- package/dist/core/utils/debounce.d.ts +0 -160
- package/dist/core/utils/resources.d.ts +0 -41
- package/dist/core/validation/field-path.d.ts +0 -7
- package/dist/core/validation/validators/date.d.ts +0 -38
- package/dist/registry-helpers-BRxAr6nG.js +0 -490
- package/dist/validators-gXoHPdqM.js +0 -418
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { BehaviorSchemaFn } from '../behavior/types';
|
|
2
|
+
import { FormSchema } from './deep-schema';
|
|
3
|
+
import { ValidationSchemaFn } from './validation-schema';
|
|
4
|
+
import { FormNode } from '../nodes/form-node';
|
|
1
5
|
/**
|
|
2
6
|
* Represents any valid form value type
|
|
3
7
|
* Use this instead of 'any' for form values to maintain type safety
|
|
@@ -20,12 +24,46 @@ export type UnknownFormValue = unknown;
|
|
|
20
24
|
* @category Validation Types
|
|
21
25
|
*/
|
|
22
26
|
export type ValidatorFn<T = FormValue> = (value: T) => ValidationError | null;
|
|
27
|
+
/**
|
|
28
|
+
* Опции для асинхронного валидатора
|
|
29
|
+
* @group Types
|
|
30
|
+
* @category Validation Types
|
|
31
|
+
*/
|
|
32
|
+
export interface AsyncValidatorOptions {
|
|
33
|
+
/**
|
|
34
|
+
* AbortSignal для отмены валидации
|
|
35
|
+
* Позволяет отменить асинхронную операцию при новой валидации
|
|
36
|
+
*/
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}
|
|
23
39
|
/**
|
|
24
40
|
* Асинхронная функция валидации
|
|
41
|
+
*
|
|
42
|
+
* @param value - Значение для валидации
|
|
43
|
+
* @param options - Опции валидации (опционально)
|
|
44
|
+
* @returns Promise с ошибкой валидации или null если значение валидно
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* // Простой валидатор (без поддержки отмены)
|
|
49
|
+
* const emailExists: AsyncValidatorFn<string> = async (value) => {
|
|
50
|
+
* const exists = await checkEmail(value);
|
|
51
|
+
* return exists ? { code: 'exists', message: 'Email already exists' } : null;
|
|
52
|
+
* };
|
|
53
|
+
*
|
|
54
|
+
* // Валидатор с поддержкой отмены
|
|
55
|
+
* const emailExistsAbortable: AsyncValidatorFn<string> = async (value, options) => {
|
|
56
|
+
* const exists = await fetch(`/api/check-email?email=${value}`, {
|
|
57
|
+
* signal: options?.signal // Передаём signal в fetch для отмены запроса
|
|
58
|
+
* });
|
|
59
|
+
* return exists ? { code: 'exists', message: 'Email already exists' } : null;
|
|
60
|
+
* };
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
25
63
|
* @group Types
|
|
26
64
|
* @category Validation Types
|
|
27
65
|
*/
|
|
28
|
-
export type AsyncValidatorFn<T = FormValue> = (value: T) => Promise<ValidationError | null>;
|
|
66
|
+
export type AsyncValidatorFn<T = FormValue> = (value: T, options?: AsyncValidatorOptions) => Promise<ValidationError | null>;
|
|
29
67
|
/**
|
|
30
68
|
* Ошибка валидации
|
|
31
69
|
* @group Types
|
|
@@ -65,9 +103,6 @@ export type { ContextualValidatorFn, ContextualAsyncValidatorFn, TreeValidatorFn
|
|
|
65
103
|
export type { FormSchema, ArrayConfig } from './deep-schema';
|
|
66
104
|
export type { FormContext } from './form-context';
|
|
67
105
|
export type { FormControlsProxy, FormProxy, FormArrayProxy } from './form-proxy';
|
|
68
|
-
import type { BehaviorSchemaFn } from '../behavior/types';
|
|
69
|
-
import type { FormSchema } from './deep-schema';
|
|
70
|
-
import type { ValidationSchemaFn } from './validation-schema';
|
|
71
106
|
/**
|
|
72
107
|
* Конфигурация GroupNode с поддержкой схем
|
|
73
108
|
* Используется для создания форм с автоматическим применением behavior и validation схем
|
|
@@ -82,6 +117,18 @@ export interface GroupNodeConfig<T> {
|
|
|
82
117
|
behavior?: BehaviorSchemaFn<T>;
|
|
83
118
|
/** Схема валидации (required, email, minLength и т.д.) */
|
|
84
119
|
validation?: ValidationSchemaFn<T>;
|
|
120
|
+
/**
|
|
121
|
+
* Опциональный ValidationRegistry для dependency injection
|
|
122
|
+
* Используется для тестирования с mock-реестрами
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
_validationRegistry?: unknown;
|
|
126
|
+
/**
|
|
127
|
+
* Опциональный BehaviorRegistry для dependency injection
|
|
128
|
+
* Используется для тестирования с mock-реестрами
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
_behaviorRegistry?: unknown;
|
|
85
132
|
}
|
|
86
133
|
/**
|
|
87
134
|
* Тип для Record с unknown значениями
|
|
@@ -112,7 +159,6 @@ export interface ArrayNodeLike {
|
|
|
112
159
|
at(index: number): FormNode<unknown> | undefined;
|
|
113
160
|
length: unknown;
|
|
114
161
|
}
|
|
115
|
-
import type { FormNode } from '../nodes/form-node';
|
|
116
162
|
/**
|
|
117
163
|
* Конфиг с полем schema (для ArrayConfig)
|
|
118
164
|
* @internal
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Основано на Angular Signal Forms подходе:
|
|
5
|
-
* - Валидация определяется отдельно от схемы полей
|
|
6
|
-
* - Поддержка условной валидации (applyWhen)
|
|
7
|
-
* - Cross-field валидация (validateTree)
|
|
8
|
-
* - Асинхронная валидация с контекстом
|
|
9
|
-
*/
|
|
10
|
-
import type { FormFields, ValidationError } from './index';
|
|
11
|
-
import type { FieldPath } from './field-path';
|
|
12
|
-
import type { FormContext } from './form-context';
|
|
1
|
+
import { FormFields, ValidationError } from './index';
|
|
2
|
+
import { FieldPath } from './field-path';
|
|
3
|
+
import { FormContext } from './form-context';
|
|
13
4
|
/**
|
|
14
5
|
* Функция валидации поля с контекстом
|
|
15
6
|
*
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { RegistryStack } from './registry-stack';
|
|
2
|
+
/**
|
|
3
|
+
* Базовый класс для реестров (BehaviorRegistry, ValidationRegistry).
|
|
4
|
+
*
|
|
5
|
+
* Реализует паттерн Template Method для управления регистрацией:
|
|
6
|
+
* `beginRegistration()` → `onBeginRegistration()`, `endRegistration()` → `onEndRegistration()`.
|
|
7
|
+
*
|
|
8
|
+
* @typeParam TRegistration - Тип регистрируемых элементов.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { AbstractRegistry } from '@reformer/core';
|
|
13
|
+
*
|
|
14
|
+
* class MyRegistry extends AbstractRegistry<{ name: string }> {
|
|
15
|
+
* protected onEndRegistration(items: { name: string }[]) {
|
|
16
|
+
* console.log('Registered', items.length);
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare abstract class AbstractRegistry<TRegistration> {
|
|
22
|
+
/** Флаг активной регистрации */
|
|
23
|
+
protected isRegistering: boolean;
|
|
24
|
+
/** Массив зарегистрированных элементов */
|
|
25
|
+
protected registrations: TRegistration[];
|
|
26
|
+
/**
|
|
27
|
+
* Получить стек для конкретного класса реестра
|
|
28
|
+
* Создает новый стек если не существует
|
|
29
|
+
*
|
|
30
|
+
* @param ctor - Конструктор класса реестра
|
|
31
|
+
* @returns RegistryStack для данного класса
|
|
32
|
+
*/
|
|
33
|
+
protected static getStack<T extends AbstractRegistry<any>>(ctor: new (...args: any[]) => T): RegistryStack<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Получить текущий активный реестр из стека
|
|
36
|
+
* Должен быть переопределен в наследниках как static метод
|
|
37
|
+
*
|
|
38
|
+
* @param ctor - Конструктор класса реестра
|
|
39
|
+
* @returns Текущий активный реестр или null
|
|
40
|
+
*/
|
|
41
|
+
protected static getCurrentFromStack<T extends AbstractRegistry<any>>(ctor: new (...args: any[]) => T): T | null;
|
|
42
|
+
/**
|
|
43
|
+
* Начать регистрацию
|
|
44
|
+
*
|
|
45
|
+
* Помещает this в global stack для изоляции форм
|
|
46
|
+
* Вызывает hook onBeginRegistration()
|
|
47
|
+
*/
|
|
48
|
+
beginRegistration(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Проверить, активна ли регистрация
|
|
51
|
+
*/
|
|
52
|
+
isActive(): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Получить зарегистрированные элементы
|
|
55
|
+
*/
|
|
56
|
+
getRegistrations(): TRegistration[];
|
|
57
|
+
/**
|
|
58
|
+
* Hook: вызывается в начале регистрации
|
|
59
|
+
* Может быть переопределен в наследниках для инициализации
|
|
60
|
+
*/
|
|
61
|
+
protected onBeginRegistration(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Завершить регистрацию и извлечь из стека
|
|
64
|
+
*
|
|
65
|
+
* @param registryName - Имя реестра для отладки
|
|
66
|
+
*/
|
|
67
|
+
protected completeRegistration(registryName: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Отменить регистрацию без применения
|
|
70
|
+
*
|
|
71
|
+
* @param registryName - Имя реестра для отладки
|
|
72
|
+
*/
|
|
73
|
+
cancelRegistration(registryName: string): void;
|
|
74
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ReadonlySignal, Signal } from '@preact/signals-core';
|
|
2
|
+
import { FieldStatus, ValidationError } from '../types';
|
|
3
|
+
import { FormNode } from '../nodes/form-node';
|
|
4
|
+
/**
|
|
5
|
+
* Функция получения дочерних узлов
|
|
6
|
+
* Возвращает массив FormNode для агрегации состояния
|
|
7
|
+
*/
|
|
8
|
+
type GetChildrenFn<T> = () => FormNode<T>[];
|
|
9
|
+
/**
|
|
10
|
+
* Результат создания агрегированных сигналов
|
|
11
|
+
*/
|
|
12
|
+
export interface AggregateSignals {
|
|
13
|
+
/** Все дочерние узлы валидны и нет собственных ошибок */
|
|
14
|
+
valid: ReadonlySignal<boolean>;
|
|
15
|
+
/** Есть ошибки (инверсия valid) */
|
|
16
|
+
invalid: ReadonlySignal<boolean>;
|
|
17
|
+
/** Хотя бы один дочерний узел в состоянии pending */
|
|
18
|
+
pending: ReadonlySignal<boolean>;
|
|
19
|
+
/** Хотя бы один дочерний узел touched */
|
|
20
|
+
touched: ReadonlySignal<boolean>;
|
|
21
|
+
/** Хотя бы один дочерний узел dirty */
|
|
22
|
+
dirty: ReadonlySignal<boolean>;
|
|
23
|
+
/** Собственные ошибки + ошибки всех дочерних узлов */
|
|
24
|
+
errors: ReadonlySignal<ValidationError[]>;
|
|
25
|
+
/** Статус: disabled > pending > invalid > valid */
|
|
26
|
+
status: ReadonlySignal<FieldStatus>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Опции для создания агрегированных сигналов
|
|
30
|
+
*/
|
|
31
|
+
export interface AggregateSignalsOptions<T> {
|
|
32
|
+
/** Функция получения дочерних узлов */
|
|
33
|
+
getChildren: GetChildrenFn<T>;
|
|
34
|
+
/** Signal с собственными ошибками контейнера (form-level или array-level) */
|
|
35
|
+
ownErrors: Signal<ValidationError[]>;
|
|
36
|
+
/** Опциональный signal disabled состояния (для GroupNode) */
|
|
37
|
+
disabled?: Signal<boolean>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Создать агрегированные computed signals для контейнерного узла
|
|
41
|
+
*
|
|
42
|
+
* Используется в GroupNode и ArrayNode для унификации логики
|
|
43
|
+
* вычисления состояния на основе дочерних узлов.
|
|
44
|
+
*
|
|
45
|
+
* @param options - Опции конфигурации
|
|
46
|
+
* @returns Объект с computed signals
|
|
47
|
+
*
|
|
48
|
+
* @example GroupNode
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const signals = createAggregateSignals({
|
|
51
|
+
* getChildren: () => Array.from(this._fields.values()),
|
|
52
|
+
* ownErrors: this._formErrors,
|
|
53
|
+
* disabled: this._disabled,
|
|
54
|
+
* });
|
|
55
|
+
* this.valid = signals.valid;
|
|
56
|
+
* this.invalid = signals.invalid;
|
|
57
|
+
* // ...
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example ArrayNode
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const signals = createAggregateSignals({
|
|
63
|
+
* getChildren: () => this.items.value,
|
|
64
|
+
* ownErrors: this._arrayErrors,
|
|
65
|
+
* });
|
|
66
|
+
* this.valid = signals.valid;
|
|
67
|
+
* // ...
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function createAggregateSignals<T>(options: AggregateSignalsOptions<T>): AggregateSignals;
|
|
71
|
+
export {};
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Фабричная функция для создания формы с правильной типизацией
|
|
3
|
-
*
|
|
4
|
-
* Решает проблему с типизацией конструктора GroupNode, который возвращает
|
|
5
|
-
* Proxy (FormProxy), но TypeScript не может это вывести автоматически.
|
|
6
|
-
*
|
|
7
|
-
* @group Utilities
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* // Вместо:
|
|
12
|
-
* const form: FormProxy<MyForm> = new GroupNode<MyForm>(config);
|
|
13
|
-
*
|
|
14
|
-
* // Используйте:
|
|
15
|
-
* const form = createForm<MyForm>(config);
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
import type { FormProxy, GroupNodeConfig, FormSchema } from '../types';
|
|
1
|
+
import { FormProxy, GroupNodeConfig, FormSchema } from '../types';
|
|
19
2
|
/**
|
|
20
3
|
* Создать форму с полной конфигурацией (form, behavior, validation)
|
|
21
4
|
*
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* FormErrorHandler - централизованная обработка ошибок в формах
|
|
3
|
-
*
|
|
4
|
-
* Устраняет несогласованность обработки ошибок между:
|
|
5
|
-
* - field-node.ts (логирует и конвертирует в ValidationError)
|
|
6
|
-
* - behavior-applicator.ts (логирует и пробрасывает)
|
|
7
|
-
* - validation-applicator.ts (логирует и проглатывает)
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* try {
|
|
12
|
-
* await validator(value);
|
|
13
|
-
* } catch (error) {
|
|
14
|
-
* return FormErrorHandler.handle(error, 'AsyncValidator', ErrorStrategy.CONVERT);
|
|
15
|
-
* }
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
import type { ValidationError } from '../types';
|
|
1
|
+
import { ValidationError } from '../types';
|
|
19
2
|
/**
|
|
20
3
|
* Стратегия обработки ошибок
|
|
21
4
|
*
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* FieldPath proxy - типобезопасный доступ к путям полей формы
|
|
3
|
-
*
|
|
4
|
-
* Единый модуль для validation и behavior схем.
|
|
5
|
-
* Предоставляет типизированную навигацию по структуре формы.
|
|
6
|
-
*
|
|
7
|
-
* @module utils/field-path
|
|
8
|
-
*/
|
|
9
|
-
import type { FieldPath, FieldPathNode } from '../types';
|
|
1
|
+
import { FieldPath, FieldPathNode } from '../types';
|
|
10
2
|
/**
|
|
11
3
|
* Создать FieldPath proxy для формы
|
|
12
4
|
*
|
|
@@ -19,7 +11,17 @@ import type { FieldPath, FieldPathNode } from '../types';
|
|
|
19
11
|
*/
|
|
20
12
|
export declare function createFieldPath<T>(): FieldPath<T>;
|
|
21
13
|
/**
|
|
22
|
-
* Извлечь путь из FieldPathNode
|
|
14
|
+
* Извлечь строковый путь из {@link FieldPathNode}.
|
|
15
|
+
*
|
|
16
|
+
* @param node - Узел `FieldPathNode` либо строка-путь.
|
|
17
|
+
* @returns Путь вида `"a.b.c"`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { extractPath } from '@reformer/core';
|
|
22
|
+
*
|
|
23
|
+
* const path = (renderPath) => extractPath(renderPath.user.email); // → 'user.email'
|
|
24
|
+
* ```
|
|
23
25
|
*/
|
|
24
26
|
export declare function extractPath(node: FieldPathNode<unknown, unknown> | unknown): string;
|
|
25
27
|
/**
|
|
@@ -43,6 +45,16 @@ export declare function extractPath(node: FieldPathNode<unknown, unknown> | unkn
|
|
|
43
45
|
*/
|
|
44
46
|
export declare function toFieldPath<T>(node: FieldPathNode<unknown, T, never> | FieldPathNode<any, T, any>): FieldPath<T>;
|
|
45
47
|
/**
|
|
46
|
-
* Извлечь
|
|
48
|
+
* Извлечь имя последнего сегмента ({@link FieldPathSegment}) пути.
|
|
49
|
+
*
|
|
50
|
+
* @param node - Узел `FieldPathNode` либо строка-путь.
|
|
51
|
+
* @returns Имя поля без префикса родителя (последний сегмент).
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* import { extractKey } from '@reformer/core';
|
|
56
|
+
*
|
|
57
|
+
* extractKey(path.user.email); // → 'email'
|
|
58
|
+
* ```
|
|
47
59
|
*/
|
|
48
60
|
export declare function extractKey(node: FieldPathNode<unknown, unknown> | unknown): string;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { FormFields, FormValue, FieldStatus, ValidationError } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Тип события изменения в форме
|
|
4
|
+
*/
|
|
5
|
+
export type FormChangeType = 'value' | 'status' | 'errors' | 'touched' | 'dirty';
|
|
6
|
+
/**
|
|
7
|
+
* Событие изменения в форме
|
|
8
|
+
*/
|
|
9
|
+
export interface FormChangeEvent {
|
|
10
|
+
/** Тип изменения */
|
|
11
|
+
type: FormChangeType;
|
|
12
|
+
/** Путь к полю */
|
|
13
|
+
path: string;
|
|
14
|
+
/** Timestamp события */
|
|
15
|
+
timestamp: number;
|
|
16
|
+
/** Старое значение */
|
|
17
|
+
oldValue?: unknown;
|
|
18
|
+
/** Новое значение */
|
|
19
|
+
newValue: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Интерфейс узла формы для observer
|
|
23
|
+
*/
|
|
24
|
+
export interface ObservableFormNode {
|
|
25
|
+
value: {
|
|
26
|
+
value: FormValue;
|
|
27
|
+
};
|
|
28
|
+
status: {
|
|
29
|
+
value: FieldStatus;
|
|
30
|
+
};
|
|
31
|
+
errors: {
|
|
32
|
+
value: ValidationError[];
|
|
33
|
+
};
|
|
34
|
+
touched: {
|
|
35
|
+
value: boolean;
|
|
36
|
+
};
|
|
37
|
+
dirty: {
|
|
38
|
+
value: boolean;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Интерфейс формы для observer
|
|
43
|
+
*/
|
|
44
|
+
export interface ObservableForm<T extends FormFields> {
|
|
45
|
+
value: {
|
|
46
|
+
value: T;
|
|
47
|
+
};
|
|
48
|
+
status: {
|
|
49
|
+
value: FieldStatus;
|
|
50
|
+
};
|
|
51
|
+
errors: {
|
|
52
|
+
value: ValidationError[];
|
|
53
|
+
};
|
|
54
|
+
touched: {
|
|
55
|
+
value: boolean;
|
|
56
|
+
};
|
|
57
|
+
dirty: {
|
|
58
|
+
value: boolean;
|
|
59
|
+
};
|
|
60
|
+
getFieldByPath(path: string): ObservableFormNode | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Callback для обработки событий изменения
|
|
64
|
+
*/
|
|
65
|
+
export type FormChangeCallback = (event: FormChangeEvent) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Опции для FormObserver
|
|
68
|
+
*/
|
|
69
|
+
export interface FormObserverOptions {
|
|
70
|
+
/** Включить логирование в консоль (по умолчанию true в DEV) */
|
|
71
|
+
enableLogging?: boolean;
|
|
72
|
+
/** Фильтр типов событий */
|
|
73
|
+
eventTypes?: FormChangeType[];
|
|
74
|
+
/** Фильтр путей полей (regex или массив) */
|
|
75
|
+
pathFilter?: RegExp | string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* FormObserver - мониторинг изменений в форме
|
|
79
|
+
*
|
|
80
|
+
* Полезен для:
|
|
81
|
+
* - Отладки сложных форм
|
|
82
|
+
* - Логирования изменений для аудита
|
|
83
|
+
* - Синхронизации с внешними системами
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const observer = new FormObserver(form, {
|
|
88
|
+
* enableLogging: true,
|
|
89
|
+
* eventTypes: ['value', 'errors']
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* // Подписка на события
|
|
93
|
+
* const unsubscribe = observer.subscribe((event) => {
|
|
94
|
+
* console.log(`${event.type} at ${event.path}:`, event.newValue);
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // Включить трассировку
|
|
98
|
+
* const disposeTracing = observer.enableTracing();
|
|
99
|
+
*
|
|
100
|
+
* // Cleanup
|
|
101
|
+
* unsubscribe();
|
|
102
|
+
* disposeTracing();
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare class FormObserver<T extends FormFields> {
|
|
106
|
+
private readonly form;
|
|
107
|
+
private listeners;
|
|
108
|
+
private disposers;
|
|
109
|
+
private options;
|
|
110
|
+
/**
|
|
111
|
+
* @param form - Форма для наблюдения
|
|
112
|
+
* @param options - Опции observer
|
|
113
|
+
*/
|
|
114
|
+
constructor(form: ObservableForm<T>, options?: FormObserverOptions);
|
|
115
|
+
/**
|
|
116
|
+
* Подписаться на события изменения
|
|
117
|
+
*
|
|
118
|
+
* @param callback - Функция обработки события
|
|
119
|
+
* @returns Функция отписки
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const unsubscribe = observer.subscribe((event) => {
|
|
124
|
+
* // Отправить событие в analytics
|
|
125
|
+
* analytics.track('form_change', event);
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
subscribe(callback: FormChangeCallback): () => void;
|
|
130
|
+
/**
|
|
131
|
+
* Включить трассировку формы
|
|
132
|
+
*
|
|
133
|
+
* Подписывается на изменения основных сигналов формы
|
|
134
|
+
* и вызывает listeners при каждом изменении
|
|
135
|
+
*
|
|
136
|
+
* @returns Функция для отключения трассировки
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* const dispose = observer.enableTracing();
|
|
141
|
+
*
|
|
142
|
+
* // Позже, для отключения
|
|
143
|
+
* dispose();
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
enableTracing(): () => void;
|
|
147
|
+
/**
|
|
148
|
+
* Наблюдать за конкретным полем
|
|
149
|
+
*
|
|
150
|
+
* @param path - Путь к полю
|
|
151
|
+
* @returns Функция для отключения наблюдения
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* // Наблюдать за полем email
|
|
156
|
+
* const dispose = observer.watchField('email');
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
watchField(path: string): () => void;
|
|
160
|
+
/**
|
|
161
|
+
* Отправить событие всем listeners
|
|
162
|
+
*/
|
|
163
|
+
private emit;
|
|
164
|
+
/**
|
|
165
|
+
* Проверить, нужно ли отслеживать тип события
|
|
166
|
+
*/
|
|
167
|
+
private shouldTrack;
|
|
168
|
+
/**
|
|
169
|
+
* Проверить, соответствует ли путь фильтру
|
|
170
|
+
*/
|
|
171
|
+
private matchesPathFilter;
|
|
172
|
+
/**
|
|
173
|
+
* Очистить все подписки и disposers
|
|
174
|
+
*/
|
|
175
|
+
dispose(): void;
|
|
176
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FormNode } from '../nodes/form-node';
|
|
2
|
+
import { FormProxy } from '../types/form-proxy';
|
|
3
|
+
/**
|
|
4
|
+
* Создать Proxy для типобезопасного доступа к полям GroupNode
|
|
5
|
+
*
|
|
6
|
+
* Proxy обеспечивает:
|
|
7
|
+
* - Доступ к полям через точечную нотацию (form.email, form.address.city)
|
|
8
|
+
* - Приоритет собственных свойств GroupNode над полями
|
|
9
|
+
* - Цепочку proxy для вложенных GroupNode
|
|
10
|
+
* - Блокировку прямого присваивания полям (только через setValue/patchValue)
|
|
11
|
+
*
|
|
12
|
+
* @param target - GroupNode для которого создаётся proxy
|
|
13
|
+
* @param fields - Map полей формы
|
|
14
|
+
* @returns Типизированный Proxy
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const proxy = buildFormProxy(groupNode, groupNode._fields);
|
|
21
|
+
* proxy.email.setValue('test@example.com');
|
|
22
|
+
* console.log(proxy.email.value.value);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildFormProxy<T>(target: FormNode<T>, fields: Map<keyof T, FormNode<unknown>>): FormProxy<T>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { FormFields } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Интерфейс формы для FormSubmitter
|
|
5
|
+
* Минимальный контракт для работы с любой формой
|
|
6
|
+
*/
|
|
7
|
+
export interface SubmittableForm<T extends FormFields> {
|
|
8
|
+
/** Пометить все поля как touched */
|
|
9
|
+
markAsTouched(): void;
|
|
10
|
+
/** Валидировать форму */
|
|
11
|
+
validate(): Promise<boolean>;
|
|
12
|
+
/** Получить значения формы */
|
|
13
|
+
getValue(): T;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Опции для submit
|
|
17
|
+
*/
|
|
18
|
+
export interface SubmitOptions {
|
|
19
|
+
/** Пропустить валидацию перед submit */
|
|
20
|
+
skipValidation?: boolean;
|
|
21
|
+
/** Пропустить markAsTouched перед submit */
|
|
22
|
+
skipTouch?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Результат submit
|
|
26
|
+
*/
|
|
27
|
+
export interface SubmitResult<R> {
|
|
28
|
+
/** Успешно ли выполнен submit */
|
|
29
|
+
success: boolean;
|
|
30
|
+
/** Результат от onSubmit callback */
|
|
31
|
+
data: R | null;
|
|
32
|
+
/** Ошибка, если submit не удался */
|
|
33
|
+
error?: Error;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* FormSubmitter - управляет процессом отправки формы
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const submitter = new FormSubmitter(form);
|
|
41
|
+
*
|
|
42
|
+
* // Простой submit
|
|
43
|
+
* const result = await submitter.submit(async (values) => {
|
|
44
|
+
* return await api.saveForm(values);
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* // Проверка состояния
|
|
48
|
+
* if (submitter.submitting.value) {
|
|
49
|
+
* console.log('Форма отправляется...');
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare class FormSubmitter<T extends FormFields> {
|
|
54
|
+
private readonly form;
|
|
55
|
+
/** Внутренний сигнал состояния отправки */
|
|
56
|
+
private readonly _submitting;
|
|
57
|
+
/** Публичный read-only сигнал состояния отправки */
|
|
58
|
+
readonly submitting: ReadonlySignal<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* @param form - Форма для отправки
|
|
61
|
+
*/
|
|
62
|
+
constructor(form: SubmittableForm<T>);
|
|
63
|
+
/**
|
|
64
|
+
* Отправить форму
|
|
65
|
+
*
|
|
66
|
+
* Процесс:
|
|
67
|
+
* 1. Помечает все поля как touched (для отображения ошибок)
|
|
68
|
+
* 2. Валидирует форму
|
|
69
|
+
* 3. Если валидация успешна - вызывает onSubmit
|
|
70
|
+
* 4. Управляет состоянием submitting
|
|
71
|
+
*
|
|
72
|
+
* @param onSubmit - Callback для отправки данных
|
|
73
|
+
* @param options - Опции submit
|
|
74
|
+
* @returns Результат от onSubmit или null если валидация не пройдена
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const result = await submitter.submit(async (values) => {
|
|
79
|
+
* const response = await fetch('/api/form', {
|
|
80
|
+
* method: 'POST',
|
|
81
|
+
* body: JSON.stringify(values)
|
|
82
|
+
* });
|
|
83
|
+
* return response.json();
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* if (result === null) {
|
|
87
|
+
* console.log('Форма не прошла валидацию');
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
submit<R>(onSubmit: (values: T) => Promise<R> | R, options?: SubmitOptions): Promise<R | null>;
|
|
92
|
+
/**
|
|
93
|
+
* Отправить форму с расширенным результатом
|
|
94
|
+
*
|
|
95
|
+
* В отличие от submit(), возвращает объект с информацией об успехе/ошибке
|
|
96
|
+
*
|
|
97
|
+
* @param onSubmit - Callback для отправки данных
|
|
98
|
+
* @param options - Опции submit
|
|
99
|
+
* @returns Объект SubmitResult с данными и статусом
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const result = await submitter.submitWithResult(async (values) => {
|
|
104
|
+
* return await api.saveForm(values);
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* if (result.success) {
|
|
108
|
+
* console.log('Сохранено:', result.data);
|
|
109
|
+
* } else if (result.error) {
|
|
110
|
+
* console.error('Ошибка:', result.error.message);
|
|
111
|
+
* } else {
|
|
112
|
+
* console.log('Валидация не пройдена');
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
submitWithResult<R>(onSubmit: (values: T) => Promise<R> | R, options?: SubmitOptions): Promise<SubmitResult<R>>;
|
|
117
|
+
/**
|
|
118
|
+
* Проверить, идет ли отправка формы
|
|
119
|
+
*/
|
|
120
|
+
isSubmitting(): boolean;
|
|
121
|
+
}
|