@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,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Поддерево схемы для под-модели: содержимое строки массива (`item`) или подформы (`part`).
|
|
3
|
+
*
|
|
4
|
+
* Билдер поддерева зовут два места — сборка формы и рендерер. Оба обязаны получить ОДНО дерево:
|
|
5
|
+
* на объектах его узлов держится идентичность (ключи рендера, записи схемы-контроллера), а сам
|
|
6
|
+
* билдер не обязан быть чистым. Поэтому дерево строится один раз на пару «билдер + под-модель»
|
|
7
|
+
* и дальше отдаётся из памяти.
|
|
8
|
+
*
|
|
9
|
+
* Память привязана к под-модели: исчезла строка массива — исчезли и её поддеревья.
|
|
10
|
+
*
|
|
11
|
+
* @group Utilities
|
|
12
|
+
* @module form/schema-subtree
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Поддерево схемы для под-модели — результат `builder(subModel)`, один на пару.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam N - Тип узла, который возвращает билдер.
|
|
18
|
+
* @param builder - Билдер поддерева: `item` узла-массива или `part` узла-подформы.
|
|
19
|
+
* @param subModel - Под-модель строки массива или группы.
|
|
20
|
+
* @returns Узел поддерева; повторный вызов с той же парой отдаёт тот же объект.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const row = model.items.at(0);
|
|
25
|
+
* schemaSubtree(itemSchema, row) === schemaSubtree(itemSchema, row); // true
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function schemaSubtree<N>(builder: (subModel: any) => N, subModel: object): N;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { Signal } from '@preact/signals-core';
|
|
2
1
|
import { FormNode } from './nodes/form-node';
|
|
3
2
|
/**
|
|
4
|
-
* Связать
|
|
3
|
+
* Связать ручку модели с её нодой формы.
|
|
5
4
|
*
|
|
6
|
-
* Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого
|
|
7
|
-
*
|
|
5
|
+
* Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого поля,
|
|
6
|
+
* группы и массива под-форм. Прикладной код обычно этот реестр не заполняет напрямую.
|
|
8
7
|
*
|
|
9
|
-
* @param signal -
|
|
10
|
-
* @param node - Нода формы, отвечающая за
|
|
8
|
+
* @param signal - Ручка из {@link FormModel}: сигнал листа, узел-массив или узел-группа дерева `model.$`
|
|
9
|
+
* @param node - Нода формы, отвечающая за эту часть модели
|
|
11
10
|
*
|
|
12
11
|
* @example Привязка листовых полей при построении формы
|
|
13
12
|
* ```typescript
|
|
@@ -18,19 +17,20 @@ import { FormNode } from './nodes/form-node';
|
|
|
18
17
|
* if (sig && node) registerSignalNode(sig, node);
|
|
19
18
|
* ```
|
|
20
19
|
*
|
|
21
|
-
* @see {@link getNodeForSignal} - обратный поиск ноды по
|
|
20
|
+
* @see {@link getNodeForSignal} - обратный поиск ноды по ручке
|
|
22
21
|
* @group Utilities
|
|
23
22
|
*/
|
|
24
|
-
export declare function registerSignalNode(signal:
|
|
23
|
+
export declare function registerSignalNode(signal: object, node: FormNode<any>): void;
|
|
25
24
|
/**
|
|
26
|
-
* Найти ноду формы по
|
|
25
|
+
* Найти ноду формы по ручке модели.
|
|
27
26
|
*
|
|
28
|
-
* Используется state-операциями behavior (`enableWhen`/`disableWhen`)
|
|
29
|
-
* валидации в ноды. Возвращает `undefined`, если форма
|
|
30
|
-
*
|
|
27
|
+
* Используется state-операциями behavior (`enableWhen`/`disableWhen`), подключением под-схем
|
|
28
|
+
* (`apply`/`applyEach`) и роутингом ошибок валидации в ноды. Возвращает `undefined`, если форма
|
|
29
|
+
* ещё не построена или эта часть модели в форме не материализована (например, массив без
|
|
30
|
+
* item-схемы).
|
|
31
31
|
*
|
|
32
|
-
* @param signal -
|
|
33
|
-
* @returns Нода формы
|
|
32
|
+
* @param signal - Ручка из {@link FormModel}: `model.$.<поле>`, `model.$.<группа>`, `model.$.<массив>`
|
|
33
|
+
* @returns Нода формы — поля, группы или массива — либо `undefined`
|
|
34
34
|
*
|
|
35
35
|
* @example Роутинг ошибок валидации в ноду поля
|
|
36
36
|
* ```typescript
|
|
@@ -40,7 +40,13 @@ export declare function registerSignalNode(signal: Signal<any>, node: FormNode<a
|
|
|
40
40
|
* getNodeForSignal(sig)?.setErrors([{ code: 'required', message: 'Обязательно' }]);
|
|
41
41
|
* ```
|
|
42
42
|
*
|
|
43
|
-
* @
|
|
43
|
+
* @example Нода группы и строки массива
|
|
44
|
+
* ```typescript
|
|
45
|
+
* getNodeForSignal(model.$.address)?.disable(); // группа целиком
|
|
46
|
+
* getNodeForSignal(model.$.phones[0])?.markAsTouched(); // форма строки массива
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @see {@link registerSignalNode} - регистрация связи ручка→нода
|
|
44
50
|
* @group Utilities
|
|
45
51
|
*/
|
|
46
|
-
export declare function getNodeForSignal(signal:
|
|
52
|
+
export declare function getNodeForSignal(signal: object): FormNode<any> | undefined;
|
|
@@ -6,7 +6,7 @@ import { ValidatorFn, AsyncValidatorFn, AnyFunction } from './index';
|
|
|
6
6
|
* @group Types
|
|
7
7
|
* @category Configuration Types
|
|
8
8
|
* @remarks
|
|
9
|
-
* В схеме `
|
|
9
|
+
* В схеме `createFormFromModel({ model, schema })` лист связывают через **`value: model.$.field`**: harvest
|
|
10
10
|
* ловит узел по идентичности `Signal` (`node.value instanceof Signal` — см. `create-form.ts`) и
|
|
11
11
|
* нормализует его в `valueSignal`. Писать `valueSignal:` **в самой схеме `createForm` нельзя** —
|
|
12
12
|
* harvest его не подхватит (проверка идёт по `node.value`), и `component`/`componentProps` этого
|
|
@@ -68,7 +68,7 @@ type NonEmptyFieldConfig<T> = RequireAtLeastOne<FieldConfig<T>>;
|
|
|
68
68
|
* Использует NonNullable для корректной обработки опциональных полей.
|
|
69
69
|
*
|
|
70
70
|
* ⚠️ Не путать с {@link FormSchemaNode} — тот описывает **узел дерева** M1-схемы (лист/массив/
|
|
71
|
-
* контейнер), передаваемой в `
|
|
71
|
+
* контейнер), передаваемой в `createFormFromModel({ model, schema })`. `FormSchema` — это data-shaped конфиг
|
|
72
72
|
* (ключи = поля данных), а не узел дерева.
|
|
73
73
|
*
|
|
74
74
|
* @group Types
|
|
@@ -13,16 +13,17 @@ export interface SchemaArrayControl {
|
|
|
13
13
|
/** Реактивная длина. */
|
|
14
14
|
readonly length: number;
|
|
15
15
|
at(index: number): unknown;
|
|
16
|
-
push(item
|
|
16
|
+
push(item?: unknown): void;
|
|
17
17
|
removeAt(index: number): void;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Узел единой схемы M1 — layout-дерево, обходимое `
|
|
20
|
+
* Узел единой схемы M1 — layout-дерево, обходимое `createFormFromModel({ model, schema })`
|
|
21
21
|
* и рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).
|
|
22
22
|
*
|
|
23
23
|
* Узел совмещает несколько ролей (различаются рантаймом по форме):
|
|
24
|
-
* - **поле** —
|
|
25
|
-
* -
|
|
24
|
+
* - **поле** — `{ model: model.$.x, component, componentProps }`;
|
|
25
|
+
* - **массив под-форм** — `{ model: model.$.items, item: (model) => узел }`;
|
|
26
|
+
* - **подформа** — `{ model: model.$.group, part: (model) => узел }`;
|
|
26
27
|
* - **контейнер/ветка** — вложенные узлы (`children`), опц. условие `when`;
|
|
27
28
|
* - **record-of-fields** — под-узлы под произвольными именованными ключами (индексная сигнатура).
|
|
28
29
|
*
|
|
@@ -33,9 +34,20 @@ export interface SchemaArrayControl {
|
|
|
33
34
|
*/
|
|
34
35
|
export interface FormSchemaNode {
|
|
35
36
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* Привязка узла к части модели — ручка из дерева `model.$`. Чем узел является, решают ручка и
|
|
38
|
+
* соседние ключи:
|
|
39
|
+
* - лист или массив (`model.$.email`, `model.$.tags`) — **поле**;
|
|
40
|
+
* - массив вместе с {@link FormSchemaNode.item} — **массив под-форм**;
|
|
41
|
+
* - группа вместе с {@link FormSchemaNode.part} — **подформа**.
|
|
42
|
+
*
|
|
43
|
+
* Узел распознаётся по значению, а не по имени ключа: в записи «имя поля → узел» поле данных
|
|
44
|
+
* может называться `model`, и тогда под этим ключом лежит обычный вложенный узел.
|
|
45
|
+
*/
|
|
46
|
+
model?: unknown;
|
|
47
|
+
/**
|
|
48
|
+
* Прежняя запись привязки поля — то же, что {@link FormSchemaNode.model}.
|
|
49
|
+
*
|
|
50
|
+
* @deprecated Пишите `model: model.$.<path>`.
|
|
39
51
|
*/
|
|
40
52
|
value?: unknown;
|
|
41
53
|
/**
|
|
@@ -65,14 +77,27 @@ export interface FormSchemaNode {
|
|
|
65
77
|
* выводит на своём месте в порядке следования.
|
|
66
78
|
*/
|
|
67
79
|
children?: readonly (FormSchemaNode | string | number | Signal<any>)[];
|
|
68
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Прежняя запись привязки массива под-форм — фасад `model.<path>` (вместе с `item`).
|
|
82
|
+
*
|
|
83
|
+
* @deprecated Пишите `model: model.$.<path>`.
|
|
84
|
+
*/
|
|
69
85
|
array?: SchemaArrayControl;
|
|
70
86
|
/** Схема элемента массива: под-модель элемента → узел поддерева. */
|
|
71
|
-
item?: (
|
|
87
|
+
item?: (model: any) => FormSchemaNode;
|
|
88
|
+
/**
|
|
89
|
+
* Подформа: под-модель группы → узел поддерева. Часть объявляется один раз и подключается к
|
|
90
|
+
* любой группе той же формы данных: `{ model: model.$.registrationAddress, part: address }`.
|
|
91
|
+
* Привязки внутри части идут через `$` полученной под-модели.
|
|
92
|
+
*/
|
|
93
|
+
part?: (model: any) => FormSchemaNode;
|
|
72
94
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
95
|
+
* Шаблон нового элемента массива для кнопки «Добавить»: либо готовое значение, либо фабрика
|
|
96
|
+
* `() => value`. Запасной путь — для форм, чья модель создаётся из данных без кода: шаблон,
|
|
97
|
+
* объявленный в модели (`arrayOf(blank)`), главнее.
|
|
98
|
+
*
|
|
99
|
+
* Тип не различает варианты (union `unknown | (() => unknown)` схлопывается в `unknown`) —
|
|
100
|
+
* рантайм различает по `typeof initialValue === 'function'`.
|
|
76
101
|
*/
|
|
77
102
|
initialValue?: unknown;
|
|
78
103
|
/** Свободная вложенность: record-of-fields и произвольные под-узлы. */
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { FormModel, PathAwareSignal, ValidationError } from '../../index';
|
|
2
2
|
/** Сток одного прогона: модель scope, накопленные ошибки, незавершённые async-правила, гейты. */
|
|
3
3
|
export interface VContext {
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Модель текущей области — источник снапшота для `cross` и модель для `apply`. В корне прогона
|
|
6
|
+
* это вся модель; внутри `apply(ручка, схема)` / `applyEach` — под-модель группы или строки.
|
|
7
|
+
*/
|
|
5
8
|
model: FormModel<unknown>;
|
|
9
|
+
/** Корень прогона — модель, с которой вызван раннер. Правило получает его третьим аргументом. */
|
|
10
|
+
root: FormModel<unknown>;
|
|
6
11
|
/** Накопленные ошибки по сигналу (ключ — идентичность `PathAwareSignal`). */
|
|
7
12
|
errors: Map<PathAwareSignal<unknown>, ValidationError[]>;
|
|
8
13
|
/** Незавершённые async-правила (раннер их дожидается). */
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
* @module form/validation
|
|
19
19
|
*/
|
|
20
20
|
export type { Rule, AsyncRule, ValidationSchema } from './types';
|
|
21
|
-
export { validate, validateAsync, validateWhen, cross, each, apply } from './operators';
|
|
21
|
+
export { validate, validateAsync, validateWhen, cross, each, apply, applyEach } from './operators';
|
|
22
22
|
export { defineValidationSchema, validateModel } from './run';
|
|
23
23
|
export * from './strategy';
|
|
@@ -32,16 +32,78 @@ export declare function validateAsync<TField>(sig: PathAwareSignal<TField>, rule
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function validateWhen(cond: () => boolean, cb: () => void): void;
|
|
34
34
|
/**
|
|
35
|
-
* Cross-field правило: `fn` получает СНАПШОТ модели
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* Cross-field правило: `fn` получает СНАПШОТ модели текущей области (`model.get()`) и вешает
|
|
36
|
+
* ошибку на `sig`.
|
|
37
|
+
*
|
|
38
|
+
* Область — та модель, с которой запущена схема: в корне прогона это вся форма, а в схеме,
|
|
39
|
+
* подключённой через {@link apply} / {@link applyEach}, — под-модель группы или строки массива.
|
|
40
|
+
* Поэтому правила подформы пишутся один раз и не знают, где она стоит.
|
|
38
41
|
*/
|
|
39
42
|
export declare function cross<TSnapshot>(sig: PathAwareSignal<unknown>, fn: (form: TSnapshot) => ValidationError | null): void;
|
|
43
|
+
/** Привязка к группе: ручка `model.$.<группа>` (тип значения — из её `peek()`). */
|
|
44
|
+
interface GroupBinding<V> {
|
|
45
|
+
peek(): V;
|
|
46
|
+
}
|
|
47
|
+
/** Привязка к массиву под-форм: ручка `model.$.<массив>`. */
|
|
48
|
+
interface ArrayBinding<U> {
|
|
49
|
+
peek(): readonly U[] | null | undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Применить схему к КАЖДОМУ элементу массива под-форм. Схема элемента — обычная
|
|
53
|
+
* `ValidationSchema` над типом элемента: тот же набор правил подключается и к группе через
|
|
54
|
+
* {@link apply}.
|
|
55
|
+
*
|
|
56
|
+
* У каждого элемента своя область: `cross` внутри схемы получает снапшот элемента.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const propertyRules = defineValidationSchema<Property>(({ model }) => {
|
|
61
|
+
* validate(model.$.type, [required()]);
|
|
62
|
+
* cross<Property>(model.$.estimatedValue, (property) =>
|
|
63
|
+
* property.hasEncumbrance && property.estimatedValue < 100_000 ? tooCheap : null
|
|
64
|
+
* );
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* applyEach(model.$.properties, propertyRules);
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function applyEach<U extends object>(array: ArrayBinding<U> | ModelArray<U>, schema: ValidationSchema<U>): void;
|
|
71
|
+
/**
|
|
72
|
+
* Применить под-правила к КАЖДОМУ элементу массива — прежняя запись {@link applyEach}.
|
|
73
|
+
*
|
|
74
|
+
* Колбэк получает под-модель элемента, но области не создаёт: `cross` внутри него видит снапшот
|
|
75
|
+
* корня прогона.
|
|
76
|
+
*
|
|
77
|
+
* @deprecated Пишите `applyEach(model.$.<массив>, schema)`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function each<U extends object>(arr: ArrayBinding<U> | ModelArray<U>, itemFn: (item: FormModel<U>) => void): void;
|
|
40
80
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
81
|
+
* Подключить схему подформы к группе модели — одной или нескольким.
|
|
82
|
+
*
|
|
83
|
+
* Схема запускается в области группы: `model` в ней — под-модель, `cross` получает снапшот
|
|
84
|
+
* группы. Привязка — ручка `model.$.<группа>`, как у узла-подформы схемы и у `apply` поведения.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const addressRules = defineValidationSchema<Address>(({ model }) => {
|
|
89
|
+
* validate(model.$.city, [required()]);
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* apply(model.$.registrationAddress, addressRules);
|
|
93
|
+
* validateWhen(
|
|
94
|
+
* () => !model.sameAsRegistration,
|
|
95
|
+
* () => apply(model.$.residenceAddress, addressRules)
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function apply<V extends object | null | undefined>(group: GroupBinding<V> | GroupBinding<V>[], schema: ValidationSchema<NonNullable<V>>): void;
|
|
100
|
+
/**
|
|
101
|
+
* Композиция схем над ТОЙ ЖЕ моделью области — так форма собирается из схем шагов.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const formRules = defineValidationSchema<Form>(() => apply(loanRules, contactsRules));
|
|
106
|
+
* ```
|
|
44
107
|
*/
|
|
45
|
-
export declare function each<U extends object>(arr: ModelArray<U>, itemFn: (item: FormModel<U>) => void): void;
|
|
46
|
-
/** Композиция под-схем в текущую (над той же моделью scope). Заменяет пошаговую группировку. */
|
|
47
108
|
export declare function apply<T>(...schemas: ValidationSchema<T>[]): void;
|
|
109
|
+
export {};
|
|
@@ -12,7 +12,8 @@ export type CallableRule = (value: unknown, model: unknown, root: unknown) => Va
|
|
|
12
12
|
* (`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`
|
|
13
13
|
* ОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля
|
|
14
14
|
* (`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).
|
|
15
|
-
* На вызове раннер приводит правило к callable и передаёт `(value,
|
|
15
|
+
* На вызове раннер приводит правило к callable и передаёт `(value, scope, root)`: модель текущей
|
|
16
|
+
* области (в корне прогона — вся модель) и корень прогона.
|
|
16
17
|
*/
|
|
17
18
|
export type Rule<TField> = (value: TField, scope: never, root: never) => ValidationError | null;
|
|
18
19
|
/**
|