@reformer/core 6.0.0 → 7.0.0-beta.2
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 +2 -1
- package/dist/core/model/behaviors.d.ts +13 -1
- package/dist/core/model/validate-model.d.ts +22 -0
- package/dist/core/nodes/field-node.d.ts +3 -3
- package/dist/core/nodes/group-node.d.ts +3 -5
- package/dist/core/nodes/model-array-node.d.ts +38 -0
- package/dist/core/types/validation-schema.d.ts +8 -2
- package/dist/core/utils/create-form.d.ts +83 -18
- package/dist/core/utils/derived-registry.d.ts +41 -2
- package/dist/core/utils/index.d.ts +0 -4
- package/dist/core/utils/signal-node-registry.d.ts +38 -4
- package/dist/core/validation/validators/email.d.ts +14 -4
- package/dist/core/validation/validators/future-date.d.ts +14 -4
- package/dist/core/validation/validators/index.d.ts +2 -1
- package/dist/core/validation/validators/integer.d.ts +13 -4
- package/dist/core/validation/validators/is-date.d.ts +14 -4
- package/dist/core/validation/validators/is-number.d.ts +15 -5
- package/dist/core/validation/validators/max-age.d.ts +16 -4
- package/dist/core/validation/validators/max-date.d.ts +16 -4
- package/dist/core/validation/validators/max-length.d.ts +18 -4
- package/dist/core/validation/validators/max.d.ts +15 -6
- package/dist/core/validation/validators/min-age.d.ts +16 -4
- package/dist/core/validation/validators/min-date.d.ts +16 -4
- package/dist/core/validation/validators/min-length.d.ts +18 -4
- package/dist/core/validation/validators/min.d.ts +15 -6
- package/dist/core/validation/validators/multiple-of.d.ts +15 -4
- package/dist/core/validation/validators/non-negative.d.ts +13 -4
- package/dist/core/validation/validators/non-zero.d.ts +13 -4
- package/dist/core/validation/validators/past-date.d.ts +14 -3
- package/dist/core/validation/validators/pattern.d.ts +24 -4
- package/dist/core/validation/validators/phone.d.ts +28 -4
- package/dist/core/validation/validators/required.d.ts +16 -5
- package/dist/core/validation/validators/url.d.ts +30 -7
- package/dist/hooks/useFormControlValue.d.ts +15 -10
- package/dist/index.js +562 -896
- package/llms.txt +2568 -2855
- package/package.json +1 -1
- package/dist/core/utils/abstract-registry.d.ts +0 -74
- package/dist/core/utils/form-observer.d.ts +0 -176
- package/dist/core/utils/registry-stack.d.ts +0 -69
|
@@ -9,7 +9,8 @@ import { FormNode } from '../nodes/form-node';
|
|
|
9
9
|
* ```typescript
|
|
10
10
|
* import { NodeFactory } from '@reformer/core';
|
|
11
11
|
*
|
|
12
|
-
* const
|
|
12
|
+
* const factory = new NodeFactory();
|
|
13
|
+
* const node = factory.createNode({ value: '', component: Input }); // → FieldNode<string>
|
|
13
14
|
* ```
|
|
14
15
|
*/
|
|
15
16
|
export declare class NodeFactory {
|
|
@@ -67,9 +67,21 @@ export declare function enableWhen(target: ReadonlySignal<unknown>, condition: (
|
|
|
67
67
|
resetOnDisable?: boolean;
|
|
68
68
|
}): BehaviorCleanup;
|
|
69
69
|
/**
|
|
70
|
-
* Условное выключение поля (инверсия {@link enableWhen}).
|
|
70
|
+
* Условное выключение поля (инверсия {@link enableWhen}). Резолвит ноду по сигналу-цели через реестр
|
|
71
|
+
* сигнал→нода и вызывает `disable()`, когда `condition` истинно (+`reset()` при `resetOnDisable`).
|
|
72
|
+
* `condition` реактивен (читает свои сигналы модели).
|
|
71
73
|
*
|
|
72
74
|
* @group Model
|
|
75
|
+
* @param target Сигнал-цель поля (`model.$.<path>`).
|
|
76
|
+
* @param condition Реактивное условие; при `true` поле выключается.
|
|
77
|
+
* @param options.resetOnDisable Сбросить значение при выключении (по умолчанию `false`).
|
|
78
|
+
* @returns Cleanup для отписки.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // Поле скидки недоступно, пока не выбран промо-тариф
|
|
83
|
+
* disableWhen(model.$.discount, () => model.plan !== 'promo', { resetOnDisable: true });
|
|
84
|
+
* ```
|
|
73
85
|
*/
|
|
74
86
|
export declare function disableWhen(target: ReadonlySignal<unknown>, condition: () => boolean, options?: {
|
|
75
87
|
resetOnDisable?: boolean;
|
|
@@ -21,6 +21,16 @@ export interface ModelValidationResult {
|
|
|
21
21
|
* Синхронная headless-валидация данных. Асинхронные валидаторы пропускаются.
|
|
22
22
|
*
|
|
23
23
|
* @group Model
|
|
24
|
+
* @param model - Модель данных ({@link FormModel}), из сигналов которой читаются значения полей.
|
|
25
|
+
* @param schema - Единая схема формы (дерево узлов с `value`/`validators`, `{ when, children }`,
|
|
26
|
+
* секциями массивов). Обходится один раз для сбора активных полей.
|
|
27
|
+
* @returns {@link ModelValidationResult} — `valid` + ошибки по пути поля (без async-валидаторов).
|
|
28
|
+
*
|
|
29
|
+
* @example Быстрая синхронная проверка (например, для gate «можно ли перейти на след. шаг»)
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const res = validateModelSync(model, schema);
|
|
32
|
+
* if (!res.valid) console.log(res.errors); // { 'email': [{ code, message }], ... }
|
|
33
|
+
* ```
|
|
24
34
|
*/
|
|
25
35
|
export declare function validateModelSync<T>(model: FormModel<T>, schema: unknown): ModelValidationResult;
|
|
26
36
|
/**
|
|
@@ -40,7 +50,19 @@ export declare function validateModel<T>(model: FormModel<T>, schema: unknown):
|
|
|
40
50
|
* И поля выключенных веток (`{ when, children }` с ложным условием). Дерево обходится ОДИН раз.
|
|
41
51
|
*
|
|
42
52
|
* @group Model
|
|
53
|
+
* @param model - Модель данных ({@link FormModel}) — источник значений полей.
|
|
54
|
+
* @param schema - Единая схема формы (та же, что передавалась в `createForm`).
|
|
55
|
+
* @returns {@link ModelValidationResult} — `valid` + ошибки по пути поля; побочный эффект — ошибки
|
|
56
|
+
* разведены по нодам формы.
|
|
43
57
|
* @remarks Поля, не материализованные в форме (элементы массива — строятся per-item), не роутятся
|
|
44
58
|
* в родительскую форму; их ошибки доступны в возвращаемом результате по пути.
|
|
59
|
+
*
|
|
60
|
+
* @example Валидация перед submit (ошибки показываются в UI автоматически)
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const form = createForm({ model, schema });
|
|
63
|
+
* form.touchAll();
|
|
64
|
+
* const res = await validateFormModel(model, schema);
|
|
65
|
+
* if (res.valid) await api.save(model.get());
|
|
66
|
+
* ```
|
|
45
67
|
*/
|
|
46
68
|
export declare function validateFormModel<T>(model: FormModel<T>, schema: unknown): Promise<ModelValidationResult>;
|
|
@@ -196,13 +196,13 @@ export declare class FieldNode<T> extends FormNode<T> {
|
|
|
196
196
|
* @example
|
|
197
197
|
* ```typescript
|
|
198
198
|
* // Сценарий 1: Instant feedback после submit
|
|
199
|
-
* const form =
|
|
199
|
+
* const form = createForm({
|
|
200
200
|
* email: {
|
|
201
201
|
* value: '',
|
|
202
202
|
* component: Input,
|
|
203
203
|
* updateOn: 'submit', // Изначально валидация только при submit
|
|
204
|
-
* validators: [required, email]
|
|
205
|
-
* }
|
|
204
|
+
* validators: [required, email],
|
|
205
|
+
* },
|
|
206
206
|
* });
|
|
207
207
|
*
|
|
208
208
|
* await form.submit(async (values) => {
|
|
@@ -134,14 +134,12 @@ export declare class GroupNode<T> extends FormNode<T> {
|
|
|
134
134
|
* @example
|
|
135
135
|
* ```typescript
|
|
136
136
|
* const form = new GroupNode({
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* name: new FieldNode({ value: '' })
|
|
140
|
-
* }
|
|
137
|
+
* email: { value: '', component: Input },
|
|
138
|
+
* name: { value: '', component: Input },
|
|
141
139
|
* });
|
|
142
140
|
*
|
|
143
141
|
* const proxy = form.getProxy();
|
|
144
|
-
* console.log(proxy.email.value); // Прямой доступ к полю
|
|
142
|
+
* console.log(proxy.email.value.value); // Прямой доступ к полю
|
|
145
143
|
* ```
|
|
146
144
|
*/
|
|
147
145
|
getProxy(): FormProxy<T>;
|
|
@@ -15,6 +15,44 @@ export interface ModelArrayControl<TItem extends object> {
|
|
|
15
15
|
clear(): void;
|
|
16
16
|
toArray(): TItem[];
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Узел массива, делегирующий данные массиву {@link FormModel} (архитектура M1).
|
|
20
|
+
*
|
|
21
|
+
* В отличие от {@link ArrayNode} (владеет элементами сам), `ModelArrayNode` НЕ владеет данными:
|
|
22
|
+
* массив принадлежит модели, а узел держит per-item формы элементов (привязанные к сигналам
|
|
23
|
+
* под-моделей) и синхронизирует их с длиной массива модели. Мутации (`push`/`removeAt`/`move`/…)
|
|
24
|
+
* делегируются массиву модели; per-item формы кэшируются по идентичности под-модели, поэтому при
|
|
25
|
+
* reorder/повторном рендере не пересоздаются (состояние и валидация сохраняются). Реализует тот же
|
|
26
|
+
* контракт, что ждут секции массива и `useFormControl` (`length`/`value`/`valid`/`errors`/`at`/`push`/…).
|
|
27
|
+
*
|
|
28
|
+
* Обычно создаётся не напрямую, а `createForm({ model, schema })`: когда в схеме встречается узел
|
|
29
|
+
* массива `{ array: model.<field>, item: (item) => itemSchema }`, форма материализует его как
|
|
30
|
+
* `ModelArrayNode` и кладёт под `form.<field>` (совместим с `FormArraySection`).
|
|
31
|
+
*
|
|
32
|
+
* @group Nodes
|
|
33
|
+
*
|
|
34
|
+
* @example Массив как часть формы (через createForm)
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });
|
|
37
|
+
*
|
|
38
|
+
* const rowItem = (item: FormModel<{ name: string; qty: number }>) => ({
|
|
39
|
+
* name: { value: item.$.name, component: Input },
|
|
40
|
+
* qty: { value: item.$.qty, component: Input },
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* const form = createForm({
|
|
44
|
+
* model,
|
|
45
|
+
* schema: {
|
|
46
|
+
* children: [{ array: model.rows, item: rowItem }],
|
|
47
|
+
* },
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* const rows = form.rows as unknown as ModelArrayNode<{ name: string; qty: number }>;
|
|
51
|
+
* rows.push({ name: 'A', qty: 1 }); // мутация уезжает в model.rows
|
|
52
|
+
* rows.at(0)?.name.setValue('B'); // правка поля элемента доезжает в под-модель
|
|
53
|
+
* rows.length.value; // 1 (реактивная длина)
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
18
56
|
export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
|
|
19
57
|
private readonly control;
|
|
20
58
|
private readonly buildItem;
|
|
@@ -6,13 +6,19 @@ import { FormProxy } from './form-proxy';
|
|
|
6
6
|
* Принимает значение поля, прокси текущего поля (control) и прокси корня формы (root).
|
|
7
7
|
* Возвращает ValidationError либо null. Не знает про реестр валидации.
|
|
8
8
|
*
|
|
9
|
-
* @example
|
|
9
|
+
* @example Кастомный валидатор в массиве `validators` поля схемы
|
|
10
10
|
* ```typescript
|
|
11
11
|
* const isAdult: Validator<MyForm, number> = (value, control, root) => {
|
|
12
12
|
* if (value < 18) return { code: 'tooYoung', message: '18+' };
|
|
13
13
|
* return null;
|
|
14
14
|
* };
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* // Фабрики и кастомные валидаторы кладутся в `validators: [...]` поля:
|
|
17
|
+
* const schema = {
|
|
18
|
+
* children: [
|
|
19
|
+
* { value: model.$.age, component: Input, validators: [required(), isAdult] },
|
|
20
|
+
* ],
|
|
21
|
+
* };
|
|
16
22
|
* ```
|
|
17
23
|
*/
|
|
18
24
|
export type Validator<TForm, TField> = (value: TField, control: FormProxy<TField>, root: FormProxy<TForm>) => ValidationError | null;
|
|
@@ -21,43 +21,108 @@ export interface CreateFormFromModelArgs<T> {
|
|
|
21
21
|
*/
|
|
22
22
|
behavior?: FormBehavior<T>;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика архитектуры M1).
|
|
26
|
+
*
|
|
27
|
+
* Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
|
|
28
|
+
* ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
|
|
29
|
+
* модели, привязывает конфиг поля (component/componentProps/validators) из схемы, материализует
|
|
30
|
+
* top-level массивы как {@link ModelArrayNode}, заполняет реестр сигнал→нода (для `enableWhen`/
|
|
31
|
+
* роутинга ошибок) и, при наличии, запускает декларативное поведение (cleanup живёт на форме).
|
|
32
|
+
*
|
|
33
|
+
* Обычно вызывается неявно через {@link createForm} с аргументом `{ model, schema }` — прямой вызов
|
|
34
|
+
* нужен редко (например, для построения формы элемента массива).
|
|
35
|
+
*
|
|
36
|
+
* @typeParam T - Тип модели данных формы
|
|
37
|
+
* @param args - Модель, единая схема и (опционально) декларативное поведение {@link CreateFormFromModelArgs}
|
|
38
|
+
* @returns Типизированная форма с Proxy-доступом к полям {@link FormProxy}
|
|
39
|
+
*
|
|
40
|
+
* @example Форма из модели + схемы (эквивалент `createForm({ model, schema })`)
|
|
41
|
+
* ```typescript
|
|
42
|
+
* import { createModel, createFormFromModel } from '@reformer/core';
|
|
43
|
+
*
|
|
44
|
+
* interface Form {
|
|
45
|
+
* email: string;
|
|
46
|
+
* profile: { name: string; age: number };
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* const model = createModel<Form>({ email: '', profile: { name: '', age: 0 } });
|
|
50
|
+
* const schema = {
|
|
51
|
+
* component: Section,
|
|
52
|
+
* children: [
|
|
53
|
+
* { value: model.$.email, component: Input, validators: [required] },
|
|
54
|
+
* { value: model.$.profile.name, component: Input },
|
|
55
|
+
* ],
|
|
56
|
+
* };
|
|
57
|
+
*
|
|
58
|
+
* const form = createFormFromModel<Form>({ model, schema });
|
|
59
|
+
*
|
|
60
|
+
* // Двусторонняя связь нода ↔ модель:
|
|
61
|
+
* form.email.setValue('user@mail.com');
|
|
62
|
+
* console.log(model.email); // 'user@mail.com'
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @see {@link createForm} - основная фабрика (диспетчеризует сюда при аргументе `{ model, schema }`)
|
|
66
|
+
* @group Utilities
|
|
67
|
+
*/
|
|
24
68
|
export declare function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
|
|
25
69
|
/**
|
|
26
|
-
* Создать форму
|
|
70
|
+
* Создать форму из {@link FormModel} + единой схемы (архитектура M1, рекомендуемый путь).
|
|
27
71
|
*
|
|
28
72
|
* @group Utilities
|
|
29
73
|
*
|
|
30
|
-
* @param
|
|
74
|
+
* @param args - Модель данных, единая схема (component/componentProps/validators) и (опционально) поведение
|
|
31
75
|
* @returns Типизированная форма с Proxy-доступом к полям
|
|
32
76
|
*
|
|
33
|
-
* @example
|
|
77
|
+
* @example Архитектура M1: `createModel` + единая схема + `createForm({ model, schema })`
|
|
34
78
|
* ```typescript
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* email(path.email);
|
|
43
|
-
* required(path.password);
|
|
44
|
-
* minLength(path.password, 8);
|
|
45
|
-
* },
|
|
46
|
-
* });
|
|
79
|
+
* import { createModel, createForm, validateFormModel } from '@reformer/core';
|
|
80
|
+
* import { required, email, minLength } from '@reformer/core/validators';
|
|
81
|
+
*
|
|
82
|
+
* interface UserForm {
|
|
83
|
+
* email: string;
|
|
84
|
+
* password: string;
|
|
85
|
+
* }
|
|
47
86
|
*
|
|
48
|
-
*
|
|
87
|
+
* const model = createModel<UserForm>({ email: '', password: '' });
|
|
88
|
+
*
|
|
89
|
+
* const schema = {
|
|
90
|
+
* children: [
|
|
91
|
+
* {
|
|
92
|
+
* value: model.$.email,
|
|
93
|
+
* component: Input,
|
|
94
|
+
* validators: [required(), email()],
|
|
95
|
+
* },
|
|
96
|
+
* {
|
|
97
|
+
* value: model.$.password,
|
|
98
|
+
* component: Input,
|
|
99
|
+
* validators: [required(), minLength(8)],
|
|
100
|
+
* },
|
|
101
|
+
* ],
|
|
102
|
+
* };
|
|
103
|
+
*
|
|
104
|
+
* const form = createForm<UserForm>({ model, schema });
|
|
105
|
+
*
|
|
106
|
+
* // TypeScript знает о полях (нода привязана к сигналу модели):
|
|
49
107
|
* form.email.setValue('test@mail.com');
|
|
108
|
+
*
|
|
109
|
+
* // Валидация всей модели по схеме (sync + async):
|
|
110
|
+
* const { valid } = await validateFormModel(model, schema);
|
|
50
111
|
* ```
|
|
51
112
|
*/
|
|
52
113
|
export declare function createForm<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
|
|
53
114
|
export declare function createForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
|
|
54
115
|
/**
|
|
55
|
-
* Создать форму только со схемой
|
|
116
|
+
* Создать форму только со схемой полей.
|
|
117
|
+
*
|
|
118
|
+
* @deprecated Legacy / back-compat: плоская `FormSchema` с инлайн-значениями (`value: ''`) — путь
|
|
119
|
+
* ДО архитектуры M1. Для нового кода используйте перегрузку `createForm({ model, schema })`
|
|
120
|
+
* (значения принадлежат {@link FormModel}, валидаторы — фабрики в `validators: [...]`).
|
|
56
121
|
*
|
|
57
122
|
* @param schema - Схема полей формы
|
|
58
123
|
* @returns Типизированная форма с Proxy-доступом к полям
|
|
59
124
|
*
|
|
60
|
-
* @example
|
|
125
|
+
* @example Legacy (back-compat) — плоская схема без модели
|
|
61
126
|
* ```typescript
|
|
62
127
|
* const form = createForm<UserForm>({
|
|
63
128
|
* email: { value: '', component: Input },
|
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
import { Signal } from '@preact/signals-core';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Пометить сигнал производным (вычисляемым).
|
|
4
|
+
*
|
|
5
|
+
* Вызывается операторами {@link computeFrom}/`compute` для их сигнала-цели. После этого
|
|
6
|
+
* bulk-сеттеры (`model.set`/`model.patch`, `patchValue`/`setValue`) пропускают это поле, чтобы
|
|
7
|
+
* значение из payload не затирало вычисляемое. Прикладной код напрямую обычно не вызывает.
|
|
8
|
+
*
|
|
9
|
+
* @param signal - Сигнал значения из {@link FormModel}, которым владеет compute
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { markDerived } from '@reformer/core';
|
|
14
|
+
*
|
|
15
|
+
* // Помечаем поле total как вычисляемое — bulk-set его не перезапишет
|
|
16
|
+
* markDerived(model.$.total);
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @see {@link isDerived} - проверка пометки
|
|
20
|
+
* @group Utilities
|
|
21
|
+
*/
|
|
3
22
|
export declare function markDerived(signal: Signal<any>): void;
|
|
4
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Производный ли сигнал (помечен через {@link markDerived}).
|
|
25
|
+
*
|
|
26
|
+
* Читается bulk-сеттерами модели/группы, чтобы не затирать вычисляемые (`computeFrom`) поля
|
|
27
|
+
* значениями из payload.
|
|
28
|
+
*
|
|
29
|
+
* @param signal - Сигнал значения из {@link FormModel}
|
|
30
|
+
* @returns `true`, если сигнал помечен производным
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { isDerived } from '@reformer/core';
|
|
35
|
+
*
|
|
36
|
+
* if (!isDerived(model.$.total)) {
|
|
37
|
+
* model.$.total.value = payload.total; // писать в payload-значение только для «ручных» полей
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @see {@link markDerived} - пометить сигнал производным
|
|
42
|
+
* @group Utilities
|
|
43
|
+
*/
|
|
5
44
|
export declare function isDerived(signal: Signal<any>): boolean;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Централизованные вспомогательные классы и функции.
|
|
5
5
|
*/
|
|
6
6
|
export { SubscriptionManager } from './subscription-manager';
|
|
7
|
-
export { RegistryStack } from './registry-stack';
|
|
8
7
|
export { isFormNode, isFieldNode, isGroupNode, isArrayNode, getNodeType } from './type-guards';
|
|
9
8
|
export { FormErrorHandler, ErrorStrategy } from './error-handler';
|
|
10
9
|
export { createForm, createFormFromModel } from './create-form';
|
|
@@ -13,10 +12,7 @@ export { registerSignalNode, getNodeForSignal } from './signal-node-registry';
|
|
|
13
12
|
export { markDerived, isDerived } from './derived-registry';
|
|
14
13
|
export { uniqueId } from './unique-id';
|
|
15
14
|
export { safeCallback, runOutsideEffect, safeDebouncedCallback } from './safe-effect';
|
|
16
|
-
export { AbstractRegistry } from './abstract-registry';
|
|
17
15
|
export { FormSubmitter } from './form-submitter';
|
|
18
16
|
export type { SubmittableForm, SubmitOptions, SubmitResult } from './form-submitter';
|
|
19
17
|
export { FormStatusMachine } from './status-machine';
|
|
20
18
|
export type { StatusEvent } from './status-machine';
|
|
21
|
-
export { FormObserver } from './form-observer';
|
|
22
|
-
export type { FormChangeType, FormChangeEvent, FormChangeCallback, FormObserverOptions, ObservableForm, ObservableFormNode, } from './form-observer';
|
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
import { Signal } from '@preact/signals-core';
|
|
2
2
|
import { FormNode } from '../nodes/form-node';
|
|
3
3
|
/**
|
|
4
|
-
* Связать сигнал модели с его
|
|
5
|
-
*
|
|
4
|
+
* Связать сигнал модели с его нодой формы.
|
|
5
|
+
*
|
|
6
|
+
* Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого
|
|
7
|
+
* листового поля. Прикладной код обычно этот реестр не заполняет напрямую.
|
|
8
|
+
*
|
|
9
|
+
* @param signal - Сигнал значения из {@link FormModel} (ручка поля, `model.$.path`)
|
|
10
|
+
* @param node - Нода формы, отвечающая за это поле
|
|
11
|
+
*
|
|
12
|
+
* @example Привязка листовых полей при построении формы
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { registerSignalNode } from '@reformer/core';
|
|
15
|
+
*
|
|
16
|
+
* const sig = model.signalAt('profile.email');
|
|
17
|
+
* const node = group.getFieldByPath('profile.email');
|
|
18
|
+
* if (sig && node) registerSignalNode(sig, node);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @see {@link getNodeForSignal} - обратный поиск ноды по сигналу
|
|
22
|
+
* @group Utilities
|
|
6
23
|
*/
|
|
7
24
|
export declare function registerSignalNode(signal: Signal<any>, node: FormNode<any>): void;
|
|
8
25
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
26
|
+
* Найти ноду формы по сигналу модели.
|
|
27
|
+
*
|
|
28
|
+
* Используется state-операциями behavior (`enableWhen`/`disableWhen`) и роутингом ошибок
|
|
29
|
+
* валидации в ноды. Возвращает `undefined`, если форма ещё не построена или поле не
|
|
30
|
+
* материализовано в этой форме (например, элемент массива, который строится per-item).
|
|
31
|
+
*
|
|
32
|
+
* @param signal - Сигнал значения из {@link FormModel}
|
|
33
|
+
* @returns Нода формы для этого поля или `undefined`
|
|
34
|
+
*
|
|
35
|
+
* @example Роутинг ошибок валидации в ноду поля
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { getNodeForSignal } from '@reformer/core';
|
|
38
|
+
*
|
|
39
|
+
* const sig = model.$.email;
|
|
40
|
+
* getNodeForSignal(sig)?.setErrors([{ code: 'required', message: 'Обязательно' }]);
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @see {@link registerSignalNode} - регистрация связи сигнал→нода
|
|
44
|
+
* @group Utilities
|
|
11
45
|
*/
|
|
12
46
|
export declare function getNodeForSignal(signal: Signal<any>): FormNode<any> | undefined;
|
|
@@ -2,12 +2,22 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
2
2
|
/**
|
|
3
3
|
* Фабрика валидатора формата email.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Проверяет по упрощённому regex `^[^\s@]+@[^\s@]+\.[^\s@]+$`. Пустые значения
|
|
6
|
+
* (`''`/`null`/`undefined`) пропускаются (используйте {@link required} для обязательности).
|
|
6
7
|
*
|
|
7
|
-
* @
|
|
8
|
+
* @param options - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
9
|
+
* @returns Чистый валидатор {@link Validator} для строкового поля
|
|
10
|
+
*
|
|
11
|
+
* @example Проверка формата email
|
|
8
12
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
*
|
|
13
|
+
* import { required, email } from '@reformer/core/validators';
|
|
14
|
+
*
|
|
15
|
+
* // Внутри FieldConfig схемы формы:
|
|
16
|
+
* email: {
|
|
17
|
+
* value: model.$.email,
|
|
18
|
+
* component: Input,
|
|
19
|
+
* validators: [required(), email({ message: 'Введите корректный email' })],
|
|
20
|
+
* },
|
|
11
21
|
* ```
|
|
12
22
|
*/
|
|
13
23
|
export declare function email<TForm = unknown, TField extends string | undefined = string>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -2,12 +2,22 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
2
2
|
/**
|
|
3
3
|
* Фабрика валидатора, проверяющего что дата не в прошлом.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Дата не должна быть раньше сегодняшнего дня (сравнение по нормализованным датам).
|
|
6
|
+
* Пустые и невалидные даты пропускаются (используйте {@link required} и {@link isDate}).
|
|
6
7
|
*
|
|
7
|
-
* @
|
|
8
|
+
* @param options - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
9
|
+
* @returns Чистый валидатор {@link Validator} для поля даты (`string | Date`)
|
|
10
|
+
*
|
|
11
|
+
* @example Дата не в прошлом
|
|
8
12
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
*
|
|
13
|
+
* import { required, futureDate } from '@reformer/core/validators';
|
|
14
|
+
*
|
|
15
|
+
* // Внутри FieldConfig схемы формы:
|
|
16
|
+
* appointmentDate: {
|
|
17
|
+
* value: model.$.appointmentDate,
|
|
18
|
+
* component: DatePicker,
|
|
19
|
+
* validators: [required(), futureDate({ message: 'Дата записи должна быть в будущем' })],
|
|
20
|
+
* },
|
|
11
21
|
* ```
|
|
12
22
|
*/
|
|
13
23
|
export declare function futureDate<TForm = unknown, TField extends string | Date | undefined = string | Date>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Reusable validator factories.
|
|
3
3
|
*
|
|
4
4
|
* Все примитивы — фабрики, возвращающие чистый Validator<TForm, TField>.
|
|
5
|
-
*
|
|
5
|
+
* Импортируются из `@reformer/core/validators` и передаются в массив `validators`
|
|
6
|
+
* поля схемы формы: `validators: [required(), min(18)]`.
|
|
6
7
|
*/
|
|
7
8
|
export { required } from './required';
|
|
8
9
|
export { min } from './min';
|
|
@@ -2,12 +2,21 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
2
2
|
/**
|
|
3
3
|
* Фабрика валидатора, проверяющего что число — целое.
|
|
4
4
|
*
|
|
5
|
-
* Пустые значения и не-числа пропускаются (используйте
|
|
5
|
+
* Пустые значения и не-числа пропускаются (используйте {@link required} и {@link isNumber}).
|
|
6
6
|
*
|
|
7
|
-
* @
|
|
7
|
+
* @param options - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
8
|
+
* @returns Чистый валидатор {@link Validator} для числового поля
|
|
9
|
+
*
|
|
10
|
+
* @example Проверка целого числа
|
|
8
11
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
*
|
|
12
|
+
* import { required, integer } from '@reformer/core/validators';
|
|
13
|
+
*
|
|
14
|
+
* // Внутри FieldConfig схемы формы:
|
|
15
|
+
* count: {
|
|
16
|
+
* value: model.$.count,
|
|
17
|
+
* component: Input,
|
|
18
|
+
* validators: [required(), integer({ message: 'Должно быть целым числом' })],
|
|
19
|
+
* },
|
|
11
20
|
* ```
|
|
12
21
|
*/
|
|
13
22
|
export declare function integer<TForm = unknown, TField extends number | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -2,12 +2,22 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
2
2
|
/**
|
|
3
3
|
* Фабрика валидатора, проверяющего что значение — валидная дата.
|
|
4
4
|
*
|
|
5
|
-
* Пустые значения
|
|
5
|
+
* Принимает `Date` или строку, парсимую в дату. Пустые значения (`''`/`null`/`undefined`)
|
|
6
|
+
* пропускаются (используйте {@link required} для обязательности).
|
|
6
7
|
*
|
|
7
|
-
* @
|
|
8
|
+
* @param options - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
9
|
+
* @returns Чистый валидатор {@link Validator} для поля даты (`string | Date`)
|
|
10
|
+
*
|
|
11
|
+
* @example Проверка валидности даты
|
|
8
12
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
*
|
|
13
|
+
* import { required, isDate } from '@reformer/core/validators';
|
|
14
|
+
*
|
|
15
|
+
* // Внутри FieldConfig схемы формы:
|
|
16
|
+
* eventDate: {
|
|
17
|
+
* value: model.$.eventDate,
|
|
18
|
+
* component: DatePicker,
|
|
19
|
+
* validators: [required(), isDate({ message: 'Введите корректную дату' })],
|
|
20
|
+
* },
|
|
11
21
|
* ```
|
|
12
22
|
*/
|
|
13
23
|
export declare function isDate<TForm = unknown, TField extends string | Date | undefined = string | Date>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -2,13 +2,23 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
2
2
|
/**
|
|
3
3
|
* Фабрика валидатора, проверяющего что значение — конечное число (не NaN, не строка).
|
|
4
4
|
*
|
|
5
|
-
* Пустые значения (`null`/`undefined`) пропускаются (используйте
|
|
6
|
-
* В отличие от других number-валидаторов, **не** пропускает не-числа
|
|
5
|
+
* Пустые значения (`null`/`undefined`) пропускаются (используйте {@link required} для
|
|
6
|
+
* обязательности). В отличие от других number-валидаторов, **не** пропускает не-числа
|
|
7
|
+
* и `NaN` — это его задача.
|
|
7
8
|
*
|
|
8
|
-
* @
|
|
9
|
+
* @param options - Опции валидатора ({@link ValidateOptions}): `message`, `params`
|
|
10
|
+
* @returns Чистый валидатор {@link Validator} для числового поля
|
|
11
|
+
*
|
|
12
|
+
* @example Проверка, что значение — число
|
|
9
13
|
* ```typescript
|
|
10
|
-
*
|
|
11
|
-
*
|
|
14
|
+
* import { required, isNumber } from '@reformer/core/validators';
|
|
15
|
+
*
|
|
16
|
+
* // Внутри FieldConfig схемы формы:
|
|
17
|
+
* amount: {
|
|
18
|
+
* value: model.$.amount,
|
|
19
|
+
* component: Input,
|
|
20
|
+
* validators: [required(), isNumber({ message: 'Введите число' })],
|
|
21
|
+
* },
|
|
12
22
|
* ```
|
|
13
23
|
*/
|
|
14
24
|
export declare function isNumber<TForm = unknown, TField extends number | undefined = number>(options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -2,12 +2,24 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
2
2
|
/**
|
|
3
3
|
* Фабрика валидатора максимального возраста (по дате рождения).
|
|
4
4
|
*
|
|
5
|
-
* Пустые и невалидные
|
|
5
|
+
* Возраст вычисляется по дате рождения относительно сегодняшнего дня. Пустые и невалидные
|
|
6
|
+
* даты пропускаются (используйте {@link required} и {@link isDate}).
|
|
6
7
|
*
|
|
7
|
-
* @
|
|
8
|
+
* @param maxAgeValue - Максимально допустимый возраст (в полных годах)
|
|
9
|
+
* @param options - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
10
|
+
* попадают `maxAge` и `currentAge`.
|
|
11
|
+
* @returns Чистый валидатор {@link Validator} для поля даты рождения (`string | Date`)
|
|
12
|
+
*
|
|
13
|
+
* @example Максимальный возраст
|
|
8
14
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
*
|
|
15
|
+
* import { maxAge } from '@reformer/core/validators';
|
|
16
|
+
*
|
|
17
|
+
* // Внутри FieldConfig схемы формы:
|
|
18
|
+
* birthDate: {
|
|
19
|
+
* value: model.$.birthDate,
|
|
20
|
+
* component: DatePicker,
|
|
21
|
+
* validators: [maxAge(100, { message: 'Проверьте дату рождения' })],
|
|
22
|
+
* },
|
|
11
23
|
* ```
|
|
12
24
|
*/
|
|
13
25
|
export declare function maxAge<TForm = unknown, TField extends string | Date | null | undefined = string | Date>(maxAgeValue: number, options?: ValidateOptions): Validator<TForm, TField>;
|
|
@@ -2,12 +2,24 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
|
|
|
2
2
|
/**
|
|
3
3
|
* Фабрика валидатора максимальной даты (включительно).
|
|
4
4
|
*
|
|
5
|
-
* Пустые и невалидные даты
|
|
5
|
+
* Сравнение по нормализованным датам (время обнуляется). Пустые и невалидные даты
|
|
6
|
+
* пропускаются (используйте {@link required} и {@link isDate}).
|
|
6
7
|
*
|
|
7
|
-
* @
|
|
8
|
+
* @param maxDateValue - Максимально допустимая дата (включительно)
|
|
9
|
+
* @param options - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
10
|
+
* попадает `maxDate`.
|
|
11
|
+
* @returns Чистый валидатор {@link Validator} для поля даты (`string | Date`)
|
|
12
|
+
*
|
|
13
|
+
* @example Максимальная дата
|
|
8
14
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
*
|
|
15
|
+
* import { maxDate } from '@reformer/core/validators';
|
|
16
|
+
*
|
|
17
|
+
* // Внутри FieldConfig схемы формы:
|
|
18
|
+
* birthDate: {
|
|
19
|
+
* value: model.$.birthDate,
|
|
20
|
+
* component: DatePicker,
|
|
21
|
+
* validators: [maxDate(new Date(), { message: 'Дата не может быть в будущем' })],
|
|
22
|
+
* },
|
|
11
23
|
* ```
|
|
12
24
|
*/
|
|
13
25
|
export declare function maxDate<TForm = unknown, TField extends string | Date | null | undefined = string | Date>(maxDateValue: Date, options?: ValidateOptions): Validator<TForm, TField>;
|