# ReFormer Renderer React - LLM Integration Guide
# AUTO-GENERATED. Edit docs/llms/*.md or JSDoc in src/ and run npm run generate:llms.

> UI renderer for @reformer/core
> Package: @reformer/renderer-react  •  Version: 6.0.0

## Table of Contents
- 01-overview.md — Overview
- 02-render-schema.md — Render Schema
- 03-render-behavior.md — Render Behavior
- 04-troubleshooting.md — Troubleshooting / FAQ
- 05-cookbook.md — Cookbook
- API Reference (auto-generated from JSDoc)

## 1. Installation

```bash
npm install @reformer/renderer-react @reformer/core react react-dom
```

## 2. Import Patterns

```typescript
// recommended
import {
  FormRenderer,
  createRenderSchema,
  hideWhen,
  renderEffect,
  onComponentEvent,
  type RenderSchemaFn,
  type RenderNode,
} from '@reformer/renderer-react';
```

## 3. Quick Start

> **Ключевой момент** — `FormRenderer` НЕ принимает проп `form`. Форма создаётся из той же
> M1-схемы (`createForm({ model, schema })`) и передаётся wizard/root-узлу через его
> `componentProps.form`. Лист-узел резолвит state-ноду по сигналу через реестр, который
> заполняет `createForm`. Без `createForm` реестр пуст — поля рендерятся как `null` с warning.

```tsx
import { useMemo } from 'react';
import { createForm, type FormModel } from '@reformer/core';
import {
  FormRenderer,
  createRenderSchema,
  type RenderNode,
  type RenderSchemaFn,
} from '@reformer/renderer-react';
import { Box, Section, Input, FormField } from '@reformer/ui-kit';

interface MyForm {
  email: string;
  password: string;
}

// (1) Построить M1-дерево: листья привязаны к сигналам модели (`model.$.<field>`).
function buildSchema(model: FormModel<MyForm>): RenderNode<MyForm> {
  const m = model.$;
  return {
    component: Box,
    componentProps: { className: 'space-y-4' },
    children: [
      {
        component: Section,
        componentProps: { title: 'Вход' },
        children: [
          { value: m.email, component: Input, componentProps: { label: 'Email' } },
          {
            value: m.password,
            component: Input,
            componentProps: { label: 'Пароль', type: 'password' },
          },
        ],
      },
    ],
  };
}

function MyFormPage() {
  // (2) createForm({ model, schema }) — строит форму ИЗ той же схемы
  //     (harvest листьев по сигналу + материализация массивов).
  const { form, model } = useMemo(() => {
    const model = createModel<MyForm>({ email: '', password: '' }); // ваша фабрика модели
    const form = createForm<MyForm>({ model, schema: buildSchema(model) });
    return { form, model };
  }, []);

  // (3) Render-схема (то же дерево). Для программного управления — createRenderSchema.
  const schema = useMemo(() => createRenderSchema<MyForm>(() => buildSchema(model)), [model]);

  return <FormRenderer render={schema} settings={{ fieldWrapper: FormField }} />;
}
```

### Multi-step forms

Для многошаговых форм корневой узел — `RendererFormWizard` (compat-shim над `FormWizard` из
`@reformer/ui-kit/form-wizard`), которому передаётся `form` через `componentProps`, а шаги —
через `componentProps.steps` (массив `RenderNode`, каждый с `component: Step`). Форма для
wizard-узла добавляется в схему при её сборке. Каноническая схема wizard-узла:

```tsx
// упрощённо
function buildSchema(model, form?) {
  return {
    selector: 'wizard',
    component: RendererFormWizard,
    componentProps: {
      ...(form ? { form } : {}), // form нужен только рендеру; при createForm его не передаём
      steps: [
        { component: Step, componentProps: { title: 'Шаг 1' }, children: [ /* поля */ ] },
        // ...
      ],
    },
  };
}
```

### Container `children` — top-level свойство

`children` контейнера задаётся на самом узле, НЕ внутри `componentProps`. Рендерер
деструктурирует `const { children } = node`:

```typescript
// CORRECT
{ component: Section, componentProps: { title: 'X' }, children: [ /* nodes */ ] }

// WRONG — children в componentProps игнорируется, поддерево не рендерится
{ component: Section, componentProps: { title: 'X', children: [ /* nodes */ ] } }
```

## 4. Key Concepts

- **`RenderSchemaFn<T>`** — `() => RenderNode<T>`. Возвращает корневой узел дерева. Аргумента-пути нет: привязка к данным идёт через сигналы модели в листьях.
- **`RenderNode<T>`** — узел дерева, дискриминированный union: **field** (`ModelFieldRenderNode` — есть `value: Signal`), **array** (`ArrayRenderNode` — есть `array` + `item`), **container** (`ContainerRenderNode` — есть `component` + `children`).
- **`fieldWrapper`** — общая обёртка вокруг каждого поля (label, error). Передаётся через `settings`. Можно перекрыть для конкретного поля через `componentProps.fieldWrapper`.
- **`createRenderSchema(fn)`** — превращает `RenderSchemaFn` в `RenderSchemaProxy` для программного управления узлами (`setHidden`, `patchProps`, `getRef`) и точкой подключения декларативного behavior.
- **`RenderBehaviorFn<T>`** — функция `(schema) => void`, применяющая standalone-хелперы (`hideWhen`, `renderEffect`, `onComponentEvent`, `onInit`, `onMount`, `onUnmount`) к `RenderSchemaProxy`.

## 5. Components and exports

| Export                                                                           | Purpose                                                    |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `FormRenderer`                                                                   | Главный React-компонент, отрисовывающий форму по схеме.    |
| `RenderNodeComponent`                                                            | Рекурсивный рендер одного узла (для ручной композиции).    |
| `RenderModelNode`, `RenderModelArray`                                            | Низкоуровневый рендер узла/массива M1-схемы.               |
| `RenderContextProvider`, `useRenderContext`                                      | Контекст рендеринга: `form`, `settings`.                   |
| `createRenderSchema`, `isRenderSchemaProxy`                                      | Программное управление схемой.                             |
| `isModelFieldRenderNode`, `isArrayRenderNode`, `isContainerRenderNode`           | Type guards для `RenderNode`.                              |
| `hideWhen`, `renderEffect`, `onComponentEvent`, `onInit`, `onMount`, `onUnmount` | Декларативные behavior-хелперы.                            |

## 6. See also

- [02-render-schema.md](02-render-schema.md) — формат `RenderSchemaFn`, `RenderNode`, массивы.
- [03-render-behavior.md](03-render-behavior.md) — hideWhen, renderEffect, lifecycle.
- [04-troubleshooting.md](04-troubleshooting.md) — частые ошибки.
- [05-cookbook.md](05-cookbook.md) — рецепты из реального кода.

## 7. Key Concepts

Единая схема (M1): одно дерево `RenderNode` описывает и layout, и привязку полей к модели. Привязка идёт через **сигналы модели** (`model.$.<field>`), а не через отдельный `path`-прокси.

- **`RenderSchemaFn<T>`** — `() => RenderNode<T>`. Без аргументов: возвращает корневой узел. Привязка к данным — через сигналы в листьях, поэтому legacy-аргумент `path` удалён.
- **`ModelFieldRenderNode`** — узел-поле. Несёт `value: Signal` (сигнал модели, `model.$.<field>`), `component` (UI-компонент), `componentProps` (пропсы поля). State-нода (errors/disabled/validation) резолвится по сигналу через реестр `getNodeForSignal` (реестр заполняет `createForm`).
- **`ArrayRenderNode<T>`** — узел-массив модели. Данные принадлежат модели (`array: model.<path>`), форма элемента описывается `item(itemModel)`, `initialValue` — значение/фабрика нового элемента.
- **`ContainerRenderNode<T>`** — узел-контейнер. В `component` — React-компонент, дочерние узлы задаются в **top-level** `children` (НЕ в `componentProps`).

Type guards:

```typescript
import {
  isModelFieldRenderNode,
  isArrayRenderNode,
  isContainerRenderNode,
} from '@reformer/renderer-react';

if (isModelFieldRenderNode(node)) {
  /* node.value — Signal; node.component — UI-компонент */
}
if (isArrayRenderNode(node)) {
  /* node.array — реактивный массив; node.item(im) — поддерево элемента */
}
if (isContainerRenderNode(node)) {
  /* node.children — RenderNode[] (top-level, не в componentProps) */
}
```

## 8. Examples

Двухколоночная форма с секцией. Листья несут `value: model.$.x` (сигнал) + `component` + `componentProps`. `children` — top-level свойство контейнера:

```tsx
import type { FormModel } from '@reformer/core';
import type { RenderSchemaFn } from '@reformer/renderer-react';
import { Box, Section, Input } from '@reformer/ui-kit';

const buildSchema = (model: FormModel<MyForm>): RenderSchemaFn<MyForm> => {
  const m = model.$;
  return () => ({
    component: Box,
    componentProps: { className: 'grid grid-cols-2 gap-4' },
    children: [
      {
        component: Section,
        componentProps: { title: 'Личные данные', className: 'space-y-4' },
        children: [
          { value: m.firstName, component: Input, componentProps: { label: 'Имя' } },
          { value: m.lastName, component: Input, componentProps: { label: 'Фамилия' } },
        ],
      },
    ],
  });
};
```

### Массив модели

Узел-массив: `array` — реактивный массив модели, `item(itemModel)` строит поддерево по под-модели элемента, `initialValue` — фабрика нового элемента для кнопки «Добавить». Оформление секции (заголовок, кнопки, empty-message, reorder) — в `componentProps`:

```tsx
import { Box, Input, Select } from '@reformer/ui-kit';

const coBorrowersNode = {
  selector: 'co-borrowers-array',
  array: model.coBorrowers,
  initialValue: createBlankCoBorrower,
  componentProps: {
    title: 'Созаемщики',
    itemLabel: 'Созаемщик',
    addButtonLabel: '+ Добавить созаемщика',
    emptyMessage: 'Нажмите «Добавить созаемщика»',
    reorderable: true,
  },
  item: (im: any) => ({
    component: Box,
    componentProps: { className: 'space-y-3' },
    children: [
      { value: im.$.phone, component: Input, componentProps: { label: 'Телефон' } },
      { value: im.$.relationship, component: Select, componentProps: { label: 'Отношение' } },
    ],
  }),
};
```

Внутри `item` листья привязываются к сигналам **под-модели** элемента (`im.$.<field>`). Per-item форму создаёт `ModelArrayNode`, материализованный `createForm` — рендерер итерирует элементы и рисует поддерево для каждого.

## 9. Programmatic API

`createRenderSchema(fn)` оборачивает `RenderSchemaFn` в `RenderSchemaProxy`, который позволяет адресовать узлы по `selector` и навешивать поведение/lifecycle. Селектор задаётся на самом узле (`node.selector`):

```tsx
import { createRenderSchema, hideWhen } from '@reformer/renderer-react';

const schema = createRenderSchema<MyForm>(buildSchema(model));

// Императивное управление узлом по selector:
schema.node('extra-section').setHidden(true);
schema.node('extra-section').patchProps({ title: 'Новый заголовок' });
schema.node('extra-section').resetHidden();

// Декларативное реактивное скрытие (standalone-хелпер, читает сигналы формы):
hideWhen(schema.node('extra-section'), () => !form.subscribe.value.value);

<FormRenderer render={schema} settings={{ fieldWrapper: FormField }} />;
```

`FormRenderer` не принимает проп `form` — форма для wizard-узла передаётся через `componentProps` этого узла. См. [01-overview.md](01-overview.md).

## 10. Anti-patterns

- **Возвращать React-element вместо `RenderNode`** — `RenderSchemaFn` должна возвращать описание (`{ component, componentProps, children }` или `{ value, component }`), а не JSX. Сам JSX строит `FormRenderer`.
- **Класть `children` в `componentProps`** — `children` это TOP-LEVEL свойство контейнера. Рендерер деструктурирует `const { children } = node`. В `componentProps.children` узлы не отрисуются.
- **Хранить `RenderSchemaProxy` внутри компонента без `useMemo`** — на каждом ре-рендере создаётся новый proxy, что ломает override-карты и lifecycle-хуки.
- **Забыть `createForm` перед рендером** — лист-узел резолвит state-ноду по сигналу через реестр. Без `createForm({ model, schema })` реестр пуст, поле логирует warning и рендерится как `null`.

## 11. See also

- [01-overview.md](01-overview.md) — mount-путь и `FormRenderer`.
- [03-render-behavior.md](03-render-behavior.md) — `hideWhen`, `renderEffect`, lifecycle.
- [04-troubleshooting.md](04-troubleshooting.md).

## 12. Helpers

| Helper                                      | Первый аргумент       | Назначение                                                              |
| ------------------------------------------- | --------------------- | ---------------------------------------------------------------------- |
| `hideWhen(node, conditionFn)`               | `RenderNodeControl`   | Скрывает узел, пока `conditionFn()` истинна (реактивно по сигналам).    |
| `renderEffect(schema, effectFn)`            | `RenderSchemaProxy`   | Реактивный side-effect (Preact `effect()`); может вернуть cleanup.      |
| `onComponentEvent(node, event, handler)`    | `RenderNodeControl`   | Регистрирует колбэк на проп-событие компонента (`onSubmit`, ...).       |
| `onInit(node, fn)`                          | `RenderNodeControl`   | Синхронный build-time hook: вызывается сразу при применении behavior.   |
| `onMount(node, fn)`                         | `RenderNodeControl`   | После mount узла (`useEffect`); может вернуть cleanup.                  |
| `onUnmount(node, fn)`                       | `RenderNodeControl`   | При unmount узла.                                                       |

## 13. Examples

Скрыть «mortgage-section», пока `loanType !== 'mortgage'`. Условие реактивно — пересчитывается при изменении сигнала формы:

```tsx
import { hideWhen, type RenderBehaviorFn } from '@reformer/renderer-react';

// form захвачен в замыкание фабрики поведения.
const behavior: RenderBehaviorFn<CreditForm> = (schema) => {
  hideWhen(schema.node('mortgage-section'), () => form.loanType.value.value !== 'mortgage');
};
```

Реактивный эффект — принимает **схему**, а не узел. Эффекты живут на уровне рендера всего дерева и автоматически диспозятся при unmount `FormRenderer`:

```tsx
import { renderEffect } from '@reformer/renderer-react';

const wizardRef = schema.node('wizard').getRef<FormWizardHandle<CreditForm>>();
renderEffect(schema, () => {
  if (form.loanType.value.value === 'mortgage') {
    wizardRef.current?.goToStep(1);
  }
});
```

Проп-событие компонента — `onComponentEvent` получает ровно те же аргументы, что и оригинальный проп:

```tsx
import { onComponentEvent } from '@reformer/renderer-react';

onComponentEvent(schema.node('wizard'), 'onSubmit', async (values: CreditForm) => {
  await submitCreditApplication(values);
});
```

Lifecycle (несколько хелперов на одном узле — просто вызываем подряд). `onMount` может вернуть cleanup, который выполнится до `onUnmount`:

```tsx
import { onMount, onUnmount } from '@reformer/renderer-react';

const boundary = schema.node('data-boundary');
onMount(boundary, () => {
  void loadApplication(); // напр. загрузить данные и boundary.patchProps({ status: 'ready' })
  return () => console.log('cleanup');
});
onUnmount(schema.node('wizard'), () => console.log('wizard unmounted'));
```

## 14. Anti-patterns

- **Предикат `hideWhen`, не читающий сигналы реактивно** — узел не будет переоцениваться. Читай сигнал целиком (`form.x.value.value`), не сохраняй значение заранее в переменную.
- **`renderEffect(node, ...)` вместо `renderEffect(schema, ...)`** — первый аргумент `renderEffect` это схема, а не узел (в отличие от остальных хелперов). Node-аргумент не даст эффекта.
- **Подписываться на `form` напрямую внутри React-компонента вместо `renderEffect`** — теряется автоматический dispose при unmount.
- **Бросать исключения из `onInit`** — он синхронный и вызывается при построении схемы (до первого рендера); исключение сломает mount. Логируй и обрабатывай ошибки внутри.

## 15. See also

- [02-render-schema.md](02-render-schema.md) — что такое `RenderSchemaProxy` и `schema.node(selector)`.
- [05-cookbook.md](05-cookbook.md) — совмещение нескольких behavior на одном узле.
- [04-troubleshooting.md](04-troubleshooting.md).

## 16. Field renders without label/error

Не передан `fieldWrapper` в `settings`. Добавь:

```tsx
import { FormField } from '@reformer/ui-kit';

<FormRenderer render={schema} settings={{ fieldWrapper: FormField }} />;
```

Для конкретного поля можно перекрыть глобальный wrapper через `componentProps.fieldWrapper`.

## 17. Field renders as null (console warning `[RenderSchema] No form node for signal ...`)

State-нода поля резолвится по сигналу через реестр `getNodeForSignal`, а реестр заполняет `createForm`. Если поле рисуется как `null` и в консоли `[RenderSchema] No form node for signal "<path>" — render value-leaf after createForm`:

- Форма не построена из этой же схемы. Вызови `createForm({ model, schema })` до рендера — реестр `сигнал→нода` заполняется именно там.
- Лист привязан к сигналу другой модели (не той, что передана в `createForm`). Убедись, что `value: model.$.<field>` берётся из того же `model`.
- Внутри массива — под-модель элемента (`im.$.<field>`) должна проходить через `createForm`/`ModelArrayNode` (материализуется автоматически при обработке `ArrayRenderNode`).

## 18. Container children не рендерятся

`children` контейнера — это TOP-LEVEL свойство узла, а не часть `componentProps`. Рендерер деструктурирует `const { children } = node`. Если положить их в `componentProps.children`, `node.children` будет undefined и поддерево не отрисуется.

```typescript
// CORRECT
{ component: Section, componentProps: { title: 'X' }, children: [ /* nodes */ ] }
// WRONG
{ component: Section, componentProps: { title: 'X', children: [ /* nodes */ ] } }
```

## 19. FormRenderer не принимает form

`FormRenderer` принимает только `render` и `settings`. Формы через проп нет — она передаётся wizard/root-узлу через `componentProps.form` в самой схеме (см. [01-overview.md](01-overview.md)). Не пиши `<FormRenderer render={schema} form={form} />`.

## 20. Unknown component in renderSchema

В `component` листа/контейнера должен лежать React-компонент (`Input`, `Section`, ...), а не строка. Лист поля дополнительно требует `value: model.$.<field>` (сигнал). Если хочешь адресовать компоненты по строковым именам — переходи на `@reformer/renderer-json`.

## 21. RenderSchemaProxy nodes don't react to behavior

`hideWhen`/`patchProps` работают только при адресации узла через `schema.node(selector)`. Убедись, что:

- У узла есть `selector` (top-level свойство узла). Без него `schema.node('x')` вернёт контроллер с пустыми переопределениями — без ошибок, но и без эффекта.
- `RenderSchemaFn` обёрнута через `createRenderSchema`, а результат передан в `<FormRenderer>`. Если передать «сырую» `RenderSchemaFn`, override-карты/behavior не подключатся.

## 22. Hidden node still mounts

`hideWhen` / `setHidden` возвращают `null` для узла (узел не рендерится, пока условие истинно). Скрытие реактивное: приоритет `setHidden` (программный override) > `hideWhen` (декларативное условие) > видимо. Чтобы вернуть автоматику после `setHidden(true)` — `resetHidden()`.

## 23. Form changes don't trigger re-render

Скорее всего чтение значения идёт без `.value`, или подписка не доходит до React. Проверь:

- Внутри `hideWhen`/`renderEffect` сигнал читается целиком: `form.x.value.value` (первый `.value` — доступ к сигналу поля, второй — к его значению).
- В пользовательском компоненте используется `useFormControl` (он подписывается на state-ноду).

## 24. See also

- [01-overview.md](01-overview.md)
- [02-render-schema.md](02-render-schema.md)
- [03-render-behavior.md](03-render-behavior.md)
- [05-cookbook.md](05-cookbook.md)

## 25. Custom fieldWrapper

**Problem.** Нужна собственная обёртка вокруг каждого поля (label, error, hint, аналитика, нестандартный layout) вместо стандартного `FormField` из `@reformer/ui-kit`.

**Solution.** `RenderSchema` использует `fieldWrapper` через `settings`. Кастомный wrapper получает `control` (FieldNode), `children` (отрендеренный input), `className`, `testId` — и сам строит обвязку с помощью compound-API из `@reformer/cdk/form-field`.

```tsx
import { FormField as CdkFormField } from '@reformer/cdk/form-field';
import { useFormControl, type FieldNode } from '@reformer/core';
import { FormRenderer, type FieldWrapperProps } from '@reformer/renderer-react';

function MyFieldWrapper({ control, className, children, testId }: FieldWrapperProps) {
  // Hint можно тащить из componentProps через useFormFieldContext.
  const { error } = useFormControl(control as FieldNode<unknown>);
  return (
    <CdkFormField.Root control={control}>
      <div className={className} data-testid={`field-${testId ?? 'unknown'}`}>
        <CdkFormField.Label className="text-xs uppercase text-slate-500" />
        <div className="rounded border bg-white p-2">
          {children ? (
            <CdkFormField.Control asChild>{children}</CdkFormField.Control>
          ) : (
            <CdkFormField.Control />
          )}
        </div>
        {error && <small className="text-red-600">{error}</small>}
      </div>
    </CdkFormField.Root>
  );
}

<FormRenderer render={schema} settings={{ fieldWrapper: MyFieldWrapper }} />;
```

**Notes.**

- Wrapper вызывается на каждое поле (`ModelFieldRenderNode`). Если поле — Checkbox, обычно label рендерится внутри input (см. ветку `isCheckbox` в `@reformer/ui-kit/FormField`); своему wrapper'у проверку нужно добавить вручную, иначе будет двойной label.
- Для конкретного поля можно перекрыть глобальный wrapper через `componentProps.fieldWrapper` (поле типа `ComponentType<FieldWrapperProps>` в `ModelFieldRenderNode.componentProps`).
- Не оборачивай wrapper в `React.memo` без сравнения по `control` и `children` — иначе DOM будет «застревать» на старом инпуте.

## 26. Programmatic node manipulation

**Problem.** Нужно динамически прятать/показывать секцию или менять props ноды снаружи schema (например, по событию из `useEffect`, по кнопке debug-панели, или после загрузки данных).

**Solution.** Любая `RenderSchemaProxy` (результат `createRenderSchema`) даёт API `proxy.node(selector)` с методами `setHidden`, `resetHidden`, `patchProps`, `resetProps`. Это императивные мутации, реактивные через Preact-сигналы внутри прокси — перерендеривается только затронутая нода.

```tsx
import { useEffect, useMemo } from 'react';
import { FormRenderer, createRenderSchema } from '@reformer/renderer-react';
import { Section, Input } from '@reformer/ui-kit';

function CreditApplicationPage() {
  const schema = useMemo(
    () =>
      createRenderSchema<CreditForm>(() => ({
        selector: 'mortgage-section',
        component: Section,
        children: [{ value: model.$.propertyValue, component: Input }],
      })),
    []
  );

  useEffect(() => {
    // Скрыть секцию по внешнему сигналу.
    schema.node('mortgage-section').setHidden(true);
    // Точечно обновить пропс (мерджится с предыдущими patchProps).
    schema.node('mortgage-section').patchProps({ title: 'Недвижимость (скрыта)' });
    return () => {
      schema.node('mortgage-section').resetHidden();
      schema.node('mortgage-section').resetProps();
    };
  }, [schema]);

  return <FormRenderer render={schema} settings={{ fieldWrapper: FormField }} />;
}
```

**Notes.**

- Методы `setHidden/patchProps/resetHidden/resetProps` чейнятся (`return this`).
- `patchProps` именно мерджит — повторный вызов с `{ disabled: true }` не сбросит ранее заданный `title`. Для полной очистки используй `resetProps`.
- `setHidden(true)` перекрывает реактивное условие из `hideWhen`. Чтобы вернуть автоматику — `resetHidden()`.
- Селектор должен быть указан в `RenderNode.selector`. Без него `proxy.node('x')` вернёт контроллер с пустыми переопределениями (никаких ошибок не будет, но эффекта тоже не будет).

## 27. Custom container with collapsible children

**Problem.** Нужен контейнер с собственной логикой рендеринга `children` (например, `Section` с заголовком, который можно свернуть, или wizard с табами).

**Solution.** Обычный React-компонент, принимающий `children: ReactNode`. Реестр child-узлов уже отрендерен `FormRenderer` к моменту, когда твой контейнер получит `children` — внутри их можно свободно оборачивать, фильтровать, группировать.

```tsx
import { useState, type ReactNode } from 'react';
import { ChevronDown, ChevronRight } from 'lucide-react';
import type { ContainerComponentProps } from '@reformer/renderer-react';

interface CollapsibleSectionProps extends ContainerComponentProps {
  title: string;
  defaultOpen?: boolean;
  children?: ReactNode;
}

export function CollapsibleSection({
  title,
  defaultOpen = true,
  className,
  children,
}: CollapsibleSectionProps) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <section className={className}>
      <button type="button" onClick={() => setOpen((v) => !v)} className="flex w-full gap-2">
        {open ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
        <h3 className="font-semibold">{title}</h3>
      </button>
      {open && <div className="mt-2 space-y-3">{children}</div>}
    </section>
  );
}

// В RenderSchema (children — top-level; листья на сигналах модели):
{
  selector: 'extras',
  component: CollapsibleSection,
  componentProps: { title: 'Дополнительно', defaultOpen: false },
  children: [
    { value: model.$.notes, component: Textarea },
    { value: model.$.tags, component: Input },
  ],
}
```

**Notes.**

- `children` всегда `ReactNode` — обходить как массив `RenderNode` нельзя: к этому моменту они уже превращены в React-элементы.
- Если контейнеру нужны ноды как данные (вычислить количество, отрисовать таб-бар) — описывай их через `componentProps`, а не через `children`. Пример — `RendererFormWizard` с `componentProps.steps`. Конвертер JSON-схемы поддерживает `JsonNode` и `$template` внутри произвольных props (см. [renderer-json/05-cookbook.md](../../../reformer-renderer-json/docs/llms/05-cookbook.md#template-arrays)).
- Контейнер можно адресовать через `selector` — тогда `setHidden` будет работать на его содержимое целиком.

## 28. Combining behaviors on one node

**Problem.** На одном узле нужно сразу несколько эффектов: скрытие по условию, реактивный side-effect, обработчик события компонента, lifecycle-хук — без дублирования selector-кода.

**Solution.** Собрать ссылку на ноду один раз и навешать standalone-helpers по очереди. `apply([...])` в API нет — каждый helper принимает контроллер ноды и стейкает свой override в общие override-карты.

```tsx
import {
  hideWhen,
  renderEffect,
  onComponentEvent,
  onMount,
  type RenderBehaviorFn,
} from '@reformer/renderer-react';

// form захвачен в замыкание фабрики поведения; ref — из schema.node('wizard').getRef().
const behavior: RenderBehaviorFn<CreditForm> = (schema) => {
  const wizard = schema.node('wizard');
  const wizardRef = wizard.getRef<FormWizardHandle<CreditForm>>();
  const mortgage = schema.node('mortgage-section');

  // 1. Реактивное условие — пересчитывается при изменении сигналов формы.
  hideWhen(mortgage, () => form.loanType.value.value !== 'mortgage');

  // 2. Реактивный эффект на схеме — Preact effect() с автодиспозом.
  renderEffect(schema, () => {
    if (form.loanType.value.value === 'mortgage') wizardRef.current?.goToStep(1);
  });

  // 3. Подписка на проп-событие компонента (получает родные args).
  onComponentEvent(wizard, 'onSubmit', async (values) => {
    await submitCreditApplication(values);
  });

  // 4. Lifecycle: onMount может вернуть cleanup.
  onMount(wizard, () => {
    console.log('wizard mounted');
    return () => console.log('cleanup');
  });
};
```

**Notes.**

- Повторный `hideWhen` на одном selector затирает предыдущее условие — это последняя запись побеждает.
- `onComponentEvent` мерджит обработчики по имени события (`onSubmit`, `onChange`, ...). Если schema уже содержит такой проп — он будет полностью заменён обработчиком из behavior.
- `renderEffect` принимает не node, а саму схему: эффекты живут на уровне рендера всего дерева и автоматически диспозятся при unmount `FormRenderer`.
- `onInit` срабатывает синхронно при applying behavior (до первого рендера). Это единственный хук, способный изменить `componentProps` так, чтобы они попали в первый рендер.

## 29. See also

- [02-render-schema.md](02-render-schema.md) — структура `RenderNode` и `RenderSchemaFn`.
- [03-render-behavior.md](03-render-behavior.md) — справочник по standalone-хелперам.
- [04-troubleshooting.md](04-troubleshooting.md) — типичные ошибки.

## 30. API Reference

_Auto-generated from JSDoc on public exports._

### ContainerComponentProps

**Kind:** `interface`

Базовые props для компонентов-контейнеров

**Signature:**
```typescript
export interface ContainerComponentProps {
  /** CSS класс */
  className?: string;

  /** Дочерние элементы (рендерятся FormRenderer) */
  children?: React.ReactNode;

  /** Произвольные дополнительные props */
  [key: string]: unknown;
}
```

_Source: src/core/types.ts_

### ContainerRenderNode

**Kind:** `interface`

Узел контейнера (Box, Section, Collapsible и т.д.).

**Важно:** `children` — это TOP-LEVEL свойство узла, НЕ часть `componentProps`.
Если положить `children` внутрь `componentProps`, то `node.children` будет undefined
и рендерер ничего не отрисует (он деструктурирует `const { children } = node`).

**Signature:**
```typescript
export interface ContainerRenderNode<T> {
  /**
   * Идентификатор узла — используется составными компонентами (wizard, tabs)
   * и renderBehavior (b.hideWhen).
   */
  selector?: string;

  /** React-компонент контейнера */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  component: ComponentType<any>;

  /** Дочерние узлы рендеринга */
  children?: RenderNode<T>[];

  /** Props для компонента-контейнера (className, title и т.д.) */
  componentProps?: ContainerRenderNodeProps;
}
```

**Examples:**

```typescript
{
  component: Section,
  componentProps: {
    title: 'Личные данные',
    className: 'grid grid-cols-2 gap-4',
  },
  children: [
    { value: model.$.firstName, component: Input },
    { value: model.$.lastName, component: Input },
  ],
}
```

_Source: src/core/types.ts_

### ContainerRenderNodeProps

**Kind:** `interface`

Props для ContainerRenderNode

Произвольные props для компонента-контейнера.
Дочерние узлы задаются через `ContainerRenderNode.children`, а не здесь.

**Signature:**
```typescript
export interface ContainerRenderNodeProps {
  /** CSS класс для контейнера */
  className?: string;

  /** Произвольные props для компонента контейнера */
  [key: string]: unknown;
}
```

_Source: src/core/types.ts_

### createRenderSchema

**Kind:** `function`

Оборачивает {@link RenderSchemaFn} в {@link RenderSchemaProxy} — функцию-схему
с дополнительным API `.node(selector)` для императивного управления нодами
(видимость, `componentProps`, ref) и точкой применения декларативного поведения
(`hideWhen`/`renderEffect`/`onComponentEvent`/lifecycle-хуки).

Возвращённый прокси остаётся вызываемой `RenderSchemaFn`, поэтому его напрямую
передают в `render` у {@link FormRenderer}. Переопределения хранятся в Map-ах и
применяются реактивно (через версионный сигнал) — перерисовывается только затронутая нода.

**Signature:**
```typescript
export function createRenderSchema<T>(fn: RenderSchemaFn<T>): RenderSchemaProxy<T>
```

**Parameters:**
- `fn` — - Исходная функция-схема (без аргументов; привязка к данным — через сигналы в листьях)

**Returns:** 

**Examples:**

Программное управление нодами
```tsx
const schema = createRenderSchema<MyForm>(() => ({
selector: 'root',
component: Box,
children: [
{ selector: 'extra-section', component: Section, componentProps: { title: 'Доп.' } },
],
}));

schema.node('extra-section').setHidden(true);
schema.node('extra-section').patchProps({ title: 'Новый заголовок' });
schema.node('extra-section').resetHidden();

<FormRenderer render={schema} />
```

_Source: src/core/render-schema-proxy.ts_

### FieldWrapperProps

**Kind:** `interface`

Props для компонента-обёртки поля

Обёртка получает control и рендерит label, input и errors.

**Signature:**
```typescript
export interface FieldWrapperProps {
  /** Контрол поля */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  control: any;
  /** CSS класс */
  className?: string;
  /** Дочерний элемент (отрендеренный input) */
  children: React.ReactNode;
  /**
   * testId для генерации data-testid на wrapper/label/error.
   * Выводится рендерером из пути сигнала (`model.$.<path>`, точки → дефисы)
   * или переопределяется через `componentProps.testId`.
   */
  testId?: string;
}
```

_Source: src/core/types.ts_

### FormRenderer

**Kind:** `function`

Рендеринг формы по {@link RenderSchemaFn} или {@link RenderSchemaProxy}.

Принимает `render` — функцию-схему (или обёртку из {@link createRenderSchema})
и опциональные `settings` (например, глобальный `fieldWrapper`). Разворачивает
корневой узел и рекурсивно рендерит дерево через {@link RenderNodeComponent}.
Если `render` — прокси, дополнительно монтирует реактивные эффекты (`renderEffect`)
и прокидывает карты переопределений (`setHidden`/`patchProps`/`hideWhen`) через контекст.

Форму (для wizard-узла) прокидывают через `componentProps` соответствующего узла
схемы, а не отдельным пропом `FormRenderer`.

**Signature:**
```typescript
export function FormRenderer<T>({ render, settings }: FormRendererProps<T>): ReactNode
```

**Parameters:**
- `props` — - {@link FormRendererProps}: `render` (схема) и опц. `settings`

**Returns:** React-дерево формы

**Examples:**

Схема + поведение + рендер
```tsx
import { createForm } from '@reformer/core';
import { FormRenderer, createRenderSchema } from '@reformer/renderer-react';
import { FormField } from '@reformer/ui-kit';

const { form, model } = useMemo(() => {
const model = createMyModel();
const form = createForm<MyForm>({ model, schema: buildSchema(model) });
return { form, model };
}, []);

// Прокси с form для wizard-узла + применённое render-поведение
const schema = useMemo(() => {
const s = createRenderSchema<MyForm>(() => buildSchema(model, form));
myBehavior(form)(s);
return s;
}, [form, model]);

<FormRenderer render={schema} settings={{ fieldWrapper: FormField }} />
```

_Source: src/core/form-renderer.tsx_

### FormRendererProps

**Kind:** `interface`

Props для FormRenderer

**Signature:**
```typescript
export interface FormRendererProps<T> {
  /** Функция создания RenderSchema (или RenderSchemaProxy из createRenderSchema) */
  render: RenderSchemaFn<T>;

  /**
   * Настройки рендерера
   *
   * @example
   * ```tsx
   * <FormRenderer render={schema} settings={{ fieldWrapper: FormField }} />
   * ```
   */
  settings?: RendererSettings;
}
```

_Source: src/core/types.ts_

### hideWhen

**Kind:** `function`

Объявить условие скрытия для ноды.

Условие реактивно — пересчитывается при изменении любого Preact-сигнала,
прочитанного внутри conditionFn (в т.ч. сигналов формы через ref).

**Signature:**
```typescript
export function hideWhen(node: RenderNodeControl, conditionFn: () => boolean): void
```

**Examples:**

```typescript
const wizardRef = schema.node('wizard').getRef<FormWizardHandle<MyForm>>();
hideWhen(schema.node('mortgage-section'), () =>
  wizardRef.current?.form.loanType.value.value !== 'mortgage'
);
```

_Source: src/core/render-behavior.ts_

### isArrayRenderNode

**Kind:** `function`

Type guard для {@link ArrayRenderNode} (M1): массив модели `{ array, item }`.
Проверяется до контейнера (у array-узла нет `component`).

**Signature:**
```typescript
export function isArrayRenderNode<T>(node: RenderNode<T>): node is ArrayRenderNode<T>
```

**Parameters:**
- `node` — - Узел {@link RenderNode}

**Returns:** `true`, если узел — секция массива (есть `array` и `item`-фабрика)

**Examples:**

Сужение к массиву
```typescript
if (isArrayRenderNode(node)) {
node.array; // реактивный массив модели
node.item; // (itemModel) => RenderNode поддерева элемента
}
```

_Source: src/core/utils.ts_

### isContainerRenderNode

**Kind:** `function`

Type guard для ContainerRenderNode

Проверяет, что узел является контейнером (Box, Section и т.д.).

Принимает любой валидный React component reference:
- plain function component (`function Foo() {...}`),
- `React.memo(...)` / `React.forwardRef(...)` обёртки (объекты с `$$typeof`),
- lazy / context provider'ы / прочие React-внутренности.

**Signature:**
```typescript
export function isContainerRenderNode<T>(node: RenderNode<T>): node is ContainerRenderNode<T>
```

**Examples:**

```typescript
if (isContainerRenderNode(node)) {
  // node.component - React component
  // node.children - дочерние узлы
}
```

_Source: src/core/utils.ts_

### isModelFieldRenderNode

**Kind:** `function`

Type guard для {@link ModelFieldRenderNode} (M1): лист, привязанный к сигналу модели.
Проверяется ПЕРВЫМ — такой узел несёт реальный `component`, иначе спутается с контейнером.

**Signature:**
```typescript
export function isModelFieldRenderNode<T>(node: RenderNode<T>): node is ModelFieldRenderNode
```

**Parameters:**
- `node` — - Узел {@link RenderNode}

**Returns:** `true`, если узел — поле-лист (`value instanceof Signal`)

**Examples:**

Сужение к полю
```typescript
if (isModelFieldRenderNode(node)) {
node.value; // Signal модели
node.component; // UI-компонент поля
}
```

_Source: src/core/utils.ts_

### isRenderSchemaProxy

**Kind:** `function`

Type guard: проверяет, что `fn` — это результат {@link createRenderSchema}.

**Signature:**
```typescript
export function isRenderSchemaProxy<T>(fn: RenderSchemaFn<T>): fn is RenderSchemaProxy<T>
```

**Parameters:**
- `fn` — - Произвольная `RenderSchemaFn`.

**Returns:** `true`, если `fn` обёрнута через `createRenderSchema`.

**Examples:**

```typescript
import { isRenderSchemaProxy, createRenderSchema } from '@reformer/renderer-react';

const proxy = createRenderSchema(renderSchemaFn);
isRenderSchemaProxy(proxy); // true
isRenderSchemaProxy(renderSchemaFn); // false
```

_Source: src/core/render-schema-proxy.ts_

### ModelArrayControl

**Kind:** `interface`

Реактивный массив модели (минимальный контракт, используемый рендерером).

**Signature:**
```typescript
export interface ModelArrayControl {
  length: number;
  at(index: number): unknown;
  push(item: unknown): void;
  removeAt(index: number): void;
}
```

_Source: src/core/render-model.tsx_

### ModelContainerNode

**Kind:** `interface`

Узел-контейнер: React-компонент + дочерние узлы.

**Signature:**
```typescript
export interface ModelContainerNode {
  component: ComponentType<any>;
  componentProps?: Record<string, unknown>;
  children?: ModelNode[];
  selector?: string;
}
```

_Source: src/core/render-model.tsx_

### ModelFieldNode

**Kind:** `interface`

Узел-поле единой схемы: значение — сигнал модели.

**Signature:**
```typescript
export interface ModelFieldNode {
  value: Signal<any>;
  component: ComponentType<any>;
  componentProps?: Record<string, unknown>;
  selector?: string;
  testId?: string;
}
```

_Source: src/core/render-model.tsx_

### ModelNode

**Kind:** `type`

Узел дерева единой схемы (M1) для {@link RenderModelNode}: либо лист-поле
{@link ModelFieldNode} (несёт `value: Signal` модели), либо контейнер
{@link ModelContainerNode} (React-компонент + `children`). Дискриминируется
по наличию `value instanceof Signal`.

**Signature:**
```typescript
export type ModelNode = ModelFieldNode | ModelContainerNode;
```

_Source: src/core/render-model.tsx_

### NodeLifecycleHooks

**Kind:** `interface`

Хуки жизненного цикла ноды, регистрируемые через render-behavior.
Каждый хук опционален; повторная регистрация перезаписывает предыдущее значение.

**Signature:**
```typescript
export interface NodeLifecycleHooks {
  /** Срабатывает один раз при mount ноды. Может вернуть cleanup-функцию. */
  onMount?: () => void | (() => void);
  /** Срабатывает один раз при unmount ноды. */
  onUnmount?: () => void;
}
```

_Source: src/core/render-schema-proxy.ts_

### onComponentEvent

**Kind:** `function`

Зарегистрировать колбэк на проп-событие компонента.

Позволяет объявить обработчики (onSubmit, onChange и т.п.) в behavior
вместо жёсткого указания в componentProps схемы.
Колбэк получает ровно те же аргументы, что и оригинальный проп компонента.

**Signature:**
```typescript
export function onComponentEvent(
  node: RenderNodeControl,
  event: string,
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  handler: (...args: any[]) => any
): void
```

**Examples:**

```typescript
onComponentEvent(
  schema.node('wizard'),
  'onSubmit',
  async (values: MyForm) => {
    await submitForm(values);
  }
);
```

_Source: src/core/render-behavior.ts_

### onInit

**Kind:** `function`

Синхронный build-time hook. Вызывается один раз при применении behavior к схеме
(до первого рендера ноды). Это единственный хук, способный повлиять на первый
рендер — внутри можно дергать `schema.node(selector).patchProps({ ... })`
для установки/обновления componentProps.

Типичный кейс: создать форму/стейт, закрепить за нодой через patchProps.

**Signature:**
```typescript
export function onInit(node: RenderNodeControl, fn: () => void): void
```

**Examples:**

```typescript
onInit(schema.node('wizard'), () => {
  const form = createMyForm();
  schema.node('wizard').patchProps({ form });
});
```

_Source: src/core/render-behavior.ts_

### onMount

**Kind:** `function`

Post-mount hook. Срабатывает после первого mount ноды (через useEffect).
Может вернуть cleanup, который выполнится при unmount.

**Signature:**
```typescript
export function onMount(node: RenderNodeControl, fn: () => void | (() => void)): void
```

**Examples:**

```typescript
onMount(schema.node('wizard'), () => {
  console.log('wizard mounted');
  return () => console.log('wizard cleanup from onMount');
});
```

_Source: src/core/render-behavior.ts_

### onUnmount

**Kind:** `function`

Pre-unmount hook. Срабатывает при unmount ноды.

**Signature:**
```typescript
export function onUnmount(node: RenderNodeControl, fn: () => void): void
```

**Examples:**

```typescript
onUnmount(schema.node('wizard'), () => {
  console.log('wizard unmounted');
});
```

_Source: src/core/render-behavior.ts_

### RenderBehaviorFn

**Kind:** `type`

Функция-схема поведения рендера.
Аналог BehaviorSchemaFn из

**Signature:**
```typescript
export type RenderBehaviorFn<T> = (schema: RenderSchemaProxy<T>) => void;
```

**Examples:**

```typescript
const behavior: RenderBehaviorFn<MyForm> = (schema) => {
const wizardRef = schema.node('wizard').getRef<FormWizardHandle<MyForm>>();

hideWhen(schema.node('mortgage-section'), () =>
wizardRef.current?.form.loanType.value.value !== 'mortgage'
);

renderEffect(schema, () => {
const form = wizardRef.current?.form;
if (form?.loanType.value.value === 'mortgage') {
wizardRef.current?.goToStep(1);
}
});
};
```

_Source: src/core/render-behavior.ts_

### RenderContextProvider

**Kind:** `function`

Provider для контекста рендеринга. Снабжает дочерние компоненты текущей формой
и настройками (`settings`). Обычно создаётся {@link FormRenderer} автоматически — явно
нужен только при ручном построении дерева через {@link RenderNodeComponent}.

**Signature:**
```typescript
export function RenderContextProvider<T>({
  value,
  children,
}: {
  value: RenderContextValue<T>;
  children: ReactNode;
}): ReactNode
```

**Examples:**

```tsx
import { RenderContextProvider, RenderNodeComponent } from '@reformer/renderer-react';

<RenderContextProvider value={{ form, settings: { fieldWrapper } }}>
  <RenderNodeComponent node={rootNode} />
</RenderContextProvider>
```

_Source: src/core/render-context.tsx_

### RenderContextValue

**Kind:** `interface`

Значение контекста рендеринга

**Signature:**
```typescript
export interface RenderContextValue<T = unknown> {
  /** Proxy формы (опционально — может быть предоставлена wizard-компонентом через props) */
  form?: FormProxy<T>;
  /** Настройки рендерера */
  settings?: RendererSettings;
}
```

_Source: src/core/render-context.tsx_

### renderEffect

**Kind:** `function`

Зарегистрировать реактивный side-effect.

effectFn оборачивается в Preact effect() — автоматически перезапускается
при изменении любого сигнала, прочитанного внутри effectFn.
Может вернуть функцию очистки.

**Signature:**
```typescript
export function renderEffect<T>(
  schema: RenderSchemaProxy<T>,
  effectFn: () => void | (() => void)
): void
```

**Examples:**

```typescript
const wizardRef = schema.node('wizard').getRef<FormWizardHandle<MyForm>>();
renderEffect(schema, () => {
  const form = wizardRef.current?.form;
  if (form?.loanType.value.value === 'mortgage') {
    wizardRef.current?.goToStep(1);
  }
});
```

_Source: src/core/render-behavior.ts_

### RendererSettings

**Kind:** `interface`

Настройки рендерера формы

**Signature:**
```typescript
export interface RendererSettings {
  /**
   * Компонент-обёртка для полей (опционально)
   *
   * Если указан, каждое поле будет обёрнуто этим компонентом.
   * Обёртка отвечает за рендеринг label, errors и т.д.
   */
  fieldWrapper?: React.ComponentType<FieldWrapperProps>;
}
```

_Source: src/core/types.ts_

### RenderModelArray

**Kind:** `const`

Рендер массива единой схемы (M1): элементы принадлежат модели (`control`), поддерево
каждого элемента строит `itemComponent` по под-модели и рендерит {@link RenderModelNode}
(поля привязываются к сигналам под-модели). Длина реактивна — `push`/`removeAt` по модели
перерисовывают список. При заданном `newItem` рендерится кнопка «Добавить».

Низкоуровневый рендерер: для полноценной секции массива (карточки, удаление, reorder,
`fieldWrapper`) используйте `ArrayRenderNode` (`{ array, item }`) внутри
{@link RenderNodeComponent}/{@link FormRenderer}.

**Signature:**
```typescript
export const RenderModelArray
```

**Parameters:**
- `props` — - {@link RenderModelArrayProps}

**Returns:** Обёртка со списком элементов и опц. кнопкой добавления

**Examples:**

Массив под-моделей
```tsx
<RenderModelArray
control={model.coBorrowers}
itemComponent={(im) => ({
component: Box,
children: [{ value: im.$.phone, component: Input }],
})}
newItem={createBlankCoBorrower}
addButtonLabel="Добавить созаёмщика"
/>
```

_Source: src/core/render-model.tsx_

### RenderModelArrayProps

**Kind:** `interface`

Props для {@link RenderModelArray}: реактивный массив модели + схема элемента и оформление.

**Signature:**
```typescript
export interface RenderModelArrayProps {
  /** Массив модели (`model.coBorrowers`). */
  control: ModelArrayControl;
  /** Схема элемента: под-модель элемента → узел поддерева. */
  itemComponent: (item: unknown) => ModelNode;
  /** Обёртка элементов (по умолчанию 'div'). */
  wrapper?: ElementType;
  className?: string;
  /** Значение/фабрика для кнопки «Добавить» (если задано — рендерится кнопка). */
  newItem?: unknown | (() => unknown);
  addButtonLabel?: string;
}
```

_Source: src/core/render-model.tsx_

### RenderModelNode

**Kind:** `const`

Рекурсивный рендер узла единой схемы {@link ModelNode}: field-узел → контрол (разворот
сигнала, `value`/`onChange`, state по сигналу через реестр), container-узел → React-компонент
с дочерними узлами. `node == null` → рендерит `null`.

**Signature:**
```typescript
export const RenderModelNode
```

**Parameters:**
- `props` — - `{ node }` — узел {@link ModelNode} или `null`/`undefined`

**Returns:** Отрендеренное поддерево или `null`

**Examples:**

Дерево «контейнер + поле»
```tsx
const node: ModelNode = {
component: Box,
children: [{ value: model.$.email, component: Input }],
};
<RenderModelNode node={node} />
```

_Source: src/core/render-model.tsx_

### RenderNode

**Kind:** `type`

Узел рендеринга формы

Дискриминированный union из типов узлов:
- ModelFieldRenderNode — поле формы, привязанное к СИГНАЛУ модели (M1, единая схема)
- ArrayRenderNode — массив модели (M1): данные `{ array, item }`, рендер-секция
- ContainerRenderNode — контейнер (Box, Section, wizard и т.д.)

**Signature:**
```typescript
export type RenderNode<T> = ModelFieldRenderNode | ArrayRenderNode<T> | ContainerRenderNode<T>;
```

_Source: src/core/types.ts_

### RenderNodeComponent

**Kind:** `function`

Рекурсивный рендеринг узла {@link RenderNode}. Определяет тип узла и рендерит
соответственно: {@link ModelFieldRenderNode} → компонент поля с wrapper (значение
из сигнала модели, state — по сигналу через реестр), {@link ArrayRenderNode} → секция
массива модели, {@link ContainerRenderNode} → контейнер с дочерними узлами. Учитывает
`hideWhen`/`setHidden`, `patchProps`, `onComponentEvent`, lifecycle-хуки и ref из
{@link RenderSchemaProxy}. Обычно вызывается {@link FormRenderer}; явный вызов нужен
при ручной композиции.

**Signature:**
```typescript
export function RenderNodeComponent<T>({
  node,
  form,
  fieldWrapper: fieldWrapperProp,
}: RenderNodeComponentProps<T>): ReactNode
```

**Parameters:**
- `props` — - `node` (узел), опц. `form` и `fieldWrapper`

**Returns:** Отрендеренное поддерево или `null` (если узел скрыт / нет ноды для сигнала)

**Examples:**

```tsx
import { RenderNodeComponent } from '@reformer/renderer-react';

<RenderContextProvider value={{ settings: { fieldWrapper: FormField } }}>
  <RenderNodeComponent node={rootNode} />
</RenderContextProvider>
```

_Source: src/core/render-node.tsx_

### RenderNodeControl

**Kind:** `interface`

API для программного управления конкретной нодой схемы рендера.
Получается через schema.node(selector).

**Signature:**
```typescript
export interface RenderNodeControl {
  /** Принудительно скрыть/показать ноду, игнорируя условие hidden из схемы */
  setHidden(value: boolean): this;
  /** Убрать переопределение hidden — восстанавливает исходное условие из схемы */
  resetHidden(): this;
  /** Подмердить объект в componentProps ноды */
  patchProps(partial: Record<string, unknown>): this;
  /** Убрать переопределение пропсов — восстанавливает исходные componentProps из схемы */
  resetProps(): this;
  /**
   * Получить React ref на компонент с данным selector.
   * Ref создаётся один раз (idempotent) и передаётся в компонент через render-node.
   * Компонент должен поддерживать ref (forwardRef или React 19 ref prop).
   */
  getRef<H>(): RefObject<H>;
  /** @internal — selector этой ноды (используется standalone helpers hideWhen/renderEffect) */
  __selector: string;
  /** @internal — override maps схемы (используется standalone helpers) */
  __overrideMaps: RenderSchemaOverrideMaps;
}
```

_Source: src/core/render-schema-proxy.ts_

### RenderSchemaFn

**Kind:** `type`

Функция создания RenderSchema (M1).

Возвращает дерево узлов рендеринга. Привязка к данным — через сигналы модели в листьях
(`value: model.$.x`), поэтому аргумент-путь больше не нужен (legacy FieldPath удалён).

**Signature:**
```typescript
export type RenderSchemaFn<T> = () => RenderNode<T>;
```

**Examples:**

```typescript
const renderSchema: RenderSchemaFn<MyForm> = () => ({
  component: Box,
  children: [
    { value: model.$.email, component: Input },
    { value: model.$.password, component: InputPassword },
  ],
});
```

_Source: src/core/types.ts_

### RenderSchemaProxy

**Kind:** `type`

RenderSchemaFn с дополнительным API программного управления.
Создаётся через createRenderSchema().

**Signature:**
```typescript
export type RenderSchemaProxy<T> = RenderSchemaFn<T> & {
  [PROXY_MARKER]: true;
  /** Получить контроллер ноды по selector */
  node(selector: string): RenderNodeControl;
  /** @internal — карты переопределений для передачи через контекст */
  __overrideMaps: RenderSchemaOverrideMaps;
};
```

_Source: src/core/render-schema-proxy.ts_

### useRenderContext

**Kind:** `function`

Хук для получения контекста рендеринга.

Используется в пользовательских компонентах-контейнерах (например, wizard) для доступа
к `form` и `settings`. Бросает ошибку, если вызван вне {@link RenderContextProvider}
(его создаёт {@link FormRenderer}).

**Signature:**
```typescript
export function useRenderContext<T = unknown>(): RenderContextValue<T>
```

**Returns:** 

**Examples:**

Доступ к form/settings в self-managed компоненте
```tsx
function MyWizard({ children }) {
const { form, settings } = useRenderContext();

return (
<FormWizard form={form}>
{children.map((child) => (
<RenderNodeComponent node={child} form={form} />
))}
</FormWizard>
);
}
```

_Source: src/core/render-context.tsx_
