@reformer/core 11.0.0-beta.8 → 11.0.0-beta.9
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 +91 -73
- package/dist/behaviors-value-HA_Q03hc.js +108 -0
- package/dist/behaviors.js +370 -242
- package/dist/{create-model-BBRHhHwK.js → create-model-BvcbNPgL.js} +6 -6
- package/dist/date-utils-90-qZbse.js +43 -0
- package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
- package/dist/form/aggregate-signals.d.ts +21 -63
- package/dist/form/behaviors/collections.d.ts +28 -7
- package/dist/form/behaviors/context.d.ts +19 -2
- package/dist/form/behaviors/index.d.ts +3 -3
- package/dist/form/behaviors/internals.d.ts +9 -2
- package/dist/form/behaviors/node.d.ts +25 -2
- package/dist/form/behaviors/operators.d.ts +17 -9
- package/dist/form/behaviors/schema-operators.d.ts +6 -4
- package/dist/form/behaviors/types.d.ts +12 -4
- package/dist/form/create-form.d.ts +47 -35
- package/dist/form/flow/flow.d.ts +87 -0
- package/dist/form/form-bundle.d.ts +45 -28
- package/dist/form/form-config-base.d.ts +46 -0
- package/dist/form/form-level.d.ts +121 -0
- package/dist/form/form-proxy-builder.d.ts +6 -22
- package/dist/form/index.d.ts +16 -21
- package/dist/form/nodes/form-array-node.d.ts +100 -0
- package/dist/form/nodes/form-control-node.d.ts +73 -0
- package/dist/form/nodes/form-group-node.d.ts +66 -0
- package/dist/form/nodes/node-kind.d.ts +29 -0
- package/dist/form/nodes/state/availability.d.ts +6 -0
- package/dist/form/nodes/state/component.d.ts +6 -0
- package/dist/form/nodes/state/index.d.ts +12 -0
- package/dist/form/nodes/state/interaction.d.ts +13 -0
- package/dist/form/nodes/state/issues.d.ts +9 -0
- package/dist/form/nodes/state/pending.d.ts +6 -0
- package/dist/form/schema/compile.d.ts +59 -0
- package/dist/form/schema/compiled.d.ts +143 -0
- package/dist/form/schema/row-key.d.ts +11 -0
- package/dist/form/schema-controller.d.ts +30 -14
- package/dist/form/signal-node-registry.d.ts +6 -7
- package/dist/form/submission.d.ts +93 -0
- package/dist/form/type-guards.d.ts +14 -73
- package/dist/form/types/contracts.d.ts +19 -92
- package/dist/form/types/field-config.d.ts +27 -0
- package/dist/form/types/index.d.ts +5 -57
- package/dist/form/types/nodes.d.ts +159 -0
- package/dist/form/types/schema-node.d.ts +139 -63
- package/dist/form/types/validation-schema.d.ts +23 -27
- package/dist/form/validation/context.d.ts +42 -7
- package/dist/form/validation/index.d.ts +9 -8
- package/dist/form/validation/operators.d.ts +22 -30
- package/dist/form/validation/run.d.ts +93 -18
- package/dist/form/validation/strategy.d.ts +41 -13
- package/dist/form/validation/types.d.ts +74 -20
- package/dist/form/validators/date-utils.d.ts +13 -1
- package/dist/form/validators/email.d.ts +8 -7
- package/dist/form/validators/file-type.d.ts +8 -7
- package/dist/form/validators/file-utils.d.ts +2 -1
- package/dist/form/validators/future-date.d.ts +8 -7
- package/dist/form/validators/index.d.ts +1 -1
- package/dist/form/validators/integer.d.ts +8 -7
- package/dist/form/validators/is-date.d.ts +11 -9
- package/dist/form/validators/is-number.d.ts +11 -9
- package/dist/form/validators/max-age.d.ts +13 -9
- package/dist/form/validators/max-date.d.ts +8 -7
- package/dist/form/validators/max-file-size.d.ts +8 -7
- package/dist/form/validators/max-files.d.ts +8 -7
- package/dist/form/validators/max-length.d.ts +14 -7
- package/dist/form/validators/max-total-file-size.d.ts +8 -7
- package/dist/form/validators/max.d.ts +10 -8
- package/dist/form/validators/min-age.d.ts +12 -9
- package/dist/form/validators/min-date.d.ts +8 -7
- package/dist/form/validators/min-file-size.d.ts +8 -7
- package/dist/form/validators/min-files.d.ts +11 -9
- package/dist/form/validators/min-length.d.ts +14 -7
- package/dist/form/validators/min.d.ts +10 -8
- package/dist/form/validators/multiple-of.d.ts +10 -8
- package/dist/form/validators/non-negative.d.ts +8 -7
- package/dist/form/validators/non-zero.d.ts +8 -7
- package/dist/form/validators/past-date.d.ts +8 -7
- package/dist/form/validators/pattern.d.ts +9 -8
- package/dist/form/validators/phone.d.ts +8 -7
- package/dist/form/validators/required.d.ts +11 -9
- package/dist/form/validators/url.d.ts +20 -13
- package/dist/form/validators/validation-issue.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +1 -1
- package/dist/i18n/en.json.d.ts +5 -1
- package/dist/i18n/locale-store.d.ts +7 -0
- package/dist/i18n/message-format.d.ts +24 -4
- package/dist/i18n/ru.json.d.ts +5 -1
- package/dist/i18n/translator.d.ts +11 -0
- package/dist/i18n/validation-message.d.ts +42 -7
- package/dist/i18n.d.ts +7 -12
- package/dist/i18n.js +66 -283
- package/dist/index.d.ts +16 -7
- package/dist/index.js +924 -2855
- package/dist/loader-BWkigpVe.js +52 -0
- package/dist/locale/en.js +1 -1
- package/dist/locale/en.json +4 -1
- package/dist/locale/ru.js +4 -1
- package/dist/locale/ru.json +4 -1
- package/dist/locale-CG54AU2Q.js +219 -0
- package/dist/locale.js +1 -1
- package/dist/model/behaviors-value.d.ts +24 -6
- package/dist/model/create-model.d.ts +2 -2
- package/dist/model/derived-registry.d.ts +1 -1
- package/dist/model/index.d.ts +11 -5
- package/dist/model/model-nodes.d.ts +79 -12
- package/dist/model/model-signals-proxy.d.ts +5 -6
- package/dist/model/model-value-proxy.d.ts +20 -15
- package/dist/model/safe-effect.d.ts +33 -1
- package/dist/model/types.d.ts +63 -13
- package/dist/model.js +20 -18
- package/dist/node-kind-C-r6ZMdo.js +15 -0
- package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
- package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
- package/dist/platforms/react/form-bundle-context.d.ts +2 -2
- package/dist/platforms/react/hooks/types.d.ts +57 -38
- package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
- package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
- package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
- package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
- package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
- package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
- package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
- package/dist/platforms/react/i18n/context.d.ts +6 -1
- package/dist/platforms/react/i18n/hooks.d.ts +16 -10
- package/dist/platforms/react/i18n/provider.d.ts +9 -2
- package/dist/platforms/react/index.d.ts +6 -4
- package/dist/react.d.ts +22 -0
- package/dist/react.js +505 -0
- package/dist/runtime-token.d.ts +1 -2
- package/dist/safe-effect-BbtX3KuN.js +643 -0
- package/dist/schema-controller-CpiO2JoK.js +143 -0
- package/dist/signals.d.ts +15 -9
- package/dist/signals.js +11 -6
- package/dist/strategy-CoFSemuP.js +375 -0
- package/dist/type-guards-D-N_5Ddh.js +19 -0
- package/dist/validation-issue-B9mjI8jz.js +7 -0
- package/dist/validation-message-Ced9MGxs.js +194 -0
- package/dist/validation.js +9 -9
- package/dist/validators/email.js +5 -5
- package/dist/validators/future-date.js +8 -8
- package/dist/validators/integer.js +3 -3
- package/dist/validators/is-date.js +4 -4
- package/dist/validators/is-number.js +4 -4
- package/dist/validators/max-age.js +8 -9
- package/dist/validators/max-date.js +8 -8
- package/dist/validators/max-length.js +7 -7
- package/dist/validators/max.js +4 -4
- package/dist/validators/min-age.js +10 -8
- package/dist/validators/min-date.js +3 -3
- package/dist/validators/min-length.js +5 -5
- package/dist/validators/min.js +4 -4
- package/dist/validators/multiple-of.js +11 -7
- package/dist/validators/non-negative.js +3 -3
- package/dist/validators/non-zero.js +3 -3
- package/dist/validators/past-date.js +8 -8
- package/dist/validators/pattern.js +5 -5
- package/dist/validators/phone.js +11 -9
- package/dist/validators/required.js +2 -2
- package/dist/validators/url.js +48 -12
- package/dist/validators.js +17 -17
- package/docs/llms/01-api-reference.md +60 -34
- package/docs/llms/02-quick-start.md +111 -80
- package/docs/llms/03-api-signatures.md +368 -153
- package/docs/llms/04-common-patterns.md +58 -56
- package/docs/llms/05-common-mistakes.md +69 -44
- package/docs/llms/06-troubleshooting.md +14 -9
- package/docs/llms/07-complete-import.md +74 -43
- package/docs/llms/08-form-types.md +11 -4
- package/docs/llms/09-formschema.md +129 -81
- package/docs/llms/10-arrays.md +106 -75
- package/docs/llms/11-async-watchfield.md +2 -2
- package/docs/llms/13-multi-step.md +134 -63
- package/docs/llms/14-extended-mistakes.md +20 -17
- package/docs/llms/15-project-structure.md +221 -74
- package/docs/llms/16-ui-components.md +55 -42
- package/docs/llms/17-nonexistent-api.md +199 -32
- package/docs/llms/18-conditional-fields.md +66 -32
- package/docs/llms/19-reading-values.md +15 -3
- package/docs/llms/20-compute-vs-watch.md +16 -10
- package/docs/llms/21-array-operations.md +51 -27
- package/docs/llms/22-cycle-detection.md +1 -1
- package/docs/llms/23-copy-from.md +2 -2
- package/docs/llms/24-sync-fields.md +6 -5
- package/docs/llms/25-reset-when.md +14 -7
- package/docs/llms/26-transform-value.md +4 -4
- package/docs/llms/27-revalidate-when.md +16 -15
- package/docs/llms/28-submit-and-reset.md +276 -128
- package/docs/llms/29-async-preload.md +39 -29
- package/docs/llms/30-type-safety-recipes.md +105 -51
- package/docs/llms/31-async-validator-debounce.md +96 -53
- package/docs/llms/33-validation-strategy.md +177 -65
- package/docs/llms/34-i18n.md +69 -43
- package/docs/llms/35-unified-contract.md +100 -39
- package/docs/llms/36-issue-messages.md +284 -0
- package/docs/llms/37-compiled-schema.md +332 -0
- package/docs/llms/38-form-flow.md +397 -0
- package/llms-index.json +1 -1
- package/llms.txt +6084 -3420
- package/package.json +23 -4
- package/dist/behaviors-value-C7dSEhCc.js +0 -134
- package/dist/date-utils-dTT_x_Be.js +0 -31
- package/dist/form/create-core-form.d.ts +0 -72
- package/dist/form/factories/index.d.ts +0 -6
- package/dist/form/factories/node-factory.d.ts +0 -189
- package/dist/form/form-submitter.d.ts +0 -121
- package/dist/form/nodes/array-node.d.ts +0 -276
- package/dist/form/nodes/field-node.d.ts +0 -297
- package/dist/form/nodes/form-node.d.ts +0 -326
- package/dist/form/nodes/group-node.d.ts +0 -284
- package/dist/form/nodes/model-array-node.d.ts +0 -126
- package/dist/form/nodes/subscription-manager.d.ts +0 -180
- package/dist/form/status-machine.d.ts +0 -115
- package/dist/form/types/deep-schema.d.ts +0 -107
- package/dist/form/types/form-proxy.d.ts +0 -141
- package/dist/form/unique-id.d.ts +0 -45
- package/dist/form/validation/config.d.ts +0 -73
- package/dist/form/validation/error-handler.d.ts +0 -166
- package/dist/form/validators/validation-error.d.ts +0 -8
- package/dist/index-BrNkQj1y.js +0 -105
- package/dist/loader-Dv_Hl7JC.js +0 -216
- package/dist/model-value-proxy-caTmAiUd.js +0 -419
- package/dist/node-BeV_lV5i.js +0 -100
- package/dist/strategy-DgzELWno.js +0 -204
- package/dist/validation-error-B-U3RsV0.js +0 -11
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import { ElementType } from 'react';
|
|
2
1
|
import { Signal } from '../../signals';
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Ссылка на компонент узла схемы: то, что слой представления умеет нарисовать. Для React это
|
|
4
|
+
* компонент (функция либо объект `forwardRef`/`memo`) или имя HTML-тега (`'div'`, `'h3'`).
|
|
5
|
+
*
|
|
6
|
+
* Ядро ссылку не интерпретирует и о слое представления не знает: она доезжает до рендерера как
|
|
7
|
+
* есть, а к типу своего UI-слоя её приводит уже он.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const byComponent: ComponentRef = Input; // React-компонент
|
|
12
|
+
* const byTag: ComponentRef = 'section'; // HTML-тег
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @group Types
|
|
16
|
+
*/
|
|
17
|
+
export type ComponentRef = string | object;
|
|
18
|
+
/**
|
|
19
|
+
* Контракт реактивного массива модели — фасада `model.<массив>`. Рендерерский
|
|
6
20
|
* `RenderModelArrayControl` — его расширение (добавляет `move`).
|
|
7
21
|
*
|
|
8
22
|
* @group Types
|
|
@@ -17,80 +31,95 @@ export interface SchemaArrayControl {
|
|
|
17
31
|
removeAt(index: number): void;
|
|
18
32
|
}
|
|
19
33
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
34
|
+
* Ручка значения из дерева `model.$`: лист (`model.$.email`) или массив целиком
|
|
35
|
+
* (`model.$.tags`). Группа ручкой значения не является — у неё нет пути.
|
|
22
36
|
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
* @group Types
|
|
38
|
+
*/
|
|
39
|
+
export type SchemaValueHandle = Signal<any> & {
|
|
40
|
+
readonly __path: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Ручка массива из дерева `model.$` (`model.$.phones`) — ручка значения с длиной.
|
|
29
44
|
*
|
|
30
|
-
*
|
|
31
|
-
|
|
45
|
+
* @group Types
|
|
46
|
+
*/
|
|
47
|
+
export type SchemaArrayHandle = SchemaValueHandle & {
|
|
48
|
+
readonly length: number;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Ручка группы из дерева `model.$` (`model.$.address`).
|
|
32
52
|
*
|
|
33
53
|
* @group Types
|
|
34
54
|
*/
|
|
35
|
-
export interface
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
* Узел распознаётся по значению, а не по имени ключа: в записи «имя поля → узел» поле данных
|
|
44
|
-
* может называться `model`, и тогда под этим ключом лежит обычный вложенный узел.
|
|
45
|
-
*/
|
|
46
|
-
model?: unknown;
|
|
55
|
+
export interface SchemaGroupHandle {
|
|
56
|
+
peek(): object | null | undefined;
|
|
57
|
+
/** Признак «не ручка значения»: у листа и массива путь есть, у группы — нет. */
|
|
58
|
+
readonly __path?: never;
|
|
59
|
+
}
|
|
60
|
+
/** Ключи, общие для узла любого вида. */
|
|
61
|
+
interface SchemaNodeBase {
|
|
47
62
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
63
|
+
* UI-компонент либо нативный HTML-тег (`'div'`, `'p'`, `'h3'`) для презентационной вёрстки
|
|
64
|
+
* прямо в схеме. Опционален: ядро работает без UI и `component` не интерпретирует — он доезжает
|
|
65
|
+
* до рендерера как есть.
|
|
51
66
|
*/
|
|
52
|
-
|
|
67
|
+
component?: ComponentRef;
|
|
53
68
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* не интерпретирует — он доезжает до рендерера как есть.
|
|
69
|
+
* Пропсы компонента. Вложенных узлов здесь нет: обход схемы в пропсы не заглядывает, дети узла
|
|
70
|
+
* пишутся в `children`.
|
|
57
71
|
*/
|
|
58
|
-
component?: ElementType;
|
|
59
|
-
/** Props компонента. Также «клапан» для вложенности под-узлов (напр. steps визарда). */
|
|
60
72
|
componentProps?: Record<string, unknown>;
|
|
61
|
-
|
|
62
|
-
disabled?: boolean;
|
|
63
|
-
/** Задержка (мс) перед запуском асинхронной валидации. */
|
|
64
|
-
debounce?: number;
|
|
65
|
-
/** Идентификатор узла (для wizard/tabs/renderBehavior). */
|
|
73
|
+
/** Идентификатор узла: по нему узел адресуют поведение (`schema.node(...)`) и шаги визарда. */
|
|
66
74
|
selector?: string;
|
|
67
75
|
/**
|
|
68
|
-
*
|
|
69
|
-
* (иначе выводит из пути сигнала). Поле оставлено только потому, что `RenderSchemaNode`
|
|
70
|
-
* рендерера объявляет свой одноимённый; пишите `componentProps: { testId: '…' }`.
|
|
71
|
-
*/
|
|
72
|
-
testId?: string;
|
|
73
|
-
/**
|
|
74
|
-
* Содержимое узла: под-узлы (даёт контекстную типизацию вложенным литералам — value/validators/when)
|
|
75
|
-
* и текстовые части. Текст (литерал, число, сигнал модели) — такой же ребёнок, как узел: core его
|
|
76
|
-
* не интерпретирует (обход пропускает примитивы и не спускается внутрь сигнала), а рендерер
|
|
77
|
-
* выводит на своём месте в порядке следования.
|
|
78
|
-
*/
|
|
79
|
-
children?: readonly (FormSchemaNode | string | number | Signal<any>)[];
|
|
80
|
-
/**
|
|
81
|
-
* Прежняя запись привязки массива под-форм — фасад `model.<path>` (вместе с `item`).
|
|
76
|
+
* Прежний ключ привязки поля — его нет: пишите `model: model.$.<поле>`.
|
|
82
77
|
*
|
|
83
|
-
*
|
|
78
|
+
* Запрет объявлен явно: в `children` узел стоит в объединении с реактивным текстом
|
|
79
|
+
* (`Signal`, у которого есть `value`), и без него прежний ключ проходил проверку лишних ключей.
|
|
84
80
|
*/
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
|
|
81
|
+
value?: never;
|
|
82
|
+
/** Прежний ключ привязки массива — его нет: пишите `model: model.$.<массив>` вместе с `item`. */
|
|
83
|
+
array?: never;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Поле: узел, привязанный к ручке значения — листу или массиву целиком (мультивыбор, теги, файлы).
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* { model: model.$.email, component: Input, componentProps: { label: 'Email' } }
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @group Types
|
|
94
|
+
*/
|
|
95
|
+
export interface SchemaFieldNode extends SchemaNodeBase {
|
|
96
|
+
/** Привязка поля — ручка значения `model.$.<лист | массив>`. */
|
|
97
|
+
model: SchemaValueHandle;
|
|
88
98
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
99
|
+
* Поле создаётся отключённым — и остаётся: это постоянная блокировка (вычисляемые `total`,
|
|
100
|
+
* `fullName`). `enable()` группы, массива или формы её не снимает; снимает только явный
|
|
101
|
+
* `enable()` самого поля либо оператор `enableWhen` на нём.
|
|
92
102
|
*/
|
|
93
|
-
|
|
103
|
+
disabled?: boolean;
|
|
104
|
+
item?: never;
|
|
105
|
+
part?: never;
|
|
106
|
+
children?: never;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Массив под-форм: у каждой строки своя форма, разметку строки строит `item`.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* { model: model.$.phones, component: FieldArray, item: (row) => ({ children: [...] }) }
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @group Types
|
|
117
|
+
*/
|
|
118
|
+
export interface SchemaArrayNode extends SchemaNodeBase {
|
|
119
|
+
/** Привязка — ручка массива `model.$.<массив>`. */
|
|
120
|
+
model: SchemaArrayHandle;
|
|
121
|
+
/** Схема строки: под-модель строки → узел поддерева. */
|
|
122
|
+
item: (model: any) => FormSchemaNode;
|
|
94
123
|
/**
|
|
95
124
|
* Шаблон нового элемента массива для кнопки «Добавить»: либо готовое значение, либо фабрика
|
|
96
125
|
* `() => value`. Запасной путь — для форм, чья модель создаётся из данных без кода: шаблон,
|
|
@@ -100,6 +129,53 @@ export interface FormSchemaNode {
|
|
|
100
129
|
* рантайм различает по `typeof initialValue === 'function'`.
|
|
101
130
|
*/
|
|
102
131
|
initialValue?: unknown;
|
|
103
|
-
|
|
104
|
-
|
|
132
|
+
part?: never;
|
|
133
|
+
children?: never;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Подформа: часть схемы, подключённая к группе модели. Часть объявляется один раз и подключается
|
|
137
|
+
* к любой группе той же формы данных; привязки внутри части идут через `$` полученной под-модели.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* { model: model.$.registrationAddress, part: address }
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* @group Types
|
|
145
|
+
*/
|
|
146
|
+
export interface SchemaPartNode extends SchemaNodeBase {
|
|
147
|
+
/** Привязка — ручка группы `model.$.<группа>`. */
|
|
148
|
+
model: SchemaGroupHandle;
|
|
149
|
+
/** Часть схемы: под-модель группы → узел поддерева. */
|
|
150
|
+
part: (model: any) => FormSchemaNode;
|
|
151
|
+
item?: never;
|
|
152
|
+
children?: never;
|
|
153
|
+
}
|
|
154
|
+
/** Ребёнок контейнера: вложенный узел либо текстовая часть — литерал, число, сигнал модели. */
|
|
155
|
+
export type SchemaChild = FormSchemaNode | string | number | Signal<any>;
|
|
156
|
+
/**
|
|
157
|
+
* Контейнер: узел с детьми (секция, шаг визарда, html-тег). Текст — такой же ребёнок, как узел:
|
|
158
|
+
* ядро его не интерпретирует, а рендерер выводит на своём месте в порядке следования.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* { component: Section, componentProps: { title: 'Контакты' }, children: [phoneField, emailField] }
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @group Types
|
|
166
|
+
*/
|
|
167
|
+
export interface SchemaContainerNode extends SchemaNodeBase {
|
|
168
|
+
children?: readonly SchemaChild[];
|
|
169
|
+
model?: never;
|
|
170
|
+
item?: never;
|
|
171
|
+
part?: never;
|
|
105
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Узел схемы формы: поле, массив под-форм, подформа или контейнер — см. описание модуля.
|
|
175
|
+
*
|
|
176
|
+
* Тип закрыт: опечатка в ключе, группа без `part` и `item` не на массиве — ошибки компиляции.
|
|
177
|
+
*
|
|
178
|
+
* @group Types
|
|
179
|
+
*/
|
|
180
|
+
export type FormSchemaNode = SchemaFieldNode | SchemaArrayNode | SchemaPartNode | SchemaContainerNode;
|
|
181
|
+
export {};
|
|
@@ -1,36 +1,32 @@
|
|
|
1
|
-
import { FormValue, ValidationError } from './index';
|
|
2
|
-
import { FormModel } from '../../model/types';
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Типы фабрик валидаторов (`@reformer/core/validators`).
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
* `validateModel(model, schema)`. Cross-field — оператор `cross(sig, (f) => …)` над снапшотом
|
|
10
|
-
* `model.get()` (третий аргумент `root` больше не нужен). Тип экспортируется только ради
|
|
11
|
-
* обратной совместимости (`SchemaValidator`-union).
|
|
4
|
+
* Контракт валидации — `@reformer/core/validation`: `defineValidation` + операторы
|
|
5
|
+
* (`validate`/`validateAsync`/`validateWhen`/`cross`/`apply`/`applyEach`) и раннер
|
|
6
|
+
* `validateModel(model, definition)`; правила там — `Rule<T> = (value) => issue | null`.
|
|
12
7
|
*/
|
|
13
|
-
export type Validator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => ValidationError | null;
|
|
14
8
|
/**
|
|
15
9
|
* Опции валидатора-фабрики (`required()`/`pattern()`/…). Передаются вторым (или последним)
|
|
16
|
-
* аргументом в фабрику и попадают в
|
|
10
|
+
* аргументом в фабрику и попадают в возвращаемое {@link ValidationIssue}.
|
|
11
|
+
*
|
|
12
|
+
* Текста среди опций нет: правило сообщает только код и параметры, а строку по коду даёт слой
|
|
13
|
+
* представления (`resolveIssueMessage` из `@reformer/core/i18n`). Нужен свой текст для
|
|
14
|
+
* конкретного поля — дайте правилу свой код и запись в словаре формы.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // правило
|
|
19
|
+
* required({ code: 'loanAmount.required' });
|
|
20
|
+
* // словарь формы (`defineIssueMessages`)
|
|
21
|
+
* const messages = defineIssueMessages({ 'loanAmount.required': 'Укажите сумму кредита' });
|
|
22
|
+
* ```
|
|
17
23
|
*/
|
|
18
|
-
export interface
|
|
19
|
-
/** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст
|
|
20
|
-
* берётся из словаря локали по `code` (`validation.<code>`; см. `resolveValidationError` в
|
|
21
|
-
* `@reformer/core/i18n`). Явное сообщение важнее словаря, но не переключается вместе с языком —
|
|
22
|
-
* для локализуемого текста используйте {@link ValidateOptions.messageKey}. */
|
|
23
|
-
message?: string;
|
|
24
|
+
export interface RuleOptions {
|
|
24
25
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```ts
|
|
30
|
-
* required({ messageKey: 'profile.name.required', message: 'Name is required' });
|
|
31
|
-
* ```
|
|
26
|
+
* Свой код вместо встроенного. По нему текст ищется сначала в словаре формы, затем в локали
|
|
27
|
+
* (`validation.<code>`); принято именовать `<поле>.<правило>`.
|
|
32
28
|
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
params?: Record<string,
|
|
29
|
+
code?: string;
|
|
30
|
+
/** Дополнительные параметры замечания: подставляются в текст поверх параметров правила. */
|
|
31
|
+
params?: Readonly<Record<string, unknown>>;
|
|
36
32
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { FormModel, PathAwareSignal
|
|
1
|
+
import { FormModel, PathAwareSignal } from '../../model/types';
|
|
2
|
+
import { ValidationIssue } from '../types/contracts';
|
|
3
|
+
import { ValidationFailure } from './types';
|
|
2
4
|
/** Сток одного прогона: модель scope, накопленные ошибки, незавершённые async-правила, гейты. */
|
|
3
5
|
export interface VContext {
|
|
4
6
|
/**
|
|
@@ -6,16 +8,39 @@ export interface VContext {
|
|
|
6
8
|
* это вся модель; внутри `apply(ручка, схема)` / `applyEach` — под-модель группы или строки.
|
|
7
9
|
*/
|
|
8
10
|
model: FormModel<unknown>;
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Накопленные ошибки по сигналу (ключ — идентичность `PathAwareSignal`). Здесь и поля выключенной
|
|
13
|
+
* ветки `validateWhen` — с пустым списком: при разносе их прежние ошибки гасятся.
|
|
14
|
+
*/
|
|
15
|
+
issues: Map<PathAwareSignal<unknown>, ValidationIssue[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Поля, правила которых в этом прогоне действительно выполнялись — ветка активна. Только их разнос
|
|
18
|
+
* с `touch` помечает тронутыми: поле выключенной ветки пользователь не видел.
|
|
19
|
+
*/
|
|
20
|
+
checked: Set<PathAwareSignal<unknown>>;
|
|
13
21
|
/** Незавершённые async-правила (раннер их дожидается). */
|
|
14
22
|
pending: Promise<void>[];
|
|
23
|
+
/** Поля с async-правилами: на время ожидания раннер держит их ноды в `pending`. */
|
|
24
|
+
asyncFields: Set<PathAwareSignal<unknown>>;
|
|
25
|
+
/**
|
|
26
|
+
* Правила, которые не вернули результат. Список — один на весь прогон: области `apply` /
|
|
27
|
+
* `applyEach` получают поверхностную копию контекста и пишут в ту же ссылку.
|
|
28
|
+
*/
|
|
29
|
+
failures: ValidationFailure[];
|
|
15
30
|
/** Стек активных условий `validateWhen` (все должны быть истинны, чтобы правило сработало). */
|
|
16
31
|
whenStack: Array<() => boolean>;
|
|
17
32
|
/** Отмена устаревшего прогона: прокидывается в async-правила, гасит роутинг после await. */
|
|
18
33
|
signal: AbortSignal;
|
|
34
|
+
/**
|
|
35
|
+
* Определение, правила которого исполняются сейчас: корень прогона либо схема, подключённая
|
|
36
|
+
* `apply` / `applyEach`. Замечание помечается им: так разнос отличает замечания одного
|
|
37
|
+
* определения, полученные разными прогонами (шаг и вся форма), от замечаний чужого.
|
|
38
|
+
*/
|
|
39
|
+
origin: object;
|
|
40
|
+
/** Замечания по полю и определению-источнику. Одна ссылка на весь прогон, как `failures`. */
|
|
41
|
+
origins: Map<PathAwareSignal<unknown>, Map<object, ValidationIssue[]>>;
|
|
42
|
+
/** Определения, исполненные в прогоне: за их замечания на проверенных полях прогон отвечает. */
|
|
43
|
+
executed: Set<object>;
|
|
19
44
|
}
|
|
20
45
|
/**
|
|
21
46
|
* Открыть ambient-окно на время `fn` и закрыть его в `finally`.
|
|
@@ -26,7 +51,17 @@ export interface VContext {
|
|
|
26
51
|
export declare function runWithContext<R>(ctx: VContext, fn: () => R): R;
|
|
27
52
|
/** Контекст активного прогона либо понятная ошибка, если оператор вызван вне схемы. */
|
|
28
53
|
export declare function requireCtx(op: string): VContext;
|
|
29
|
-
/**
|
|
30
|
-
|
|
54
|
+
/**
|
|
55
|
+
* «Тронуть» сигнал: гарантирует его наличие в замечаниях прогона (даже пустым) → он попадёт в
|
|
56
|
+
* очистку. Возвращает список текущего определения ({@link VContext.origin}): в него и пишут правила.
|
|
57
|
+
*/
|
|
58
|
+
export declare function touch(ctx: VContext, sig: PathAwareSignal<unknown>): ValidationIssue[];
|
|
59
|
+
/** Исполнить определение: его замечания помечаются им же. */
|
|
60
|
+
export declare function withOrigin(ctx: VContext, origin: object, fn: () => void): void;
|
|
31
61
|
/** Активна ли текущая ветка (все `validateWhen`-условия истинны). */
|
|
32
62
|
export declare const gated: (ctx: VContext) => boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Правила поля выполняются: ветка активна. Поле запоминается проверенным ({@link VContext.checked}).
|
|
65
|
+
* Без активной ветки — `false`, и поле остаётся только в `issues` для гашения.
|
|
66
|
+
*/
|
|
67
|
+
export declare function checking(ctx: VContext, sig: PathAwareSignal<unknown>): boolean;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Декларативный контракт СХЕМЫ ВАЛИДАЦИИ — `@reformer/core/validation`.
|
|
3
3
|
*
|
|
4
|
-
* Схема валидации — обычная функция `(
|
|
5
|
-
* (`validate`/`validateAsync`/`validateWhen`/`
|
|
6
|
-
* текущего прогона — автор не видит ни коллектора, ни `getNodeForSignal`, ни `.push`.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Схема валидации — обычная функция `({ model, cross }) => void`. Внутри вызываются свободные операторы
|
|
5
|
+
* (`validate`/`validateAsync`/`validateWhen`/`apply`/`applyEach`) и `cross` области, которые САМИ пишут ошибки в
|
|
6
|
+
* ambient-сток текущего прогона — автор не видит ни коллектора, ни `getNodeForSignal`, ни `.push`.
|
|
7
|
+
* Раннер открывает ambient-окно на время СИНХРОННОГО прогона схемы и дожидается async-правил
|
|
8
|
+
* ({@link runValidation} — сбор результата), затем разносит ошибки по нодам формы, гася поля, ставшие
|
|
9
|
+
* валидными ({@link applyValidationResult}); {@link validateModel} делает оба шага и отвечает `boolean`.
|
|
9
10
|
*
|
|
10
11
|
* Зеркалит контракт поведения (`@reformer/core/behaviors`): тот же ambient-стиль, но отдельный слой —
|
|
11
12
|
* валидация НЕ реактивна (прогон по требованию: submit/шаг), поведение — реактивно (живые подписки).
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
* @group Validation
|
|
18
19
|
* @module form/validation
|
|
19
20
|
*/
|
|
20
|
-
export type { Rule, AsyncRule,
|
|
21
|
-
export { validate, validateAsync, validateWhen,
|
|
22
|
-
export {
|
|
21
|
+
export type { Rule, AsyncRule, ValidationDefinition, ValidationScope, ValidationStatus, ValidationFailure, ValidationResult, } from './types';
|
|
22
|
+
export { validate, validateAsync, validateWhen, apply, applyEach } from './operators';
|
|
23
|
+
export { defineValidation, runValidation, applyValidationResult, validateModel } from './run';
|
|
23
24
|
export * from './strategy';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FormModel, ModelArray, PathAwareSignal
|
|
2
|
-
import { AsyncRule, Rule,
|
|
1
|
+
import { FormModel, ModelArray, PathAwareSignal } from '../../model/types';
|
|
2
|
+
import { AsyncRule, Rule, ValidationDefinition, ValidationScope } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Синхронные правила поля.
|
|
5
5
|
*
|
|
@@ -8,38 +8,39 @@ import { AsyncRule, Rule, ValidationSchema } from './types';
|
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```ts
|
|
11
|
-
* validate(model.$.loanAmount, [required({
|
|
11
|
+
* validate(model.$.loanAmount, [required({ code: 'loanAmount.required' }), min(50000)]);
|
|
12
12
|
* validate(model.$.tags, [required(), maxLength(3)]); // tags: string[] — массив как значение
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
export declare function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void;
|
|
16
16
|
/**
|
|
17
|
-
* Асинхронные правила
|
|
18
|
-
*
|
|
17
|
+
* Асинхронные правила поля. Раннер дожидается их и прокидывает `AbortSignal` для отмены
|
|
18
|
+
* устаревших ответов.
|
|
19
|
+
*
|
|
20
|
+
* Отклонённое правило (сеть, исключение) — сбой проверки: он попадает в `failures` результата
|
|
21
|
+
* прогона, статус прогона становится `error`, а на поле ложится блокирующая ошибка `ruleFailed`
|
|
22
|
+
* (текст — из словаря локали). Отмена устаревшего прогона сбоем не считается.
|
|
19
23
|
*
|
|
20
24
|
* @example
|
|
21
25
|
* ```ts
|
|
22
26
|
* validateAsync(model.$.username, [async (v, { signal }) => {
|
|
23
27
|
* const r = await fetch(`/api/free?u=${v}`, { signal });
|
|
24
|
-
* return (await r.json()).free ? null : { code: 'taken'
|
|
28
|
+
* return (await r.json()).free ? null : { code: 'taken' };
|
|
25
29
|
* }]);
|
|
26
30
|
* ```
|
|
27
31
|
*/
|
|
28
32
|
export declare function validateAsync<TField>(sig: PathAwareSignal<TField>, rules: AsyncRule<TField>[]): void;
|
|
29
33
|
/**
|
|
30
34
|
* Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля
|
|
31
|
-
* ГАСЯТСЯ (пустой bucket → `
|
|
35
|
+
* ГАСЯТСЯ (пустой bucket → `setIssues([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).
|
|
32
36
|
*/
|
|
33
37
|
export declare function validateWhen(cond: () => boolean, cb: () => void): void;
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
36
|
-
* ошибку на `sig`.
|
|
39
|
+
* Область схемы над моделью: сама модель и `cross`, снимок которого — эта модель.
|
|
37
40
|
*
|
|
38
|
-
*
|
|
39
|
-
* подключённой через {@link apply} / {@link applyEach}, — под-модель группы или строки массива.
|
|
40
|
-
* Поэтому правила подформы пишутся один раз и не знают, где она стоит.
|
|
41
|
+
* @internal
|
|
41
42
|
*/
|
|
42
|
-
export declare function
|
|
43
|
+
export declare function scopeOf<T>(model: FormModel<T>): ValidationScope<T>;
|
|
43
44
|
/** Привязка к группе: ручка `model.$.<группа>` (тип значения — из её `peek()`). */
|
|
44
45
|
interface GroupBinding<V> {
|
|
45
46
|
peek(): V;
|
|
@@ -50,16 +51,16 @@ interface ArrayBinding<U> {
|
|
|
50
51
|
}
|
|
51
52
|
/**
|
|
52
53
|
* Применить схему к КАЖДОМУ элементу массива под-форм. Схема элемента — обычная
|
|
53
|
-
* `
|
|
54
|
+
* `ValidationDefinition` над типом элемента: тот же набор правил подключается и к группе через
|
|
54
55
|
* {@link apply}.
|
|
55
56
|
*
|
|
56
57
|
* У каждого элемента своя область: `cross` внутри схемы получает снапшот элемента.
|
|
57
58
|
*
|
|
58
59
|
* @example
|
|
59
60
|
* ```ts
|
|
60
|
-
* const propertyRules =
|
|
61
|
+
* const propertyRules = defineValidation<Property>(({ model, cross }) => {
|
|
61
62
|
* validate(model.$.type, [required()]);
|
|
62
|
-
* cross
|
|
63
|
+
* cross(model.$.estimatedValue, (property) =>
|
|
63
64
|
* property.hasEncumbrance && property.estimatedValue < 100_000 ? tooCheap : null
|
|
64
65
|
* );
|
|
65
66
|
* });
|
|
@@ -67,16 +68,7 @@ interface ArrayBinding<U> {
|
|
|
67
68
|
* applyEach(model.$.properties, propertyRules);
|
|
68
69
|
* ```
|
|
69
70
|
*/
|
|
70
|
-
export declare function applyEach<U extends object>(array: ArrayBinding<U> | ModelArray<U>, schema:
|
|
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;
|
|
71
|
+
export declare function applyEach<U extends object>(array: ArrayBinding<U> | ModelArray<U>, schema: ValidationDefinition<U>): void;
|
|
80
72
|
/**
|
|
81
73
|
* Подключить схему подформы к группе модели — одной или нескольким.
|
|
82
74
|
*
|
|
@@ -85,7 +77,7 @@ export declare function each<U extends object>(arr: ArrayBinding<U> | ModelArray
|
|
|
85
77
|
*
|
|
86
78
|
* @example
|
|
87
79
|
* ```ts
|
|
88
|
-
* const addressRules =
|
|
80
|
+
* const addressRules = defineValidation<Address>(({ model }) => {
|
|
89
81
|
* validate(model.$.city, [required()]);
|
|
90
82
|
* });
|
|
91
83
|
*
|
|
@@ -96,14 +88,14 @@ export declare function each<U extends object>(arr: ArrayBinding<U> | ModelArray
|
|
|
96
88
|
* );
|
|
97
89
|
* ```
|
|
98
90
|
*/
|
|
99
|
-
export declare function apply<V extends object | null | undefined>(group: GroupBinding<V> | GroupBinding<V>[], schema:
|
|
91
|
+
export declare function apply<V extends object | null | undefined>(group: GroupBinding<V> | GroupBinding<V>[], schema: ValidationDefinition<NonNullable<V>>): void;
|
|
100
92
|
/**
|
|
101
93
|
* Композиция схем над ТОЙ ЖЕ моделью области — так форма собирается из схем шагов.
|
|
102
94
|
*
|
|
103
95
|
* @example
|
|
104
96
|
* ```ts
|
|
105
|
-
* const formRules =
|
|
97
|
+
* const formRules = defineValidation<Form>(() => apply(loanRules, contactsRules));
|
|
106
98
|
* ```
|
|
107
99
|
*/
|
|
108
|
-
export declare function apply<T>(...schemas:
|
|
100
|
+
export declare function apply<T>(...schemas: ValidationDefinition<T>[]): void;
|
|
109
101
|
export {};
|