@reformer/core 8.0.0 → 9.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/dist/core/factories/node-factory.d.ts +5 -4
- package/dist/core/model/types.d.ts +41 -12
- package/dist/core/model/validate-model.d.ts +8 -4
- package/dist/core/nodes/field-node.d.ts +2 -2
- package/dist/core/nodes/group-node.d.ts +7 -1
- package/dist/core/nodes/model-array-node.d.ts +5 -0
- package/dist/core/types/deep-schema.d.ts +16 -4
- package/dist/core/types/form-proxy.d.ts +1 -1
- package/dist/core/types/index.d.ts +4 -10
- package/dist/core/types/schema-node.d.ts +79 -0
- package/dist/core/types/validation-schema.d.ts +22 -11
- package/dist/core/utils/create-form.d.ts +4 -3
- package/dist/core/utils/error-handler.d.ts +4 -1
- package/dist/core/utils/form-submitter.d.ts +2 -3
- package/dist/core/utils/type-guards.d.ts +3 -3
- package/dist/core/validation/validators/date-utils.d.ts +5 -1
- package/dist/core/validation/validators/integer.d.ts +1 -1
- package/dist/core/validation/validators/is-number.d.ts +1 -1
- package/dist/core/validation/validators/max.d.ts +1 -1
- package/dist/core/validation/validators/min.d.ts +1 -1
- package/dist/core/validation/validators/multiple-of.d.ts +1 -1
- package/dist/core/validation/validators/non-negative.d.ts +1 -1
- package/dist/core/validation/validators/non-zero.d.ts +1 -1
- package/dist/{date-utils-xUWFslTj.js → date-utils-dTT_x_Be.js} +12 -10
- package/dist/hooks/types.d.ts +1 -1
- package/dist/hooks/useArrayLength.d.ts +1 -2
- package/dist/hooks/useFormControl.d.ts +2 -2
- package/dist/index.js +514 -514
- package/dist/signals.d.ts +13 -0
- package/dist/signals.js +9 -0
- package/dist/validators/future-date.js +1 -1
- package/dist/validators/is-date.js +1 -1
- package/dist/validators/max-age.js +1 -1
- package/dist/validators/max-date.js +1 -1
- package/dist/validators/min-age.js +1 -1
- package/dist/validators/min-date.js +1 -1
- package/dist/validators/multiple-of.js +11 -7
- package/dist/validators/past-date.js +1 -1
- package/llms.txt +568 -155
- package/package.json +6 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FormNode } from '../nodes/form-node';
|
|
2
|
+
import { ConfigWithSchema, ConfigWithValue, UnknownRecord } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* Фабрика для создания узлов формы.
|
|
4
5
|
*
|
|
@@ -59,7 +60,7 @@ export declare class NodeFactory {
|
|
|
59
60
|
* ]);
|
|
60
61
|
* ```
|
|
61
62
|
*/
|
|
62
|
-
createNode
|
|
63
|
+
createNode(config: unknown): FormNode<unknown>;
|
|
63
64
|
/**
|
|
64
65
|
* Создать ArrayNode из массива [schema, ...initialItems]
|
|
65
66
|
*
|
|
@@ -135,7 +136,7 @@ export declare class NodeFactory {
|
|
|
135
136
|
* factory.isFieldConfig(null); // false
|
|
136
137
|
* ```
|
|
137
138
|
*/
|
|
138
|
-
isFieldConfig(config: unknown):
|
|
139
|
+
isFieldConfig(config: unknown): config is ConfigWithValue;
|
|
139
140
|
/**
|
|
140
141
|
* Проверяет, является ли конфиг конфигурацией массива (ArrayConfig)
|
|
141
142
|
*
|
|
@@ -157,7 +158,7 @@ export declare class NodeFactory {
|
|
|
157
158
|
* factory.isArrayConfig({ email: { value: '' } }); // false
|
|
158
159
|
* ```
|
|
159
160
|
*/
|
|
160
|
-
isArrayConfig(config: unknown):
|
|
161
|
+
isArrayConfig(config: unknown): config is ConfigWithSchema;
|
|
161
162
|
/**
|
|
162
163
|
* Проверяет, является ли конфиг конфигурацией группы (GroupConfig)
|
|
163
164
|
*
|
|
@@ -183,5 +184,5 @@ export declare class NodeFactory {
|
|
|
183
184
|
* factory.isGroupConfig(null); // false
|
|
184
185
|
* ```
|
|
185
186
|
*/
|
|
186
|
-
isGroupConfig(config: unknown):
|
|
187
|
+
isGroupConfig(config: unknown): config is UnknownRecord;
|
|
187
188
|
}
|
|
@@ -14,6 +14,14 @@ export type PathAwareSignal<T> = Signal<T> & {
|
|
|
14
14
|
* @internal
|
|
15
15
|
*/
|
|
16
16
|
type Opaque = Date | File | Blob;
|
|
17
|
+
/**
|
|
18
|
+
* Фасад элемента массива при обходе (`at`/`map`/`forEach`): массив → {@link ModelArray},
|
|
19
|
+
* Opaque (Date/File/Blob) и примитив → значение, объект → под-модель {@link FormModel}.
|
|
20
|
+
* Зеркалит листовой/объектный сплит {@link ModelValue} (но объект → под-модель с API),
|
|
21
|
+
* чтобы `files: File[]`/`dates: Date[]`/вложенные массивы не мис-типизировались как `FormModel`.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
type ModelArrayItem<U> = NonNullable<U> extends ReadonlyArray<infer E> ? ModelArray<E> : NonNullable<U> extends Opaque ? U : NonNullable<U> extends object ? FormModel<NonNullable<U>> : U;
|
|
17
25
|
/**
|
|
18
26
|
* Значение поля в value-доступе модели:
|
|
19
27
|
* - массив → {@link ModelArray}
|
|
@@ -40,6 +48,11 @@ export type ModelObject<T> = {
|
|
|
40
48
|
* @group Model
|
|
41
49
|
*/
|
|
42
50
|
export interface ModelArray<U> {
|
|
51
|
+
/**
|
|
52
|
+
* Путь массива в модели (dot-нотация). Предоставляется рантаймом (value-прокси) и требуется
|
|
53
|
+
* рендер-слою для резолва узла массива (напр. `ArrayRenderNode` в `@reformer/renderer-react`).
|
|
54
|
+
*/
|
|
55
|
+
readonly __path: string;
|
|
43
56
|
/** Реактивная длина. */
|
|
44
57
|
readonly length: number;
|
|
45
58
|
/** Добавить элемент в конец (значение элемента целиком). */
|
|
@@ -54,12 +67,12 @@ export interface ModelArray<U> {
|
|
|
54
67
|
swap(a: number, b: number): void;
|
|
55
68
|
/** Очистить массив. */
|
|
56
69
|
clear(): void;
|
|
57
|
-
/**
|
|
58
|
-
at(index: number): U extends object ?
|
|
59
|
-
/** Map по
|
|
60
|
-
map<R>(fn: (item:
|
|
61
|
-
/** Итерация по
|
|
62
|
-
forEach(fn: (item:
|
|
70
|
+
/** Элемент по индексу: объект → под-модель, массив → {@link ModelArray}, лист → значение. */
|
|
71
|
+
at(index: number): NonNullable<U> extends object ? ModelArrayItem<U> : ModelArrayItem<U> | undefined;
|
|
72
|
+
/** Map по элементам (объект → {@link FormModel}, Opaque/примитив → значение, массив → {@link ModelArray}). */
|
|
73
|
+
map<R>(fn: (item: ModelArrayItem<U>, index: number) => R): R[];
|
|
74
|
+
/** Итерация по элементам (см. {@link ModelArrayItem}). */
|
|
75
|
+
forEach(fn: (item: ModelArrayItem<U>, index: number) => void): void;
|
|
63
76
|
/** Снимок массива значений (без подписки). */
|
|
64
77
|
toArray(): U[];
|
|
65
78
|
/** Индексный value-доступ. */
|
|
@@ -72,11 +85,19 @@ export interface ModelArray<U> {
|
|
|
72
85
|
* @group Model
|
|
73
86
|
*/
|
|
74
87
|
export type ModelSignals<T> = {
|
|
75
|
-
[K in keyof T]:
|
|
76
|
-
readonly length: number;
|
|
77
|
-
readonly [index: number]: ModelSignals<U & object>;
|
|
78
|
-
} : NonNullable<T[K]> extends Opaque ? PathAwareSignal<T[K]> : NonNullable<T[K]> extends object ? ModelSignals<NonNullable<T[K]>> : PathAwareSignal<T[K]>;
|
|
88
|
+
[K in keyof T]: ModelSignalNode<T[K]>;
|
|
79
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* Узел дерева сигналов `model.$`: массив → индексируемый узел под-сигналов, Opaque (Date/File/Blob)
|
|
92
|
+
* и примитив → {@link PathAwareSignal}, объект → вложенное дерево {@link ModelSignals}. Для примитивных
|
|
93
|
+
* и Opaque массивов элемент — сам сигнал листа (а не под-дерево), поэтому `model.$.tags[0]` — это
|
|
94
|
+
* `PathAwareSignal<string>`, а не `ModelSignals<never>`.
|
|
95
|
+
* @internal
|
|
96
|
+
*/
|
|
97
|
+
type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? {
|
|
98
|
+
readonly length: number;
|
|
99
|
+
readonly [index: number]: ModelSignalNode<U>;
|
|
100
|
+
} : NonNullable<V> extends Opaque ? PathAwareSignal<V> : NonNullable<V> extends object ? ModelSignals<NonNullable<V>> : PathAwareSignal<V>;
|
|
80
101
|
/**
|
|
81
102
|
* API уровня модели (доступно на корне и под-моделях элементов массива).
|
|
82
103
|
*
|
|
@@ -90,9 +111,17 @@ export interface ModelApi<T> {
|
|
|
90
111
|
readonly $: ModelSignals<T>;
|
|
91
112
|
/** Снимок значений (без подписки) — для submit. */
|
|
92
113
|
get(): T;
|
|
93
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* @deprecated Алиас {@link ModelApi.patch}: `set` выполняет то же частичное **слияние** —
|
|
116
|
+
* обновляет только переданные ключи, отсутствующие НЕ трогает (это НЕ полная замена, вопреки
|
|
117
|
+
* старому названию). Используйте `patch`; для замены с обнулением лишних полей делайте
|
|
118
|
+
* `patch({ ...defaults, ...dto })` явно. Не меняет initial-снимок.
|
|
119
|
+
*/
|
|
94
120
|
set(value: Partial<T>): void;
|
|
95
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* Частичное слияние значений (load/patch с сервера): обновляет только переданные ключи,
|
|
123
|
+
* отсутствующие ключи НЕ трогаются. Не меняет initial-снимок.
|
|
124
|
+
*/
|
|
96
125
|
patch(value: Partial<T>): void;
|
|
97
126
|
/** Отличаются ли текущие значения от initial-снимка (value-diff). */
|
|
98
127
|
isDirty(): boolean;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { ValidationError } from '../types';
|
|
2
|
+
import { FormSchemaNode } from '../types/schema-node';
|
|
2
3
|
import { FormModel } from './types';
|
|
3
4
|
/**
|
|
4
|
-
* Валидатор слоя
|
|
5
|
+
* Валидатор слоя **данных**. `value` — значение поля; `model` — ближайший scope (под-модель элемента
|
|
5
6
|
* массива или корень); `root` — корневая модель.
|
|
6
7
|
*
|
|
8
|
+
* Отличие от {@link Validator}: 2-й/3-й аргументы — сами данные ({@link FormModel}/scope), а не
|
|
9
|
+
* `FormProxy`-узлы формы. Оба совместимы с полем `validators` узла схемы (см. {@link SchemaValidator}).
|
|
10
|
+
*
|
|
7
11
|
* @group Model
|
|
8
12
|
*/
|
|
9
13
|
export type ModelValidator<TValue = unknown, TModel = unknown, TRoot = unknown> = (value: TValue, model: TModel, root: TRoot) => ValidationError | null | Promise<ValidationError | null>;
|
|
@@ -32,7 +36,7 @@ export interface ModelValidationResult {
|
|
|
32
36
|
* if (!res.valid) console.log(res.errors); // { 'email': [{ code, message }], ... }
|
|
33
37
|
* ```
|
|
34
38
|
*/
|
|
35
|
-
export declare function validateModelSync<T>(model: FormModel<T>, schema:
|
|
39
|
+
export declare function validateModelSync<T>(model: FormModel<T>, schema: FormSchemaNode): ModelValidationResult;
|
|
36
40
|
/**
|
|
37
41
|
* Headless-валидация данных (sync + async). Работает без UI/нод.
|
|
38
42
|
*
|
|
@@ -43,7 +47,7 @@ export declare function validateModelSync<T>(model: FormModel<T>, schema: unknow
|
|
|
43
47
|
* if (!res.valid) console.log(res.errors); // { 'email': [{ code, message }], ... }
|
|
44
48
|
* ```
|
|
45
49
|
*/
|
|
46
|
-
export declare function validateModel<T>(model: FormModel<T>, schema:
|
|
50
|
+
export declare function validateModel<T>(model: FormModel<T>, schema: FormSchemaNode): Promise<ModelValidationResult>;
|
|
47
51
|
/**
|
|
48
52
|
* In-form валидация: прогоняет {@link validateModel} и роутит ошибки в ноды формы
|
|
49
53
|
* (через реестр сигнал→нода): `node.setErrors(errors[path] ?? [])` — заодно очищает прошедшие поля
|
|
@@ -65,4 +69,4 @@ export declare function validateModel<T>(model: FormModel<T>, schema: unknown):
|
|
|
65
69
|
* if (res.valid) await api.save(model.get());
|
|
66
70
|
* ```
|
|
67
71
|
*/
|
|
68
|
-
export declare function validateFormModel<T>(model: FormModel<T>, schema:
|
|
72
|
+
export declare function validateFormModel<T>(model: FormModel<T>, schema: FormSchemaNode): Promise<ModelValidationResult>;
|
|
@@ -35,7 +35,7 @@ export declare class FieldNode<T> extends FormNode<T> {
|
|
|
35
35
|
readonly status: ReadonlySignal<FieldStatus>;
|
|
36
36
|
readonly disabled: ReadonlySignal<boolean>;
|
|
37
37
|
readonly errors: ReadonlySignal<ValidationError[]>;
|
|
38
|
-
readonly componentProps: ReadonlySignal<Record<string,
|
|
38
|
+
readonly componentProps: ReadonlySignal<Record<string, unknown>>;
|
|
39
39
|
/**
|
|
40
40
|
* Вычисляемое свойство: нужно ли показывать ошибку
|
|
41
41
|
* Ошибка показывается если поле невалидно И (touched ИЛИ dirty)
|
|
@@ -186,7 +186,7 @@ export declare class FieldNode<T> extends FormNode<T> {
|
|
|
186
186
|
* });
|
|
187
187
|
* ```
|
|
188
188
|
*/
|
|
189
|
-
updateComponentProps(props: Partial<Record<string,
|
|
189
|
+
updateComponentProps(props: Partial<Record<string, unknown>>): void;
|
|
190
190
|
/**
|
|
191
191
|
* Динамически изменяет триггер валидации (updateOn)
|
|
192
192
|
* Полезно для адаптивной валидации - например, переключиться на instant feedback после первого submit
|
|
@@ -244,7 +244,13 @@ export declare class GroupNode<T> extends FormNode<T> {
|
|
|
244
244
|
* useEffect(() => dispose, []);
|
|
245
245
|
* ```
|
|
246
246
|
*/
|
|
247
|
-
|
|
247
|
+
/** Подписка на top-level поле — value типизирован как `T[K]`. */
|
|
248
|
+
watchField<K extends keyof T & string>(fieldPath: K, callback: (value: T[K]) => void | Promise<void>): () => void;
|
|
249
|
+
/**
|
|
250
|
+
* Подписка на вложенное поле по строковому пути ("address.city").
|
|
251
|
+
* Путь нельзя выразить в типах узла → value честно `unknown`, потребитель сужает.
|
|
252
|
+
*/
|
|
253
|
+
watchFieldByPath(path: string, callback: (value: unknown) => void | Promise<void>): () => void;
|
|
248
254
|
/**
|
|
249
255
|
* Hook: вызывается после disable()
|
|
250
256
|
*/
|
|
@@ -104,6 +104,11 @@ export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
104
104
|
setErrors(errors: ValidationError[]): void;
|
|
105
105
|
clearErrors(): void;
|
|
106
106
|
protected onMarkAsTouched(): void;
|
|
107
|
+
protected onMarkAsUntouched(): void;
|
|
108
|
+
protected onMarkAsDirty(): void;
|
|
109
|
+
protected onMarkAsPristine(): void;
|
|
110
|
+
protected onDisable(): void;
|
|
111
|
+
protected onEnable(): void;
|
|
107
112
|
/** Очистка подписки синхронизации. */
|
|
108
113
|
dispose(): void;
|
|
109
114
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { ComponentType } from 'react';
|
|
2
2
|
import { Signal } from '@preact/signals-core';
|
|
3
|
-
import { ValidatorFn, AsyncValidatorFn,
|
|
3
|
+
import { ValidatorFn, AsyncValidatorFn, AnyFunction } from './index';
|
|
4
4
|
/**
|
|
5
5
|
* Конфигурация поля
|
|
6
6
|
* @group Types
|
|
7
7
|
* @category Configuration Types
|
|
8
|
+
* @remarks
|
|
9
|
+
* В схеме `createForm({ model, schema })` лист связывают через **`value: model.$.field`**: harvest
|
|
10
|
+
* ловит узел по идентичности `Signal` (`node.value instanceof Signal` — см. `create-form.ts`) и
|
|
11
|
+
* нормализует его в `valueSignal`. Писать `valueSignal:` **в самой схеме `createForm` нельзя** —
|
|
12
|
+
* harvest его не подхватит (проверка идёт по `node.value`), и `component`/`componentProps` этого
|
|
13
|
+
* узла молча отбросятся. `value`/`valueSignal` на прямом FieldNode-конфиге — это
|
|
14
|
+
* post-normalization / legacy-слой, а не форма записи в схеме `createForm`.
|
|
8
15
|
*/
|
|
9
16
|
export interface FieldConfig<T> {
|
|
10
17
|
/**
|
|
@@ -35,17 +42,22 @@ export interface FieldConfig<T> {
|
|
|
35
42
|
* @group Types
|
|
36
43
|
* @category Configuration Types
|
|
37
44
|
*/
|
|
38
|
-
export interface ArrayConfig<T extends
|
|
45
|
+
export interface ArrayConfig<T extends object> {
|
|
39
46
|
itemSchema: FormSchema<T>;
|
|
40
47
|
initial?: Partial<T>[];
|
|
41
48
|
}
|
|
42
49
|
/**
|
|
43
|
-
*
|
|
50
|
+
* **Data-shaped** конфиг формы: ключи повторяют структуру данных `T`, значения — {@link FieldConfig}
|
|
51
|
+
* (или вложенный `FormSchema`). Форма конфига, из которой строится {@link GroupNode}.
|
|
44
52
|
* - `T[] -> [FormSchema<T>]` (массив с одним элементом)
|
|
45
53
|
* - `object -> FormSchema<T>` (группа)
|
|
46
54
|
* - `primitive -> FieldConfig<T>` (поле)
|
|
47
55
|
*
|
|
48
|
-
* Использует NonNullable для корректной обработки опциональных
|
|
56
|
+
* Использует NonNullable для корректной обработки опциональных полей.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ Не путать с {@link FormSchemaNode} — тот описывает **узел дерева** M1-схемы (лист/массив/
|
|
59
|
+
* контейнер), передаваемой в `createForm({ model, schema })`. `FormSchema` — это data-shaped конфиг
|
|
60
|
+
* (ключи = поля данных), а не узел дерева.
|
|
49
61
|
*
|
|
50
62
|
* @group Types
|
|
51
63
|
* @category Configuration Types
|
|
@@ -12,7 +12,7 @@ import { ArrayNode } from '../nodes/array-node';
|
|
|
12
12
|
*
|
|
13
13
|
* @internal
|
|
14
14
|
*/
|
|
15
|
-
type IsGroupObject<V> = V extends ReadonlyArray<unknown> ? false : V extends Date | File | Blob ? false : V extends object ? true : false;
|
|
15
|
+
type IsGroupObject<V> = V extends ReadonlyArray<unknown> ? false : V extends Date | File | Blob ? false : V extends (...args: never[]) => unknown ? false : V extends object ? true : false;
|
|
16
16
|
/**
|
|
17
17
|
* Мапит тип модели данных T на правильные типы узлов формы
|
|
18
18
|
*
|
|
@@ -70,7 +70,7 @@ export type AsyncValidatorFn<T = FormValue> = (value: T, options?: AsyncValidato
|
|
|
70
70
|
export interface ValidationError {
|
|
71
71
|
code: string;
|
|
72
72
|
message: string;
|
|
73
|
-
params?:
|
|
73
|
+
params?: Record<string, FormValue>;
|
|
74
74
|
/** Severity level: 'error' (default) blocks submission, 'warning' shows message but allows submission */
|
|
75
75
|
severity?: 'error' | 'warning';
|
|
76
76
|
}
|
|
@@ -85,7 +85,7 @@ export interface ErrorFilterOptions {
|
|
|
85
85
|
/** Фильтр по сообщению (поддерживает частичное совпадение) */
|
|
86
86
|
message?: string;
|
|
87
87
|
/** Фильтр по параметрам ошибки */
|
|
88
|
-
params?:
|
|
88
|
+
params?: Record<string, FormValue>;
|
|
89
89
|
/** Кастомный предикат для фильтрации */
|
|
90
90
|
predicate?: (error: ValidationError) => boolean;
|
|
91
91
|
}
|
|
@@ -98,6 +98,7 @@ export type FieldStatus = 'valid' | 'invalid' | 'pending' | 'disabled';
|
|
|
98
98
|
export type { FieldConfig } from './deep-schema';
|
|
99
99
|
export type { Validator, AsyncValidator, ConditionFn, ValidateOptions, ValidateAsyncOptions, } from './validation-schema';
|
|
100
100
|
export type { FormSchema, ArrayConfig } from './deep-schema';
|
|
101
|
+
export type { FormSchemaNode, SchemaArrayControl, SchemaValidator } from './schema-node';
|
|
101
102
|
export type { FormControlsProxy, FormProxy, FormArrayProxy } from './form-proxy';
|
|
102
103
|
/**
|
|
103
104
|
* Конфигурация GroupNode.
|
|
@@ -117,7 +118,7 @@ export interface GroupNodeConfig<T> {
|
|
|
117
118
|
* Используется вместо инлайнового `Record<string, unknown>`
|
|
118
119
|
* @internal
|
|
119
120
|
*/
|
|
120
|
-
export type UnknownRecord = Record<string,
|
|
121
|
+
export type UnknownRecord = Record<string, unknown>;
|
|
121
122
|
/**
|
|
122
123
|
* Интерфейс для узлов с методом applyValidationSchema
|
|
123
124
|
* @internal
|
|
@@ -156,13 +157,6 @@ export interface ConfigWithSchema {
|
|
|
156
157
|
export interface ConfigWithValue {
|
|
157
158
|
value: unknown;
|
|
158
159
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Тип для конфига с полями (FormSchema generic constraint)
|
|
161
|
-
* Используется вместо `Record<string, any>` для схем форм
|
|
162
|
-
* @deprecated
|
|
163
|
-
* @internal
|
|
164
|
-
*/
|
|
165
|
-
export type FormFields = Record<string, FormValue>;
|
|
166
160
|
/**
|
|
167
161
|
* Тип для путей к полям (field paths)
|
|
168
162
|
* Используется в навигации по полям вместо any
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
import { ValidationError } from './index';
|
|
3
|
+
/**
|
|
4
|
+
* Валидатор поля схемы. Узел хранит validators гетерогенно: движок `validate*` вызывает их как
|
|
5
|
+
* `(value, scope, root)`, но сюда кладут любой контракт — `ValidatorFn` (value),
|
|
6
|
+
* `Validator` (value, control, root) или `ModelValidator` (value, model, root). Поэтому тип
|
|
7
|
+
* намеренно широкий по параметрам.
|
|
8
|
+
*
|
|
9
|
+
* @group Types
|
|
10
|
+
*/
|
|
11
|
+
export type SchemaValidator = (value: any, ...rest: any[]) => ValidationError | null | Promise<ValidationError | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Минимальный контракт реактивного массива модели ({@link FormSchemaNode.array}).
|
|
14
|
+
* Совпадает по форме с рантайм-фасадом `model.<array>` (см. `ModelArray`); рендерерский
|
|
15
|
+
* `RenderModelArrayControl` — его расширение (добавляет `move`).
|
|
16
|
+
*
|
|
17
|
+
* @group Types
|
|
18
|
+
*/
|
|
19
|
+
export interface SchemaArrayControl {
|
|
20
|
+
/** Путь массива в модели (dot-нотация) — нужен для резолва узла массива. */
|
|
21
|
+
readonly __path: string;
|
|
22
|
+
/** Реактивная длина. */
|
|
23
|
+
readonly length: number;
|
|
24
|
+
at(index: number): unknown;
|
|
25
|
+
push(item: unknown): void;
|
|
26
|
+
removeAt(index: number): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Узел единой схемы M1 — дерево, обходимое `createForm({ model, schema })`,
|
|
30
|
+
* `validateModel`/`validateFormModel` и рендерерами.
|
|
31
|
+
*
|
|
32
|
+
* Узел совмещает несколько ролей (различаются рантаймом по форме):
|
|
33
|
+
* - **поле** — несёт `value: Signal` (сигнал модели `model.$.x`) + `component`/`validators`;
|
|
34
|
+
* - **массив** — `{ array: model.<path>, item(itemModel) }`;
|
|
35
|
+
* - **контейнер/ветка** — вложенные узлы (`children`), опц. условие `when`;
|
|
36
|
+
* - **record-of-fields** — под-узлы под произвольными именованными ключами (индексная сигнатура).
|
|
37
|
+
*
|
|
38
|
+
* Индексная сигнатура (`[key: string]: unknown`) отражает свободный рекурсивный обход: под-узлы
|
|
39
|
+
* допустимы под любым ключом. Известные поля типизированы (даёт автокомплит и проверку их типов).
|
|
40
|
+
*
|
|
41
|
+
* @group Types
|
|
42
|
+
*/
|
|
43
|
+
export interface FormSchemaNode {
|
|
44
|
+
/**
|
|
45
|
+
* «Ручка» значения поля — маркер узла-поля. Обычно сигнал модели (`model.$.<path>`), но форма
|
|
46
|
+
* зависит от таргета (для массива `model.$.x` — дерево сигналов; в renderer-типах сужается до
|
|
47
|
+
* `Signal`). Движок разбирает узел как поле рантаймом по `value instanceof Signal`.
|
|
48
|
+
*/
|
|
49
|
+
value?: unknown;
|
|
50
|
+
/** UI-компонент. Опционален: core-часть работает без UI (значение/валидация). */
|
|
51
|
+
component?: ComponentType<any>;
|
|
52
|
+
/** Props компонента. Также «клапан» для вложенности под-узлов (напр. steps визарда). */
|
|
53
|
+
componentProps?: Record<string, unknown>;
|
|
54
|
+
validators?: SchemaValidator[];
|
|
55
|
+
asyncValidators?: SchemaValidator[];
|
|
56
|
+
updateOn?: 'change' | 'blur' | 'submit';
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
/** Задержка (мс) перед запуском асинхронной валидации. */
|
|
59
|
+
debounce?: number;
|
|
60
|
+
/** Идентификатор узла (для wizard/tabs/renderBehavior). */
|
|
61
|
+
selector?: string;
|
|
62
|
+
testId?: string;
|
|
63
|
+
/** Дочерние узлы (даёт контекстную типизацию вложенным литералам — value/validators/when). */
|
|
64
|
+
children?: readonly FormSchemaNode[];
|
|
65
|
+
/** Условие включения поддерева (branch-узел `{ when, children }`). */
|
|
66
|
+
when?: (scope: any, root: any) => boolean;
|
|
67
|
+
/** Реактивный массив модели (`model.<path>`) — маркер узла-массива (вместе с `item`). */
|
|
68
|
+
array?: SchemaArrayControl;
|
|
69
|
+
/** Схема элемента массива: под-модель элемента → узел поддерева. */
|
|
70
|
+
item?: (itemModel: any) => FormSchemaNode;
|
|
71
|
+
/**
|
|
72
|
+
* Значение нового элемента массива для кнопки «Добавить»: либо готовое значение,
|
|
73
|
+
* либо фабрика `() => value`. Тип не различает варианты (union `unknown | (() => unknown)`
|
|
74
|
+
* схлопывается в `unknown`) — рантайм различает по `typeof initialValue === 'function'`.
|
|
75
|
+
*/
|
|
76
|
+
initialValue?: unknown;
|
|
77
|
+
/** Свободная вложенность: record-of-fields и произвольные под-узлы. */
|
|
78
|
+
[key: string]: unknown;
|
|
79
|
+
}
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { FormValue, ValidationError } from './index';
|
|
2
|
+
import { FormModel } from '../model/types';
|
|
3
3
|
/**
|
|
4
4
|
* Чистый синхронный валидатор поля.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Сигнатура зеркалит то, что реально вызывает движок M1
|
|
7
|
+
* (`validateModel`/`validateFormModel`/`validateModelSync`): `validator(value, scope, root)`.
|
|
8
|
+
* - `value` — значение поля (`TField`);
|
|
9
|
+
* - `scope` — ближайшая scope-**модель** (под-модель элемента массива или корень). Из `TForm`/`TField`
|
|
10
|
+
* её тип не выводится, поэтому `unknown` — потребитель сужает сам (для типизированного scope
|
|
11
|
+
* используйте {@link ModelValidator}`<TField, TScope, TForm>`);
|
|
12
|
+
* - `root` — корневая модель формы `FormModel<TForm>` (реактивный value-proxy: `root.field` читает
|
|
13
|
+
* значение). Возвращает `ValidationError` либо `null`. Не знает про реестр валидации.
|
|
14
|
+
*
|
|
15
|
+
* Совместим с полем `validators` узла схемы (см. `SchemaValidator`) — там же лежат `ModelValidator`
|
|
16
|
+
* и `ValidatorFn`. Встроенные фабрики (`required()`/`email()`/…) возвращают `(value) => …` и
|
|
17
|
+
* дополнительные аргументы игнорируют.
|
|
8
18
|
*
|
|
9
19
|
* @example Кастомный валидатор в массиве `validators` поля схемы
|
|
10
20
|
* ```typescript
|
|
11
|
-
* const isAdult: Validator<MyForm, number> = (value,
|
|
21
|
+
* const isAdult: Validator<MyForm, number> = (value, _scope, root) => {
|
|
12
22
|
* if (value < 18) return { code: 'tooYoung', message: '18+' };
|
|
23
|
+
* // root — FormModel<MyForm>: root.someOtherField читается как значение
|
|
13
24
|
* return null;
|
|
14
25
|
* };
|
|
15
26
|
*
|
|
@@ -21,13 +32,13 @@ import { FormProxy } from './form-proxy';
|
|
|
21
32
|
* };
|
|
22
33
|
* ```
|
|
23
34
|
*/
|
|
24
|
-
export type Validator<TForm, TField> = (value: TField,
|
|
35
|
+
export type Validator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => ValidationError | null;
|
|
25
36
|
/**
|
|
26
|
-
* Чистый асинхронный валидатор поля.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
37
|
+
* Чистый асинхронный валидатор поля. Та же тройка аргументов `(value, scope, root)`, что у
|
|
38
|
+
* {@link Validator}, но возвращает `Promise`. Регистрируется через
|
|
39
|
+
* `validateAsync(path, validator, { debounce })`.
|
|
29
40
|
*/
|
|
30
|
-
export type AsyncValidator<TForm, TField> = (value: TField,
|
|
41
|
+
export type AsyncValidator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => Promise<ValidationError | null>;
|
|
31
42
|
/**
|
|
32
43
|
* Функция условия для applyWhen.
|
|
33
44
|
*/
|
|
@@ -39,7 +50,7 @@ export interface ValidateOptions {
|
|
|
39
50
|
/** Сообщение об ошибке */
|
|
40
51
|
message?: string;
|
|
41
52
|
/** Параметры ошибки */
|
|
42
|
-
params?:
|
|
53
|
+
params?: Record<string, FormValue>;
|
|
43
54
|
}
|
|
44
55
|
/**
|
|
45
56
|
* Опции для функции validateAsync.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FormProxy, GroupNodeConfig, FormSchema } from '../types';
|
|
2
|
+
import { FormSchemaNode } from '../types/schema-node';
|
|
2
3
|
import { FormModel } from '../model/types';
|
|
3
4
|
import { FormBehavior } from '../../behaviors';
|
|
4
5
|
/**
|
|
@@ -11,10 +12,10 @@ export interface CreateFormFromModelArgs<T> {
|
|
|
11
12
|
/** Реактивная модель данных (источник истины значений). */
|
|
12
13
|
model: FormModel<T>;
|
|
13
14
|
/**
|
|
14
|
-
* Единая Schema (дерево узлов). createForm обходит её и привязывает конфиг
|
|
15
|
-
* по идентичности сигнала (`node.value === model.$.path`). Опциональна.
|
|
15
|
+
* Единая Schema (дерево узлов {@link FormSchemaNode}). createForm обходит её и привязывает конфиг
|
|
16
|
+
* поля к ноде по идентичности сигнала (`node.value === model.$.path`). Опциональна.
|
|
16
17
|
*/
|
|
17
|
-
schema?:
|
|
18
|
+
schema?: FormSchemaNode;
|
|
18
19
|
/**
|
|
19
20
|
* Декларативная схема поведения ({@link defineFormBehavior}). Запускается ПОСЛЕ построения нод и
|
|
20
21
|
* заполнения реестра сигнал→нода; cleanup живёт на форме и вызывается в `form.dispose()`.
|
|
@@ -93,7 +93,10 @@ export declare class FormErrorHandler {
|
|
|
93
93
|
* }
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
|
-
static handle(error: unknown, context: string, strategy
|
|
96
|
+
static handle(error: unknown, context: string, strategy: ErrorStrategy.CONVERT): ValidationError;
|
|
97
|
+
static handle(error: unknown, context: string, strategy: ErrorStrategy.THROW): never;
|
|
98
|
+
static handle(error: unknown, context: string, strategy: ErrorStrategy.LOG): void;
|
|
99
|
+
static handle(error: unknown, context: string): never;
|
|
97
100
|
/**
|
|
98
101
|
* Извлечь сообщение из ошибки
|
|
99
102
|
*
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
-
import { FormFields } from '../types';
|
|
3
2
|
/**
|
|
4
3
|
* Интерфейс формы для FormSubmitter
|
|
5
4
|
* Минимальный контракт для работы с любой формой
|
|
6
5
|
*/
|
|
7
|
-
export interface SubmittableForm<T extends
|
|
6
|
+
export interface SubmittableForm<T extends object> {
|
|
8
7
|
/** Пометить все поля как touched */
|
|
9
8
|
markAsTouched(): void;
|
|
10
9
|
/** Валидировать форму */
|
|
@@ -50,7 +49,7 @@ export interface SubmitResult<R> {
|
|
|
50
49
|
* }
|
|
51
50
|
* ```
|
|
52
51
|
*/
|
|
53
|
-
export declare class FormSubmitter<T extends
|
|
52
|
+
export declare class FormSubmitter<T extends object> {
|
|
54
53
|
private readonly form;
|
|
55
54
|
/** Внутренний сигнал состояния отправки */
|
|
56
55
|
private readonly _submitting;
|
|
@@ -2,7 +2,7 @@ import { FormNode } from '../nodes/form-node';
|
|
|
2
2
|
import { FieldNode } from '../nodes/field-node';
|
|
3
3
|
import { GroupNode } from '../nodes/group-node';
|
|
4
4
|
import { ArrayNode } from '../nodes/array-node';
|
|
5
|
-
import {
|
|
5
|
+
import { FormValue } from '../types';
|
|
6
6
|
/**
|
|
7
7
|
* Проверить, является ли значение любым FormNode
|
|
8
8
|
*
|
|
@@ -61,7 +61,7 @@ export declare function isFieldNode(value: unknown): value is FieldNode<FormValu
|
|
|
61
61
|
* }
|
|
62
62
|
* ```
|
|
63
63
|
*/
|
|
64
|
-
export declare function isGroupNode(value: unknown): value is GroupNode<
|
|
64
|
+
export declare function isGroupNode(value: unknown): value is GroupNode<object>;
|
|
65
65
|
/**
|
|
66
66
|
* Проверить, является ли значение ArrayNode (массив форм)
|
|
67
67
|
*
|
|
@@ -80,7 +80,7 @@ export declare function isGroupNode(value: unknown): value is GroupNode<FormFiel
|
|
|
80
80
|
* }
|
|
81
81
|
* ```
|
|
82
82
|
*/
|
|
83
|
-
export declare function isArrayNode(value: unknown): value is ArrayNode<
|
|
83
|
+
export declare function isArrayNode(value: unknown): value is ArrayNode<object>;
|
|
84
84
|
/**
|
|
85
85
|
* Получить тип узла как строку (для отладки)
|
|
86
86
|
*
|
|
@@ -21,6 +21,10 @@ export declare function getToday(): Date;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function normalizeDate(date: Date): Date;
|
|
23
23
|
/**
|
|
24
|
-
* Вычисляет возраст
|
|
24
|
+
* Вычисляет полный возраст (в годах) по календарным полям.
|
|
25
|
+
*
|
|
26
|
+
* Считаем по годам/месяцам/дням, а не делением миллисекунд на усреднённый год (365.25 дн): усреднение
|
|
27
|
+
* даёт off-by-one на границе — в день N-летия накопленных реальных дней меньше `N * 365.25`, и `floor`
|
|
28
|
+
* роняет возраст до `N - 1` (т.е. `minAge(18)` ошибочно отклонял 18-летнего в его день рождения).
|
|
25
29
|
*/
|
|
26
30
|
export declare function calculateAge(birthDate: Date): number;
|
|
@@ -19,4 +19,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
19
19
|
* },
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
export declare function integer<TForm = unknown, TField extends number | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
22
|
+
export declare function integer<TForm = unknown, TField extends number | null | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -21,4 +21,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
21
21
|
* },
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
export declare function isNumber<TForm = unknown, TField extends number | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
24
|
+
export declare function isNumber<TForm = unknown, TField extends number | null | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -22,4 +22,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
22
22
|
* },
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
-
export declare function max<TForm = unknown, TField extends number | undefined = number>(maxValue: number, options?: ValidateOptions): Validator<TForm, TField>;
|
|
25
|
+
export declare function max<TForm = unknown, TField extends number | null | undefined = number>(maxValue: number, options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -22,4 +22,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
22
22
|
* },
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
-
export declare function min<TForm = unknown, TField extends number | undefined = number>(minValue: number, options?: ValidateOptions): Validator<TForm, TField>;
|
|
25
|
+
export declare function min<TForm = unknown, TField extends number | null | undefined = number>(minValue: number, options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -21,4 +21,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
21
21
|
* },
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
export declare function multipleOf<TForm = unknown, TField extends number | undefined = number>(divisor: number, options?: ValidateOptions): Validator<TForm, TField>;
|
|
24
|
+
export declare function multipleOf<TForm = unknown, TField extends number | null | undefined = number>(divisor: number, options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -19,4 +19,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
19
19
|
* },
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
export declare function nonNegative<TForm = unknown, TField extends number | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
22
|
+
export declare function nonNegative<TForm = unknown, TField extends number | null | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -19,4 +19,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
19
19
|
* },
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
export declare function nonZero<TForm = unknown, TField extends number | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
22
|
+
export declare function nonZero<TForm = unknown, TField extends number | null | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|