@reformer/core 1.0.0-beta.1
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 +143 -0
- 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.d.ts +6 -0
- package/dist/behaviors.js +29 -0
- package/dist/core/behavior/behavior-context.d.ts +47 -0
- package/dist/core/behavior/behavior-registry.d.ts +85 -0
- package/dist/core/behavior/behaviors/compute-from.d.ts +70 -0
- package/dist/core/behavior/behaviors/copy-from.d.ts +56 -0
- package/dist/core/behavior/behaviors/enable-when.d.ts +118 -0
- package/dist/core/behavior/behaviors/index.d.ts +11 -0
- package/dist/core/behavior/behaviors/reset-when.d.ts +64 -0
- package/dist/core/behavior/behaviors/revalidate-when.d.ts +53 -0
- package/dist/core/behavior/behaviors/sync-fields.d.ts +48 -0
- package/dist/core/behavior/behaviors/transform-value.d.ts +192 -0
- package/dist/core/behavior/behaviors/watch-field.d.ts +80 -0
- package/dist/core/behavior/compose-behavior.d.ts +96 -0
- package/dist/core/behavior/index.d.ts +11 -0
- package/dist/core/behavior/types.d.ts +146 -0
- package/dist/core/factories/index.d.ts +6 -0
- package/dist/core/factories/node-factory.d.ts +186 -0
- package/dist/core/nodes/array-node.d.ts +328 -0
- package/dist/core/nodes/field-node.d.ts +294 -0
- package/dist/core/nodes/form-node.d.ts +340 -0
- package/dist/core/nodes/group-node.d.ts +319 -0
- package/dist/core/types/deep-schema.d.ts +68 -0
- package/dist/core/types/field-path.d.ts +42 -0
- package/dist/core/types/form-context.d.ts +89 -0
- package/dist/core/types/form-proxy.d.ts +105 -0
- package/dist/core/types/index.d.ts +209 -0
- package/dist/core/types/validation-schema.d.ts +95 -0
- 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 +44 -0
- package/dist/core/utils/error-handler.d.ts +163 -0
- package/dist/core/utils/field-path-navigator.d.ts +240 -0
- package/dist/core/utils/field-path.d.ts +60 -0
- 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 +22 -0
- package/dist/core/utils/registry-helpers.d.ts +43 -0
- package/dist/core/utils/registry-stack.d.ts +69 -0
- package/dist/core/utils/safe-effect.d.ts +73 -0
- package/dist/core/utils/status-machine.d.ts +153 -0
- package/dist/core/utils/subscription-manager.d.ts +180 -0
- package/dist/core/utils/type-guards.d.ts +98 -0
- package/dist/core/utils/unique-id.d.ts +53 -0
- package/dist/core/validation/core/apply-when.d.ts +22 -0
- package/dist/core/validation/core/apply.d.ts +52 -0
- package/dist/core/validation/core/index.d.ts +8 -0
- package/dist/core/validation/core/validate-async.d.ts +36 -0
- package/dist/core/validation/core/validate-tree.d.ts +35 -0
- package/dist/core/validation/core/validate.d.ts +26 -0
- package/dist/core/validation/index.d.ts +27 -0
- package/dist/core/validation/validate-form.d.ts +48 -0
- package/dist/core/validation/validation-applicator.d.ts +70 -0
- package/dist/core/validation/validation-context.d.ts +86 -0
- package/dist/core/validation/validation-registry.d.ts +142 -0
- package/dist/core/validation/validators/array-validators.d.ts +53 -0
- package/dist/core/validation/validators/date-utils.d.ts +26 -0
- package/dist/core/validation/validators/email.d.ts +37 -0
- package/dist/core/validation/validators/future-date.d.ts +35 -0
- package/dist/core/validation/validators/index.d.ts +20 -0
- 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 +38 -0
- package/dist/core/validation/validators/max.d.ts +38 -0
- 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 +38 -0
- package/dist/core/validation/validators/min.d.ts +38 -0
- package/dist/core/validation/validators/number.d.ts +31 -0
- package/dist/core/validation/validators/past-date.d.ts +35 -0
- package/dist/core/validation/validators/pattern.d.ts +40 -0
- package/dist/core/validation/validators/phone.d.ts +27 -0
- package/dist/core/validation/validators/required.d.ts +41 -0
- package/dist/core/validation/validators/url.d.ts +22 -0
- package/dist/date-utils-xUWFslTj.js +29 -0
- package/dist/field-path-DuKdGcIE.js +66 -0
- package/dist/hooks/types.d.ts +328 -0
- package/dist/hooks/useArrayLength.d.ts +31 -0
- package/dist/hooks/useFormControl.d.ts +24 -0
- package/dist/hooks/useFormControlValue.d.ts +167 -0
- package/dist/hooks/useHiddenCondition.d.ts +25 -0
- package/dist/hooks/useSignalSubscription.d.ts +17 -0
- package/dist/index-D25LsbRm.js +73 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +3271 -0
- 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.d.ts +6 -0
- package/dist/validators.js +56 -0
- package/llms.txt +9351 -0
- package/package.json +166 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
import { ValidatorFn, AsyncValidatorFn, FormFields, AnyFunction } from './index';
|
|
3
|
+
/**
|
|
4
|
+
* Конфигурация поля
|
|
5
|
+
* @group Types
|
|
6
|
+
* @category Configuration Types
|
|
7
|
+
*/
|
|
8
|
+
export interface FieldConfig<T> {
|
|
9
|
+
value: T | null;
|
|
10
|
+
component: ComponentType<any>;
|
|
11
|
+
componentProps?: any;
|
|
12
|
+
validators?: ValidatorFn<T>[];
|
|
13
|
+
asyncValidators?: AsyncValidatorFn<T>[];
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
updateOn?: 'change' | 'blur' | 'submit';
|
|
16
|
+
/** Задержка (в мс) перед запуском асинхронной валидации */
|
|
17
|
+
debounce?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Конфигурация массива
|
|
21
|
+
* @group Types
|
|
22
|
+
* @category Configuration Types
|
|
23
|
+
*/
|
|
24
|
+
export interface ArrayConfig<T extends FormFields> {
|
|
25
|
+
itemSchema: FormSchema<T>;
|
|
26
|
+
initial?: Partial<T>[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Автоматически определяет тип схемы на основе TypeScript типа:
|
|
30
|
+
* - `T[] -> [FormSchema<T>]` (массив с одним элементом)
|
|
31
|
+
* - `object -> FormSchema<T>` (группа)
|
|
32
|
+
* - `primitive -> FieldConfig<T>` (поле)
|
|
33
|
+
*
|
|
34
|
+
* Использует NonNullable для корректной обработки опциональных полей
|
|
35
|
+
*
|
|
36
|
+
* @group Types
|
|
37
|
+
* @category Configuration Types
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* interface Form {
|
|
42
|
+
* name: string; // → FieldConfig<string>
|
|
43
|
+
* address: { // → FormSchema<Address>
|
|
44
|
+
* city: string;
|
|
45
|
+
* street: string;
|
|
46
|
+
* };
|
|
47
|
+
* items?: Array<{ // → [FormSchema<Item>] (опциональный)
|
|
48
|
+
* title: string;
|
|
49
|
+
* price: number;
|
|
50
|
+
* }>;
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* const schema: FormSchema<Form> = {
|
|
54
|
+
* name: { value: '', component: Input },
|
|
55
|
+
* address: {
|
|
56
|
+
* city: { value: '', component: Input },
|
|
57
|
+
* street: { value: '', component: Input },
|
|
58
|
+
* },
|
|
59
|
+
* items: [{
|
|
60
|
+
* title: { value: '', component: Input },
|
|
61
|
+
* price: { value: 0, component: Input },
|
|
62
|
+
* }],
|
|
63
|
+
* };
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export type FormSchema<T> = {
|
|
67
|
+
[K in keyof T]: NonNullable<T[K]> extends string | number | boolean ? FieldConfig<T[K]> : NonNullable<T[K]> extends Array<infer U> ? U extends string | number | boolean ? FieldConfig<T[K]> : U extends Date | File | Blob | AnyFunction ? FieldConfig<T[K]> : [FormSchema<U>] : NonNullable<T[K]> extends Date | File | Blob | AnyFunction ? FieldConfig<T[K]> : FormSchema<NonNullable<T[K]>>;
|
|
68
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AnyFunction } from './index';
|
|
2
|
+
/**
|
|
3
|
+
* FieldPath предоставляет типобезопасный доступ к путям полей формы
|
|
4
|
+
*
|
|
5
|
+
* Рекурсивно обрабатывает вложенные объекты для поддержки вложенных форм.
|
|
6
|
+
*
|
|
7
|
+
* Использование:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const validation = (path: FieldPath<MyForm>) => {
|
|
10
|
+
* required(path.email, { message: 'Email обязателен' });
|
|
11
|
+
*
|
|
12
|
+
* // Вложенные объекты
|
|
13
|
+
* required(path.registrationAddress.city);
|
|
14
|
+
* minLength(path.registrationAddress.street, 3);
|
|
15
|
+
*
|
|
16
|
+
* applyWhen(
|
|
17
|
+
* path.loanType,
|
|
18
|
+
* (type) => type === 'mortgage',
|
|
19
|
+
* (path) => {
|
|
20
|
+
* required(path.propertyValue, { message: 'Укажите стоимость' });
|
|
21
|
+
* }
|
|
22
|
+
* );
|
|
23
|
+
* };
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export type FieldPath<T> = {
|
|
27
|
+
[K in keyof T]: NonNullable<T[K]> extends Array<unknown> ? FieldPathNode<T, T[K], K> : NonNullable<T[K]> extends Date | File | Blob | AnyFunction ? FieldPathNode<T, T[K], K> : NonNullable<T[K]> extends object ? FieldPathNode<T, T[K], K> & FieldPath<NonNullable<T[K]>> : FieldPathNode<T, T[K], K>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Узел в пути поля
|
|
31
|
+
* Содержит метаинформацию о поле для валидации
|
|
32
|
+
*/
|
|
33
|
+
export interface FieldPathNode<TForm, TField, TKey = unknown> {
|
|
34
|
+
/** Ключ поля */
|
|
35
|
+
readonly __key: TKey;
|
|
36
|
+
/** Путь к полю (для вложенных объектов) */
|
|
37
|
+
readonly __path: string;
|
|
38
|
+
/** Тип формы */
|
|
39
|
+
readonly __formType?: TForm;
|
|
40
|
+
/** Тип поля */
|
|
41
|
+
readonly __fieldType?: TField;
|
|
42
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { FormProxy } from './form-proxy';
|
|
2
|
+
import { FieldPathNode } from './field-path';
|
|
3
|
+
import { FormNode } from '../nodes/form-node';
|
|
4
|
+
import { FormValue } from './index';
|
|
5
|
+
/**
|
|
6
|
+
* Единый контекст для работы с формой
|
|
7
|
+
*
|
|
8
|
+
* Предоставляет:
|
|
9
|
+
* - `form` - типизированный Proxy-доступ к полям формы
|
|
10
|
+
* - `setFieldValue` - безопасная установка значения (emitEvent: false)
|
|
11
|
+
*/
|
|
12
|
+
export interface FormContext<TForm> {
|
|
13
|
+
/**
|
|
14
|
+
* Форма с типизированным Proxy-доступом к полям
|
|
15
|
+
*
|
|
16
|
+
* Позволяет обращаться к полям напрямую через точечную нотацию:
|
|
17
|
+
* - `ctx.form.email` → FieldNode
|
|
18
|
+
* - `ctx.form.address.city` → FieldNode (вложенный)
|
|
19
|
+
* - `ctx.form.items` → ArrayNode
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Получить значение
|
|
24
|
+
* ctx.form.email.value.value
|
|
25
|
+
*
|
|
26
|
+
* // Установить значение (⚠️ может вызвать цикл в behavior!)
|
|
27
|
+
* ctx.form.email.setValue('new@mail.com')
|
|
28
|
+
*
|
|
29
|
+
* // Безопасно установить значение
|
|
30
|
+
* ctx.form.email.setValue('new@mail.com', { emitEvent: false })
|
|
31
|
+
*
|
|
32
|
+
* // Обновить пропсы компонента
|
|
33
|
+
* ctx.form.city.updateComponentProps({ options: cities })
|
|
34
|
+
*
|
|
35
|
+
* // Валидация поля
|
|
36
|
+
* await ctx.form.email.validate()
|
|
37
|
+
*
|
|
38
|
+
* // Работа с массивами
|
|
39
|
+
* ctx.form.items.push({ title: 'New' })
|
|
40
|
+
* ctx.form.items.clear()
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
readonly form: FormProxy<TForm>;
|
|
44
|
+
/**
|
|
45
|
+
* Безопасно установить значение поля по строковому пути или FieldPath
|
|
46
|
+
*
|
|
47
|
+
* Автоматически использует `emitEvent: false` для предотвращения
|
|
48
|
+
* бесконечных циклов в behavior схемах.
|
|
49
|
+
*
|
|
50
|
+
* @param path - Путь к полю (строка или FieldPath)
|
|
51
|
+
* @param value - Новое значение
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* // Сброс города при смене страны (строковый путь)
|
|
56
|
+
* watchField(path.country, (country, ctx) => {
|
|
57
|
+
* ctx.setFieldValue('city', null);
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* // Использование FieldPath напрямую (более типобезопасно)
|
|
61
|
+
* watchField(path.country, (country, ctx) => {
|
|
62
|
+
* ctx.setFieldValue(path.city, null);
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
setFieldValue(path: string | FieldPathNode<TForm, unknown>, value: unknown): void;
|
|
67
|
+
/**
|
|
68
|
+
* Получить поле формы по строковому пути
|
|
69
|
+
*
|
|
70
|
+
* Используется для динамического доступа к вложенным полям,
|
|
71
|
+
* особенно в модульных behavior схемах, применяемых через apply().
|
|
72
|
+
*
|
|
73
|
+
* @param path - Строковый путь к полю (например "address.city")
|
|
74
|
+
* @returns FormNode или undefined если поле не найдено
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // В модульной behavior схеме, применяемой к вложенному полю:
|
|
79
|
+
* // apply([path.registrationAddress, path.residenceAddress], addressBehavior)
|
|
80
|
+
*
|
|
81
|
+
* watchField(path.region, (region, ctx) => {
|
|
82
|
+
* // path.city.__path = "registrationAddress.city" или "residenceAddress.city"
|
|
83
|
+
* const cityField = ctx.getFieldByPath(path.city.__path);
|
|
84
|
+
* cityField?.updateComponentProps({ options: cities });
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
getFieldByPath(path: string): FormNode<FormValue> | undefined;
|
|
89
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { FormFields } from './index';
|
|
2
|
+
type FieldNode<_T> = any;
|
|
3
|
+
type GroupNode<_T> = any;
|
|
4
|
+
type ArrayNode<_T> = any;
|
|
5
|
+
/**
|
|
6
|
+
* Мапит тип модели данных T на правильные типы узлов формы
|
|
7
|
+
*
|
|
8
|
+
* Рекурсивно определяет типы узлов на основе структуры данных:
|
|
9
|
+
* - `T[K] extends Array<infer U>` где U - объект → `FormArrayProxy<U>`
|
|
10
|
+
* - `T[K] extends Array<infer U>` где U - примитив → `FieldNode<T[K]>` (массив как обычное поле)
|
|
11
|
+
* - `T[K] extends object` → `FormProxy<T[K]>` (вложенная форма с типизацией)
|
|
12
|
+
* - `T[K]` примитив → `FieldNode<T[K]>` (простое поле)
|
|
13
|
+
*
|
|
14
|
+
* Использует NonNullable для правильной обработки опциональных полей
|
|
15
|
+
*
|
|
16
|
+
* @group Types
|
|
17
|
+
* @category Proxy Types
|
|
18
|
+
*
|
|
19
|
+
* @template T - Тип модели данных формы
|
|
20
|
+
*/
|
|
21
|
+
export type FormControlsProxy<T> = {
|
|
22
|
+
[K in keyof T]: NonNullable<T[K]> extends Array<infer U> ? U extends FormFields ? FormArrayProxy<U> : FieldNode<T[K]> : NonNullable<T[K]> extends FormFields ? NonNullable<T[K]> extends Date | File | Blob ? FieldNode<T[K]> : FormProxy<NonNullable<T[K]>> : FieldNode<T[K]>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Комбинированный тип для GroupNode с Proxy доступом к полям
|
|
26
|
+
*
|
|
27
|
+
* Объединяет методы и свойства GroupNode с типизированными полями формы.
|
|
28
|
+
* Это позволяет использовать как API GroupNode, так и прямой доступ к полям.
|
|
29
|
+
*
|
|
30
|
+
* @group Types
|
|
31
|
+
* @category Proxy Types
|
|
32
|
+
*
|
|
33
|
+
* @template T - Тип модели данных формы
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* interface UserForm {
|
|
38
|
+
* email: string;
|
|
39
|
+
* profile: {
|
|
40
|
+
* name: string;
|
|
41
|
+
* age: number;
|
|
42
|
+
* };
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* const form = createForm<UserForm>(schema);
|
|
46
|
+
*
|
|
47
|
+
* // Доступ к методам GroupNode
|
|
48
|
+
* await form.validate();
|
|
49
|
+
* const values = form.getValue();
|
|
50
|
+
* console.log(form.valid.value);
|
|
51
|
+
*
|
|
52
|
+
* // Прямой доступ к полям (через Proxy)
|
|
53
|
+
* form.email.setValue('test@mail.com');
|
|
54
|
+
* form.profile.name.setValue('John');
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export type FormProxy<T> = GroupNode<T> & FormControlsProxy<T>;
|
|
58
|
+
/**
|
|
59
|
+
* Комбинированный тип для ArrayNode с Proxy доступом к элементам
|
|
60
|
+
*
|
|
61
|
+
* Объединяет методы и свойства ArrayNode с типизированным доступом к элементам массива.
|
|
62
|
+
*
|
|
63
|
+
* @group Types
|
|
64
|
+
* @category Proxy Types
|
|
65
|
+
*
|
|
66
|
+
* @template T - Тип модели данных элемента массива
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* interface TodoItem {
|
|
71
|
+
* title: string;
|
|
72
|
+
* completed: boolean;
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* const todos: FormArrayProxy<TodoItem> = new ArrayNode(schema);
|
|
76
|
+
*
|
|
77
|
+
* // Доступ к методам ArrayNode
|
|
78
|
+
* todos.push({ title: 'New todo', completed: false });
|
|
79
|
+
* todos.removeAt(0);
|
|
80
|
+
*
|
|
81
|
+
* // Доступ к элементам (через Proxy)
|
|
82
|
+
* todos.at(0)?.title.setValue('Updated title');
|
|
83
|
+
*
|
|
84
|
+
* // Итерация
|
|
85
|
+
* todos.forEach((item, i) => {
|
|
86
|
+
* console.log(item.title.value.value);
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export type FormArrayProxy<T extends FormFields> = ArrayNode<T> & {
|
|
91
|
+
/**
|
|
92
|
+
* Безопасный доступ к элементу массива по индексу
|
|
93
|
+
* Возвращает GroupNode с типизированными полями или undefined
|
|
94
|
+
*/
|
|
95
|
+
at(index: number): FormProxy<T> | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Итерация по элементам массива с типизированными элементами
|
|
98
|
+
*/
|
|
99
|
+
forEach(callback: (item: FormProxy<T>, index: number) => void): void;
|
|
100
|
+
/**
|
|
101
|
+
* Маппинг элементов массива с типизированными элементами
|
|
102
|
+
*/
|
|
103
|
+
map<R>(callback: (item: FormProxy<T>, index: number) => R): R[];
|
|
104
|
+
};
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
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';
|
|
5
|
+
/**
|
|
6
|
+
* Represents any valid form value type
|
|
7
|
+
* Use this instead of 'any' for form values to maintain type safety
|
|
8
|
+
*
|
|
9
|
+
* @group Types
|
|
10
|
+
* @category Core Types
|
|
11
|
+
*/
|
|
12
|
+
export type FormValue = string | number | boolean | null | undefined | Date | File | FormValue[] | {
|
|
13
|
+
[key: string]: FormValue;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Type-safe alternative to 'any' for unknown form values
|
|
17
|
+
* Requires explicit type checking before use
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export type UnknownFormValue = unknown;
|
|
21
|
+
/**
|
|
22
|
+
* Синхронная функция валидации
|
|
23
|
+
* @group Types
|
|
24
|
+
* @category Validation Types
|
|
25
|
+
*/
|
|
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
|
+
}
|
|
39
|
+
/**
|
|
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
|
+
*
|
|
63
|
+
* @group Types
|
|
64
|
+
* @category Validation Types
|
|
65
|
+
*/
|
|
66
|
+
export type AsyncValidatorFn<T = FormValue> = (value: T, options?: AsyncValidatorOptions) => Promise<ValidationError | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Ошибка валидации
|
|
69
|
+
* @group Types
|
|
70
|
+
* @category Validation Types
|
|
71
|
+
*/
|
|
72
|
+
export interface ValidationError {
|
|
73
|
+
code: string;
|
|
74
|
+
message: string;
|
|
75
|
+
params?: FormFields;
|
|
76
|
+
/** Severity level: 'error' (default) blocks submission, 'warning' shows message but allows submission */
|
|
77
|
+
severity?: 'error' | 'warning';
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Опции для фильтрации ошибок в методе getErrors()
|
|
81
|
+
* @group Types
|
|
82
|
+
* @category Validation Types
|
|
83
|
+
*/
|
|
84
|
+
export interface ErrorFilterOptions {
|
|
85
|
+
/** Фильтр по коду ошибки */
|
|
86
|
+
code?: string | string[];
|
|
87
|
+
/** Фильтр по сообщению (поддерживает частичное совпадение) */
|
|
88
|
+
message?: string;
|
|
89
|
+
/** Фильтр по параметрам ошибки */
|
|
90
|
+
params?: FormFields;
|
|
91
|
+
/** Кастомный предикат для фильтрации */
|
|
92
|
+
predicate?: (error: ValidationError) => boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Статус поля формы
|
|
96
|
+
* @group Types
|
|
97
|
+
* @category Core Types
|
|
98
|
+
*/
|
|
99
|
+
export type FieldStatus = 'valid' | 'invalid' | 'pending' | 'disabled';
|
|
100
|
+
export type { FieldConfig } from './deep-schema';
|
|
101
|
+
export type { FieldPath, FieldPathNode } from '../types/field-path';
|
|
102
|
+
export type { ContextualValidatorFn, ContextualAsyncValidatorFn, TreeValidatorFn, ConditionFn, ValidateOptions, ValidateAsyncOptions, ValidateTreeOptions, ValidationSchemaFn, ValidatorRegistration, } from './validation-schema';
|
|
103
|
+
export type { FormSchema, ArrayConfig } from './deep-schema';
|
|
104
|
+
export type { FormContext } from './form-context';
|
|
105
|
+
export type { FormControlsProxy, FormProxy, FormArrayProxy } from './form-proxy';
|
|
106
|
+
/**
|
|
107
|
+
* Конфигурация GroupNode с поддержкой схем
|
|
108
|
+
* Используется для создания форм с автоматическим применением behavior и validation схем
|
|
109
|
+
*
|
|
110
|
+
* @group Types
|
|
111
|
+
* @category Configuration Types
|
|
112
|
+
*/
|
|
113
|
+
export interface GroupNodeConfig<T> {
|
|
114
|
+
/** Схема структуры формы (поля и их конфигурация) */
|
|
115
|
+
form: FormSchema<T>;
|
|
116
|
+
/** Схема реактивного поведения (copyFrom, enableWhen, computeFrom и т.д.) */
|
|
117
|
+
behavior?: BehaviorSchemaFn<T>;
|
|
118
|
+
/** Схема валидации (required, email, minLength и т.д.) */
|
|
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;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Тип для Record с unknown значениями
|
|
135
|
+
* Используется вместо инлайнового `Record<string, unknown>`
|
|
136
|
+
* @internal
|
|
137
|
+
*/
|
|
138
|
+
export type UnknownRecord = Record<string, any>;
|
|
139
|
+
/**
|
|
140
|
+
* Интерфейс для узлов с методом applyValidationSchema
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
export interface WithValidationSchema {
|
|
144
|
+
applyValidationSchema(schemaFn: unknown): void;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Интерфейс для узлов с методом applyBehaviorSchema
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
150
|
+
export interface WithBehaviorSchema {
|
|
151
|
+
applyBehaviorSchema(schemaFn: unknown): void;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Интерфейс для узлов, похожих на ArrayNode (с методом at)
|
|
155
|
+
* Используется для duck typing при обходе путей
|
|
156
|
+
* @internal
|
|
157
|
+
*/
|
|
158
|
+
export interface ArrayNodeLike {
|
|
159
|
+
at(index: number): FormNode<unknown> | undefined;
|
|
160
|
+
length: unknown;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Конфиг с полем schema (для ArrayConfig)
|
|
164
|
+
* @internal
|
|
165
|
+
*/
|
|
166
|
+
export interface ConfigWithSchema {
|
|
167
|
+
schema: unknown;
|
|
168
|
+
initialItems?: unknown[];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Конфиг с полем value (для извлечения значений)
|
|
172
|
+
* @internal
|
|
173
|
+
*/
|
|
174
|
+
export interface ConfigWithValue {
|
|
175
|
+
value: unknown;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Тип для конфига с полями (FormSchema generic constraint)
|
|
179
|
+
* Используется вместо `Record<string, any>` для схем форм
|
|
180
|
+
* @deprecated
|
|
181
|
+
* @internal
|
|
182
|
+
*/
|
|
183
|
+
export type FormFields = Record<string, FormValue>;
|
|
184
|
+
/**
|
|
185
|
+
* Тип для путей к полям (field paths)
|
|
186
|
+
* Используется в навигации по полям вместо any
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
export type FieldPathSegment = {
|
|
190
|
+
key: string;
|
|
191
|
+
index?: number;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Тип для коллбэков и обработчиков событий
|
|
195
|
+
* Используется вместо (...args: any[]) => any
|
|
196
|
+
* @internal
|
|
197
|
+
*/
|
|
198
|
+
export type UnknownCallback = (...args: unknown[]) => unknown;
|
|
199
|
+
/**
|
|
200
|
+
* Тип для проверки на функцию в conditional types
|
|
201
|
+
* Используется вместо Function для type narrowing
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
export type AnyFunction = (...args: never[]) => unknown;
|
|
205
|
+
/**
|
|
206
|
+
* Тип для результатов загрузки ресурсов
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
export type ResourceLoadResult = unknown;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { FormFields, ValidationError } from './index';
|
|
2
|
+
import { FieldPath } from './field-path';
|
|
3
|
+
import { FormContext } from './form-context';
|
|
4
|
+
/**
|
|
5
|
+
* Функция валидации поля с контекстом
|
|
6
|
+
*
|
|
7
|
+
* Новый паттерн: (value, ctx: FormContext) => ValidationError | null
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* validate(path.email, (value, ctx) => {
|
|
12
|
+
* if (!value) return { code: 'required', message: 'Email required' };
|
|
13
|
+
* const confirm = ctx.form.confirmEmail.value.value;
|
|
14
|
+
* if (value !== confirm) return { code: 'mismatch', message: 'Must match' };
|
|
15
|
+
* return null;
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export type ContextualValidatorFn<TForm, TField> = (value: TField, ctx: FormContext<TForm>) => ValidationError | null;
|
|
20
|
+
/**
|
|
21
|
+
* Асинхронная функция валидации поля с контекстом
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* validateAsync(path.email, async (value, ctx) => {
|
|
26
|
+
* const exists = await checkEmailExists(value);
|
|
27
|
+
* if (exists) return { code: 'exists', message: 'Email already taken' };
|
|
28
|
+
* return null;
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export type ContextualAsyncValidatorFn<TForm, TField> = (value: TField, ctx: FormContext<TForm>) => Promise<ValidationError | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Функция cross-field валидации
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* validateTree((ctx) => {
|
|
39
|
+
* const password = ctx.form.password.value.value;
|
|
40
|
+
* const confirm = ctx.form.confirmPassword.value.value;
|
|
41
|
+
* if (password !== confirm) {
|
|
42
|
+
* return { code: 'mismatch', message: 'Passwords must match' };
|
|
43
|
+
* }
|
|
44
|
+
* return null;
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export type TreeValidatorFn<TForm> = (ctx: FormContext<TForm>) => ValidationError | null;
|
|
49
|
+
/**
|
|
50
|
+
* Функция условия для applyWhen
|
|
51
|
+
*/
|
|
52
|
+
export type ConditionFn<T> = (value: T) => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Опции для функции validate
|
|
55
|
+
*/
|
|
56
|
+
export interface ValidateOptions {
|
|
57
|
+
/** Сообщение об ошибке */
|
|
58
|
+
message?: string;
|
|
59
|
+
/** Параметры ошибки */
|
|
60
|
+
params?: FormFields;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Опции для функции validateAsync
|
|
64
|
+
*/
|
|
65
|
+
export interface ValidateAsyncOptions extends ValidateOptions {
|
|
66
|
+
/** Задержка перед выполнением валидации (в мс) */
|
|
67
|
+
debounce?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Опции для функции validateTree
|
|
71
|
+
*/
|
|
72
|
+
export interface ValidateTreeOptions {
|
|
73
|
+
/** Поле, к которому привязать ошибку */
|
|
74
|
+
targetField?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Функция validation schema
|
|
78
|
+
*
|
|
79
|
+
* Принимает FieldPath и определяет все правила валидации для формы
|
|
80
|
+
*/
|
|
81
|
+
export type ValidationSchemaFn<T> = (path: FieldPath<T>) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Регистрация валидатора в системе
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
export interface ValidatorRegistration {
|
|
87
|
+
fieldPath: string;
|
|
88
|
+
type: 'sync' | 'async' | 'tree';
|
|
89
|
+
validator: ContextualValidatorFn<unknown, unknown> | ContextualAsyncValidatorFn<unknown, unknown> | TreeValidatorFn<unknown>;
|
|
90
|
+
options?: ValidateOptions | ValidateAsyncOptions | ValidateTreeOptions;
|
|
91
|
+
condition?: {
|
|
92
|
+
fieldPath: string;
|
|
93
|
+
conditionFn: ConditionFn<unknown>;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -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
|
+
}
|