@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/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_