@reformer/renderer-json 8.0.0 → 9.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -0
- package/dist/converter/json-to-render-schema.test.d.ts +1 -0
- package/dist/index-Bu5XPCQ7.js +41 -0
- package/dist/index.js +102 -88
- package/dist/schema/form-schema.schema.json.d.ts +8 -5
- package/dist/schema/index.d.ts +7 -3
- package/dist/validate.js +665 -648
- package/llms.txt +1929 -0
- package/package.json +3 -2
- package/dist/index-BZLcW0SX.js +0 -42
package/llms.txt
ADDED
|
@@ -0,0 +1,1929 @@
|
|
|
1
|
+
# ReFormer Renderer Json - LLM Integration Guide
|
|
2
|
+
# AUTO-GENERATED. Edit docs/llms/*.md or JSDoc in src/ and run npm run generate:llms.
|
|
3
|
+
|
|
4
|
+
> JSON-based form renderer for @reformer ecosystem
|
|
5
|
+
> Package: @reformer/renderer-json • Version: 6.0.0
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
- 01-overview.md — Overview
|
|
9
|
+
- 02-json-schema.md — JSON Schema
|
|
10
|
+
- 03-registry.md — Component Registry
|
|
11
|
+
- 04-troubleshooting.md — Troubleshooting / FAQ
|
|
12
|
+
- 05-cookbook.md — Cookbook
|
|
13
|
+
- 06-validation.md — Validation
|
|
14
|
+
- 07-form-wizard.md — FormWizard
|
|
15
|
+
- API Reference (auto-generated from JSDoc)
|
|
16
|
+
|
|
17
|
+
## 1. Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @reformer/renderer-json @reformer/renderer-react @reformer/core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Опционально для готовых UI-компонентов:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @reformer/ui-kit
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 2. Import Patterns
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// recommended
|
|
33
|
+
import {
|
|
34
|
+
JsonFormRenderer,
|
|
35
|
+
JsonRendererProvider,
|
|
36
|
+
defineRegistry,
|
|
37
|
+
FIELD_WRAPPER,
|
|
38
|
+
convertJsonToM1Tree,
|
|
39
|
+
type JsonFormSchema,
|
|
40
|
+
} from '@reformer/renderer-json';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 3. Quick Start
|
|
44
|
+
|
|
45
|
+
Ключевая идея M1: **модель (`FormModel`) — источник данных, JSON-схема — layout**. Форма строится из той же JSON-схемы через `convertJsonToM1Tree`, а `JsonFormRenderer` получает модель через `JsonRendererProvider` settings (`model`). `JsonFormRenderer` НЕ имеет `form`-пропа — это by-design: JSON статичен, модель runtime.
|
|
46
|
+
|
|
47
|
+
Минимальный рабочий монтаж:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { useMemo } from 'react';
|
|
51
|
+
import { createForm, createModel } from '@reformer/core';
|
|
52
|
+
import { Input, Box, FormField } from '@reformer/ui-kit';
|
|
53
|
+
import {
|
|
54
|
+
JsonFormRenderer,
|
|
55
|
+
JsonRendererProvider,
|
|
56
|
+
defineRegistry,
|
|
57
|
+
FIELD_WRAPPER,
|
|
58
|
+
convertJsonToM1Tree,
|
|
59
|
+
type JsonFormSchema,
|
|
60
|
+
} from '@reformer/renderer-json';
|
|
61
|
+
|
|
62
|
+
type MyForm = { email: string };
|
|
63
|
+
|
|
64
|
+
// 1. JSON-схема — чистые данные, операторы-строки, никаких React-импортов.
|
|
65
|
+
const jsonSchema: JsonFormSchema = {
|
|
66
|
+
version: '1.0',
|
|
67
|
+
root: {
|
|
68
|
+
component: '$component(Box)',
|
|
69
|
+
children: [
|
|
70
|
+
{ selector: 'email', value: '$model(email)', component: '$component(Input)',
|
|
71
|
+
componentProps: { label: 'Email' } },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 2. Реестр: имена из JSON → React-компоненты.
|
|
77
|
+
const registry = defineRegistry((reg) => {
|
|
78
|
+
reg.component('Input', Input);
|
|
79
|
+
reg.component('Box', Box);
|
|
80
|
+
reg.component(FIELD_WRAPPER, FormField);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function MyFormPage() {
|
|
84
|
+
// 3. Модель + форма строятся ИЗ JSON-схемы (единая схема, без отдельной схемы формы).
|
|
85
|
+
const { model } = useMemo(() => {
|
|
86
|
+
const model = createModel<MyForm>({ email: '' });
|
|
87
|
+
createForm<MyForm>({ model, schema: convertJsonToM1Tree(jsonSchema, registry, model) });
|
|
88
|
+
return { model };
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
// 4. Модель прокидывается через провайдер; JsonFormRenderer биндит листья к её сигналам.
|
|
92
|
+
return (
|
|
93
|
+
<JsonRendererProvider settings={{ registry, model }}>
|
|
94
|
+
<JsonFormRenderer<MyForm> schema={jsonSchema} />
|
|
95
|
+
</JsonRendererProvider>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Почему `model` через провайдер, а не `<JsonFormRenderer form={...}/>`?** Под M1 листья схемы (`value: '$model(path)'`) биндятся к сигналам модели (`model.signalAt(path)`) конвертером. Модель обязательна и передаётся через `JsonRendererProvider` settings — `JsonFormRenderer` без неё бросит `settings.model is required (M1)`. Сам рендерер принимает только `{ schema, renderBehavior?, onSchemaReady?, validate? }`.
|
|
101
|
+
|
|
102
|
+
## 4. Key Concepts
|
|
103
|
+
|
|
104
|
+
- **JSON-схема** — дерево `JsonNode` (см. [02-json-schema.md](02-json-schema.md)). Узлы: **field** (`value: '$model(...)'`), **array** (`array` + `item.$template`), **container** (`component` + `children`).
|
|
105
|
+
- **Операторы** — строки `$model(path)` / `$component(Name)` / `$dataSource(NAME)`. Только они резолвятся; голые строки идут как есть.
|
|
106
|
+
- **Модель (`model`)** — `FormModel`, источник данных. Передаётся в `JsonRendererProvider` settings; листья биндятся к её сигналам.
|
|
107
|
+
- **Реестр** — карта имени из `$component(...)`/`$dataSource(...)` на React-компонент или source-значение. Без регистрации схема не сконвертируется (ошибка `Component "X" not found in registry`).
|
|
108
|
+
- **`FIELD_WRAPPER`** — зарезервированный ключ реестра (`'$fieldWrapper'`) для компонента-обёртки полей (label, error, hint). Обычно `FormField` из `@reformer/ui-kit`.
|
|
109
|
+
- **`convertJsonToM1Tree`** — конвертер JSON → RenderNode-дерево для `createForm({ model, schema })`.
|
|
110
|
+
- **`renderBehavior`** — TS-функция `RenderBehaviorFn<T>` (hideWhen/patchProps/onInit), применяется поверх готовой схемы; в JSON поведение не выражается.
|
|
111
|
+
|
|
112
|
+
## 5. Components and exports
|
|
113
|
+
|
|
114
|
+
| Export | Purpose |
|
|
115
|
+
| ----------------------------------------------- | -------------------------------------------------------------------------- |
|
|
116
|
+
| `JsonFormRenderer` | Главный компонент-рендерер. Пропы: `{ schema, renderBehavior?, onSchemaReady?, validate? }`. |
|
|
117
|
+
| `JsonRendererProvider` | Контекст-провайдер: реестр (`registry`), модель (`model`), настройки. |
|
|
118
|
+
| `useJsonRendererSettings` | Хук для чтения текущих настроек контекста. |
|
|
119
|
+
| `defineRegistry` | Builder реестра компонентов и dataSource-значений. |
|
|
120
|
+
| `FIELD_WRAPPER` | Ключ реестра (`'$fieldWrapper'`) для компонента-обёртки полей. |
|
|
121
|
+
| `JsonFormSchema`, `JsonNode` | Типы JSON-схемы (`JsonFieldNode`/`JsonArrayNode`/`JsonContainerNode`). |
|
|
122
|
+
| `isFieldNode`, `isArrayNode`, `isContainerNode` | Type guards для узлов. |
|
|
123
|
+
| `parseOperator`, `isModelOp`, `isComponentOp`, `isDataSourceOp` | Разбор и type-guards строк-операторов. |
|
|
124
|
+
| `ModelOp`, `ComponentOp`, `DataSourceOp` | Template-literal типы операторов. |
|
|
125
|
+
| `convertJsonToM1Tree` | JSON → сырое RenderNode-дерево (для `createForm({ model, schema })`). |
|
|
126
|
+
| `createRenderSchemaFromJsonM1` | JSON → `RenderSchemaFn` (низкоуровневый, для `FormRenderer`/`JsonFormRenderer`). |
|
|
127
|
+
| `SchemaErrorPanel` | Панель ошибок валидации схемы (рисуется при `validate` + невалидной схеме). |
|
|
128
|
+
| `formSchemaMetaSchema`, `buildFormSchemaMetaSchema`, `getComponentNames`, `getDataSourceNames` | Мета-схема form-DSL + утилиты (ajv-free). |
|
|
129
|
+
|
|
130
|
+
> `validateFormSchema` живёт в отдельной точке входа `@reformer/renderer-json/validate` (тянет ajv, не попадает в render-бандл). `JsonFormRenderer` грузит её динамически при `validate={true}`.
|
|
131
|
+
|
|
132
|
+
## 6. See also
|
|
133
|
+
|
|
134
|
+
- [02-json-schema.md](02-json-schema.md) — формат `JsonFormSchema`/`JsonNode` и синтаксис операторов.
|
|
135
|
+
- [03-registry.md](03-registry.md) — как наполнять реестр.
|
|
136
|
+
- [04-troubleshooting.md](04-troubleshooting.md) — частые ошибки.
|
|
137
|
+
- [05-cookbook.md](05-cookbook.md) — массивы, dataSource-функции, миграция из TS RenderSchema.
|
|
138
|
+
|
|
139
|
+
## 7. Key Concepts
|
|
140
|
+
|
|
141
|
+
- **`JsonFormSchema`** — корневой документ: `version` (для миграций), опциональный `$schema` (путь к мета-схеме для IDE), единственный корневой узел `root`.
|
|
142
|
+
- **`JsonNode`** — узел дерева. Дискриминированный union по строке-оператору, которую он несёт:
|
|
143
|
+
- **field-node** (`JsonFieldNode`) — лист: `value: '$model(path)'` + опциональный `component: '$component(Name)'` (дефолт — Input). Не имеет `children`. Несёт **только layout** — валидаторов в JSON нет, оператора `$validator(...)` не существует. Валидация значений — отдельная TS-схема над моделью, см. [06-validation.md](06-validation.md).
|
|
144
|
+
- **array-node** (`JsonArrayNode`) — массив: `array: '$model(path)'` + `item: { $template: <JsonNode> }` + опциональный `initialValue` (литерал нового элемента для кнопки «Добавить»).
|
|
145
|
+
- **container-node** (`JsonContainerNode`) — контейнер (Box/Section/Wizard/Step): `component: '$component(Name)'` + опциональные `children`.
|
|
146
|
+
- **Операторы** — единственный способ привязки (см. [`operators.ts`](../../src/operators.ts)):
|
|
147
|
+
- `'$model(path)'` — путь к полю/массиву модели (лист → `model.signalAt(path)`, массив → value-прокси массива).
|
|
148
|
+
- `'$component(Name)'` — имя компонента в реестре (`reg.component`).
|
|
149
|
+
- `'$dataSource(NAME)'` — имя registry-source (`reg.dataSource`): options, itemLabel, константы, loading-компоненты.
|
|
150
|
+
- **`selector`** — plain-строка, id узла для render-behavior (`schema.node('…')`, `hideWhen`, `patchProps`). **Не** путь модели.
|
|
151
|
+
- **`componentProps`** — что прокидывается в React-компонент. Значения могут содержать строки-операторы (`'$dataSource(NAME)'`, `'$model(...)'`, `'$component(...)'`) или вложенные `JsonNode` — конвертер резолвит их рекурсивно. Обычные значения (числа, инлайн-массивы options, `label`) идут как есть.
|
|
152
|
+
|
|
153
|
+
## 8. Type Guards
|
|
154
|
+
|
|
155
|
+
Порядок важен: `isArrayNode` проверяй **первым** (array-node тоже несёт `$model`, но в поле `array`).
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { isArrayNode, isFieldNode, isContainerNode, type JsonNode } from '@reformer/renderer-json';
|
|
159
|
+
|
|
160
|
+
function inspect(node: JsonNode) {
|
|
161
|
+
if (isArrayNode(node)) {
|
|
162
|
+
// node.array: '$model(...)', node.item.$template: JsonNode
|
|
163
|
+
} else if (isFieldNode(node)) {
|
|
164
|
+
// node.value: '$model(...)', node.component?: '$component(...)'
|
|
165
|
+
} else if (isContainerNode(node)) {
|
|
166
|
+
// node.component: '$component(...)', node.children?: JsonNode[]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 9. Examples
|
|
172
|
+
|
|
173
|
+
Минимальная схема с одним полем (лист `value` + контейнер `Box`):
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import type { JsonFormSchema } from '@reformer/renderer-json';
|
|
177
|
+
|
|
178
|
+
const schema: JsonFormSchema = {
|
|
179
|
+
version: '1.0',
|
|
180
|
+
root: {
|
|
181
|
+
component: '$component(Box)',
|
|
182
|
+
children: [
|
|
183
|
+
{
|
|
184
|
+
selector: 'email',
|
|
185
|
+
value: '$model(email)',
|
|
186
|
+
component: '$component(Input)',
|
|
187
|
+
componentProps: { label: 'Email' },
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Вложенный путь к полю и ссылка на dataSource-константу в `componentProps`:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
{
|
|
198
|
+
value: '$model(personalData.firstName)',
|
|
199
|
+
component: '$component(Input)',
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
{
|
|
203
|
+
value: '$model(loanType)',
|
|
204
|
+
component: '$component(Select)',
|
|
205
|
+
componentProps: { label: 'Тип кредита', options: '$dataSource(LOAN_TYPES)' },
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Массив с шаблоном элемента (`array` + `item.$template` + `initialValue`). Внутри `$template` пути `$model(...)` резолвятся **относительно элемента** массива:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
{
|
|
213
|
+
selector: 'properties-array',
|
|
214
|
+
array: '$model(properties)',
|
|
215
|
+
initialValue: { type: 'apartment', description: '', estimatedValue: 0, hasEncumbrance: false },
|
|
216
|
+
componentProps: {
|
|
217
|
+
title: 'Имущество',
|
|
218
|
+
addButtonLabel: '+ Добавить имущество',
|
|
219
|
+
itemLabel: '$dataSource(PROPERTY_ITEM_LABEL_SOURCE_FN)',
|
|
220
|
+
},
|
|
221
|
+
item: {
|
|
222
|
+
$template: {
|
|
223
|
+
component: '$component(Box)',
|
|
224
|
+
componentProps: { className: 'space-y-3' },
|
|
225
|
+
children: [
|
|
226
|
+
{ value: '$model(type)', component: '$component(Select)',
|
|
227
|
+
componentProps: { label: 'Тип', options: '$dataSource(PROPERTY_TYPES)' } },
|
|
228
|
+
{ value: '$model(estimatedValue)', component: '$component(Input)',
|
|
229
|
+
componentProps: { label: 'Стоимость', type: 'number' } },
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 10. Anti-patterns
|
|
237
|
+
|
|
238
|
+
- **Голые строки вместо операторов** — `component: 'Input'` или `value: 'email'` не резолвятся. Нужны операторы: `component: '$component(Input)'`, `value: '$model(email)'`. Template-literal типы (`ModelOp`/`ComponentOp`) отловят это на этапе компиляции.
|
|
239
|
+
- **Использовать `selector` как путь к полю** — `selector` это id для behavior; путь задаётся только через `value`/`array` оператором `$model(...)`.
|
|
240
|
+
- **Забыть `item.$template` у массива** — array-node требует `array` **и** `item: { $template }`. Без `$template` `isArrayNode` вернёт false и узел не отрендерится как массив.
|
|
241
|
+
- **`initialValue` как FieldConfig** — это plain-литерал по форме элемента (`{ field: value }`), а не `{ value, component }`. Клонируется через `JSON.parse(JSON.stringify(...))`, поэтому только сериализуемые значения.
|
|
242
|
+
- **Ссылаться на `$dataSource(NAME)` без регистрации** — при `validate` неизвестное имя даст ошибку; без валидации строка просто прокинется как есть (молчаливый баг).
|
|
243
|
+
- **Класть `validators` в field-node** — `JsonFieldNode` несёт только layout, поля `validators` в нём нет и оператора `$validator(...)` не существует. Валидация значений живёт в отдельной TS-схеме над моделью (`validateFormModel`), а не в JSON — см. [06-validation.md](06-validation.md).
|
|
244
|
+
|
|
245
|
+
## 11. See also
|
|
246
|
+
|
|
247
|
+
- [01-overview.md](01-overview.md) — как схема монтируется через `model` + `JsonRendererProvider`.
|
|
248
|
+
- [03-registry.md](03-registry.md) — какие компоненты и source можно зарегистрировать.
|
|
249
|
+
- [05-cookbook.md](05-cookbook.md) — `$template`, dataSource-функции, миграция из TS RenderSchema.
|
|
250
|
+
- [06-validation.md](06-validation.md) — валидация значений (TS-схема над моделью + инъекция в wizard).
|
|
251
|
+
- [Типы JsonFormSchema/JsonNode](../../src/types/json-schema.ts) и [операторы](../../src/operators.ts).
|
|
252
|
+
|
|
253
|
+
## 12. Key Concepts
|
|
254
|
+
|
|
255
|
+
- **component** — любой React-компонент, зарегистрированный под именем и доступный в схеме как `component: '$component(name)'`. Один метод `reg.component(name, Component)` регистрирует и компоненты-листья (Input/Select — узел несёт `value: '$model(path)'`), и контейнеры-обёртки (Box/Section/FormField — узел несёт `children`). Роль узла (лист vs контейнер) определяется **структурой узла в схеме** (`value` vs `children`), а не тем, как компонент зарегистрирован.
|
|
256
|
+
- **dataSource value** — именованная константа, функция или React-компонент, на которые ссылаются строкой `'$dataSource(NAME)'` из `componentProps`. Регистрируется через `reg.dataSource(name, value)`.
|
|
257
|
+
- **`FIELD_WRAPPER`** — зарезервированное имя (`'$fieldWrapper'`) для контейнера-обёртки полей (label, error, hint). Обычно регистрируется как `FormField` из `@reformer/ui-kit`.
|
|
258
|
+
|
|
259
|
+
## 13. Builder API
|
|
260
|
+
|
|
261
|
+
| Method | Purpose |
|
|
262
|
+
| -------------------------------- | ----------------------------------------------------- |
|
|
263
|
+
| `reg.component(name, Component)` | Регистрирует компонент (лист или контейнер — роль решает структура узла). |
|
|
264
|
+
| `reg.dataSource(name, value)` | Регистрирует dataSource-значение (константу или функцию). |
|
|
265
|
+
|
|
266
|
+
## 14. Examples
|
|
267
|
+
|
|
268
|
+
Минимальный реестр:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { defineRegistry, FIELD_WRAPPER } from '@reformer/renderer-json';
|
|
272
|
+
import { Input, Select, Box, FormField } from '@reformer/ui-kit';
|
|
273
|
+
|
|
274
|
+
const registry = defineRegistry((reg) => {
|
|
275
|
+
reg.component('Input', Input);
|
|
276
|
+
reg.component('Select', Select);
|
|
277
|
+
reg.component('Box', Box);
|
|
278
|
+
reg.component(FIELD_WRAPPER, FormField);
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
dataSource values для `componentProps` (в схеме — ссылка `'$dataSource(NAME)'`):
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const registry = defineRegistry((reg) => {
|
|
286
|
+
reg.component('Select', Select);
|
|
287
|
+
reg.dataSource('LOAN_TYPES', [
|
|
288
|
+
{ value: 'consumer', label: 'Потребительский' },
|
|
289
|
+
{ value: 'mortgage', label: 'Ипотека' },
|
|
290
|
+
]);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// В JSON-схеме (лист + операторы):
|
|
294
|
+
{
|
|
295
|
+
value: '$model(loanType)',
|
|
296
|
+
component: '$component(Select)',
|
|
297
|
+
componentProps: { options: '$dataSource(LOAN_TYPES)' },
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## 15. Anti-patterns
|
|
302
|
+
|
|
303
|
+
- **Забыть зарегистрировать `FIELD_WRAPPER`** — поля будут рендериться без обёртки (нет label/error). В большинстве случаев это ошибка.
|
|
304
|
+
- **Регистрировать React-element вместо компонента** — `reg.component('Input', <Input />)` не сработает, нужно передавать сам тип компонента: `reg.component('Input', Input)`.
|
|
305
|
+
- **Ожидать наследования между вложенными `JsonRendererProvider`** — реестр сливается через `withParent`, дубли решаются по приоритету (внешний > внутренний).
|
|
306
|
+
- **Использовать `$dataSource(NAME)` без регистрации** — без `validate` строка просто прокинется в проп как есть (молчаливый баг); с `validate` даст ошибку `unknown dataSource "NAME"`.
|
|
307
|
+
- **Ссылаться на dataSource как на компонент** — `component: '$component(LoadingState)'`, где `LoadingState` зарегистрирован через `reg.dataSource`, бросит `Entry "..." is a 'dataSource' and cannot be used as $component(...)`. dataSource — только для значений в `componentProps`.
|
|
308
|
+
|
|
309
|
+
## 16. See also
|
|
310
|
+
|
|
311
|
+
- [01-overview.md](01-overview.md) — как реестр прокидывается через `JsonRendererProvider`.
|
|
312
|
+
- [02-json-schema.md](02-json-schema.md) — где имена компонентов появляются в схеме (операторы `$component`/`$dataSource`).
|
|
313
|
+
|
|
314
|
+
## 17. Component "X" not found in registry
|
|
315
|
+
|
|
316
|
+
Имя из оператора `$component(X)` (или `$dataSource(X)`) не зарегистрировано в реестре. Проверь:
|
|
317
|
+
|
|
318
|
+
- `defineRegistry` действительно содержит `reg.component('X', ...)` или `reg.dataSource('X', ...)`.
|
|
319
|
+
- В схеме используется оператор, а не голая строка: `component: '$component(X)'`, а не `component: 'X'`.
|
|
320
|
+
- `JsonFormRenderer` обёрнут в `JsonRendererProvider` с этим реестром.
|
|
321
|
+
- Если используются вложенные провайдеры — реестр внутреннего провайдера наследуется через `withParent`, но дубли разрешаются в пользу внешнего.
|
|
322
|
+
|
|
323
|
+
## 18. Field renders without label/error
|
|
324
|
+
|
|
325
|
+
Не зарегистрирован контейнер с ключом `FIELD_WRAPPER`. Добавь:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { FIELD_WRAPPER } from '@reformer/renderer-json';
|
|
329
|
+
import { FormField } from '@reformer/ui-kit';
|
|
330
|
+
|
|
331
|
+
reg.component(FIELD_WRAPPER, FormField);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## 19. No model signal for "..." / settings.model is required
|
|
335
|
+
|
|
336
|
+
Два разных симптома одной причины — модель.
|
|
337
|
+
|
|
338
|
+
- `JsonFormRenderer: settings.model is required (M1)` — не передана модель. Под M1 листья схемы биндятся к сигналам `FormModel`; передай её в `JsonRendererProvider settings={{ registry, model }}`.
|
|
339
|
+
- `[JsonRenderer/M1] No model signal for "path"` (warn) — путь в `$model(path)` не соответствует структуре модели. Проверь: `value: '$model(personalData.firstName)'` — поле `firstName` реально существует внутри `personalData` в `createModel(...)` initial-значениях.
|
|
340
|
+
|
|
341
|
+
## 20. componentProps string passes through as plain string
|
|
342
|
+
|
|
343
|
+
Строка `'$dataSource(NAME)'` в `componentProps` ссылается на source, который не зарегистрирован — конвертер оставляет её как есть. Используй `reg.dataSource('NAME', value)` либо передавай значение литералом напрямую. Голые строки (без `$dataSource(...)`) намеренно не резолвятся — это обычные значения пропа (`label`, `placeholder`).
|
|
344
|
+
|
|
345
|
+
## 21. useJsonRendererSettings throws outside provider
|
|
346
|
+
|
|
347
|
+
`useJsonRendererSettings` в dev-режиме бросает, если вызван вне `JsonRendererProvider` **или** если в провайдере не передан `registry`. Оберни вызывающий компонент в провайдер с реестром.
|
|
348
|
+
|
|
349
|
+
## 22. "version" missing / invalid schema (при validate)
|
|
350
|
+
|
|
351
|
+
`validate={true}` прогоняет схему через мета-схему (ajv) + обход имён операторов. Типичные ошибки: узел не подходит ни под field/array/container (нет ни `value`, ни `array`+`item`, ни `component`), голая строка вместо оператора, неизвестное `$component(...)`/`$dataSource(...)` имя. Ошибки рисуются в `SchemaErrorPanel` вместо формы. `$model(...)`-пути мета-схема не проверяет (только синтаксис) — они динамичны.
|
|
352
|
+
|
|
353
|
+
## 23. Behavior selector matches nothing
|
|
354
|
+
|
|
355
|
+
`hideWhen`/`patchProps` ищут узел по `selector`. Убедись, что у узла он явно задан (`selector: 'mortgage-section'`), и что значение совпадает с тем, на которое смотрит behavior. `selector` — plain-строка, НЕ оператор.
|
|
356
|
+
|
|
357
|
+
## 24. $template inside array doesn't render rows
|
|
358
|
+
|
|
359
|
+
Массив — это array-node, а не container с `itemComponent`. Проверь:
|
|
360
|
+
|
|
361
|
+
- Узел использует `array: '$model(path)'` **и** `item: { $template: <JsonNode> }` (оба обязательны — иначе `isArrayNode` вернёт false).
|
|
362
|
+
- Внутри `$template` пути `$model(...)` заданы **относительно элемента** (`value: '$model(type)'`, а не `'$model(properties[0].type)'`).
|
|
363
|
+
- Есть `initialValue` (plain-литерал по форме элемента) — иначе кнопка «Добавить» создаст пустой элемент без сигналов для полей шаблона.
|
|
364
|
+
|
|
365
|
+
## 25. Массив рендерится без строк / пустой при добавлении
|
|
366
|
+
|
|
367
|
+
`initialValue` должен быть **полным** plain-объектом по форме элемента (все поля, что есть в `$template`). Если передать частичный объект (`{ type: 'apartment' }` без `estimatedValue`/`description`), под-модель нового элемента не получит сигналов для недостающих полей и они не отрендерятся. `initialValue` клонируется через `JSON.parse(JSON.stringify(...))` — только сериализуемые значения, никакого FieldConfig.
|
|
368
|
+
|
|
369
|
+
## 26. Toggle-видимость секции массива
|
|
370
|
+
|
|
371
|
+
Условный показ секции (напр. массив `properties` виден только когда `hasProperty === true`) делается **не** кастомным блоком, а через `renderBehavior`:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { hideWhen } from '@reformer/renderer-react';
|
|
375
|
+
|
|
376
|
+
const renderBehavior: RenderBehaviorFn<MyForm> = (schema) => {
|
|
377
|
+
hideWhen(schema.node('properties-array'), () => model.signalAt('hasProperty').value !== true);
|
|
378
|
+
};
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
`renderBehavior` передаётся пропом в `JsonFormRenderer`; узел адресуется по своему `selector`.
|
|
382
|
+
|
|
383
|
+
## 27. See also
|
|
384
|
+
|
|
385
|
+
- [01-overview.md](01-overview.md)
|
|
386
|
+
- [02-json-schema.md](02-json-schema.md)
|
|
387
|
+
- [03-registry.md](03-registry.md)
|
|
388
|
+
- [05-cookbook.md](05-cookbook.md)
|
|
389
|
+
|
|
390
|
+
## 28. Монтаж формы из JSON (M1) { #mounting }
|
|
391
|
+
|
|
392
|
+
**Problem.** JSON-схема статична, а данные и форма — runtime. Нужно связать их без React-glue на каждой странице.
|
|
393
|
+
|
|
394
|
+
**Solution.** Модель (`FormModel`) — источник данных, форма строится из **той же** JSON-схемы через `convertJsonToM1Tree`, а `JsonFormRenderer` получает модель через `JsonRendererProvider` settings. `JsonFormRenderer` не имеет `form`-пропа (by-design).
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
import { useMemo } from 'react';
|
|
398
|
+
import { createForm, createModel } from '@reformer/core';
|
|
399
|
+
import {
|
|
400
|
+
JsonFormRenderer,
|
|
401
|
+
JsonRendererProvider,
|
|
402
|
+
convertJsonToM1Tree,
|
|
403
|
+
type JsonFormSchema,
|
|
404
|
+
} from '@reformer/renderer-json';
|
|
405
|
+
import rawJsonSchema from './json-schema.json';
|
|
406
|
+
import { createRegistry } from './registry';
|
|
407
|
+
|
|
408
|
+
const jsonSchema = rawJsonSchema as unknown as JsonFormSchema; // «схема пришла строкой»
|
|
409
|
+
|
|
410
|
+
export function MyFormPage() {
|
|
411
|
+
const registry = useMemo(() => createRegistry(), []);
|
|
412
|
+
const { model } = useMemo(() => {
|
|
413
|
+
const model = createModel<MyForm>(initialValues);
|
|
414
|
+
// Форма строится из JSON: конвертер биндит листья к сигналам модели.
|
|
415
|
+
createForm<MyForm>({ model, schema: convertJsonToM1Tree(jsonSchema, registry, model) });
|
|
416
|
+
return { model };
|
|
417
|
+
}, [registry]);
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<JsonRendererProvider settings={{ registry, model }}>
|
|
421
|
+
<JsonFormRenderer<MyForm> schema={jsonSchema} validate={import.meta.env.DEV} />
|
|
422
|
+
</JsonRendererProvider>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Notes.**
|
|
428
|
+
|
|
429
|
+
- `convertJsonToM1Tree` бросает при битой схеме (неизвестный `$component`) **до** рендера. Оберни в try/catch, если хочешь показать `SchemaErrorPanel` вместо краша (см. `buildModelAndForm` в эталоне).
|
|
430
|
+
- `validate={import.meta.env.DEV}` — детекцию dev нельзя «запечь» в пакет; приложение передаёт значение из своего окружения.
|
|
431
|
+
- Поведение (compute/enableWhen/navigation) идёт в `createForm({ behavior })`; render-behavior (hideWhen/patchProps/onInit) — отдельным пропом `renderBehavior`.
|
|
432
|
+
|
|
433
|
+
## 29. $template для массивов { #template-arrays }
|
|
434
|
+
|
|
435
|
+
**Problem.** В JSON нельзя выразить функцию `(itemPath) => RenderNode` для item-шаблона. Массив должен остаться декларативным.
|
|
436
|
+
|
|
437
|
+
**Solution.** Array-node несёт `array: '$model(path)'` + `item: { $template: <JsonNode> }` + `initialValue`. Внутри `$template` пути `$model(...)` резолвятся **относительно элемента** массива. Массивы под M1 рендерятся native-веткой конвертера (`{ array, item }`) — отдельный контейнер-компонент не нужен.
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
{
|
|
441
|
+
selector: 'properties-array',
|
|
442
|
+
array: '$model(properties)',
|
|
443
|
+
initialValue: { type: 'apartment', description: '', estimatedValue: 0, hasEncumbrance: false },
|
|
444
|
+
componentProps: {
|
|
445
|
+
title: 'Имущество',
|
|
446
|
+
addButtonLabel: '+ Добавить имущество',
|
|
447
|
+
itemLabel: '$dataSource(PROPERTY_ITEM_LABEL_SOURCE_FN)',
|
|
448
|
+
emptyMessage: 'Нажмите "Добавить имущество"',
|
|
449
|
+
},
|
|
450
|
+
item: {
|
|
451
|
+
$template: {
|
|
452
|
+
component: '$component(Box)',
|
|
453
|
+
componentProps: { className: 'space-y-3' },
|
|
454
|
+
children: [
|
|
455
|
+
{ value: '$model(type)', component: '$component(Select)',
|
|
456
|
+
componentProps: { label: 'Тип', options: '$dataSource(PROPERTY_TYPES)' } },
|
|
457
|
+
{ value: '$model(estimatedValue)', component: '$component(Input)',
|
|
458
|
+
componentProps: { label: 'Стоимость', type: 'number' } },
|
|
459
|
+
{ value: '$model(description)', component: '$component(Textarea)',
|
|
460
|
+
componentProps: { label: 'Описание', rows: 2 } },
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Notes.**
|
|
468
|
+
|
|
469
|
+
- `array` **и** `item.$template` обязательны оба — иначе узел не считается array-node (`isArrayNode`).
|
|
470
|
+
- `initialValue` — полный plain-объект по форме элемента (все поля из `$template`). Клонируется через `JSON.parse(JSON.stringify(...))`; не FieldConfig. Частичный `initialValue` → у нового элемента нет сигналов для недостающих полей.
|
|
471
|
+
- Внутри `$template` пути относительны элементу (`'$model(type)'`, а не `'$model(properties[0].type)'`).
|
|
472
|
+
- Вложенный массив в массиве — новый array-node внутри `$template` со своим `array`/`item`.
|
|
473
|
+
|
|
474
|
+
## 30. dataSource-значения и функции { #datasource }
|
|
475
|
+
|
|
476
|
+
**Problem.** Нужно передать в проп массив options, функцию (`itemLabel: (form, index) => string`) или React-компонент (`LoadingComponent`) — а JSON хранит только примитивы и объекты.
|
|
477
|
+
|
|
478
|
+
**Solution.** Регистрируешь значение через `reg.dataSource('NAME', value)`, в JSON-схеме ссылаешься оператором `'$dataSource(NAME)'`. Конвертер при обходе `componentProps` подставит зарегистрированное значение.
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { createElement } from 'react';
|
|
482
|
+
import { defineRegistry } from '@reformer/renderer-json';
|
|
483
|
+
import { LoadingState, ErrorState } from '@reformer/ui-kit';
|
|
484
|
+
|
|
485
|
+
const registry = defineRegistry((reg) => {
|
|
486
|
+
// 1. Константа: массив options.
|
|
487
|
+
reg.dataSource('LOAN_TYPES', [
|
|
488
|
+
{ value: 'consumer', label: 'Потребительский' },
|
|
489
|
+
{ value: 'mortgage', label: 'Ипотека' },
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
// 2. React-компонент как dataSource (для AsyncBoundary.LoadingComponent).
|
|
493
|
+
reg.dataSource('LoadingState', LoadingState);
|
|
494
|
+
reg.dataSource('ErrorStateDefault', () => createElement(ErrorState, { error: 'Ошибка' }));
|
|
495
|
+
|
|
496
|
+
// 3. Функция: itemLabel для array-секции.
|
|
497
|
+
reg.dataSource('PROPERTY_ITEM_LABEL_SOURCE_FN', (_form, index: number) => `Имущество #${index + 1}`);
|
|
498
|
+
|
|
499
|
+
// 4. Computed-константа.
|
|
500
|
+
reg.dataSource('CURRENT_YEAR_PLUS_ONE', new Date().getFullYear() + 1);
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
// В JSON-схеме — ссылки операторами:
|
|
506
|
+
{
|
|
507
|
+
selector: 'data-boundary',
|
|
508
|
+
component: '$component(AsyncBoundary)',
|
|
509
|
+
componentProps: {
|
|
510
|
+
status: 'loading',
|
|
511
|
+
LoadingComponent: '$dataSource(LoadingState)', // → React-компонент
|
|
512
|
+
ErrorComponent: '$dataSource(ErrorStateDefault)',
|
|
513
|
+
},
|
|
514
|
+
children: [
|
|
515
|
+
{ value: '$model(loanType)', component: '$component(Select)',
|
|
516
|
+
componentProps: { options: '$dataSource(LOAN_TYPES)' } }, // → массив
|
|
517
|
+
{ value: '$model(carYear)', component: '$component(Input)',
|
|
518
|
+
componentProps: { type: 'number', max: '$dataSource(CURRENT_YEAR_PLUS_ONE)' } }, // → число
|
|
519
|
+
],
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Notes.**
|
|
524
|
+
|
|
525
|
+
- Резолв происходит только для строк `'$dataSource(NAME)'`. Голые строки (`label`, `placeholder`) и инлайн-массивы options идут как есть.
|
|
526
|
+
- Если имя не зарегистрировано: без `validate` строка `'$dataSource(NAME)'` останется строкой (молчаливый баг); с `validate` — ошибка `unknown dataSource "NAME"`.
|
|
527
|
+
- dataSource нельзя использовать как имя `component` (`component: '$component(LoadingState)'`, где `LoadingState` — dataSource, бросит `Entry "..." is a 'dataSource' and cannot be used as $component(...)`). dataSource — только для значений в `componentProps`.
|
|
528
|
+
|
|
529
|
+
## 31. Инъекция runtime-сущностей в компонент (form, validation) { #inject-runtime }
|
|
530
|
+
|
|
531
|
+
**Problem.** Компоненту (напр. wizard) нужен `FormProxy` или validation-конфиг — рантайм-сущности, которые нельзя выразить в статичном JSON.
|
|
532
|
+
|
|
533
|
+
**Solution.** Инжектируй их через `renderBehavior` + `onInit`/`patchProps` до первого рендера. Узел адресуется по `selector`.
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import { onInit, type RenderBehaviorFn } from '@reformer/renderer-react';
|
|
537
|
+
|
|
538
|
+
function createMyRenderBehavior(
|
|
539
|
+
form: FormProxy<MyForm>,
|
|
540
|
+
model: FormModel<MyForm>
|
|
541
|
+
): RenderBehaviorFn<MyForm> {
|
|
542
|
+
return (schema) => {
|
|
543
|
+
// JSON-схема не знает про FormProxy/валидацию — инъектим их в wizard до первого рендера.
|
|
544
|
+
onInit(schema.node('wizard'), () => {
|
|
545
|
+
schema.node('wizard').patchProps({ form, ...makeValidationConfig(model) });
|
|
546
|
+
});
|
|
547
|
+
// Остальное поведение (visibility/navigation) — из shared render-behavior.
|
|
548
|
+
createSharedRenderBehavior(form)(schema);
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// <JsonFormRenderer schema={jsonSchema} renderBehavior={createMyRenderBehavior(form, model)} />
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**Notes.**
|
|
556
|
+
|
|
557
|
+
- `onInit(node, fn)` — build-time hook, вызывается один раз до первого рендера ноды.
|
|
558
|
+
- `patchProps` мержит переданные пропы в `componentProps` ноды.
|
|
559
|
+
|
|
560
|
+
## 32. Вся форма read-only / view-mode { #readonly }
|
|
561
|
+
|
|
562
|
+
**Problem.** Нужно показать заполненную форму «только для просмотра» — все поля недоступны для ввода. Тянет искать флаг `settings.readonly` / `settings.mode`.
|
|
563
|
+
|
|
564
|
+
**Solution.** Такого флага **нет**. Настройки рендерера (`JsonRendererSettings`) — это `registry` + `model` поверх `RendererSettings`, а `RendererSettings` несёт только `fieldWrapper` (см. [json-renderer-context.tsx](../../src/context/json-renderer-context.tsx), renderer-react `RendererSettings`). Read-only задаётся **на уровне модели**: `form.disable()` каскадит `disabled` по всему поддереву — `GroupNode.onDisable()` рекурсивно зовёт `field.disable()` на всех детях, а рендерер пробрасывает per-field `disabled: state.disabled` в компонент. Один вызов на корне → вся форма read-only.
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
// Вариант A — сразу после сборки формы (самый прямой):
|
|
568
|
+
const model = createModel<MyForm>(initialValues);
|
|
569
|
+
const form = createForm<MyForm>({ model, schema: convertJsonToM1Tree(jsonSchema, registry, model) });
|
|
570
|
+
form.disable(); // каскад disabled по всему дереву → вся форма read-only
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
// Вариант B — из render-behavior (например, включить view-mode по условию/после загрузки данных):
|
|
575
|
+
import { onInit, type RenderBehaviorFn } from '@reformer/renderer-react';
|
|
576
|
+
|
|
577
|
+
function createReadonlyBehavior(form: FormProxy<MyForm>): RenderBehaviorFn<MyForm> {
|
|
578
|
+
return (schema) => {
|
|
579
|
+
onInit(schema.node('wizard'), () => {
|
|
580
|
+
form.disable(); // корневой FormProxy отдаёт disable() (делегирует в GroupNode)
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**Notes.**
|
|
587
|
+
|
|
588
|
+
- `form.disable()` — публичный метод узла (`FormNode.disable()`): ставит статус `disabled` и вызывает hook `onDisable`, который у группы каскадит на всех детей рекурсивно. Обратно — `form.enable()`.
|
|
589
|
+
- **Caveat:** поле с явным `componentProps.disabled` **перебивает** каскад. Рендерер собирает пропы как `{ value, disabled: state.disabled, ...componentProps }` — спред `componentProps` идёт ПОСЛЕ `disabled`, поэтому `componentProps.disabled: false` вернёт полю доступность даже при `form.disable()`. Не задавай `disabled` в JSON, если хочешь глобальный каскад.
|
|
590
|
+
- Отключённые узлы не валидируются и не попадают в `getValue()` — для чистого view-mode это обычно желаемо; если нужен submit disabled-значений, снимай `disable()` перед сбором.
|
|
591
|
+
- `settings.readonly` / `settings.mode` не существует — model-level каскад (`form.disable()`) это канонический механизм view-mode.
|
|
592
|
+
|
|
593
|
+
## 33. Migration from TS RenderSchema { #migration }
|
|
594
|
+
|
|
595
|
+
**Problem.** Есть готовая `RenderSchemaFn<T>` (TS-вариант с `path.email`, React-компонентами по ссылке) — нужно перенести её в JSON-схему.
|
|
596
|
+
|
|
597
|
+
**Solution.** Покомпонентная карта замен. Ключевое: TS-ссылки (`path.email`, `Box`, `LOAN_TYPES`) → строки-операторы.
|
|
598
|
+
|
|
599
|
+
| TS RenderSchema (`@reformer/renderer-react`) | JSON-схема (`@reformer/renderer-json`, M1) |
|
|
600
|
+
| ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
601
|
+
| `{ value: path.email, component: Input }` | `{ value: '$model(email)', component: '$component(Input)' }` |
|
|
602
|
+
| `{ value: path.personalData.firstName, component: Input }` | `{ value: '$model(personalData.firstName)', component: '$component(Input)' }` |
|
|
603
|
+
| `{ component: Box, componentProps: { className: 'grid' }, children: [...] }` | `{ component: '$component(Box)', componentProps: { className: 'grid' }, children: [...] }` |
|
|
604
|
+
| `{ component: Section, componentProps: { title: 'X' }, children: [...] }` | `{ component: '$component(Section)', componentProps: { title: 'X' }, children: [...] }` |
|
|
605
|
+
| `{ selector: 'mortgage-section', component: Section, ... }` | то же — `selector` сохраняется (plain-строка) |
|
|
606
|
+
| `componentProps: { options: LOAN_TYPES }` (импорт константы) | `componentProps: { options: '$dataSource(LOAN_TYPES)' }` + `reg.dataSource('LOAN_TYPES', LOAN_TYPES)` |
|
|
607
|
+
| `componentProps: { LoadingComponent: LoadingState }` | `componentProps: { LoadingComponent: '$dataSource(LoadingState)' }` + `reg.dataSource('LoadingState', ...)` |
|
|
608
|
+
| `{ array: path.properties, item: (ip) => ({...}), initialValue: () => ({...}) }` | `{ array: '$model(properties)', item: { $template: {...} }, initialValue: {...} }` |
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
// После: JSON
|
|
612
|
+
const schema: JsonFormSchema = {
|
|
613
|
+
version: '1.0',
|
|
614
|
+
root: {
|
|
615
|
+
component: '$component(Box)',
|
|
616
|
+
componentProps: { className: 'space-y-4' },
|
|
617
|
+
children: [
|
|
618
|
+
{ value: '$model(email)', component: '$component(Input)', componentProps: { label: 'Email' } },
|
|
619
|
+
{
|
|
620
|
+
component: '$component(Section)',
|
|
621
|
+
componentProps: { title: 'Адрес' },
|
|
622
|
+
children: [{ value: '$model(address.city)', component: '$component(Input)' }],
|
|
623
|
+
},
|
|
624
|
+
],
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const registry = defineRegistry((reg) => {
|
|
629
|
+
reg.component('Input', Input);
|
|
630
|
+
reg.component('Box', Box);
|
|
631
|
+
reg.component('Section', Section);
|
|
632
|
+
reg.component(FIELD_WRAPPER, FormField);
|
|
633
|
+
});
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**Notes.**
|
|
637
|
+
|
|
638
|
+
- В JSON `children` всегда отдельное поле узла (не `componentProps.children`).
|
|
639
|
+
- field/array/container взаимоисключающи: `value` → лист, `array`+`item` → массив, `component`+`children` → контейнер. Дискриминация в конвертере: array → field → container.
|
|
640
|
+
- Поведение (`hideWhen`, `onInit`, lifecycle) **не** переезжает в JSON — остаётся TS-функцией `RenderBehaviorFn<T>` и передаётся пропом `renderBehavior`. В эталоне TS- и JSON-варианты переиспользуют один shared behavior.
|
|
641
|
+
|
|
642
|
+
## 34. See also
|
|
643
|
+
|
|
644
|
+
- [01-overview.md](01-overview.md) — монтаж через `model` + `JsonRendererProvider`.
|
|
645
|
+
- [02-json-schema.md](02-json-schema.md) — справочник по узлам `JsonNode` и операторам.
|
|
646
|
+
- [03-registry.md](03-registry.md) — все методы `defineRegistry`.
|
|
647
|
+
- [04-troubleshooting.md](04-troubleshooting.md) — типичные ошибки.
|
|
648
|
+
|
|
649
|
+
## 35. Mental model — почему валидаторов нет в JSON { #mental-model }
|
|
650
|
+
|
|
651
|
+
Одна ключевая мысль: **JSON-DSL несёт только layout, а не правила валидации.**
|
|
652
|
+
|
|
653
|
+
- `JsonFieldNode` — это `{ selector?, value, component?, componentProps?, wrapper? }` и **ничего больше**. Поля `validators` в нём нет (см. [json-schema.ts](../../src/types/json-schema.ts) — интерфейс `JsonFieldNode`).
|
|
654
|
+
- Операторы DSL — только `$model(...)`, `$component(...)`, `$dataSource(...)` (см. [operators.ts](../../src/operators.ts)). Оператора `$validator(...)` **не существует**.
|
|
655
|
+
- `JsonFormRenderer` с пропом `validate` (и функция `validateFormSchema`) проверяют **структуру** схемы через ajv: корректность узлов + синтаксис операторов + известность имён `$component`/`$dataSource`. Это НЕ валидация введённых пользователем значений (см. [validate.ts](../../src/validate.ts)).
|
|
656
|
+
- Значит, валидацию значений выражают **отдельной TS-схемой над МОДЕЛЬЮ** (`FormModel`), а не в JSON. Схема исполняется `validateFormModel(model, schema)` — тем же движком, что и в TS-варианте формы. Одна валидация на все варианты рендера.
|
|
657
|
+
|
|
658
|
+
Дальше — три шага: (1) построить схему над моделью, (2) обернуть её в `{ validateStep, validateAll }`, (3) инъектировать в wizard через render-behavior.
|
|
659
|
+
|
|
660
|
+
## 36. Шаг 1 — построить схему валидации над моделью { #build-schema }
|
|
661
|
+
|
|
662
|
+
Схема валидации — дерево узлов движка M1: лист `{ value: signal, validators: [...] }`, контейнер `{ children: [...] }`. `value` — сигнал модели (`model.$.path`), НЕ строка-оператор `$model(...)` (операторы — только для layout-JSON; схема валидации это обычный TS-код). Фабрики правил импортируются из `@reformer/core/validators`.
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
import { type FormModel } from '@reformer/core';
|
|
666
|
+
import { required, min, max, minLength, email } from '@reformer/core/validators';
|
|
667
|
+
|
|
668
|
+
type M = FormModel<CreditForm>;
|
|
669
|
+
|
|
670
|
+
// Под-схема одного шага: дерево field-узлов { value, validators }.
|
|
671
|
+
const step1 = (model: M) => ({
|
|
672
|
+
children: [
|
|
673
|
+
{ value: model.$.loanType, validators: [required({ message: 'Выберите тип кредита' })] },
|
|
674
|
+
{
|
|
675
|
+
value: model.$.loanAmount,
|
|
676
|
+
validators: [required(), min(50000, { message: 'Минимум 50 000 ₽' }), max(10000000)],
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const step2 = (model: M) => ({
|
|
682
|
+
children: [
|
|
683
|
+
{ value: model.$.personalData.firstName, validators: [required(), minLength(2)] },
|
|
684
|
+
{ value: model.$.email, validators: [required(), email()] },
|
|
685
|
+
],
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Карта шагов + полная схема (объединение для submit).
|
|
689
|
+
const STEP_BUILDERS = [step1, step2];
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Условные группы (`{ when, children }`), cross-field правила, секции массивов — тот же движок, что в TS-форме. Полное описание узлов схемы и `validateFormModel` — в `@reformer/core` [13-multi-step.md](../../../reformer/docs/llms/13-multi-step.md) (не дублируем здесь).
|
|
693
|
+
|
|
694
|
+
## 37. Шаг 2 — исполнить: `{ validateStep, validateAll }` { #execute }
|
|
695
|
+
|
|
696
|
+
`validateFormModel(model, schema)` прогоняет схему по текущим значениям модели и **сам роутит ошибки в ноды формы** (UI подсветит проблемные поля). Возвращает `{ valid, errors }`. Оборачиваем в две функции — контракт `FormWizardConfig` из `@reformer/cdk` (см. [form-wizard/types.ts](../../../reformer-cdk/src/components/form-wizard/types.ts): `validateStep?(step): boolean | Promise<boolean>`, `validateAll?(): boolean | Promise<boolean>`).
|
|
697
|
+
|
|
698
|
+
```typescript
|
|
699
|
+
import { validateFormModel } from '@reformer/core';
|
|
700
|
+
|
|
701
|
+
export function makeValidationConfig(model: M) {
|
|
702
|
+
// Схема зависит только от ФОРМЫ модели, не от значений — строим дерево один раз.
|
|
703
|
+
const stepSchemas = STEP_BUILDERS.map((build) => build(model));
|
|
704
|
+
const fullSchema = { children: stepSchemas };
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
validateStep: async (step: number): Promise<boolean> => {
|
|
708
|
+
const res = await validateFormModel(model, stepSchemas[step - 1] ?? { children: [] });
|
|
709
|
+
return res.valid; // ошибки уже проставлены в ноды текущего шага
|
|
710
|
+
},
|
|
711
|
+
validateAll: async (): Promise<boolean> => {
|
|
712
|
+
const res = await validateFormModel(model, fullSchema);
|
|
713
|
+
return res.valid;
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
## 38. Шаг 3 — инъекция в wizard через render-behavior { #inject }
|
|
720
|
+
|
|
721
|
+
JSON-схема статична и не знает про `FormProxy`/validation-конфиг — это рантайм-сущности. Инжектируем их в wizard-ноду через `renderBehavior` + `onInit` + `patchProps` до первого рендера (общий приём — см. [05-cookbook.md#inject-runtime](05-cookbook.md#inject-runtime)). Wizard-узел JSON-схемы должен нести `selector: 'wizard'`, чтобы адресоваться через `schema.node('wizard')`.
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
import { onInit, type RenderBehaviorFn } from '@reformer/renderer-react';
|
|
725
|
+
import type { FormProxy, FormModel } from '@reformer/core';
|
|
726
|
+
|
|
727
|
+
export function createJsonRenderBehavior(
|
|
728
|
+
form: FormProxy<CreditForm>,
|
|
729
|
+
model: FormModel<CreditForm>
|
|
730
|
+
): RenderBehaviorFn<CreditForm> {
|
|
731
|
+
return (schema) => {
|
|
732
|
+
// JSON не выражает FormProxy/валидацию — инъектим их в wizard до первого рендера.
|
|
733
|
+
onInit(schema.node('wizard'), () => {
|
|
734
|
+
schema.node('wizard').patchProps({ form, ...makeValidationConfig(model) });
|
|
735
|
+
});
|
|
736
|
+
// Остальное поведение (visibility/navigation) — из shared render-behavior.
|
|
737
|
+
createSharedRenderBehavior(form)(schema);
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// <JsonFormRenderer schema={jsonSchema} renderBehavior={createJsonRenderBehavior(form, model)} />
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
`patchProps({ form, validateStep, validateAll })` кладёт `form` + оба колбэка в `componentProps` wizard-ноды. Wizard-компонент читает их как `FormWizardConfig` и запускает `validateStep` на «Далее», `validateAll` на submit.
|
|
745
|
+
|
|
746
|
+
## 39. Полный рабочий пример { #full-example }
|
|
747
|
+
|
|
748
|
+
Зеркалит golden-эталон (`complex-multy-step-form-renderer-json`): валидация — TS-схема над моделью, переиспользуемая всеми вариантами рендера, инъектируется в JSON-wizard. Submit и навигация между шагами приходят **не** отсюда, а из shared render-behavior — JSON-вариант лишь до-инъектит `form`/валидацию и делегирует остальное (см. [07-form-wizard.md](07-form-wizard.md)).
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
// validation.ts — TS-схема над МОДЕЛЬЮ (не JSON)
|
|
752
|
+
import { validateFormModel, type FormModel } from '@reformer/core';
|
|
753
|
+
import { required, min, minLength, email } from '@reformer/core/validators';
|
|
754
|
+
import type { CreditForm } from './types';
|
|
755
|
+
|
|
756
|
+
type M = FormModel<CreditForm>;
|
|
757
|
+
|
|
758
|
+
const step1 = (model: M) => ({
|
|
759
|
+
children: [
|
|
760
|
+
{ value: model.$.loanType, validators: [required({ message: 'Выберите тип кредита' })] },
|
|
761
|
+
{ value: model.$.loanAmount, validators: [required(), min(50000, { message: 'Минимум 50 000 ₽' })] },
|
|
762
|
+
],
|
|
763
|
+
});
|
|
764
|
+
const step2 = (model: M) => ({
|
|
765
|
+
children: [
|
|
766
|
+
{ value: model.$.personalData.firstName, validators: [required(), minLength(2)] },
|
|
767
|
+
{ value: model.$.email, validators: [required(), email()] },
|
|
768
|
+
],
|
|
769
|
+
});
|
|
770
|
+
const STEP_BUILDERS = [step1, step2];
|
|
771
|
+
|
|
772
|
+
/** Контракт FormWizardConfig: per-step + полная валидация через validateFormModel. */
|
|
773
|
+
export function makeValidationConfig(model: M) {
|
|
774
|
+
const stepSchemas = STEP_BUILDERS.map((build) => build(model));
|
|
775
|
+
const fullSchema = { children: stepSchemas };
|
|
776
|
+
return {
|
|
777
|
+
validateStep: async (step: number) =>
|
|
778
|
+
(await validateFormModel(model, stepSchemas[step - 1] ?? { children: [] })).valid,
|
|
779
|
+
validateAll: async () => (await validateFormModel(model, fullSchema)).valid,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
```typescript
|
|
785
|
+
// render-behavior.ts — инъекция form + валидации в JSON-wizard
|
|
786
|
+
import { onInit, type RenderBehaviorFn } from '@reformer/renderer-react';
|
|
787
|
+
import type { FormProxy, FormModel } from '@reformer/core';
|
|
788
|
+
import type { CreditForm } from './types';
|
|
789
|
+
import { makeValidationConfig } from './validation';
|
|
790
|
+
// Единый behavior (submit/навигация/visibility) — общий для TS- и JSON-варианта. См. 07-form-wizard.md.
|
|
791
|
+
import { createSharedRenderBehavior } from './render-behavior.shared';
|
|
792
|
+
|
|
793
|
+
export function createJsonRenderBehavior(
|
|
794
|
+
form: FormProxy<CreditForm>,
|
|
795
|
+
model: FormModel<CreditForm>
|
|
796
|
+
): RenderBehaviorFn<CreditForm> {
|
|
797
|
+
return (schema) => {
|
|
798
|
+
// (1) Инъекция рантайм-сущностей: form + validation-конфиг в wizard-ноду.
|
|
799
|
+
onInit(schema.node('wizard'), () => {
|
|
800
|
+
schema.node('wizard').patchProps({ form, ...makeValidationConfig(model) });
|
|
801
|
+
});
|
|
802
|
+
// (2) Submit + навигация между шагами + visibility — из shared render-behavior.
|
|
803
|
+
// БЕЗ этого вызова форма валидирует, но НЕ сабмитит и не навигирует (golden
|
|
804
|
+
// `complex-multy-step-form-renderer-json/render-behavior.ts` делегирует так же).
|
|
805
|
+
createSharedRenderBehavior(form)(schema);
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
## 40. Anti-patterns
|
|
811
|
+
|
|
812
|
+
- **Ждать, что `validate={true}` (или `validateFormSchema`) валидирует значения** — этот проп проверяет только СТРУКТУРУ схемы через ajv (узлы + синтаксис операторов + имена компонентов). Введённые пользователем значения он не трогает. Валидацию значений исполняет `validateFormModel` над моделью.
|
|
813
|
+
- **Пытаться добавить `validators` в JSON field-node** — `JsonFieldNode` несёт только layout (`value`/`component`/`componentProps`/`wrapper`). Поля `validators` в нём нет, оператора `$validator(...)` не существует. TypeScript отклонит лишнее поле; даже если протащить через `as`, конвертер его проигнорирует.
|
|
814
|
+
- **Строить схему валидации по значениям, а не по shape модели** — узел это `{ value: model.$.path, validators }`, где `value` — сигнал (стабильная ссылка на форму модели), а не текущее значение поля. Схему собирают один раз на `model`; значения читаются движком в момент прогона `validateFormModel`.
|
|
815
|
+
- **Забыть `selector: 'wizard'` у wizard-ноды** — без `selector` узел не адресуется через `schema.node('wizard')`, `onInit`/`patchProps` не найдут его и валидация не инъектируется (submit пройдёт без блокировки).
|
|
816
|
+
|
|
817
|
+
## 41. See also
|
|
818
|
+
|
|
819
|
+
- [07-form-wizard.md](07-form-wizard.md) — end-to-end wizard в JSON: submit (`onComponentEvent`), навигация (`renderEffect`) и инъекция этой валидации в одном месте.
|
|
820
|
+
- [02-json-schema.md](02-json-schema.md) — справочник по узлам `JsonNode` (field-node несёт только layout).
|
|
821
|
+
- [05-cookbook.md#inject-runtime](05-cookbook.md#inject-runtime) — общий приём инъекции runtime-сущностей через `onInit`/`patchProps`.
|
|
822
|
+
- `@reformer/core` [13-multi-step.md](../../../reformer/docs/llms/13-multi-step.md) — узлы схемы валидации, `validateFormModel`, STEP_SCHEMAS.
|
|
823
|
+
- [Типы JsonFormSchema/JsonNode](../../src/types/json-schema.ts) и [validate.ts](../../src/validate.ts) — структурная валидация схемы.
|
|
824
|
+
|
|
825
|
+
## 42. Половина 1 — layout шагов в JSON { #json-shape }
|
|
826
|
+
|
|
827
|
+
Wizard — обычная container-нода со `selector: 'wizard'` (чтобы адресоваться через `schema.node('wizard')`). Шаги лежат в **`componentProps.steps`** — массив container-нод, а **не** в top-level `children`. Каждый шаг — нода `$component(Step)` с `componentProps: { title, icon }` и собственным `children` (поддерево layout шага).
|
|
828
|
+
|
|
829
|
+
```json
|
|
830
|
+
{
|
|
831
|
+
"selector": "wizard",
|
|
832
|
+
"component": "$component(Wizard)",
|
|
833
|
+
"componentProps": {
|
|
834
|
+
"className": "bg-white p-8 rounded-lg shadow-md",
|
|
835
|
+
"steps": [
|
|
836
|
+
{
|
|
837
|
+
"component": "$component(Step)",
|
|
838
|
+
"componentProps": { "title": "Кредит", "icon": "💰" },
|
|
839
|
+
"children": [
|
|
840
|
+
{
|
|
841
|
+
"selector": "mortgage-section",
|
|
842
|
+
"component": "$component(Section)",
|
|
843
|
+
"componentProps": { "title": "Ипотека" },
|
|
844
|
+
"children": [
|
|
845
|
+
{ "value": "$model(loanAmount)", "component": "$component(Input)",
|
|
846
|
+
"componentProps": { "label": "Сумма кредита (₽)", "type": "number" } }
|
|
847
|
+
]
|
|
848
|
+
}
|
|
849
|
+
]
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
"component": "$component(Step)",
|
|
853
|
+
"componentProps": { "title": "Заявитель", "icon": "🧑" },
|
|
854
|
+
"children": [
|
|
855
|
+
{ "value": "$model(personalData.firstName)", "component": "$component(Input)",
|
|
856
|
+
"componentProps": { "label": "Имя" } }
|
|
857
|
+
]
|
|
858
|
+
}
|
|
859
|
+
]
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
Ground truth: golden `complex-multy-step-form-renderer-json/json-schema.json` (wizard-нода — `selector: 'wizard'`, `steps` внутри `componentProps`, каждый шаг — `$component(Step)` + `componentProps.title/icon` + `children`).
|
|
865
|
+
|
|
866
|
+
> Отличие от `@reformer/renderer-react`: там шаг — объект `{ number, title, icon, body }`, где `body` — самостоятельный `RenderNode` (см. renderer-react [01-overview.md](../../../reformer-renderer-react/docs/llms/01-overview.md#multi-step-forms)). В JSON-DSL нельзя вписать `RenderNode` как значение пропа, поэтому шаг выражается **container-нодой** `Step` + `children`, а wizard-компонент адаптирует эту форму под `step.body`.
|
|
867
|
+
|
|
868
|
+
## 43. Регистрируй свой wizard-компонент в реестре { #register }
|
|
869
|
+
|
|
870
|
+
`$component(Wizard)` — это **запись в реестре**, а не библиотечный экспорт: имя резолвится через registry (`reg.component('Wizard', <твой компонент>)`). Имя произвольное — важно лишь совпадение строки в JSON и ключа в реестре.
|
|
871
|
+
|
|
872
|
+
Канонический shipped-компонент — `FormWizard` из `@reformer/ui-kit/form-wizard` (см. renderer-react [01-overview.md](../../../reformer-renderer-react/docs/llms/01-overview.md#multi-step-forms)). Он принимает `componentProps.form`, `componentProps.steps` и `FormWizardConfig` (`validateStep`/`validateAll`), а `step.body` полиморфен (`FC | ReactNode | RenderNode<T>`). Твой зарегистрированный компонент должен быть совместим с этим контрактом.
|
|
873
|
+
|
|
874
|
+
```typescript
|
|
875
|
+
import { defineRegistry } from '@reformer/renderer-json';
|
|
876
|
+
import { Step } from '@reformer/cdk/form-wizard';
|
|
877
|
+
import { MyWizard } from './MyWizard'; // тонкая обёртка над ui-kit FormWizard
|
|
878
|
+
|
|
879
|
+
const registry = defineRegistry((reg) => {
|
|
880
|
+
reg.component('Wizard', MyWizard); // ← имя из JSON `$component(Wizard)`
|
|
881
|
+
reg.component('Step', Step); // container-нода шага
|
|
882
|
+
// ...остальные компоненты (Input, Select, Section, ...)
|
|
883
|
+
});
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
> В golden-эталоне под `$component(Wizard)` зарегистрирован app-shim `RendererFormWizard`: он снимает `title`/`icon` c `componentProps` Step-ноды, а сам Step-узел кладёт в `step.body` ui-kit `FormWizard`. Shim — деталь приложения, **не** канон библиотеки; регистрируй под этим именем любой совместимый с `FormWizard` компонент.
|
|
887
|
+
|
|
888
|
+
## 44. Половина 2 — поведение в одном render-behavior { #render-behavior }
|
|
889
|
+
|
|
890
|
+
Один `RenderBehaviorFn<T>` навешивает всё рантайм-поведение на wizard-ноду. Порядок: (a) инъекция `form` + валидации через `onInit`; (b) submit через `onComponentEvent`; (c) навигация через `renderEffect` + `wizardRef`; (d) условные секции через `hideWhen`. Семантику хелперов см. renderer-react [03-render-behavior.md](../../../reformer-renderer-react/docs/llms/03-render-behavior.md).
|
|
891
|
+
|
|
892
|
+
```typescript
|
|
893
|
+
import {
|
|
894
|
+
onInit,
|
|
895
|
+
onComponentEvent,
|
|
896
|
+
renderEffect,
|
|
897
|
+
hideWhen,
|
|
898
|
+
type RenderBehaviorFn,
|
|
899
|
+
} from '@reformer/renderer-react';
|
|
900
|
+
import type { FormProxy, FormModel } from '@reformer/core';
|
|
901
|
+
import type { FormWizardHandle } from '@reformer/cdk/form-wizard';
|
|
902
|
+
import type { CreditForm } from './types';
|
|
903
|
+
import { makeValidationConfig } from './validation'; // см. 06-validation.md
|
|
904
|
+
import { submitCreditApplication } from './api';
|
|
905
|
+
|
|
906
|
+
export function createWizardRenderBehavior(
|
|
907
|
+
form: FormProxy<CreditForm>,
|
|
908
|
+
model: FormModel<CreditForm>
|
|
909
|
+
): RenderBehaviorFn<CreditForm> {
|
|
910
|
+
return (schema) => {
|
|
911
|
+
const wizard = schema.node('wizard');
|
|
912
|
+
const wizardRef = wizard.getRef<FormWizardHandle<CreditForm>>();
|
|
913
|
+
|
|
914
|
+
// (a) Инъекция рантайма: form + validateStep/validateAll в wizard-ноду до первого рендера.
|
|
915
|
+
// Валидация — TS-схема над МОДЕЛЬЮ (не JSON). Детали — 06-validation.md.
|
|
916
|
+
onInit(wizard, () => {
|
|
917
|
+
wizard.patchProps({ form, ...makeValidationConfig(model) });
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// (b) Submit: onComponentEvent получает те же аргументы, что и оригинальный проп onSubmit.
|
|
921
|
+
onComponentEvent(wizard, 'onSubmit', async (values: CreditForm) => {
|
|
922
|
+
await submitCreditApplication(values);
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
// (c) Навигация: реактивный эффект принимает СХЕМУ (не ноду); wizardRef доступен после mount.
|
|
926
|
+
renderEffect(schema, () => {
|
|
927
|
+
if (form.loanType.value.value === 'mortgage') {
|
|
928
|
+
wizardRef.current?.goToStep(1);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
// (d) Условные секции: реактивно по сигналам формы (читай сигнал целиком: `.value.value`).
|
|
933
|
+
hideWhen(schema.node('mortgage-section'), () => form.loanType.value.value !== 'mortgage');
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// <JsonFormRenderer schema={jsonSchema} renderBehavior={createWizardRenderBehavior(form, model)} />
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
Ground truth: golden `complex-multy-step-form-renderer/render-behavior.ts` — `onComponentEvent(schema.node('wizard'), 'onSubmit', ...)`, `renderEffect(schema, () => wizardRef.current?.goToStep(1))`, `hideWhen(...)`; инъекция `form`+валидации — `complex-multy-step-form-renderer-json/render-behavior.ts`.
|
|
941
|
+
|
|
942
|
+
## 45. Anti-patterns
|
|
943
|
+
|
|
944
|
+
- **Класть шаги в top-level `children` wizard-ноды** — шаги живут в `componentProps.steps`. Top-level `children` wizard-компонент не читает как шаги.
|
|
945
|
+
- **Ждать шаг как `{ number, title, icon, body }` в JSON** — это форма renderer-react. В JSON шаг — container-нода `$component(Step)` + `componentProps.title/icon` + `children`.
|
|
946
|
+
- **Считать `RendererFormWizard` библиотечным экспортом** — это app-shim эталона. Wizard-компонент подключается через реестр под любым именем; канон — ui-kit `FormWizard`.
|
|
947
|
+
- **Забыть `createWizardRenderBehavior` (только `onInit` с валидацией)** — форма будет валидировать, но `onSubmit`/навигация не подключатся: submit-less форма. Submit и навигация приходят из этого же behavior.
|
|
948
|
+
- **`renderEffect(node, ...)` вместо `renderEffect(schema, ...)`** — первый аргумент `renderEffect` это схема, а не узел (в отличие от `hideWhen`/`onComponentEvent`).
|
|
949
|
+
- **Забыть `selector: 'wizard'`** — без селектора `schema.node('wizard')` не адресует узел, инъекция/submit/навигация не навесятся.
|
|
950
|
+
|
|
951
|
+
## 46. See also
|
|
952
|
+
|
|
953
|
+
- [06-validation.md](06-validation.md) — `makeValidationConfig` (TS-схема над моделью), инъекция `validateStep`/`validateAll` в wizard.
|
|
954
|
+
- [05-cookbook.md#inject-runtime](05-cookbook.md#inject-runtime) — общий приём инъекции runtime-сущностей (`form`) через `onInit`/`patchProps`.
|
|
955
|
+
- renderer-react [03-render-behavior.md](../../../reformer-renderer-react/docs/llms/03-render-behavior.md) — семантика `hideWhen`/`renderEffect`/`onComponentEvent`/`onInit`.
|
|
956
|
+
- renderer-react [01-overview.md](../../../reformer-renderer-react/docs/llms/01-overview.md#multi-step-forms) — канонический `FormWizard`, форма шага `{ number, title, icon, body }`.
|
|
957
|
+
|
|
958
|
+
## 47. API Reference
|
|
959
|
+
|
|
960
|
+
_Auto-generated from JSDoc on public exports._
|
|
961
|
+
|
|
962
|
+
### buildFormSchemaMetaSchema
|
|
963
|
+
|
|
964
|
+
**Kind:** `function`
|
|
965
|
+
|
|
966
|
+
Конкретная мета-схема: базовая + (если заданы `componentNames`) сужение `$component(...)` до
|
|
967
|
+
enum допустимых значений (`["$component(Input)", "$component(Select)", …]`). `$dataSource`-имена
|
|
968
|
+
JSON Schema'й не покрыть (вложены в произвольный componentProps) — их проверяет рекурсивный обход
|
|
969
|
+
в `validateFormSchema`.
|
|
970
|
+
|
|
971
|
+
enum, а не regex-`pattern`: ajv перечисляет допустимые имена в тексте ошибки, IDE даёт
|
|
972
|
+
автодополнение по значениям, а имена не нужно экранировать под regex (напр. `$fieldWrapper`).
|
|
973
|
+
|
|
974
|
+
**Signature:**
|
|
975
|
+
```typescript
|
|
976
|
+
export function buildFormSchemaMetaSchema(opts?: {
|
|
977
|
+
componentNames?: string[];
|
|
978
|
+
}): Record<string, unknown>
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
**Examples:**
|
|
982
|
+
|
|
983
|
+
```ts
|
|
984
|
+
const schema = buildFormSchemaMetaSchema({ componentNames: getComponentNames(registry) });
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
_Source: src/schema/index.ts_
|
|
988
|
+
|
|
989
|
+
### ComponentMetadata
|
|
990
|
+
|
|
991
|
+
**Kind:** `interface`
|
|
992
|
+
|
|
993
|
+
Запись реестра. Хранит сам компонент (или dataSource-значение) и его роль.
|
|
994
|
+
|
|
995
|
+
**Signature:**
|
|
996
|
+
```typescript
|
|
997
|
+
export interface ComponentMetadata<P = any> {
|
|
998
|
+
component: ComponentType<P> | unknown;
|
|
999
|
+
type: 'component' | 'dataSource';
|
|
1000
|
+
description?: string;
|
|
1001
|
+
}
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
**Examples:**
|
|
1005
|
+
|
|
1006
|
+
```typescript
|
|
1007
|
+
const meta: ComponentMetadata = registry.get('Input')!;
|
|
1008
|
+
meta.type; // 'component'
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
_Source: src/registry/types.ts_
|
|
1012
|
+
|
|
1013
|
+
### ComponentOp
|
|
1014
|
+
|
|
1015
|
+
**Kind:** `type`
|
|
1016
|
+
|
|
1017
|
+
Строка-оператор ссылки на компонент реестра: `` `$component(${name})` ``.
|
|
1018
|
+
|
|
1019
|
+
**Signature:**
|
|
1020
|
+
```typescript
|
|
1021
|
+
export type ComponentOp = `$component(${string})`;
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
_Source: src/operators.ts_
|
|
1025
|
+
|
|
1026
|
+
### ComponentRegistry
|
|
1027
|
+
|
|
1028
|
+
**Kind:** `interface`
|
|
1029
|
+
|
|
1030
|
+
Read-only API реестра, доступное в рантайме.
|
|
1031
|
+
|
|
1032
|
+
Создаётся через {@link defineRegistry}. Передаётся в `JsonRendererProvider`
|
|
1033
|
+
через `settings.registry`.
|
|
1034
|
+
|
|
1035
|
+
**Signature:**
|
|
1036
|
+
```typescript
|
|
1037
|
+
export interface ComponentRegistry {
|
|
1038
|
+
get(name: string): ComponentMetadata | undefined;
|
|
1039
|
+
getDataSource<T = unknown>(name: string): T | undefined;
|
|
1040
|
+
has(name: string): boolean;
|
|
1041
|
+
names(): string[];
|
|
1042
|
+
}
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
**Examples:**
|
|
1046
|
+
|
|
1047
|
+
```typescript
|
|
1048
|
+
if (registry.has('Input')) {
|
|
1049
|
+
registry.get('Input'); // ComponentMetadata
|
|
1050
|
+
}
|
|
1051
|
+
registry.names(); // ['Input', 'Box', 'LOAN_TYPES', ...]
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
_Source: src/registry/types.ts_
|
|
1055
|
+
|
|
1056
|
+
### convertJsonToM1Tree
|
|
1057
|
+
|
|
1058
|
+
**Kind:** `function`
|
|
1059
|
+
|
|
1060
|
+
Сырое дерево RenderNode из JSON (M1) — для `createForm({ model, schema })`. Листья привязываются
|
|
1061
|
+
к сигналам модели (`'$model(path)'` → `model.signalAt`), компоненты/источники — из реестра.
|
|
1062
|
+
|
|
1063
|
+
**Signature:**
|
|
1064
|
+
```typescript
|
|
1065
|
+
export function convertJsonToM1Tree<T>(
|
|
1066
|
+
schema: JsonFormSchema,
|
|
1067
|
+
registry: ComponentRegistry,
|
|
1068
|
+
model: FormModel<T>
|
|
1069
|
+
): RenderNode<T>
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
**Parameters:**
|
|
1073
|
+
- `schema` — - JSON-схема формы ({@link JsonFormSchema}).
|
|
1074
|
+
- `registry` — - Реестр компонентов/источников (см. {@link defineRegistry}).
|
|
1075
|
+
- `model` — - Модель данных — источник значений (`FormModel`).
|
|
1076
|
+
|
|
1077
|
+
**Returns:** Корневой {@link RenderNode} — кладётся в `createForm({ schema })`.
|
|
1078
|
+
|
|
1079
|
+
**Examples:**
|
|
1080
|
+
|
|
1081
|
+
Собрать форму из JSON-схемы (M1)
|
|
1082
|
+
```ts
|
|
1083
|
+
import { createForm } from '@reformer/core';
|
|
1084
|
+
|
|
1085
|
+
const form = createForm<MyForm>({
|
|
1086
|
+
model,
|
|
1087
|
+
schema: convertJsonToM1Tree(jsonSchema, registry, model),
|
|
1088
|
+
behavior,
|
|
1089
|
+
});
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
_Source: src/converter/json-to-render-schema.ts_
|
|
1093
|
+
|
|
1094
|
+
### createRenderSchemaFromJsonM1
|
|
1095
|
+
|
|
1096
|
+
**Kind:** `function`
|
|
1097
|
+
|
|
1098
|
+
`RenderSchemaFn` из JSON (M1) — для `FormRenderer`/`JsonFormRenderer`. Листья привязываются к
|
|
1099
|
+
сигналам модели (`'$model(path)'` → `model.signalAt`), компоненты/источники — из реестра.
|
|
1100
|
+
|
|
1101
|
+
В отличие от {@link convertJsonToM1Tree} (который возвращает готовое дерево), здесь результат —
|
|
1102
|
+
ленивая функция-фабрика дерева, как её ждёт `createRenderSchema`. Обычно вызывается внутри
|
|
1103
|
+
{@link JsonFormRenderer}; напрямую нужен для интеграции с `FormRenderer` без JSON-обёртки.
|
|
1104
|
+
|
|
1105
|
+
**Signature:**
|
|
1106
|
+
```typescript
|
|
1107
|
+
export function createRenderSchemaFromJsonM1<T>(
|
|
1108
|
+
schema: JsonFormSchema,
|
|
1109
|
+
registry: ComponentRegistry,
|
|
1110
|
+
model: FormModel<T>
|
|
1111
|
+
): RenderSchemaFn<T>
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
**Parameters:**
|
|
1115
|
+
- `schema` — - JSON-схема формы ({@link JsonFormSchema}).
|
|
1116
|
+
- `registry` — - Реестр компонентов/источников (см. {@link defineRegistry}).
|
|
1117
|
+
- `model` — - Модель данных — источник значений (`FormModel`).
|
|
1118
|
+
|
|
1119
|
+
**Returns:** `RenderSchemaFn<T>` — фабрика {@link RenderNode}-дерева для `createRenderSchema`.
|
|
1120
|
+
|
|
1121
|
+
**Examples:**
|
|
1122
|
+
|
|
1123
|
+
```ts
|
|
1124
|
+
import { createRenderSchema, FormRenderer } from '@reformer/renderer-react';
|
|
1125
|
+
|
|
1126
|
+
const fn = createRenderSchemaFromJsonM1<MyForm>(jsonSchema, registry, model);
|
|
1127
|
+
const proxy = createRenderSchema<MyForm>(fn);
|
|
1128
|
+
<FormRenderer render={proxy} />;
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
_Source: src/converter/json-to-render-schema.ts_
|
|
1132
|
+
|
|
1133
|
+
### DataSourceOp
|
|
1134
|
+
|
|
1135
|
+
**Kind:** `type`
|
|
1136
|
+
|
|
1137
|
+
Строка-оператор ссылки на registry-source: `` `$dataSource(${name})` ``.
|
|
1138
|
+
|
|
1139
|
+
**Signature:**
|
|
1140
|
+
```typescript
|
|
1141
|
+
export type DataSourceOp = `$dataSource(${string})`;
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
_Source: src/operators.ts_
|
|
1145
|
+
|
|
1146
|
+
### defineRegistry
|
|
1147
|
+
|
|
1148
|
+
**Kind:** `function`
|
|
1149
|
+
|
|
1150
|
+
Создаёт реестр компонентов через builder-callback.
|
|
1151
|
+
|
|
1152
|
+
Реестр обязателен для работы {@link JsonFormRenderer} — иначе компоненты,
|
|
1153
|
+
упомянутые в JSON-схеме, не отрезолвятся.
|
|
1154
|
+
|
|
1155
|
+
**Signature:**
|
|
1156
|
+
```typescript
|
|
1157
|
+
export function defineRegistry(fn: (reg: RegistryBuilder) => void): ComponentRegistry
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
**Parameters:**
|
|
1161
|
+
- `fn` — - Builder-callback. Получает {@link RegistryBuilder} с методами `component`, `dataSource`.
|
|
1162
|
+
|
|
1163
|
+
**Returns:** Готовый {@link ComponentRegistry}, который кладётся в `JsonRendererProvider`.
|
|
1164
|
+
|
|
1165
|
+
**Examples:**
|
|
1166
|
+
|
|
1167
|
+
```typescript
|
|
1168
|
+
import { defineRegistry, FIELD_WRAPPER } from '@reformer/renderer-json';
|
|
1169
|
+
import { Input, Select, FormField } from '@reformer/ui-kit';
|
|
1170
|
+
|
|
1171
|
+
const registry = defineRegistry((reg) => {
|
|
1172
|
+
reg.component('Input', Input);
|
|
1173
|
+
reg.component('Select', Select);
|
|
1174
|
+
reg.component(FIELD_WRAPPER, FormField);
|
|
1175
|
+
reg.dataSource('LOAN_TYPES', [
|
|
1176
|
+
{ value: 'consumer', label: 'Потребительский' },
|
|
1177
|
+
{ value: 'mortgage', label: 'Ипотека' },
|
|
1178
|
+
]);
|
|
1179
|
+
});
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
**See also:**
|
|
1183
|
+
- [docs/llms/03-registry.md](../../docs/llms/03-registry.md)
|
|
1184
|
+
|
|
1185
|
+
_Source: src/registry/component-registry.ts_
|
|
1186
|
+
|
|
1187
|
+
### FIELD_WRAPPER
|
|
1188
|
+
|
|
1189
|
+
**Kind:** `const`
|
|
1190
|
+
|
|
1191
|
+
Зарезервированный ключ реестра для контейнера-обёртки полей.
|
|
1192
|
+
|
|
1193
|
+
Зарегистрируй компонент под этим именем (обычно `FormField` из `@reformer/ui-kit`),
|
|
1194
|
+
чтобы каждое поле получало label, error и hint автоматически.
|
|
1195
|
+
|
|
1196
|
+
**Signature:**
|
|
1197
|
+
```typescript
|
|
1198
|
+
export const FIELD_WRAPPER
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
**Examples:**
|
|
1202
|
+
|
|
1203
|
+
```typescript
|
|
1204
|
+
import { defineRegistry, FIELD_WRAPPER } from '@reformer/renderer-json';
|
|
1205
|
+
import { FormField } from '@reformer/ui-kit';
|
|
1206
|
+
|
|
1207
|
+
const registry = defineRegistry((reg) => {
|
|
1208
|
+
reg.component(FIELD_WRAPPER, FormField);
|
|
1209
|
+
});
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
_Source: src/registry/constants.ts_
|
|
1213
|
+
|
|
1214
|
+
### formSchemaMetaSchema
|
|
1215
|
+
|
|
1216
|
+
**Kind:** `const`
|
|
1217
|
+
|
|
1218
|
+
Базовая мета-схема form-DSL (draft-07): структура узлов + синтаксис операторов, имена компонентов
|
|
1219
|
+
НЕ ограничены (паттерн `$component(...)` открыт). Для сужения до конкретного реестра —
|
|
1220
|
+
{@link buildFormSchemaMetaSchema}. Используется как `$schema` в IDE и как база валидатора.
|
|
1221
|
+
|
|
1222
|
+
**Signature:**
|
|
1223
|
+
```typescript
|
|
1224
|
+
export const formSchemaMetaSchema
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
**Examples:**
|
|
1228
|
+
|
|
1229
|
+
Подключить как `$schema` в JSON-файле схемы
|
|
1230
|
+
```ts
|
|
1231
|
+
// record можно записать в файл и сослаться на него из "$schema" JSON-схемы.
|
|
1232
|
+
formSchemaMetaSchema.$schema; // 'http://json-schema.org/draft-07/schema#'
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
_Source: src/schema/index.ts_
|
|
1236
|
+
|
|
1237
|
+
### getComponentNames
|
|
1238
|
+
|
|
1239
|
+
**Kind:** `function`
|
|
1240
|
+
|
|
1241
|
+
Имена компонентов реестра (тип `component`) — то, что валидно в `$component(...)`.
|
|
1242
|
+
|
|
1243
|
+
**Signature:**
|
|
1244
|
+
```typescript
|
|
1245
|
+
export function getComponentNames(registry: ComponentRegistry): string[]
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
**Parameters:**
|
|
1249
|
+
- `registry` — - Реестр (см. {@link defineRegistry}).
|
|
1250
|
+
|
|
1251
|
+
**Returns:** Массив имён компонентов.
|
|
1252
|
+
|
|
1253
|
+
**Examples:**
|
|
1254
|
+
|
|
1255
|
+
Сузить мета-схему до компонентов реестра
|
|
1256
|
+
```ts
|
|
1257
|
+
const names = getComponentNames(registry); // ['Input', 'Select', 'Box', ...]
|
|
1258
|
+
const schema = buildFormSchemaMetaSchema({ componentNames: names });
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
_Source: src/schema/index.ts_
|
|
1262
|
+
|
|
1263
|
+
### getDataSourceNames
|
|
1264
|
+
|
|
1265
|
+
**Kind:** `function`
|
|
1266
|
+
|
|
1267
|
+
Имена registry-dataSource (`reg.dataSource`) — то, что валидно в `$dataSource(...)`:
|
|
1268
|
+
options/itemLabel/константы/loading-компоненты.
|
|
1269
|
+
|
|
1270
|
+
**Signature:**
|
|
1271
|
+
```typescript
|
|
1272
|
+
export function getDataSourceNames(registry: ComponentRegistry): string[]
|
|
1273
|
+
```
|
|
1274
|
+
|
|
1275
|
+
**Parameters:**
|
|
1276
|
+
- `registry` — - Реестр (см. {@link defineRegistry}).
|
|
1277
|
+
|
|
1278
|
+
**Returns:** Массив имён источников.
|
|
1279
|
+
|
|
1280
|
+
**Examples:**
|
|
1281
|
+
|
|
1282
|
+
```ts
|
|
1283
|
+
getDataSourceNames(registry); // ['LOAN_TYPES', 'GENDERS', 'LoadingState', ...]
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
_Source: src/schema/index.ts_
|
|
1287
|
+
|
|
1288
|
+
### isArrayNode
|
|
1289
|
+
|
|
1290
|
+
**Kind:** `function`
|
|
1291
|
+
|
|
1292
|
+
Type-guard: узел — массив (`array: '$model(...)'` + `item.$template`). Проверять ПЕРВЫМ
|
|
1293
|
+
(лист/контейнер отсеиваются после, т.к. массив тоже несёт `$model`).
|
|
1294
|
+
|
|
1295
|
+
**Signature:**
|
|
1296
|
+
```typescript
|
|
1297
|
+
export function isArrayNode(node: JsonNode): node is JsonArrayNode
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
**Parameters:**
|
|
1301
|
+
- `node` — - Узел JSON-схемы.
|
|
1302
|
+
|
|
1303
|
+
**Returns:** `true`, если узел — {@link JsonArrayNode}.
|
|
1304
|
+
|
|
1305
|
+
**Examples:**
|
|
1306
|
+
|
|
1307
|
+
Сузить тип узла перед доступом к `item.$template`
|
|
1308
|
+
```ts
|
|
1309
|
+
if (isArrayNode(node)) {
|
|
1310
|
+
node.array; // ModelOp
|
|
1311
|
+
node.item.$template; // JsonNode
|
|
1312
|
+
}
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
_Source: src/types/json-schema.ts_
|
|
1316
|
+
|
|
1317
|
+
### isComponentOp
|
|
1318
|
+
|
|
1319
|
+
**Kind:** `const`
|
|
1320
|
+
|
|
1321
|
+
Type-guard: строка — оператор `"$component(...)"` (ссылка на компонент реестра).
|
|
1322
|
+
|
|
1323
|
+
**Signature:**
|
|
1324
|
+
```typescript
|
|
1325
|
+
export const isComponentOp
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
**Parameters:**
|
|
1329
|
+
- `v` — - Проверяемое значение.
|
|
1330
|
+
|
|
1331
|
+
**Returns:** `true`, если `v` — {@link ComponentOp}.
|
|
1332
|
+
|
|
1333
|
+
**Examples:**
|
|
1334
|
+
|
|
1335
|
+
```ts
|
|
1336
|
+
if (isComponentOp(node.component)) {
|
|
1337
|
+
const name = parseOperator(node.component)!.arg; // 'Select'
|
|
1338
|
+
}
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
_Source: src/operators.ts_
|
|
1342
|
+
|
|
1343
|
+
### isContainerNode
|
|
1344
|
+
|
|
1345
|
+
**Kind:** `function`
|
|
1346
|
+
|
|
1347
|
+
Type-guard: узел — контейнер (`component: '$component(...)'`, без `value`/`array`).
|
|
1348
|
+
|
|
1349
|
+
**Signature:**
|
|
1350
|
+
```typescript
|
|
1351
|
+
export function isContainerNode(node: JsonNode): node is JsonContainerNode
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
**Parameters:**
|
|
1355
|
+
- `node` — - Узел JSON-схемы.
|
|
1356
|
+
|
|
1357
|
+
**Returns:** `true`, если узел — {@link JsonContainerNode}.
|
|
1358
|
+
|
|
1359
|
+
**Examples:**
|
|
1360
|
+
|
|
1361
|
+
Сузить тип узла перед обходом `children`
|
|
1362
|
+
```ts
|
|
1363
|
+
if (isContainerNode(node)) {
|
|
1364
|
+
node.component; // ComponentOp
|
|
1365
|
+
node.children?.forEach(walk);
|
|
1366
|
+
}
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
_Source: src/types/json-schema.ts_
|
|
1370
|
+
|
|
1371
|
+
### isDataSourceOp
|
|
1372
|
+
|
|
1373
|
+
**Kind:** `const`
|
|
1374
|
+
|
|
1375
|
+
Type-guard: строка — оператор `"$dataSource(...)"` (ссылка на registry-source).
|
|
1376
|
+
|
|
1377
|
+
**Signature:**
|
|
1378
|
+
```typescript
|
|
1379
|
+
export const isDataSourceOp
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
**Parameters:**
|
|
1383
|
+
- `v` — - Проверяемое значение.
|
|
1384
|
+
|
|
1385
|
+
**Returns:** `true`, если `v` — {@link DataSourceOp}.
|
|
1386
|
+
|
|
1387
|
+
**Examples:**
|
|
1388
|
+
|
|
1389
|
+
```ts
|
|
1390
|
+
if (isDataSourceOp(props.options)) {
|
|
1391
|
+
const name = parseOperator(props.options)!.arg; // 'LOAN_TYPES'
|
|
1392
|
+
}
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
_Source: src/operators.ts_
|
|
1396
|
+
|
|
1397
|
+
### isFieldNode
|
|
1398
|
+
|
|
1399
|
+
**Kind:** `function`
|
|
1400
|
+
|
|
1401
|
+
Type-guard: узел — лист (`value: '$model(...)'`).
|
|
1402
|
+
|
|
1403
|
+
**Signature:**
|
|
1404
|
+
```typescript
|
|
1405
|
+
export function isFieldNode(node: JsonNode): node is JsonFieldNode
|
|
1406
|
+
```
|
|
1407
|
+
|
|
1408
|
+
**Parameters:**
|
|
1409
|
+
- `node` — - Узел JSON-схемы.
|
|
1410
|
+
|
|
1411
|
+
**Returns:** `true`, если узел — {@link JsonFieldNode}.
|
|
1412
|
+
|
|
1413
|
+
**Examples:**
|
|
1414
|
+
|
|
1415
|
+
Сузить тип узла перед доступом к `value`/`component`
|
|
1416
|
+
```ts
|
|
1417
|
+
if (isFieldNode(node)) {
|
|
1418
|
+
node.value; // ModelOp
|
|
1419
|
+
node.component; // ComponentOp | undefined
|
|
1420
|
+
}
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
_Source: src/types/json-schema.ts_
|
|
1424
|
+
|
|
1425
|
+
### isModelOp
|
|
1426
|
+
|
|
1427
|
+
**Kind:** `const`
|
|
1428
|
+
|
|
1429
|
+
Type-guard: строка — оператор `"$model(...)"` (привязка к полю/массиву модели).
|
|
1430
|
+
|
|
1431
|
+
**Signature:**
|
|
1432
|
+
```typescript
|
|
1433
|
+
export const isModelOp
|
|
1434
|
+
```
|
|
1435
|
+
|
|
1436
|
+
**Parameters:**
|
|
1437
|
+
- `v` — - Проверяемое значение.
|
|
1438
|
+
|
|
1439
|
+
**Returns:** `true`, если `v` — {@link ModelOp}.
|
|
1440
|
+
|
|
1441
|
+
**Examples:**
|
|
1442
|
+
|
|
1443
|
+
Сузить тип значения prop до ModelOp
|
|
1444
|
+
```ts
|
|
1445
|
+
if (isModelOp(value)) {
|
|
1446
|
+
const path = parseOperator(value)!.arg; // 'loanType'
|
|
1447
|
+
}
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
_Source: src/operators.ts_
|
|
1451
|
+
|
|
1452
|
+
### JsonArrayNode
|
|
1453
|
+
|
|
1454
|
+
**Kind:** `interface`
|
|
1455
|
+
|
|
1456
|
+
Массив форм: данные из модели (`$model`) + шаблон элемента (`$template`).
|
|
1457
|
+
|
|
1458
|
+
**Signature:**
|
|
1459
|
+
```typescript
|
|
1460
|
+
export interface JsonArrayNode {
|
|
1461
|
+
/** Id для render-behavior. */
|
|
1462
|
+
selector?: string;
|
|
1463
|
+
/** Привязка к массиву модели: `'$model(coBorrowers)'`. */
|
|
1464
|
+
array: ModelOp;
|
|
1465
|
+
/** Шаблон под-схемы элемента. */
|
|
1466
|
+
item: { $template: JsonNode };
|
|
1467
|
+
/**
|
|
1468
|
+
* «Пустой» элемент для кнопки «Добавить» (литерал-объект по форме элемента).
|
|
1469
|
+
* Нужен, т.к. листья шаблона несут `value: '$model(...)'`, а не литерал-дефолт.
|
|
1470
|
+
*/
|
|
1471
|
+
initialValue?: Record<string, unknown>;
|
|
1472
|
+
/** Оформление секции массива (title/addButtonLabel/itemLabel/emptyMessage/…). */
|
|
1473
|
+
componentProps?: Record<string, unknown>;
|
|
1474
|
+
}
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
_Source: src/types/json-schema.ts_
|
|
1478
|
+
|
|
1479
|
+
### JsonContainerNode
|
|
1480
|
+
|
|
1481
|
+
**Kind:** `interface`
|
|
1482
|
+
|
|
1483
|
+
Контейнер (Box/Section/Wizard/Step/…) с дочерними узлами.
|
|
1484
|
+
|
|
1485
|
+
**Signature:**
|
|
1486
|
+
```typescript
|
|
1487
|
+
export interface JsonContainerNode {
|
|
1488
|
+
/** Id для render-behavior. */
|
|
1489
|
+
selector?: string;
|
|
1490
|
+
/** Компонент-контейнер из реестра: `'$component(Section)'`. */
|
|
1491
|
+
component: ComponentOp;
|
|
1492
|
+
/** Props компонента; значения могут содержать строки-операторы или вложенные узлы. */
|
|
1493
|
+
componentProps?: Record<string, unknown>;
|
|
1494
|
+
/** Дочерние узлы. */
|
|
1495
|
+
children?: JsonNode[];
|
|
1496
|
+
}
|
|
1497
|
+
```
|
|
1498
|
+
|
|
1499
|
+
_Source: src/types/json-schema.ts_
|
|
1500
|
+
|
|
1501
|
+
### JsonFieldNode
|
|
1502
|
+
|
|
1503
|
+
**Kind:** `interface`
|
|
1504
|
+
|
|
1505
|
+
Лист формы: значение из модели (`$model`) + опциональный компонент (`$component`, дефолт — Input).
|
|
1506
|
+
|
|
1507
|
+
**Signature:**
|
|
1508
|
+
```typescript
|
|
1509
|
+
export interface JsonFieldNode {
|
|
1510
|
+
/** Id для render-behavior (hideWhen/patchProps). Опционален. */
|
|
1511
|
+
selector?: string;
|
|
1512
|
+
/** Привязка к сигналу модели: `'$model(personalData.lastName)'`. */
|
|
1513
|
+
value: ModelOp;
|
|
1514
|
+
/** Компонент поля из реестра: `'$component(Select)'`. Опционален. */
|
|
1515
|
+
component?: ComponentOp;
|
|
1516
|
+
/** Props компонента; значения могут содержать строки-операторы (`'$dataSource(NAME)'`) или вложенные узлы. */
|
|
1517
|
+
componentProps?: Record<string, unknown>;
|
|
1518
|
+
/** Обёртка поля (например, FormField). */
|
|
1519
|
+
wrapper?: JsonNode;
|
|
1520
|
+
}
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
_Source: src/types/json-schema.ts_
|
|
1524
|
+
|
|
1525
|
+
### JsonFormRenderer
|
|
1526
|
+
|
|
1527
|
+
**Kind:** `function`
|
|
1528
|
+
|
|
1529
|
+
Главный компонент пакета. Рендерит форму, описанную JSON-схемой.
|
|
1530
|
+
|
|
1531
|
+
Должен использоваться внутри {@link JsonRendererProvider}, который снабжает рендерер
|
|
1532
|
+
реестром компонентов. Без реестра компонент бросит исключение при попытке резолва.
|
|
1533
|
+
|
|
1534
|
+
**Signature:**
|
|
1535
|
+
```typescript
|
|
1536
|
+
export function JsonFormRenderer<T>({
|
|
1537
|
+
schema,
|
|
1538
|
+
renderBehavior,
|
|
1539
|
+
onSchemaReady,
|
|
1540
|
+
validate = false,
|
|
1541
|
+
}: JsonFormRendererProps<T>): ReactNode
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
**Examples:**
|
|
1545
|
+
|
|
1546
|
+
Форма из JSON-схемы (M1)
|
|
1547
|
+
```tsx
|
|
1548
|
+
import { useMemo } from 'react';
|
|
1549
|
+
import { createModel } from '@reformer/core';
|
|
1550
|
+
import { Input, Box, FormField } from '@reformer/ui-kit';
|
|
1551
|
+
import {
|
|
1552
|
+
JsonFormRenderer,
|
|
1553
|
+
JsonRendererProvider,
|
|
1554
|
+
defineRegistry,
|
|
1555
|
+
FIELD_WRAPPER,
|
|
1556
|
+
type JsonFormSchema,
|
|
1557
|
+
} from '@reformer/renderer-json';
|
|
1558
|
+
|
|
1559
|
+
// Привязки — строки-операторы: '$model(...)', '$component(...)', '$dataSource(...)'.
|
|
1560
|
+
const schema: JsonFormSchema = {
|
|
1561
|
+
version: '1.0',
|
|
1562
|
+
root: {
|
|
1563
|
+
component: '$component(Box)',
|
|
1564
|
+
children: [
|
|
1565
|
+
{
|
|
1566
|
+
value: '$model(email)',
|
|
1567
|
+
component: '$component(Input)',
|
|
1568
|
+
componentProps: { label: 'Email' },
|
|
1569
|
+
},
|
|
1570
|
+
],
|
|
1571
|
+
},
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
type MyForm = { email: string };
|
|
1575
|
+
|
|
1576
|
+
function MyFormPage() {
|
|
1577
|
+
// M1: модель — источник истины значений; листья схемы биндятся к её сигналам.
|
|
1578
|
+
const model = useMemo(() => createModel<MyForm>({ email: '' }), []);
|
|
1579
|
+
const registry = useMemo(() => defineRegistry((reg) => {
|
|
1580
|
+
reg.component('Input', Input);
|
|
1581
|
+
reg.component('Box', Box);
|
|
1582
|
+
reg.component(FIELD_WRAPPER, FormField); // системная обёртка полей
|
|
1583
|
+
}), []);
|
|
1584
|
+
|
|
1585
|
+
// Модель передаётся через провайдер (settings.model), НЕ пропом рендерера.
|
|
1586
|
+
return (
|
|
1587
|
+
<JsonRendererProvider settings={{ registry, model }}>
|
|
1588
|
+
<JsonFormRenderer<MyForm> schema={schema} validate={import.meta.env.DEV} />
|
|
1589
|
+
</JsonRendererProvider>
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
**Note**: `JsonFormRenderer` принимает ТОЛЬКО `{ schema, renderBehavior?, onSchemaReady?, validate? }`.
|
|
1595
|
+
Под M1 модель (`FormModel`) передаётся через {@link JsonRendererProvider} settings (`model`);
|
|
1596
|
+
листья JSON-схемы биндятся к её сигналам конвертером {@link createRenderSchemaFromJsonM1}.
|
|
1597
|
+
|
|
1598
|
+
**See also:**
|
|
1599
|
+
- [docs/llms/01-overview.md](../../docs/llms/01-overview.md)
|
|
1600
|
+
|
|
1601
|
+
_Source: src/components/json-form-renderer.tsx_
|
|
1602
|
+
|
|
1603
|
+
### JsonFormRendererProps
|
|
1604
|
+
|
|
1605
|
+
**Kind:** `interface`
|
|
1606
|
+
|
|
1607
|
+
Props of {@link JsonFormRenderer}.
|
|
1608
|
+
|
|
1609
|
+
**Signature:**
|
|
1610
|
+
```typescript
|
|
1611
|
+
export interface JsonFormRendererProps<T> {
|
|
1612
|
+
/** JSON-схема формы. См. {@link JsonFormSchema}. */
|
|
1613
|
+
schema: JsonFormSchema;
|
|
1614
|
+
/** Опциональный behavior: hideWhen/patchProps/onComponentEvent поверх готовой схемы. */
|
|
1615
|
+
renderBehavior?: RenderBehaviorFn<T>;
|
|
1616
|
+
/** Колбэк, получающий построенный `RenderSchemaProxy` для внешних манипуляций. */
|
|
1617
|
+
onSchemaReady?: (schema: RenderSchemaProxy<T>) => void;
|
|
1618
|
+
/**
|
|
1619
|
+
* Валидировать JSON-схему против мета-схемы перед рендером. При ошибках рисует
|
|
1620
|
+
* {@link SchemaErrorPanel} вместо формы. ajv грузится **динамически** (`import('../validate')`) —
|
|
1621
|
+
* в prod-бандл не попадает, пока `validate` не включён.
|
|
1622
|
+
*
|
|
1623
|
+
* По умолчанию `false`. Чтобы валидировать только в dev, приложение передаёт значение из
|
|
1624
|
+
* СВОЕГО окружения: `validate={import.meta.env.DEV}` — детекцию dev нельзя «запечь» в пакет,
|
|
1625
|
+
* т.к. `import.meta.env.DEV` инлайнится в `false` при production-сборке самого пакета.
|
|
1626
|
+
*/
|
|
1627
|
+
validate?: boolean;
|
|
1628
|
+
}
|
|
1629
|
+
```
|
|
1630
|
+
|
|
1631
|
+
_Source: src/components/json-form-renderer.tsx_
|
|
1632
|
+
|
|
1633
|
+
### JsonFormSchema
|
|
1634
|
+
|
|
1635
|
+
**Kind:** `interface`
|
|
1636
|
+
|
|
1637
|
+
Корневая JSON-схема формы.
|
|
1638
|
+
|
|
1639
|
+
**Signature:**
|
|
1640
|
+
```typescript
|
|
1641
|
+
export interface JsonFormSchema {
|
|
1642
|
+
/**
|
|
1643
|
+
* Путь к мета-схеме для IDE (VSCode подсветит структуру/синтаксис/имена `$component`).
|
|
1644
|
+
* Игнорируется конвертером. Сгенерировать конкретную мета-схему: `gen-form-json-schema.ts`.
|
|
1645
|
+
*/
|
|
1646
|
+
$schema?: string;
|
|
1647
|
+
/** Версия схемы (для миграций). */
|
|
1648
|
+
version?: string;
|
|
1649
|
+
/** Корневой узел. */
|
|
1650
|
+
root: JsonNode;
|
|
1651
|
+
}
|
|
1652
|
+
```
|
|
1653
|
+
|
|
1654
|
+
**Examples:**
|
|
1655
|
+
|
|
1656
|
+
```ts
|
|
1657
|
+
const schema: JsonFormSchema = {
|
|
1658
|
+
version: '1.0',
|
|
1659
|
+
root: {
|
|
1660
|
+
component: '$component(Box)',
|
|
1661
|
+
children: [{ value: '$model(email)', component: '$component(Input)' }],
|
|
1662
|
+
},
|
|
1663
|
+
};
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
_Source: src/types/json-schema.ts_
|
|
1667
|
+
|
|
1668
|
+
### JsonNode
|
|
1669
|
+
|
|
1670
|
+
**Kind:** `type`
|
|
1671
|
+
|
|
1672
|
+
Узел JSON-схемы (M1).
|
|
1673
|
+
|
|
1674
|
+
**Signature:**
|
|
1675
|
+
```typescript
|
|
1676
|
+
export type JsonNode = JsonFieldNode | JsonArrayNode | JsonContainerNode;
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
_Source: src/types/json-schema.ts_
|
|
1680
|
+
|
|
1681
|
+
### JsonOperator
|
|
1682
|
+
|
|
1683
|
+
**Kind:** `type`
|
|
1684
|
+
|
|
1685
|
+
Любой строковый оператор JSON-схемы.
|
|
1686
|
+
|
|
1687
|
+
**Signature:**
|
|
1688
|
+
```typescript
|
|
1689
|
+
export type JsonOperator = ModelOp | ComponentOp | DataSourceOp;
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
_Source: src/operators.ts_
|
|
1693
|
+
|
|
1694
|
+
### JsonRendererProvider
|
|
1695
|
+
|
|
1696
|
+
**Kind:** `function`
|
|
1697
|
+
|
|
1698
|
+
Провайдер настроек для {@link JsonFormRenderer}. Прокидывает реестр и
|
|
1699
|
+
fieldWrapper во вложенные компоненты через React Context.
|
|
1700
|
+
|
|
1701
|
+
Поддерживает вложенность: внутренний провайдер сливается с внешним
|
|
1702
|
+
(внешний имеет приоритет в случае дублей имён в реестре).
|
|
1703
|
+
|
|
1704
|
+
**Signature:**
|
|
1705
|
+
```typescript
|
|
1706
|
+
export function JsonRendererProvider({ settings, children }: JsonRendererProviderProps): ReactNode
|
|
1707
|
+
```
|
|
1708
|
+
|
|
1709
|
+
**Examples:**
|
|
1710
|
+
|
|
1711
|
+
M1: реестр + модель через settings
|
|
1712
|
+
```tsx
|
|
1713
|
+
// Модель — источник истины (M1); листья схемы биндятся к её сигналам.
|
|
1714
|
+
const model = useMemo(() => createModel<MyForm>({ email: '' }), []);
|
|
1715
|
+
const registry = useMemo(() => defineRegistry((reg) => {
|
|
1716
|
+
reg.component('Input', Input);
|
|
1717
|
+
reg.component(FIELD_WRAPPER, FormField);
|
|
1718
|
+
}), []);
|
|
1719
|
+
|
|
1720
|
+
<JsonRendererProvider settings={{ registry, model }}>
|
|
1721
|
+
<JsonFormRenderer<MyForm> schema={schema} />
|
|
1722
|
+
</JsonRendererProvider>
|
|
1723
|
+
```
|
|
1724
|
+
|
|
1725
|
+
_Source: src/context/json-renderer-context.tsx_
|
|
1726
|
+
|
|
1727
|
+
### JsonRendererProviderProps
|
|
1728
|
+
|
|
1729
|
+
**Kind:** `interface`
|
|
1730
|
+
|
|
1731
|
+
Props {@link JsonRendererProvider}.
|
|
1732
|
+
|
|
1733
|
+
**Signature:**
|
|
1734
|
+
```typescript
|
|
1735
|
+
export interface JsonRendererProviderProps {
|
|
1736
|
+
/** Настройки рендерера, как минимум содержащие `registry`. */
|
|
1737
|
+
settings: JsonRendererSettings;
|
|
1738
|
+
/** Дочернее поддерево, в котором доступен `JsonFormRenderer` и `useJsonRendererSettings`. */
|
|
1739
|
+
children: ReactNode;
|
|
1740
|
+
}
|
|
1741
|
+
```
|
|
1742
|
+
|
|
1743
|
+
_Source: src/context/json-renderer-context.tsx_
|
|
1744
|
+
|
|
1745
|
+
### JsonRendererSettings
|
|
1746
|
+
|
|
1747
|
+
**Kind:** `interface`
|
|
1748
|
+
|
|
1749
|
+
Расширенные настройки рендерера: всё из `RendererSettings` плюс реестр.
|
|
1750
|
+
|
|
1751
|
+
**Signature:**
|
|
1752
|
+
```typescript
|
|
1753
|
+
export interface JsonRendererSettings extends RendererSettings {
|
|
1754
|
+
/** Реестр компонентов и source-значений. См. {@link defineRegistry}. */
|
|
1755
|
+
registry?: ComponentRegistry;
|
|
1756
|
+
/**
|
|
1757
|
+
* Модель данных (M1, единая схема). Если задана — JSON-листья биндятся к сигналам модели
|
|
1758
|
+
* (`model.signalAt(selector)`), а форма строится из той же JSON-схемы (без отдельной схемы формы).
|
|
1759
|
+
*/
|
|
1760
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1761
|
+
model?: FormModel<any>;
|
|
1762
|
+
}
|
|
1763
|
+
```
|
|
1764
|
+
|
|
1765
|
+
_Source: src/context/json-renderer-context.tsx_
|
|
1766
|
+
|
|
1767
|
+
### ModelOp
|
|
1768
|
+
|
|
1769
|
+
**Kind:** `type`
|
|
1770
|
+
|
|
1771
|
+
Строка-оператор привязки к полю/массиву модели: `` `$model(${path})` ``.
|
|
1772
|
+
|
|
1773
|
+
**Signature:**
|
|
1774
|
+
```typescript
|
|
1775
|
+
export type ModelOp = `$model(${string})`;
|
|
1776
|
+
```
|
|
1777
|
+
|
|
1778
|
+
_Source: src/operators.ts_
|
|
1779
|
+
|
|
1780
|
+
### ParsedOperator
|
|
1781
|
+
|
|
1782
|
+
**Kind:** `interface`
|
|
1783
|
+
|
|
1784
|
+
Разобранный оператор: тип + аргумент (путь/имя).
|
|
1785
|
+
|
|
1786
|
+
**Signature:**
|
|
1787
|
+
```typescript
|
|
1788
|
+
export interface ParsedOperator {
|
|
1789
|
+
op: 'model' | 'component' | 'dataSource';
|
|
1790
|
+
arg: string;
|
|
1791
|
+
}
|
|
1792
|
+
```
|
|
1793
|
+
|
|
1794
|
+
_Source: src/operators.ts_
|
|
1795
|
+
|
|
1796
|
+
### parseOperator
|
|
1797
|
+
|
|
1798
|
+
**Kind:** `function`
|
|
1799
|
+
|
|
1800
|
+
Разбор строки-оператора `"$op(arg)"`. Возвращает `null` для не-операторов (обычных строк),
|
|
1801
|
+
чтобы вызывающий оставил значение как есть.
|
|
1802
|
+
|
|
1803
|
+
**Signature:**
|
|
1804
|
+
```typescript
|
|
1805
|
+
export function parseOperator(value: unknown): ParsedOperator | null
|
|
1806
|
+
```
|
|
1807
|
+
|
|
1808
|
+
**Parameters:**
|
|
1809
|
+
- `value` — - Проверяемое значение (не-строки сразу дают `null`).
|
|
1810
|
+
|
|
1811
|
+
**Returns:**
|
|
1812
|
+
|
|
1813
|
+
**Examples:**
|
|
1814
|
+
|
|
1815
|
+
```ts
|
|
1816
|
+
parseOperator('$model(loanType)'); // { op: 'model', arg: 'loanType' }
|
|
1817
|
+
parseOperator('$dataSource(LOAN_TYPES)'); // { op: 'dataSource', arg: 'LOAN_TYPES' }
|
|
1818
|
+
parseOperator('Введите сумму'); // null (обычная строка)
|
|
1819
|
+
```
|
|
1820
|
+
|
|
1821
|
+
_Source: src/operators.ts_
|
|
1822
|
+
|
|
1823
|
+
### RegistryBuilder
|
|
1824
|
+
|
|
1825
|
+
**Kind:** `interface`
|
|
1826
|
+
|
|
1827
|
+
Builder, который попадает в callback {@link defineRegistry}.
|
|
1828
|
+
|
|
1829
|
+
**Signature:**
|
|
1830
|
+
```typescript
|
|
1831
|
+
export interface RegistryBuilder {
|
|
1832
|
+
/**
|
|
1833
|
+
* Зарегистрировать React-компонент под именем — доступен в схеме как
|
|
1834
|
+
* `$component(name)`. Одним методом регистрируются и компоненты-листья
|
|
1835
|
+
* (Input/Select), и контейнеры (Box/Section/FormField): роль узла (лист vs
|
|
1836
|
+
* контейнер) определяется структурой узла схемы (`value` vs `children`),
|
|
1837
|
+
* а не регистрацией.
|
|
1838
|
+
*/
|
|
1839
|
+
component<P>(name: string, component: ComponentType<P>, description?: string): void;
|
|
1840
|
+
dataSource<T>(name: string, value: T, description?: string): void;
|
|
1841
|
+
}
|
|
1842
|
+
```
|
|
1843
|
+
|
|
1844
|
+
**Examples:**
|
|
1845
|
+
|
|
1846
|
+
```typescript
|
|
1847
|
+
defineRegistry((reg: RegistryBuilder) => {
|
|
1848
|
+
reg.component('Input', Input);
|
|
1849
|
+
reg.component('Box', Box);
|
|
1850
|
+
reg.dataSource('LOAN_TYPES', LOAN_TYPES);
|
|
1851
|
+
});
|
|
1852
|
+
```
|
|
1853
|
+
|
|
1854
|
+
_Source: src/registry/types.ts_
|
|
1855
|
+
|
|
1856
|
+
### SchemaErrorPanel
|
|
1857
|
+
|
|
1858
|
+
**Kind:** `function`
|
|
1859
|
+
|
|
1860
|
+
Список ошибок схемы (path + message), с пометкой источника. Без внешних UI-зависимостей
|
|
1861
|
+
(инлайн-стили), помечен `role="alert"` и `data-testid="schema-error-panel"`.
|
|
1862
|
+
|
|
1863
|
+
Обычно рендерится автоматически внутри {@link JsonFormRenderer} (при `validate` и невалидной
|
|
1864
|
+
схеме). Можно использовать напрямую для показа результата `validateFormSchema().errors`.
|
|
1865
|
+
|
|
1866
|
+
**Signature:**
|
|
1867
|
+
```typescript
|
|
1868
|
+
export function SchemaErrorPanel({ errors }: SchemaErrorPanelProps): ReactNode
|
|
1869
|
+
```
|
|
1870
|
+
|
|
1871
|
+
**Parameters:**
|
|
1872
|
+
- `props` — - {@link SchemaErrorPanelProps} (массив `errors`).
|
|
1873
|
+
|
|
1874
|
+
**Returns:** Панель со списком ошибок.
|
|
1875
|
+
|
|
1876
|
+
**Examples:**
|
|
1877
|
+
|
|
1878
|
+
Показать ошибки валидации схемы вручную
|
|
1879
|
+
```tsx
|
|
1880
|
+
import { validateFormSchema } from '@reformer/renderer-json/validate';
|
|
1881
|
+
|
|
1882
|
+
const { valid, errors } = validateFormSchema(schema, { registry });
|
|
1883
|
+
if (!valid) return <SchemaErrorPanel errors={errors} />;
|
|
1884
|
+
```
|
|
1885
|
+
|
|
1886
|
+
_Source: src/components/schema-error-panel.tsx_
|
|
1887
|
+
|
|
1888
|
+
### SchemaErrorPanelProps
|
|
1889
|
+
|
|
1890
|
+
**Kind:** `interface`
|
|
1891
|
+
|
|
1892
|
+
Props of {@link SchemaErrorPanel}.
|
|
1893
|
+
|
|
1894
|
+
**Signature:**
|
|
1895
|
+
```typescript
|
|
1896
|
+
export interface SchemaErrorPanelProps {
|
|
1897
|
+
/** Человекочитаемые ошибки (`validateFormSchema().errors`). */
|
|
1898
|
+
errors: string[];
|
|
1899
|
+
}
|
|
1900
|
+
```
|
|
1901
|
+
|
|
1902
|
+
_Source: src/components/schema-error-panel.tsx_
|
|
1903
|
+
|
|
1904
|
+
### useJsonRendererSettings
|
|
1905
|
+
|
|
1906
|
+
**Kind:** `function`
|
|
1907
|
+
|
|
1908
|
+
Хук для чтения текущих настроек {@link JsonRendererProvider}.
|
|
1909
|
+
|
|
1910
|
+
В режиме разработки бросает исключение, если вызван вне провайдера или
|
|
1911
|
+
если в провайдере не передан `registry`.
|
|
1912
|
+
|
|
1913
|
+
**Signature:**
|
|
1914
|
+
```typescript
|
|
1915
|
+
export function useJsonRendererSettings(): JsonRendererSettings
|
|
1916
|
+
```
|
|
1917
|
+
|
|
1918
|
+
**Returns:** Текущие {@link JsonRendererSettings}.
|
|
1919
|
+
|
|
1920
|
+
**Examples:**
|
|
1921
|
+
|
|
1922
|
+
```tsx
|
|
1923
|
+
function MyControl() {
|
|
1924
|
+
const { registry } = useJsonRendererSettings();
|
|
1925
|
+
return <span>{registry?.has('Input') ? 'ready' : 'not registered'}</span>;
|
|
1926
|
+
}
|
|
1927
|
+
```
|
|
1928
|
+
|
|
1929
|
+
_Source: src/context/json-renderer-context.tsx_
|