@reformer/core 11.0.0-beta.7 → 11.0.0-beta.8
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/README.md +7 -3
- package/dist/behaviors.js +211 -174
- package/dist/create-model-BBRHhHwK.js +30 -0
- package/dist/form/behaviors/collections.d.ts +14 -1
- package/dist/form/behaviors/context.d.ts +18 -2
- package/dist/form/behaviors/index.d.ts +3 -1
- package/dist/form/behaviors/internals.d.ts +0 -1
- package/dist/form/behaviors/schema-operators.d.ts +76 -0
- package/dist/form/behaviors/types.d.ts +15 -3
- package/dist/form/create-core-form.d.ts +5 -3
- package/dist/form/create-form.d.ts +28 -53
- package/dist/form/form-bundle.d.ts +136 -0
- package/dist/form/index.d.ts +6 -1
- package/dist/form/nodes/field-node.d.ts +2 -2
- package/dist/form/nodes/group-node.d.ts +1 -1
- package/dist/form/nodes/model-array-node.d.ts +10 -5
- package/dist/form/schema-controller.d.ts +137 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +22 -16
- package/dist/form/types/deep-schema.d.ts +2 -2
- package/dist/form/types/form-proxy.d.ts +1 -1
- package/dist/form/types/schema-node.d.ts +37 -12
- package/dist/form/validation/context.d.ts +6 -1
- package/dist/form/validation/index.d.ts +1 -1
- package/dist/form/validation/operators.d.ts +70 -8
- package/dist/form/validation/types.d.ts +2 -1
- package/dist/index.js +345 -254
- package/dist/model/index.d.ts +5 -0
- package/dist/model/model-nodes.d.ts +67 -4
- package/dist/model/model-signals-proxy.d.ts +8 -1
- package/dist/model/model-value-proxy.d.ts +76 -0
- package/dist/model/types.d.ts +7 -4
- package/dist/model-value-proxy-caTmAiUd.js +419 -0
- package/dist/model.js +19 -17
- package/dist/node-BeV_lV5i.js +100 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/platforms/react/index.d.ts +1 -0
- package/dist/strategy-DgzELWno.js +204 -0
- package/dist/validation.js +7 -6
- package/docs/llms/03-api-signatures.md +4 -4
- package/docs/llms/09-formschema.md +5 -5
- package/docs/llms/10-arrays.md +23 -4
- package/docs/llms/16-ui-components.md +2 -2
- package/docs/llms/18-conditional-fields.md +1 -1
- package/docs/llms/21-array-operations.md +26 -0
- package/docs/llms/28-submit-and-reset.md +3 -3
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/33-validation-strategy.md +2 -2
- package/docs/llms/35-unified-contract.md +246 -0
- package/llms-index.json +1 -1
- package/llms.txt +1148 -122
- package/package.json +1 -1
- package/dist/create-model-B8U-COv-.js +0 -103
- package/dist/model-signals-proxy-2vqS243J.js +0 -250
- package/dist/node-DGEFMTQu.js +0 -18
- package/dist/strategy-BXS_gBfz.js +0 -170
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { SchemaNodeControl, SchemaScope } from '../schema-controller';
|
|
2
|
+
/**
|
|
3
|
+
* Скрывать узел, пока условие истинно.
|
|
4
|
+
*
|
|
5
|
+
* Условие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.
|
|
6
|
+
* Принудительное `schema.node(selector).setHidden(...)` главнее условия.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* hideWhen(schema.node('mortgage'), () => model.loanType !== 'mortgage');
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function hideWhen(node: SchemaNodeControl, condition: () => boolean): void;
|
|
14
|
+
/**
|
|
15
|
+
* Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).
|
|
16
|
+
*
|
|
17
|
+
* Обработчик получает те же аргументы, что и исходный проп компонента, и перекрывает одноимённый
|
|
18
|
+
* проп из `componentProps`.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* onComponentEvent(schema.node('wizard'), 'onSubmit', async () => {
|
|
23
|
+
* await submitApplication(model.get());
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function onComponentEvent(node: SchemaNodeControl, event: string, handler: (...args: any[]) => any): void;
|
|
28
|
+
/**
|
|
29
|
+
* Реактивный эффект, живущий, пока схема области смонтирована рендерером.
|
|
30
|
+
*
|
|
31
|
+
* В отличие от `effect` поведения, запускается после монтирования — внутри доступны ref узлов
|
|
32
|
+
* (`schema.node(selector).getRef().current`). Может вернуть функцию очистки.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const wizard = schema.node('wizard').getRef<FormWizardHandle>();
|
|
37
|
+
* renderEffect(schema, () => {
|
|
38
|
+
* if (model.loanType === 'mortgage') wizard.current?.goToStep(1);
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function renderEffect(schema: SchemaScope, effectFn: () => void | (() => void)): void;
|
|
43
|
+
/**
|
|
44
|
+
* Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла. Единственный
|
|
45
|
+
* хук, способный повлиять на первый рендер, — внутри можно править пропсы через
|
|
46
|
+
* `schema.node(selector).patchProps({ … })`.
|
|
47
|
+
*
|
|
48
|
+
* К монтированию узла не привязан; аргумент `node` принимается ради симметрии с
|
|
49
|
+
* {@link onMount} / {@link onUnmount}.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* onInit(schema.node('summary'), () => {
|
|
54
|
+
* schema.node('summary').patchProps({ currency: detectCurrency() });
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function onInit(_node: SchemaNodeControl, fn: () => void): void;
|
|
59
|
+
/**
|
|
60
|
+
* Хук после первого монтирования узла. Может вернуть очистку — она выполнится при размонтировании.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* onMount(schema.node('data-boundary'), () => void loadApplication());
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function onMount(node: SchemaNodeControl, fn: () => void | (() => void)): void;
|
|
68
|
+
/**
|
|
69
|
+
* Хук перед размонтированием узла.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* onUnmount(schema.node('wizard'), () => saveDraft(model.get()));
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function onUnmount(node: SchemaNodeControl, fn: () => void): void;
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
|
2
2
|
import { BehaviorCleanup, FormModel, FormProxy } from '../../index';
|
|
3
|
-
|
|
3
|
+
import { SchemaController, SchemaScope } from '../schema-controller';
|
|
4
|
+
/**
|
|
5
|
+
* Область схемы поведения: модель (значения и сигналы), форма (ноды) и схема (узлы разметки).
|
|
6
|
+
*
|
|
7
|
+
* `schema` адресует узлы СВОЕЙ области: в корневом поведении — корневое дерево, в поведении
|
|
8
|
+
* подформы (`apply`) и строки массива (`applyEach`) — поддерево этой части. Правила узлов
|
|
9
|
+
* (`hideWhen`, `onComponentEvent`, `onMount`) исполняет рендерер; без него они ничего не делают.
|
|
10
|
+
*/
|
|
4
11
|
export type BehaviorScope<T> = {
|
|
5
12
|
model: FormModel<T>;
|
|
6
13
|
form: FormProxy<T>;
|
|
14
|
+
schema: SchemaScope;
|
|
7
15
|
};
|
|
8
16
|
/** Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`. */
|
|
9
17
|
export interface FormBehavior<T> {
|
|
10
|
-
/**
|
|
11
|
-
|
|
18
|
+
/**
|
|
19
|
+
* @internal Запускается сборкой формы после построения нод и заполнения реестра сигнал→нода.
|
|
20
|
+
* `controller` — схема-контроллер сборки; без него поведение получает собственный, который
|
|
21
|
+
* никто не читает (модель без рендерера).
|
|
22
|
+
*/
|
|
23
|
+
__run(model: FormModel<T>, form: FormProxy<T>, controller?: SchemaController): BehaviorCleanup;
|
|
12
24
|
}
|
|
13
25
|
export type { Signal, ReadonlySignal, BehaviorCleanup };
|
|
14
26
|
/** Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка. */
|
|
@@ -27,7 +27,7 @@ export interface CreateFormConfigBase<T, B> {
|
|
|
27
27
|
seed?: (model: FormModel<T>) => void;
|
|
28
28
|
/**
|
|
29
29
|
* Донастройка ПОСЛЕ сборки. Единственное место для правок, которые обязаны идти следом за
|
|
30
|
-
*
|
|
30
|
+
* сборкой формы: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),
|
|
31
31
|
* поэтому такой префилл выполняется здесь.
|
|
32
32
|
*/
|
|
33
33
|
setup?: (bundle: B) => void;
|
|
@@ -41,14 +41,16 @@ export interface CoreForm<T> {
|
|
|
41
41
|
/** Конфиг {@link createCoreForm}. */
|
|
42
42
|
export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {
|
|
43
43
|
/**
|
|
44
|
-
* Билдер схемы формы. Именно функция, а не готовое дерево:
|
|
45
|
-
*
|
|
44
|
+
* Билдер схемы формы. Именно функция, а не готовое дерево: узлы схемы держат ручки модели
|
|
45
|
+
* (`model: model.$.email`), поэтому построить дерево до модели нечем.
|
|
46
46
|
*/
|
|
47
47
|
schema?: (model: FormModel<T>) => FormSchemaNode;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Собрать модель, форму и валидацию за один проход.
|
|
51
51
|
*
|
|
52
|
+
* @deprecated Пишите `createForm` — та же сборка, в бандле вдобавок дерево для рендерера.
|
|
53
|
+
*
|
|
52
54
|
* @typeParam T - Форма данных модели.
|
|
53
55
|
* @param config - {@link CreateCoreFormConfig}: (`initial` | `model`) + опц. `schema`, `behavior`,
|
|
54
56
|
* `validation`, `seed`, `setup`.
|
|
@@ -2,6 +2,7 @@ import { FormProxy, GroupNodeConfig, FormSchema } from './types/index';
|
|
|
2
2
|
import { FormSchemaNode } from './types/schema-node';
|
|
3
3
|
import { FormModel } from '../model/types';
|
|
4
4
|
import { FormBehavior } from './behaviors';
|
|
5
|
+
import { SchemaController } from './schema-controller';
|
|
5
6
|
/**
|
|
6
7
|
* Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
|
|
7
8
|
* конфиг полей (component/componentProps) — из единой схемы.
|
|
@@ -21,24 +22,32 @@ export interface CreateFormFromModelArgs<T> {
|
|
|
21
22
|
* заполнения реестра сигнал→нода; cleanup живёт на форме и вызывается в `form.dispose()`.
|
|
22
23
|
*/
|
|
23
24
|
behavior?: FormBehavior<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Схема-контроллер сборки: через него поведение получает `schema` своей области. Его передаёт
|
|
27
|
+
* сборка одним вызовом, чтобы правила узлов (`hideWhen`, `onComponentEvent`) дошли до рендерера.
|
|
28
|
+
* Без него поведение пишет правила в собственный контроллер, который никто не читает.
|
|
29
|
+
*/
|
|
30
|
+
schemaController?: SchemaController;
|
|
24
31
|
}
|
|
25
32
|
/**
|
|
26
33
|
* Собрать форму из {@link FormModel} и единой схемы (низкоуровневая фабрика архитектуры M1).
|
|
27
34
|
*
|
|
28
35
|
* Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
|
|
29
36
|
* ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
|
|
30
|
-
* модели, привязывает конфиг поля (component/componentProps) из схемы, материализует
|
|
31
|
-
*
|
|
32
|
-
* роутинга ошибок) и, при наличии, запускает декларативное
|
|
37
|
+
* модели, привязывает конфиг поля (component/componentProps) из схемы, материализует массивы
|
|
38
|
+
* под-форм как {@link ModelArrayNode} (на любой глубине), заполняет реестр ручка→нода (для
|
|
39
|
+
* `enableWhen`, `apply`/`applyEach` и роутинга ошибок) и, при наличии, запускает декларативное
|
|
40
|
+
* поведение (cleanup живёт на форме).
|
|
33
41
|
*
|
|
34
|
-
* Обычно вызывается
|
|
35
|
-
* нужен
|
|
42
|
+
* Обычно вызывается сборкой одним вызовом (`createForm`): она строит дерево схемы, зовёт эту
|
|
43
|
+
* фабрику и собирает валидацию. Прямой вызов нужен там, где бандл не нужен, — форма элемента
|
|
44
|
+
* массива, тесты, свой слой сборки.
|
|
36
45
|
*
|
|
37
46
|
* @typeParam T - Тип модели данных формы
|
|
38
47
|
* @param args - Модель, единая схема и (опционально) декларативное поведение {@link CreateFormFromModelArgs}
|
|
39
48
|
* @returns Типизированная форма с Proxy-доступом к полям {@link FormProxy}
|
|
40
49
|
*
|
|
41
|
-
* @example Форма из модели + схемы (эквивалент `
|
|
50
|
+
* @example Форма из модели + схемы (эквивалент `createFormFromModel({ model, schema })`)
|
|
42
51
|
* ```typescript
|
|
43
52
|
* import { createModel, createFormFromModel } from '@reformer/core';
|
|
44
53
|
*
|
|
@@ -65,69 +74,35 @@ export interface CreateFormFromModelArgs<T> {
|
|
|
65
74
|
* console.log(model.email); // 'user@mail.com'
|
|
66
75
|
* ```
|
|
67
76
|
*
|
|
68
|
-
* @see
|
|
77
|
+
* @see `createForm` — сборка одним вызовом: модель, форма, валидация и дерево для рендера
|
|
69
78
|
* @group Utilities
|
|
70
79
|
*/
|
|
71
80
|
export declare function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
|
|
72
81
|
/**
|
|
73
|
-
* Создать форму из
|
|
74
|
-
*
|
|
75
|
-
* @group Utilities
|
|
82
|
+
* Создать форму из готового конфига группы.
|
|
76
83
|
*
|
|
77
|
-
* @param
|
|
84
|
+
* @param config - Конфиг {@link GroupNode}
|
|
78
85
|
* @returns Типизированная форма с Proxy-доступом к полям
|
|
79
|
-
*
|
|
80
|
-
* @example Архитектура M1: `createModel` + layout-схема + `createForm({ model, schema })`
|
|
81
|
-
* ```typescript
|
|
82
|
-
* import { createModel, createForm } from '@reformer/core';
|
|
83
|
-
* import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
84
|
-
* import { required, email, minLength } from '@reformer/core/validators';
|
|
85
|
-
*
|
|
86
|
-
* interface UserForm {
|
|
87
|
-
* email: string;
|
|
88
|
-
* password: string;
|
|
89
|
-
* }
|
|
90
|
-
*
|
|
91
|
-
* const model = createModel<UserForm>({ email: '', password: '' });
|
|
92
|
-
*
|
|
93
|
-
* // Layout-схема НЕ несёт validators — только component/componentProps.
|
|
94
|
-
* const schema = {
|
|
95
|
-
* children: [
|
|
96
|
-
* { value: model.$.email, component: Input },
|
|
97
|
-
* { value: model.$.password, component: Input },
|
|
98
|
-
* ],
|
|
99
|
-
* };
|
|
100
|
-
*
|
|
101
|
-
* const form = createForm<UserForm>({ model, schema });
|
|
102
|
-
* form.email.setValue('test@mail.com');
|
|
103
|
-
*
|
|
104
|
-
* // Валидация — ОТДЕЛЬНАЯ схема, прогон внешним раннером (ошибки сами доезжают до нод):
|
|
105
|
-
* const validation = defineValidationSchema<UserForm>(({ model }) => {
|
|
106
|
-
* validate(model.$.email, [required(), email()]);
|
|
107
|
-
* validate(model.$.password, [required(), minLength(8)]);
|
|
108
|
-
* });
|
|
109
|
-
* const ok = await validateModel(model, validation);
|
|
110
|
-
* ```
|
|
86
|
+
* @group Utilities
|
|
111
87
|
*/
|
|
112
|
-
export declare function
|
|
113
|
-
export declare function createForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
|
|
88
|
+
export declare function createLegacyForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
|
|
114
89
|
/**
|
|
115
|
-
* Создать форму
|
|
90
|
+
* Создать форму из плоской схемы полей с инлайн-значениями (`value: ''`) — путь ДО архитектуры M1.
|
|
116
91
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* которую прогоняет `validateModel`; поля `validators` у узла layout-схемы нет).
|
|
92
|
+
* Значения живут в нодах, модели нет: поведение (`defineFormBehavior`) и внешняя валидация
|
|
93
|
+
* (`validateModel`) к такой форме не подключаются. Для нового кода — {@link createFormFromModel}
|
|
94
|
+
* либо сборка одним вызовом.
|
|
121
95
|
*
|
|
122
96
|
* @param schema - Схема полей формы
|
|
123
97
|
* @returns Типизированная форма с Proxy-доступом к полям
|
|
98
|
+
* @group Utilities
|
|
124
99
|
*
|
|
125
|
-
* @example
|
|
100
|
+
* @example
|
|
126
101
|
* ```typescript
|
|
127
|
-
* const form =
|
|
102
|
+
* const form = createLegacyForm<UserForm>({
|
|
128
103
|
* email: { value: '', component: Input },
|
|
129
104
|
* password: { value: '', component: Input },
|
|
130
105
|
* });
|
|
131
106
|
* ```
|
|
132
107
|
*/
|
|
133
|
-
export declare function
|
|
108
|
+
export declare function createLegacyForm<T>(schema: FormSchema<T>): FormProxy<T>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { FormModel } from '../model/types';
|
|
2
|
+
import { FormBehavior } from './behaviors';
|
|
3
|
+
import { SchemaController, SchemaNodeControl } from './schema-controller';
|
|
4
|
+
import { FormSchemaNode } from './types/schema-node';
|
|
5
|
+
import { FormProxy } from './types/index';
|
|
6
|
+
import { FormValidation, FormValidationBundle } from './validation/config';
|
|
7
|
+
import { ValidationSchema } from './validation';
|
|
8
|
+
/**
|
|
9
|
+
* Дерево, собранное реестром из документа схемы (JSON), и то, что реестр хочет донести до
|
|
10
|
+
* рендерера вместе с ним.
|
|
11
|
+
*
|
|
12
|
+
* @group Utilities
|
|
13
|
+
*/
|
|
14
|
+
export interface ResolvedSchema {
|
|
15
|
+
/** Корневой узел дерева. */
|
|
16
|
+
tree: FormSchemaNode;
|
|
17
|
+
/** Обёртка поля — компонент, которым рендерер оборачивает каждое поле. */
|
|
18
|
+
fieldWrapper?: unknown;
|
|
19
|
+
/** Граница ошибок — компонент, которым рендерер оборачивает дерево. */
|
|
20
|
+
errorBoundary?: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Реестр, умеющий превратить документ схемы в дерево. Метод необязателен: у интерфейса реестра
|
|
24
|
+
* есть сторонние реализации, и ядро документ само не разбирает.
|
|
25
|
+
*
|
|
26
|
+
* @group Utilities
|
|
27
|
+
*/
|
|
28
|
+
export interface SchemaResolver {
|
|
29
|
+
resolveSchema?(document: unknown, model: FormModel<never>): ResolvedSchema;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Часть бандла для рендерера: готовое дерево и схема-контроллер сборки.
|
|
33
|
+
*
|
|
34
|
+
* @group Utilities
|
|
35
|
+
*/
|
|
36
|
+
export interface FormRender {
|
|
37
|
+
/** Корневой узел дерева схемы; `undefined`, если схема не задана. */
|
|
38
|
+
readonly tree: FormSchemaNode | undefined;
|
|
39
|
+
/** Схема-контроллер сборки: области корня, строк массивов и подформ. */
|
|
40
|
+
readonly controller: SchemaController;
|
|
41
|
+
/** Управление узлом КОРНЕВОЙ области по его `selector`. */
|
|
42
|
+
node(selector: string): SchemaNodeControl;
|
|
43
|
+
/** Обёртка поля из реестра (JSON-вариант). */
|
|
44
|
+
readonly fieldWrapper?: unknown;
|
|
45
|
+
/** Граница ошибок из реестра (JSON-вариант). */
|
|
46
|
+
readonly errorBoundary?: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Результат {@link createForm}.
|
|
50
|
+
*
|
|
51
|
+
* @typeParam T - Форма данных модели.
|
|
52
|
+
* @group Utilities
|
|
53
|
+
*/
|
|
54
|
+
export interface FormBundle<T> {
|
|
55
|
+
model: FormModel<T>;
|
|
56
|
+
form: FormProxy<T>;
|
|
57
|
+
/** Собранная валидация; есть, если в конфиге заданы правила. */
|
|
58
|
+
validation?: FormValidationBundle<T>;
|
|
59
|
+
render: FormRender;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Конфиг {@link createForm}.
|
|
63
|
+
*
|
|
64
|
+
* @typeParam T - Форма данных модели.
|
|
65
|
+
* @group Utilities
|
|
66
|
+
*/
|
|
67
|
+
export interface CreateFormConfig<T> {
|
|
68
|
+
/** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
|
|
69
|
+
initial?: T;
|
|
70
|
+
/** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
|
|
71
|
+
model?: FormModel<T>;
|
|
72
|
+
/**
|
|
73
|
+
* Схема формы: билдер дерева `(model) => узел` либо документ (JSON) — тогда нужен `registry`.
|
|
74
|
+
* Билдер, а не готовое дерево: узлы держат ручки модели (`model: model.$.email`), и построить
|
|
75
|
+
* дерево до модели нечем.
|
|
76
|
+
*/
|
|
77
|
+
schema?: ((model: FormModel<T>) => FormSchemaNode) | object;
|
|
78
|
+
/** Реестр JSON-варианта: превращает документ схемы в дерево. */
|
|
79
|
+
registry?: SchemaResolver;
|
|
80
|
+
/** Поведение формы: значения, состояние нод и правила узлов схемы. */
|
|
81
|
+
behavior?: FormBehavior<T>;
|
|
82
|
+
/** Правила валидации: готовая схема либо {@link FormValidation} со стратегией и шагами. */
|
|
83
|
+
validation?: FormValidation<T> | ValidationSchema<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
|
|
86
|
+
* построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
|
|
87
|
+
* инициализации не срабатывают).
|
|
88
|
+
*/
|
|
89
|
+
seed?: (model: FormModel<T>) => void;
|
|
90
|
+
/** Донастройка ПОСЛЕ сборки: получает готовый бандл. */
|
|
91
|
+
setup?: (bundle: FormBundle<T>) => void;
|
|
92
|
+
}
|
|
93
|
+
/** Бандл формы, у которой заданы правила: `validation` в нём есть всегда. */
|
|
94
|
+
type ValidatedFormBundle<T> = FormBundle<T> & {
|
|
95
|
+
validation: FormValidationBundle<T>;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Собрать модель, форму, валидацию и дерево для рендера за один проход.
|
|
99
|
+
*
|
|
100
|
+
* @typeParam T - Форма данных модели.
|
|
101
|
+
* @param config - {@link CreateFormConfig}: (`initial` | `model`) + опц. `schema`, `registry`,
|
|
102
|
+
* `behavior`, `validation`, `seed`, `setup`.
|
|
103
|
+
* @returns {@link FormBundle} — `{ model, form, validation?, render }`.
|
|
104
|
+
*
|
|
105
|
+
* @example Рендерер по TS-схеме
|
|
106
|
+
* ```tsx
|
|
107
|
+
* const credit = useFormBundle(() =>
|
|
108
|
+
* createForm<CreditForm>({
|
|
109
|
+
* model: createCreditModel(),
|
|
110
|
+
* schema: creditSchema, // (model) => узел
|
|
111
|
+
* behavior: creditBehavior,
|
|
112
|
+
* validation: { steps: { loan: loanRules }, extras: crossRules },
|
|
113
|
+
* })
|
|
114
|
+
* );
|
|
115
|
+
* return <FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />;
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* @example React-руками — та же сборка, разметка в JSX
|
|
119
|
+
* ```tsx
|
|
120
|
+
* const credit = useFormBundle(() => createForm<CreditForm>({ initial, schema: creditSchema }));
|
|
121
|
+
* return <FormField control={credit.form.loanType} />;
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* @example JSON — документ схемы и реестр
|
|
125
|
+
* ```tsx
|
|
126
|
+
* const credit = useFormBundle(() =>
|
|
127
|
+
* createForm<CreditForm>({ model: createCreditModel(), schema: creditJson, registry })
|
|
128
|
+
* );
|
|
129
|
+
* return <FormRenderer form={credit} />;
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export declare function createForm<T extends object>(config: CreateFormConfig<T> & {
|
|
133
|
+
validation: FormValidation<T> | ValidationSchema<T>;
|
|
134
|
+
}): ValidatedFormBundle<T>;
|
|
135
|
+
export declare function createForm<T extends object>(config: CreateFormConfig<T>): FormBundle<T>;
|
|
136
|
+
export {};
|
package/dist/form/index.d.ts
CHANGED
|
@@ -18,8 +18,13 @@ export { GroupNode } from './nodes/group-node';
|
|
|
18
18
|
export { ArrayNode } from './nodes/array-node';
|
|
19
19
|
export { ModelArrayNode } from './nodes/model-array-node';
|
|
20
20
|
export type { ModelArrayControl } from './nodes/model-array-node';
|
|
21
|
-
export { createForm
|
|
21
|
+
export { createForm } from './form-bundle';
|
|
22
|
+
export type { CreateFormConfig, FormBundle, FormRender, ResolvedSchema, SchemaResolver, } from './form-bundle';
|
|
23
|
+
export { createFormFromModel, createLegacyForm } from './create-form';
|
|
22
24
|
export type { CreateFormFromModelArgs } from './create-form';
|
|
25
|
+
export { schemaSubtree } from './schema-subtree';
|
|
26
|
+
export { createSchemaController, createSchemaScope, createSchemaOverrideMaps, unknownSchemaSelectors, } from './schema-controller';
|
|
27
|
+
export type { SchemaController, SchemaScope, SchemaNodeControl, SchemaOverrideMaps, NodeLifecycleHooks, } from './schema-controller';
|
|
23
28
|
export { createCoreForm } from './create-core-form';
|
|
24
29
|
export type { CoreForm, CreateCoreFormConfig, CreateFormConfigBase } from './create-core-form';
|
|
25
30
|
export { buildValidation } from './validation/config';
|
|
@@ -196,10 +196,10 @@ export declare class FieldNode<T> extends FormNode<T> {
|
|
|
196
196
|
*
|
|
197
197
|
* @example
|
|
198
198
|
* Пример node-level: плоская схема с инлайн-значениями (back-compat путь до M1). В layout-схеме
|
|
199
|
-
* `
|
|
199
|
+
* `createFormFromModel({ model, schema })` поля `validators` нет — правила живут в `defineValidationSchema`.
|
|
200
200
|
* ```typescript
|
|
201
201
|
* // Сценарий 1: Instant feedback после submit
|
|
202
|
-
* const form =
|
|
202
|
+
* const form = createLegacyForm({
|
|
203
203
|
* email: {
|
|
204
204
|
* value: '',
|
|
205
205
|
* component: Input,
|
|
@@ -7,7 +7,7 @@ import { SubmitOptions, SubmitResult } from '../form-submitter';
|
|
|
7
7
|
* GroupNode - узел для группы полей
|
|
8
8
|
*
|
|
9
9
|
* Создаётся из {@link FormSchema} (дерево field-конфигов). Обычно строится через `createForm`
|
|
10
|
-
* (M1: `
|
|
10
|
+
* (M1: `createFormFromModel({ model, schema })`); schema-валидация/behavior живут на слое модели
|
|
11
11
|
* (`validateModel` из `@reformer/core/validation`, `computeFrom`/`enableWhen`/…), а не на ноде.
|
|
12
12
|
*
|
|
13
13
|
* @group Nodes
|
|
@@ -7,8 +7,8 @@ import { FormProxy } from '../types/form-proxy';
|
|
|
7
7
|
export interface ModelArrayControl<TItem extends object> {
|
|
8
8
|
readonly length: number;
|
|
9
9
|
at(index: number): FormModel<TItem> | undefined;
|
|
10
|
-
push(item
|
|
11
|
-
insertAt(index: number, item
|
|
10
|
+
push(item?: TItem): void;
|
|
11
|
+
insertAt(index: number, item?: TItem): void;
|
|
12
12
|
removeAt(index: number): void;
|
|
13
13
|
move(from: number, to: number): void;
|
|
14
14
|
swap(a: number, b: number): void;
|
|
@@ -25,7 +25,7 @@ export interface ModelArrayControl<TItem extends object> {
|
|
|
25
25
|
* reorder/повторном рендере не пересоздаются (состояние и валидация сохраняются). Реализует тот же
|
|
26
26
|
* контракт, что ждут секции массива и `useFormControl` (`length`/`value`/`valid`/`errors`/`at`/`push`/…).
|
|
27
27
|
*
|
|
28
|
-
* Обычно создаётся не напрямую, а `
|
|
28
|
+
* Обычно создаётся не напрямую, а `createFormFromModel({ model, schema })`: когда в схеме встречается узел
|
|
29
29
|
* массива `{ array: model.<field>, item: (item) => itemSchema }`, форма материализует его как
|
|
30
30
|
* `ModelArrayNode` и кладёт под `form.<field>` (совместим с `FormArraySection`).
|
|
31
31
|
*
|
|
@@ -40,7 +40,7 @@ export interface ModelArrayControl<TItem extends object> {
|
|
|
40
40
|
* qty: { value: item.$.qty, component: Input },
|
|
41
41
|
* });
|
|
42
42
|
*
|
|
43
|
-
* const form =
|
|
43
|
+
* const form = createFormFromModel({
|
|
44
44
|
* model,
|
|
45
45
|
* schema: {
|
|
46
46
|
* children: [{ array: model.rows, item: rowItem }],
|
|
@@ -79,13 +79,18 @@ export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
79
79
|
/**
|
|
80
80
|
* @param control Реактивный массив модели (`model.<path>`).
|
|
81
81
|
* @param buildItem Строитель формы элемента по его под-модели (инъекция, чтобы избежать цикла
|
|
82
|
-
* импорта с `create-form`). Обычно `(item) =>
|
|
82
|
+
* импорта с `create-form`). Обычно `(item) => createFormFromModel({ model: item, schema: itemSchema(item) })`.
|
|
83
83
|
*/
|
|
84
84
|
constructor(control: ModelArrayControl<T>, buildItem: (item: FormModel<T>) => FormProxy<T>);
|
|
85
85
|
at(index: number): FormProxy<T> | undefined;
|
|
86
86
|
map<R>(fn: (item: FormProxy<T>, index: number) => R): R[];
|
|
87
87
|
forEach(fn: (item: FormProxy<T>, index: number) => void): void;
|
|
88
|
+
/**
|
|
89
|
+
* Добавить элемент. Без значения — элемент по шаблону массива модели (`arrayOf(blank)` либо
|
|
90
|
+
* `initialValue` узла-массива схемы); шаблона нет — ошибка модели.
|
|
91
|
+
*/
|
|
88
92
|
push(item?: Partial<T>): void;
|
|
93
|
+
/** Вставить элемент по индексу; без значения — по шаблону, как у {@link push}. */
|
|
89
94
|
insert(index: number, item?: Partial<T>): void;
|
|
90
95
|
removeAt(index: number): void;
|
|
91
96
|
/**
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Signal } from '@preact/signals-core';
|
|
2
|
+
/**
|
|
3
|
+
* Хуки жизненного цикла узла схемы. Каждый хук опционален; повторная регистрация перезаписывает
|
|
4
|
+
* предыдущее значение.
|
|
5
|
+
*
|
|
6
|
+
* @group Schema
|
|
7
|
+
*/
|
|
8
|
+
export interface NodeLifecycleHooks {
|
|
9
|
+
/** Срабатывает один раз при mount узла. Может вернуть cleanup-функцию. */
|
|
10
|
+
onMount?: () => void | (() => void);
|
|
11
|
+
/** Срабатывает один раз при unmount узла. */
|
|
12
|
+
onUnmount?: () => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Хранилище переопределений одной области схемы. Ключ каждой карты — `selector` узла.
|
|
16
|
+
*
|
|
17
|
+
* @group Schema
|
|
18
|
+
*/
|
|
19
|
+
export interface SchemaOverrideMaps {
|
|
20
|
+
hiddenOverrides: Map<string, boolean | null>;
|
|
21
|
+
propsOverrides: Map<string, Record<string, unknown> | null>;
|
|
22
|
+
refRegistry: Map<string, {
|
|
23
|
+
current: any;
|
|
24
|
+
}>;
|
|
25
|
+
/** Условия скрытия узлов: selector → реактивное условие. */
|
|
26
|
+
conditionRegistry: Map<string, () => boolean>;
|
|
27
|
+
/** Реактивные эффекты, живущие, пока область смонтирована (`renderEffect`). */
|
|
28
|
+
effectRegistry: Array<() => void | (() => void)>;
|
|
29
|
+
/** Обработчики событий компонентов: selector → { имя пропа → обработчик }. */
|
|
30
|
+
callbackRegistry: Map<string, Map<string, (...args: any[]) => any>>;
|
|
31
|
+
/** Хуки жизненного цикла узла: selector → { onMount, onUnmount }. */
|
|
32
|
+
lifecycleRegistry: Map<string, NodeLifecycleHooks>;
|
|
33
|
+
/** Общий счётчик изменений области. Узлы на него не подписываются — см. `versionFor`. */
|
|
34
|
+
version: Signal<number>;
|
|
35
|
+
/**
|
|
36
|
+
* Версия-сигнал одного селектора (создаётся лениво). Узел подписывается только на сигнал СВОЕГО
|
|
37
|
+
* селектора, поэтому `setHidden` / `patchProps` уведомляют один узел, а не всё дерево.
|
|
38
|
+
*/
|
|
39
|
+
versionFor(selector: string): Signal<number>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Управление одним узлом схемы. Получается через `schema.node(selector)`.
|
|
43
|
+
*
|
|
44
|
+
* @group Schema
|
|
45
|
+
*/
|
|
46
|
+
export interface SchemaNodeControl {
|
|
47
|
+
/** Принудительно скрыть или показать узел, минуя условие `hideWhen`. */
|
|
48
|
+
setHidden(value: boolean): this;
|
|
49
|
+
/** Убрать принудительное значение — снова действует условие `hideWhen`. */
|
|
50
|
+
resetHidden(): this;
|
|
51
|
+
/** Подмешать объект в `componentProps` узла. */
|
|
52
|
+
patchProps(partial: Record<string, unknown>): this;
|
|
53
|
+
/** Убрать подмешанные пропсы. */
|
|
54
|
+
resetProps(): this;
|
|
55
|
+
/**
|
|
56
|
+
* Ref на компонент узла. Создаётся один раз на селектор; рендерер вешает его на компонент.
|
|
57
|
+
* Компонент обязан принимать `ref`.
|
|
58
|
+
*/
|
|
59
|
+
getRef<H>(): {
|
|
60
|
+
current: H | null;
|
|
61
|
+
};
|
|
62
|
+
/** @internal Селектор узла. */
|
|
63
|
+
__selector: string;
|
|
64
|
+
/** @internal Хранилище области. */
|
|
65
|
+
__overrideMaps: SchemaOverrideMaps;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Схема одной области: адресует узлы по `selector`.
|
|
69
|
+
*
|
|
70
|
+
* @group Schema
|
|
71
|
+
*/
|
|
72
|
+
export interface SchemaScope {
|
|
73
|
+
/** Управление узлом области по его `selector`. */
|
|
74
|
+
node(selector: string): SchemaNodeControl;
|
|
75
|
+
/** @internal Хранилище области — его читает рендерер. */
|
|
76
|
+
__overrideMaps: SchemaOverrideMaps;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Схема-контроллер сборки: области по под-моделям.
|
|
80
|
+
*
|
|
81
|
+
* @group Schema
|
|
82
|
+
*/
|
|
83
|
+
export interface SchemaController {
|
|
84
|
+
/**
|
|
85
|
+
* Область под-модели: корня сборки, строки массива или группы подформы. Создаётся при первом
|
|
86
|
+
* обращении; повторное обращение отдаёт ту же область.
|
|
87
|
+
*/
|
|
88
|
+
scopeOf(subModel: object): SchemaScope;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Пустое хранилище переопределений области.
|
|
92
|
+
*
|
|
93
|
+
* @group Schema
|
|
94
|
+
*/
|
|
95
|
+
export declare function createSchemaOverrideMaps(): SchemaOverrideMaps;
|
|
96
|
+
/**
|
|
97
|
+
* Схема области поверх хранилища: `node(selector)` → управление узлом.
|
|
98
|
+
*
|
|
99
|
+
* @param maps - Хранилище области; по умолчанию создаётся новое.
|
|
100
|
+
* @group Schema
|
|
101
|
+
*/
|
|
102
|
+
export declare function createSchemaScope(maps?: SchemaOverrideMaps): SchemaScope;
|
|
103
|
+
/**
|
|
104
|
+
* Схема-контроллер одной сборки формы.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const controller = createSchemaController();
|
|
109
|
+
* const root = controller.scopeOf(model);
|
|
110
|
+
* root.node('mortgage').setHidden(true);
|
|
111
|
+
*
|
|
112
|
+
* controller.scopeOf(model.items.at(0)); // область строки — свои селекторы
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @group Schema
|
|
116
|
+
*/
|
|
117
|
+
export declare function createSchemaController(): SchemaController;
|
|
118
|
+
/**
|
|
119
|
+
* Селекторы, на которые в области записаны правила, но которых нет в её дереве. Такое правило
|
|
120
|
+
* ничего не делает — сборка и рендерер в dev предупреждают о нём.
|
|
121
|
+
*
|
|
122
|
+
* Ref в проверку не входит: его разрешено брать по абсолютному пути модели
|
|
123
|
+
* (`phones.0.number`), а такой строки при сборке может ещё не быть.
|
|
124
|
+
*
|
|
125
|
+
* @param maps - Хранилище области.
|
|
126
|
+
* @param tree - Дерево (поддерево) области.
|
|
127
|
+
* @returns Селекторы с правилами, не найденные в дереве.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* const scope = controller.scopeOf(model);
|
|
132
|
+
* unknownSchemaSelectors(scope.__overrideMaps, tree); // ['mortgage'] — узла с таким selector нет
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @group Schema
|
|
136
|
+
*/
|
|
137
|
+
export declare function unknownSchemaSelectors(maps: SchemaOverrideMaps, tree: unknown): string[];
|