@reformer/core 11.0.0-beta.5 → 11.0.0-beta.6
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/dist/form/create-form.d.ts +6 -6
- package/dist/form/factories/node-factory.d.ts +16 -16
- package/dist/form/nodes/array-node.d.ts +3 -3
- package/dist/form/nodes/field-node.d.ts +3 -3
- package/dist/form/nodes/group-node.d.ts +7 -7
- package/dist/form/nodes/model-array-node.d.ts +2 -2
- package/dist/form/types/deep-schema.d.ts +5 -5
- package/dist/index.js +443 -399
- package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
- package/dist/platforms/react/field-binding/field-handle.d.ts +50 -0
- package/dist/platforms/react/index.d.ts +4 -0
- package/docs/llms/02-quick-start.md +7 -7
- package/docs/llms/03-api-signatures.md +5 -5
- package/docs/llms/05-common-mistakes.md +1 -1
- package/docs/llms/09-formschema.md +12 -12
- package/docs/llms/10-arrays.md +4 -4
- package/docs/llms/14-extended-mistakes.md +1 -1
- package/docs/llms/15-project-structure.md +15 -13
- package/docs/llms/16-ui-components.md +36 -9
- package/docs/llms/17-nonexistent-api.md +16 -2
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/30-type-safety-recipes.md +1 -1
- package/llms-index.json +1 -1
- package/llms.txt +340 -81
- package/package.json +1 -1
package/llms.txt
CHANGED
|
@@ -128,7 +128,7 @@ const { value, errors, disabled } = useFormControl(control.loanType);
|
|
|
128
128
|
import { createCoreForm, createModel, useFormBundle, type FormProxy } from '@reformer/core';
|
|
129
129
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
130
130
|
import { required, email } from '@reformer/core/validators';
|
|
131
|
-
import { FormField,
|
|
131
|
+
import { FormField, Input, Button } from '@reformer/ui-kit';
|
|
132
132
|
|
|
133
133
|
// 1. Define form type as `type` alias (not `interface` — see Recipe 2)
|
|
134
134
|
type ContactForm = {
|
|
@@ -144,12 +144,12 @@ const model = createModel<ContactForm>({ name: '', email: '' });
|
|
|
144
144
|
const schema = {
|
|
145
145
|
name: {
|
|
146
146
|
value: model.$.name,
|
|
147
|
-
component:
|
|
147
|
+
component: Input,
|
|
148
148
|
componentProps: { label: 'Name', placeholder: 'Your name' },
|
|
149
149
|
},
|
|
150
150
|
email: {
|
|
151
151
|
value: model.$.email,
|
|
152
|
-
component:
|
|
152
|
+
component: Input,
|
|
153
153
|
componentProps: { label: 'Email', type: 'email' },
|
|
154
154
|
},
|
|
155
155
|
};
|
|
@@ -223,14 +223,14 @@ const model = createModel<MyForm>({ properties: [] });
|
|
|
223
223
|
const propertyItem = (item: FormModel<PropertyItem>) => ({
|
|
224
224
|
type: {
|
|
225
225
|
value: item.$.type,
|
|
226
|
-
component:
|
|
226
|
+
component: SelectAsync,
|
|
227
227
|
componentProps: { label: 'Тип', options: [/* ... */] },
|
|
228
228
|
},
|
|
229
|
-
description: { value: item.$.description, component:
|
|
229
|
+
description: { value: item.$.description, component: Textarea, componentProps: { label: 'Описание' } },
|
|
230
230
|
estimatedValue: {
|
|
231
231
|
value: item.$.estimatedValue,
|
|
232
|
-
component:
|
|
233
|
-
componentProps: { label: 'Стоимость'
|
|
232
|
+
component: InputNumber,
|
|
233
|
+
componentProps: { label: 'Стоимость' },
|
|
234
234
|
},
|
|
235
235
|
});
|
|
236
236
|
|
|
@@ -328,16 +328,16 @@ import { createModel, createForm } from '@reformer/core';
|
|
|
328
328
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
329
329
|
import { required, min, max, email } from '@reformer/core/validators';
|
|
330
330
|
|
|
331
|
-
type Loan = { email: string; age: number; amount: number };
|
|
332
|
-
const model = createModel<Loan>({ email: '', age:
|
|
331
|
+
type Loan = { email: string; age: number | null; amount: number | null };
|
|
332
|
+
const model = createModel<Loan>({ email: '', age: null, amount: null });
|
|
333
333
|
|
|
334
334
|
// layout-схема createForm НЕ несёт validators — только привязка поля к сигналу + компонент
|
|
335
335
|
const form = createForm({
|
|
336
336
|
model,
|
|
337
337
|
schema: {
|
|
338
|
-
email: { value: model.$.email, component:
|
|
339
|
-
age: { value: model.$.age, component:
|
|
340
|
-
amount: { value: model.$.amount, component:
|
|
338
|
+
email: { value: model.$.email, component: Input },
|
|
339
|
+
age: { value: model.$.age, component: InputNumber },
|
|
340
|
+
amount: { value: model.$.amount, component: InputNumber },
|
|
341
341
|
},
|
|
342
342
|
});
|
|
343
343
|
|
|
@@ -837,7 +837,7 @@ computeFrom([model.$.price, model.$.quantity], model.$.total, (price, qty) => pr
|
|
|
837
837
|
```typescript
|
|
838
838
|
// ❌ WRONG - дерево { value, validators } и позиционная строка удалены
|
|
839
839
|
const schema = {
|
|
840
|
-
email: { value: model.$.email, component:
|
|
840
|
+
email: { value: model.$.email, component: Input, validators: [required(), email()] },
|
|
841
841
|
};
|
|
842
842
|
validate(path.email, required(), 'Email is required'); // старая сигнатура validate() — удалена
|
|
843
843
|
|
|
@@ -1090,7 +1090,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
1090
1090
|
```typescript
|
|
1091
1091
|
{
|
|
1092
1092
|
value: model.$.fieldName, // сигнал модели (PathAwareSignal) — обязателен
|
|
1093
|
-
component:
|
|
1093
|
+
component: Input, // React-компонент
|
|
1094
1094
|
componentProps?: object, // пропсы (label, placeholder, options, type, ...)
|
|
1095
1095
|
disabled?: boolean,
|
|
1096
1096
|
updateOn?: 'change' | 'blur' | 'submit',
|
|
@@ -1108,7 +1108,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
1108
1108
|
import { createModel, createForm } from '@reformer/core';
|
|
1109
1109
|
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
1110
1110
|
import { required } from '@reformer/core/validators';
|
|
1111
|
-
import {
|
|
1111
|
+
import { Input, InputNumber, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
1112
1112
|
|
|
1113
1113
|
const model = createModel<MyForm>({ name: '', age: null, agree: false, status: 'active' });
|
|
1114
1114
|
|
|
@@ -1120,22 +1120,22 @@ const myValidation = defineValidationSchema<MyForm>(({ model }) => {
|
|
|
1120
1120
|
const schema = {
|
|
1121
1121
|
name: {
|
|
1122
1122
|
value: model.$.name,
|
|
1123
|
-
component:
|
|
1123
|
+
component: Input,
|
|
1124
1124
|
componentProps: { label: 'Name', placeholder: 'Enter name' },
|
|
1125
1125
|
},
|
|
1126
1126
|
age: {
|
|
1127
1127
|
value: model.$.age,
|
|
1128
|
-
component:
|
|
1129
|
-
componentProps: {
|
|
1128
|
+
component: InputNumber,
|
|
1129
|
+
componentProps: { label: 'Age' },
|
|
1130
1130
|
},
|
|
1131
1131
|
agree: {
|
|
1132
1132
|
value: model.$.agree,
|
|
1133
|
-
component:
|
|
1133
|
+
component: CheckboxWithLabel,
|
|
1134
1134
|
componentProps: { label: 'I agree to terms' },
|
|
1135
1135
|
},
|
|
1136
1136
|
status: {
|
|
1137
1137
|
value: model.$.status,
|
|
1138
|
-
component:
|
|
1138
|
+
component: SelectAsync,
|
|
1139
1139
|
componentProps: {
|
|
1140
1140
|
label: 'Status',
|
|
1141
1141
|
options: [
|
|
@@ -1159,9 +1159,9 @@ const form = createForm<MyForm>({ model, schema });
|
|
|
1159
1159
|
import type { FormModel } from '@reformer/core';
|
|
1160
1160
|
|
|
1161
1161
|
const addressNodes = (m: FormModel<Address>) => ({
|
|
1162
|
-
street: { value: m.$.street, component:
|
|
1163
|
-
city: { value: m.$.city, component:
|
|
1164
|
-
zip: { value: m.$.zip, component:
|
|
1162
|
+
street: { value: m.$.street, component: Input, componentProps: { label: 'Street' } },
|
|
1163
|
+
city: { value: m.$.city, component: Input, componentProps: { label: 'City' } },
|
|
1164
|
+
zip: { value: m.$.zip, component: Input, componentProps: { label: 'ZIP' } },
|
|
1165
1165
|
});
|
|
1166
1166
|
|
|
1167
1167
|
const schema = {
|
|
@@ -1178,8 +1178,8 @@ const schema = {
|
|
|
1178
1178
|
import type { FormModel } from '@reformer/core';
|
|
1179
1179
|
|
|
1180
1180
|
const itemSchema = (item: FormModel<Item>) => ({
|
|
1181
|
-
id: { value: item.$.id, component:
|
|
1182
|
-
name: { value: item.$.name, component:
|
|
1181
|
+
id: { value: item.$.id, component: Input, componentProps: { label: 'ID' } },
|
|
1182
|
+
name: { value: item.$.name, component: Input, componentProps: { label: 'Name' } },
|
|
1183
1183
|
});
|
|
1184
1184
|
|
|
1185
1185
|
const schema = {
|
|
@@ -1233,7 +1233,7 @@ if (isFieldNode(node)) { /* ... */ }
|
|
|
1233
1233
|
|
|
1234
1234
|
```typescript
|
|
1235
1235
|
import { createModel, createForm, type FormModel } from '@reformer/core';
|
|
1236
|
-
import {
|
|
1236
|
+
import { Input, InputNumber } from '@reformer/ui-kit';
|
|
1237
1237
|
|
|
1238
1238
|
type Item = { id: string; name: string; price: number };
|
|
1239
1239
|
type MyForm = { items: Item[] };
|
|
@@ -1242,9 +1242,9 @@ const model = createModel<MyForm>({ items: [] });
|
|
|
1242
1242
|
|
|
1243
1243
|
// под-схема одного элемента (item.$.field — сигнал под-модели)
|
|
1244
1244
|
const itemSchema = (item: FormModel<Item>) => ({
|
|
1245
|
-
id: { value: item.$.id, component:
|
|
1246
|
-
name: { value: item.$.name, component:
|
|
1247
|
-
price: { value: item.$.price, component:
|
|
1245
|
+
id: { value: item.$.id, component: Input },
|
|
1246
|
+
name: { value: item.$.name, component: Input },
|
|
1247
|
+
price: { value: item.$.price, component: InputNumber },
|
|
1248
1248
|
});
|
|
1249
1249
|
|
|
1250
1250
|
const schema = {
|
|
@@ -1645,7 +1645,7 @@ import { revalidateWhen, type ValidationError } from '@reformer/core';
|
|
|
1645
1645
|
// ❌ старое (УДАЛЕНО): дерево { value, validators }, ModelValidator (value, scope, root), validateFormModel
|
|
1646
1646
|
const legacyRule: ModelValidator<number, unknown, Form> = (_value, _scope, root) =>
|
|
1647
1647
|
root.field1 > root.field2 ? { code: 'error', message: 'Invalid' } : null;
|
|
1648
|
-
const legacySchema = { field1: { value: model.$.field1, component:
|
|
1648
|
+
const legacySchema = { field1: { value: model.$.field1, component: Input, validators: [legacyRule] } };
|
|
1649
1649
|
validateFormModel(model, legacySchema);
|
|
1650
1650
|
|
|
1651
1651
|
// ✅ новое: cross-правило — обычная функция над снапшотом (model.get()), не читает scope/root
|
|
@@ -1677,11 +1677,13 @@ revalidateWhen([model.$.field2], () => void validateModel(model, schema));
|
|
|
1677
1677
|
|
|
1678
1678
|
One form is one module folder. **Default layout — flat minimalist:** every concern is a single
|
|
1679
1679
|
file at the module root, and the whole form (entry + all wizard steps inline) lives in one
|
|
1680
|
-
`index.tsx`.
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1680
|
+
`index.tsx`. Naming rule: **`form.<role>` is a form artefact, the suffix names its role** —
|
|
1681
|
+
`form.schema.*` (layout), `form.behavior.ts` (model behavior), `form.render.ts` (render-layer
|
|
1682
|
+
behavior, renderer targets only), `form.validation.ts` (validation rules); every other file has
|
|
1683
|
+
no prefix. A wizard may instead keep each
|
|
1684
|
+
step's code in `steps/<slug>/` (same file names inside) with a `steps/index.ts` aggregator. The old
|
|
1685
|
+
`renderer.schema.*` / `renderer.behavior.ts` / `renderer.wizard.tsx` / `validation.ts` names still
|
|
1686
|
+
work with a warning. No `lib/` / `schema/` / `components/` nesting until the form grows (see "Scaling up" below). Arrays are declared in `form.schema.ts`
|
|
1685
1687
|
and rendered with `FormArraySection` — no per-step component files.
|
|
1686
1688
|
|
|
1687
1689
|
```
|
|
@@ -1695,13 +1697,13 @@ src/
|
|
|
1695
1697
|
│ ├── model.ts # createModel + initial values + empty-array-element factories
|
|
1696
1698
|
│ ├── form.schema.ts # FormSchema: { value: model.$.x, component, componentProps }
|
|
1697
1699
|
│ ├── form.behavior.ts # defineFormBehavior: compute / enableWhen / hideWhen / copyFrom / onChange
|
|
1698
|
-
│ ├── validation.ts
|
|
1700
|
+
│ ├── form.validation.ts # ALL validation → { validateStep, validateAll }
|
|
1699
1701
|
│ ├── data-sources.ts # options + async loaders (dataSources)
|
|
1700
1702
|
│ └── api.ts # submit + prefill/load
|
|
1701
1703
|
```
|
|
1702
1704
|
|
|
1703
1705
|
Rule of thumb: **one concern → one file at the module root; the whole component tree (all steps) →
|
|
1704
|
-
`index.tsx`; validation is a single `validation.ts`.** This is the working default for
|
|
1706
|
+
`index.tsx`; validation is a single `form.validation.ts`.** This is the working default for
|
|
1705
1707
|
almost every form — reach for folders only when a file stops fitting on a screen.
|
|
1706
1708
|
|
|
1707
1709
|
### Key Files
|
|
@@ -1722,7 +1724,7 @@ export const createCreditApplicationModel = (): FormModel<CreditApplicationForm>
|
|
|
1722
1724
|
// forms/credit-application/form.schema.ts
|
|
1723
1725
|
import type { FormModel } from '@reformer/core';
|
|
1724
1726
|
export const creditApplicationSchema = (model: FormModel<CreditApplicationForm>) => ({
|
|
1725
|
-
loanType: { value: model.$.loanType, component:
|
|
1727
|
+
loanType: { value: model.$.loanType, component: SelectAsync, componentProps: { /* ... */ } },
|
|
1726
1728
|
personalData: personalDataNodes(model.$.personalData),
|
|
1727
1729
|
properties: { array: model.properties, item: propertyItem },
|
|
1728
1730
|
});
|
|
@@ -1770,7 +1772,7 @@ forms/
|
|
|
1770
1772
|
├── schema/ # The form definition
|
|
1771
1773
|
│ ├── model.ts # createModel factory + initial values + array-element factories
|
|
1772
1774
|
│ ├── form.schema.ts # schema builder (model) => tree ({ value: model.$.x, component })
|
|
1773
|
-
│ ├── validation.ts
|
|
1775
|
+
│ ├── form.validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
|
|
1774
1776
|
│ ├── form.behavior.ts # defineFormBehavior(...)
|
|
1775
1777
|
│ └── create-form.ts # Assembly: createCoreForm({ model, schema, behavior, validation })
|
|
1776
1778
|
│
|
|
@@ -1782,7 +1784,7 @@ forms/
|
|
|
1782
1784
|
|
|
1783
1785
|
Rule of thumb for this layout: **domain raw material → `lib/`; anything describing the form →
|
|
1784
1786
|
`schema/`; React layout → `components/`; root = entry + `index.ts`.** In very large forms you
|
|
1785
|
-
may co-locate each step's `form.schema.ts` / `validation.ts` / `form.behavior.ts` inside its
|
|
1787
|
+
may co-locate each step's `form.schema.ts` / `form.validation.ts` / `form.behavior.ts` inside its
|
|
1786
1788
|
`steps/[Step]/` folder, keeping only the shared `model` and cross-step rules in `schema/` — see
|
|
1787
1789
|
the guide below.
|
|
1788
1790
|
|
|
@@ -1798,9 +1800,9 @@ the guide below.
|
|
|
1798
1800
|
> when registering the MCP server to choose which structure the generators lead with. Default
|
|
1799
1801
|
> is `minimalist`.
|
|
1800
1802
|
|
|
1801
|
-
> Cross-target variants (renderer-react `
|
|
1802
|
-
> renderer-json `
|
|
1803
|
-
> `
|
|
1803
|
+
> Cross-target variants (renderer-react `form.schema.ts` + `form.render.ts` /
|
|
1804
|
+
> renderer-json `form.schema.ts` + `form.render.ts` + `registry.ts`, plus an optional
|
|
1805
|
+
> `wizard.tsx` shim), the `.tsx` / `.json` schema variants, the centralized-vs-co-located
|
|
1804
1806
|
> choice, and the full reuse map live in the **form-directory-layout** guide (`@reformer/mcp`) —
|
|
1805
1807
|
> `find_recipe directory-layout`.
|
|
1806
1808
|
|
|
@@ -1810,12 +1812,13 @@ the guide below.
|
|
|
1810
1812
|
|
|
1811
1813
|
> **Default rule (read first)**: для UI используй `FormField` из
|
|
1812
1814
|
> [`@reformer/ui-kit`](../../reformer-ui-kit/) — он покрывает 95% случаев одной
|
|
1813
|
-
> строкой `<FormField control={form.x} />`. Свои
|
|
1815
|
+
> строкой `<FormField control={form.x} />`. Свои обёртки поля пиши ТОЛЬКО если
|
|
1814
1816
|
> ui-kit не подходит (другая design system, особый low-level input).
|
|
1815
1817
|
>
|
|
1816
1818
|
> Канонический schema-driven подход:
|
|
1817
1819
|
>
|
|
1818
|
-
> - **компонент** объявляется в схеме как `component:
|
|
1820
|
+
> - **компонент** объявляется в схеме как `component: Input` (или `SelectAsync`, `CheckboxWithLabel`, …) —
|
|
1821
|
+
> сам компонент кита, отдельных «field-версий» нет: связывает его с полем обёртка (`FormField`)
|
|
1819
1822
|
> - **пропсы** компонента — в `componentProps: { label, placeholder, options, type, ... }`
|
|
1820
1823
|
> - **JSX рендерит**: `<FormField control={form.x} />` БЕЗ дополнительных props
|
|
1821
1824
|
>
|
|
@@ -1827,7 +1830,7 @@ the guide below.
|
|
|
1827
1830
|
```tsx
|
|
1828
1831
|
import { useMemo } from 'react';
|
|
1829
1832
|
import { createModel, createForm } from '@reformer/core';
|
|
1830
|
-
import { FormField,
|
|
1833
|
+
import { FormField, Input, SelectAsync, CheckboxWithLabel, Button } from '@reformer/ui-kit';
|
|
1831
1834
|
|
|
1832
1835
|
type RegistrationForm = {
|
|
1833
1836
|
email: string;
|
|
@@ -1841,12 +1844,12 @@ function RegistrationPage() {
|
|
|
1841
1844
|
const schema = {
|
|
1842
1845
|
email: {
|
|
1843
1846
|
value: model.$.email,
|
|
1844
|
-
component:
|
|
1847
|
+
component: Input,
|
|
1845
1848
|
componentProps: { label: 'Email', type: 'email', placeholder: 'you@example.com' },
|
|
1846
1849
|
},
|
|
1847
1850
|
country: {
|
|
1848
1851
|
value: model.$.country,
|
|
1849
|
-
component:
|
|
1852
|
+
component: SelectAsync,
|
|
1850
1853
|
componentProps: {
|
|
1851
1854
|
label: 'Country',
|
|
1852
1855
|
options: [
|
|
@@ -1857,7 +1860,7 @@ function RegistrationPage() {
|
|
|
1857
1860
|
},
|
|
1858
1861
|
agree: {
|
|
1859
1862
|
value: model.$.agree,
|
|
1860
|
-
component:
|
|
1863
|
+
component: CheckboxWithLabel,
|
|
1861
1864
|
componentProps: { label: 'I agree to terms' },
|
|
1862
1865
|
},
|
|
1863
1866
|
};
|
|
@@ -1900,7 +1903,7 @@ function RegistrationPage() {
|
|
|
1900
1903
|
✅ Всё это в схеме:
|
|
1901
1904
|
|
|
1902
1905
|
```ts
|
|
1903
|
-
{ email: { component:
|
|
1906
|
+
{ email: { component: Input, componentProps: { label: 'Email' } } }
|
|
1904
1907
|
```
|
|
1905
1908
|
|
|
1906
1909
|
```tsx
|
|
@@ -1921,7 +1924,8 @@ import { InputMask } from 'react-input-mask';
|
|
|
1921
1924
|
```
|
|
1922
1925
|
|
|
1923
1926
|
`children` оборачивается в `CdkFormField.Control asChild` и получает все нужные
|
|
1924
|
-
props (`value`, `onChange`, `onBlur`, `aria-invalid`)
|
|
1927
|
+
props (`value`, `onChange`, `onBlur`, `aria-invalid`) — в диалекте ребёнка, если тот объявил
|
|
1928
|
+
статику `reformerAdapter` (`defineFieldControl` из `@reformer/ui-kit/fields`), иначе value-based.
|
|
1925
1929
|
|
|
1926
1930
|
### Advanced — write your own from scratch (rare)
|
|
1927
1931
|
|
|
@@ -1963,12 +1967,37 @@ function MyFormField<T>({ control }: MyFormFieldProps<T>) {
|
|
|
1963
1967
|
<MyFormField control={form.email} /> // ← без label-prop
|
|
1964
1968
|
```
|
|
1965
1969
|
|
|
1970
|
+
### Связывание поля с контролом — `FieldAdapter` (`@reformer/core`)
|
|
1971
|
+
|
|
1972
|
+
Контролы говорят на разных диалектах (`checked` + `onCheckedChange`, `onValueChange`, DOM-событие
|
|
1973
|
+
в `onChange`), форма — на value-based seam (`value` + `onChange(value)` + `onBlur`). Перевод
|
|
1974
|
+
описывает `FieldAdapter` (`valueProp`, `changeProp`, `fromEmit`, `toValue`, `bindBlur`, `strip`,
|
|
1975
|
+
`passControl` — все необязательны). Компонент объявляет его статикой `reformerAdapter`, а обёртка
|
|
1976
|
+
поля (`FormField.Control` из `@reformer/cdk`, рендерер `@reformer/renderer-react`) читает статику
|
|
1977
|
+
и связывает поле сама — отдельные «field-версии» компонентов не нужны.
|
|
1978
|
+
|
|
1979
|
+
| Символ | Назначение |
|
|
1980
|
+
| ----------------------------- | --------------------------------------------------------------------------- |
|
|
1981
|
+
| `FieldAdapter` | Тип адаптера: диалект контрола |
|
|
1982
|
+
| `getFieldAdapter(component)` | Статика `component.reformerAdapter` либо `undefined` |
|
|
1983
|
+
| `bindFieldProps(adapter, seam, props)` | Props контрола под адаптер (без адаптера — seam как есть) |
|
|
1984
|
+
| `FieldHandle` | Императивный handle поля: `focus` / `blur` / `scrollIntoView` / `getElement` |
|
|
1985
|
+
| `makeElementFieldHandle(ref)` | Базовый `FieldHandle` из DOM-узла |
|
|
1986
|
+
| `useFieldHandleRef(outerRef)` | Ref для контрола + публикация handle потребителю (строит ОБЁРТКА поля) |
|
|
1987
|
+
|
|
1988
|
+
В приложении обычно достаточно `defineFieldControl(MyControl, { adapter })` из
|
|
1989
|
+
`@reformer/ui-kit/fields` (плюс готовые пресеты `checkedAdapter`, `valueChangeAdapter`, …); для
|
|
1990
|
+
компонентов чужой библиотеки без статики — `RendererSettings.resolveFieldAdapter`.
|
|
1991
|
+
|
|
1966
1992
|
### Integration with UI libraries (shadcn etc.)
|
|
1967
1993
|
|
|
1968
1994
|
> **Через рендерер — без обёртки.** Если ты рендеришь форму через `@reformer/renderer-react` /
|
|
1969
1995
|
> `@reformer/renderer-json`, вместо ручной обёртки на каждый контрол можно зарегистрировать сырой
|
|
1970
1996
|
> компонент и передать `settings.resolveFieldAdapter` — рендерер сам сведёт value-seam к диалекту
|
|
1971
|
-
> контрола.
|
|
1997
|
+
> контрола. Свой компонент можно подготовить и один раз — статикой
|
|
1998
|
+
> `defineFieldControl(MyInput, { adapter })` из `@reformer/ui-kit/fields`: его поймут и рендерер,
|
|
1999
|
+
> и `FormField.Control` из `@reformer/cdk`. Ручная обёртка на `useFormControl` (ниже) нужна для
|
|
2000
|
+
> прямого JSX без `FormField` и рендерера.
|
|
1972
2001
|
|
|
1973
2002
|
Если есть существующая design system — оборачивай её компоненты в один
|
|
1974
2003
|
`MyFormField` (как выше) и используй один прop `control`. Не множь обёртки на
|
|
@@ -2026,6 +2055,20 @@ function ShadcnFormField({ control }: { control: FieldNode<string> }) {
|
|
|
2026
2055
|
| `FormProvider`, `control` prop, `register()` | `<Component form={form} />`, `useFormControl(form.field)` | Форма передаётся через props |
|
|
2027
2056
|
| `getFieldValue()` | `model.field` / `useFormControlValue(form.field)`| Не существует |
|
|
2028
2057
|
|
|
2058
|
+
### UI: удалённые field-версии компонентов (`@reformer/ui-kit`)
|
|
2059
|
+
|
|
2060
|
+
Отдельных «field-версий» у компонентов кита больше нет: в `component` поля кладётся сам
|
|
2061
|
+
компонент, диалект он объявляет статикой `reformerAdapter`, связывает поле обёртка
|
|
2062
|
+
(`FormField.Control` / рендерер).
|
|
2063
|
+
|
|
2064
|
+
| Wrong | Correct |
|
|
2065
|
+
| ---------------------------------------------- | --------------------------------------------------------- |
|
|
2066
|
+
| `InputField`, `SelectField`, `CheckboxField`, … (любой `*Field`) | `Input`, `SelectAsync`, `CheckboxWithLabel`, … — таблица в ui-kit `01-overview.md` |
|
|
2067
|
+
| `InputField` + `type: 'number'` | `InputNumber` |
|
|
2068
|
+
| `InputField` + `suggestions` | `InputSuggest` |
|
|
2069
|
+
| `FileUploadField` + `variant: 'dropzone' \| 'input'` | `FileUploadDropzone` / `FileUploadInput` (без `variant`) |
|
|
2070
|
+
| `withFormControl(MyControl, adapter)` | `defineFieldControl(MyControl, { adapter })` из `@reformer/ui-kit/fields` |
|
|
2071
|
+
|
|
2029
2072
|
### Common Import Errors
|
|
2030
2073
|
|
|
2031
2074
|
```typescript
|
|
@@ -2064,8 +2107,8 @@ const schema = {
|
|
|
2064
2107
|
|
|
2065
2108
|
// CORRECT - layout только связывает поле с сигналом модели...
|
|
2066
2109
|
const layout = {
|
|
2067
|
-
name: { value: model.$.name, component:
|
|
2068
|
-
email: { value: model.$.email, component:
|
|
2110
|
+
name: { value: model.$.name, component: Input, componentProps: { label: 'Name' } },
|
|
2111
|
+
email: { value: model.$.email, component: Input, componentProps: { label: 'Email' } },
|
|
2069
2112
|
};
|
|
2070
2113
|
|
|
2071
2114
|
// ...а правила — отдельная ambient-схема (прогоняется раннером validateModel):
|
|
@@ -3656,16 +3699,16 @@ Initial values задаются в `createModel(initial)`; `reset()` возвр
|
|
|
3656
3699
|
|
|
3657
3700
|
```typescript
|
|
3658
3701
|
import { createModel, createForm } from '@reformer/core';
|
|
3659
|
-
import {
|
|
3702
|
+
import { Input, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
3660
3703
|
|
|
3661
3704
|
type ProfileForm = { username: string; language: 'ru' | 'en'; marketing: boolean };
|
|
3662
3705
|
|
|
3663
3706
|
const model = createModel<ProfileForm>({ username: '', language: 'ru', marketing: true });
|
|
3664
3707
|
const schema = {
|
|
3665
|
-
username: { value: model.$.username, component:
|
|
3708
|
+
username: { value: model.$.username, component: Input, componentProps: { label: 'Username' } },
|
|
3666
3709
|
language: {
|
|
3667
3710
|
value: model.$.language,
|
|
3668
|
-
component:
|
|
3711
|
+
component: SelectAsync,
|
|
3669
3712
|
componentProps: {
|
|
3670
3713
|
label: 'Язык',
|
|
3671
3714
|
options: [
|
|
@@ -3674,7 +3717,7 @@ const schema = {
|
|
|
3674
3717
|
],
|
|
3675
3718
|
},
|
|
3676
3719
|
},
|
|
3677
|
-
marketing: { value: model.$.marketing, component:
|
|
3720
|
+
marketing: { value: model.$.marketing, component: CheckboxWithLabel, componentProps: { label: 'Рассылка' } },
|
|
3678
3721
|
};
|
|
3679
3722
|
const form = createForm({ model, schema });
|
|
3680
3723
|
// model.get() === { username: '', language: 'ru', marketing: true }
|
|
@@ -3880,7 +3923,7 @@ Layout-схема БЕЗ валидаторов — правила живут в
|
|
|
3880
3923
|
const model = createModel<CreditApplicationForm>(initial);
|
|
3881
3924
|
|
|
3882
3925
|
const schema = {
|
|
3883
|
-
loanAmount: { value: model.$.loanAmount, component:
|
|
3926
|
+
loanAmount: { value: model.$.loanAmount, component: InputNumber },
|
|
3884
3927
|
// вложенная группа — builder, принимающий ModelSignals<Sub>
|
|
3885
3928
|
personalData: personalDataNodes(model.$.personalData),
|
|
3886
3929
|
// массив — { array, item }
|
|
@@ -4693,8 +4736,8 @@ export class ArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
4693
4736
|
|
|
4694
4737
|
```typescript
|
|
4695
4738
|
const array = new ArrayNode({
|
|
4696
|
-
title: { value: '', component:
|
|
4697
|
-
price: { value: 0, component:
|
|
4739
|
+
title: { value: '', component: Input },
|
|
4740
|
+
price: { value: 0, component: Input },
|
|
4698
4741
|
});
|
|
4699
4742
|
|
|
4700
4743
|
array.push({ title: 'Item 1', price: 100 });
|
|
@@ -4823,6 +4866,35 @@ export type BehaviorScope<T> = { model: FormModel<T>; form: FormProxy<T> };
|
|
|
4823
4866
|
|
|
4824
4867
|
_Source: src/form/behaviors/types.ts_
|
|
4825
4868
|
|
|
4869
|
+
### bindFieldProps
|
|
4870
|
+
|
|
4871
|
+
**Kind:** `function`
|
|
4872
|
+
|
|
4873
|
+
Props контрола под адаптер: срезает служебные ключи обёртки и `strip` адаптера, кладёт значение
|
|
4874
|
+
в `valueProp` (через `toValue`), вешает `changeProp`, который прогоняет эмит контрола через
|
|
4875
|
+
`fromEmit` в value-based `onChange` поля, и пробрасывает blur (`bindBlur` либо `onBlur`).
|
|
4876
|
+
Без адаптера seam проходит как есть (`value` / `onChange` / `onBlur`).
|
|
4877
|
+
|
|
4878
|
+
`componentProps` спредятся ПЕРВЫМИ — seam их перекрывает при совпадении ключей.
|
|
4879
|
+
|
|
4880
|
+
**Signature:**
|
|
4881
|
+
```typescript
|
|
4882
|
+
export function bindFieldProps(
|
|
4883
|
+
adapter: FieldAdapter | undefined,
|
|
4884
|
+
seam: FieldSeam,
|
|
4885
|
+
componentProps: Record<string, unknown> = {}
|
|
4886
|
+
): Record<string, unknown>
|
|
4887
|
+
```
|
|
4888
|
+
|
|
4889
|
+
**Parameters:**
|
|
4890
|
+
- `adapter` — - Адаптер контрола или `undefined` (value-based контрол).
|
|
4891
|
+
- `seam` — - Значение поля и его колбэки.
|
|
4892
|
+
- `componentProps` — - Прочие props контрола.
|
|
4893
|
+
|
|
4894
|
+
**Returns:** Плоский объект props для спреда в контрол.
|
|
4895
|
+
|
|
4896
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
4897
|
+
|
|
4826
4898
|
### buildValidation
|
|
4827
4899
|
|
|
4828
4900
|
**Kind:** `function`
|
|
@@ -5114,8 +5186,8 @@ const model = createModel<UserForm>({ email: '', password: '' });
|
|
|
5114
5186
|
// Layout-схема НЕ несёт validators — только component/componentProps.
|
|
5115
5187
|
const schema = {
|
|
5116
5188
|
children: [
|
|
5117
|
-
{ value: model.$.email, component:
|
|
5118
|
-
{ value: model.$.password, component:
|
|
5189
|
+
{ value: model.$.email, component: Input },
|
|
5190
|
+
{ value: model.$.password, component: Input },
|
|
5119
5191
|
],
|
|
5120
5192
|
};
|
|
5121
5193
|
|
|
@@ -5207,9 +5279,9 @@ const schema = {
|
|
|
5207
5279
|
component: Section,
|
|
5208
5280
|
children: [
|
|
5209
5281
|
// Layout несёт только component/componentProps; правила — в отдельной ValidationSchema.
|
|
5210
|
-
{ value: model.$.email, component:
|
|
5282
|
+
{ value: model.$.email, component: Input },
|
|
5211
5283
|
// вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)
|
|
5212
|
-
{ value: model.$.profile.name, component:
|
|
5284
|
+
{ value: model.$.profile.name, component: Input },
|
|
5213
5285
|
],
|
|
5214
5286
|
};
|
|
5215
5287
|
|
|
@@ -5641,6 +5713,83 @@ exclusiveFlag(model.$.contacts, (row) => row.$.primary);
|
|
|
5641
5713
|
|
|
5642
5714
|
_Source: src/form/behaviors/collections.ts_
|
|
5643
5715
|
|
|
5716
|
+
### FIELD_WRAPPER_ONLY_PROPS
|
|
5717
|
+
|
|
5718
|
+
**Kind:** `const`
|
|
5719
|
+
|
|
5720
|
+
Props, которые адресованы обёртке поля, а не контролу: `labelTooltip` рисует `FormField` у
|
|
5721
|
+
подписи. Они приходят в общем мешке `componentProps`, и без среза утекли бы в DOM контрола.
|
|
5722
|
+
|
|
5723
|
+
**Signature:**
|
|
5724
|
+
```typescript
|
|
5725
|
+
export const FIELD_WRAPPER_ONLY_PROPS: readonly string[]
|
|
5726
|
+
```
|
|
5727
|
+
|
|
5728
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
5729
|
+
|
|
5730
|
+
### FieldAdapter
|
|
5731
|
+
|
|
5732
|
+
**Kind:** `interface`
|
|
5733
|
+
|
|
5734
|
+
Адаптер поля: описывает диалект контрола (`checked`/`onCheckedChange`, `value`/`onValueChange`,
|
|
5735
|
+
DOM-событие в `onChange`, …). Все ключи необязательны — по умолчанию seam проходит как есть.
|
|
5736
|
+
|
|
5737
|
+
**Signature:**
|
|
5738
|
+
```typescript
|
|
5739
|
+
export interface FieldAdapter {
|
|
5740
|
+
/** Проп, из которого контрол читает значение (по умолчанию `'value'`). */
|
|
5741
|
+
valueProp?: string;
|
|
5742
|
+
/** Колбэк, через который контрол эмитит изменение (по умолчанию `'onChange'`). */
|
|
5743
|
+
changeProp?: string;
|
|
5744
|
+
/**
|
|
5745
|
+
* Эмит контрола → значение поля (по умолчанию — как есть). `rest` — прочие props контрола
|
|
5746
|
+
* (например, чтобы достать `options` при резолве значения).
|
|
5747
|
+
*/
|
|
5748
|
+
fromEmit?: (arg: unknown, rest: Record<string, unknown>) => unknown;
|
|
5749
|
+
/** Значение поля → `valueProp` контрола (коэрсия `null`/`undefined`; по умолчанию — как есть). */
|
|
5750
|
+
toValue?: (value: unknown) => unknown;
|
|
5751
|
+
/** Проброс blur нестандартным каналом (по умолчанию — `onBlur`). */
|
|
5752
|
+
bindBlur?: (onBlur: () => void) => Record<string, unknown>;
|
|
5753
|
+
/** Ключи, которые убрать из `componentProps` перед спредом в контрол. */
|
|
5754
|
+
strip?: string[];
|
|
5755
|
+
/**
|
|
5756
|
+
* Передавать ли контролу ноду формы пропом `control` (только рендерер). По умолчанию `false`:
|
|
5757
|
+
* errors/touched обслуживает обёртка поля. Ставь `true`, если контрол сам вызывает
|
|
5758
|
+
* `useFormControl(control)`.
|
|
5759
|
+
*/
|
|
5760
|
+
passControl?: boolean;
|
|
5761
|
+
}
|
|
5762
|
+
```
|
|
5763
|
+
|
|
5764
|
+
**Examples:**
|
|
5765
|
+
|
|
5766
|
+
Radix Checkbox
|
|
5767
|
+
```ts
|
|
5768
|
+
const checkedAdapter: FieldAdapter = {
|
|
5769
|
+
valueProp: 'checked',
|
|
5770
|
+
changeProp: 'onCheckedChange',
|
|
5771
|
+
fromEmit: (c) => c === true,
|
|
5772
|
+
toValue: (v) => v ?? false,
|
|
5773
|
+
};
|
|
5774
|
+
```
|
|
5775
|
+
|
|
5776
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
5777
|
+
|
|
5778
|
+
### FieldAdapterCarrier
|
|
5779
|
+
|
|
5780
|
+
**Kind:** `interface`
|
|
5781
|
+
|
|
5782
|
+
Компонент, объявивший свой диалект статикой.
|
|
5783
|
+
|
|
5784
|
+
**Signature:**
|
|
5785
|
+
```typescript
|
|
5786
|
+
export interface FieldAdapterCarrier {
|
|
5787
|
+
reformerAdapter?: FieldAdapter;
|
|
5788
|
+
}
|
|
5789
|
+
```
|
|
5790
|
+
|
|
5791
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
5792
|
+
|
|
5644
5793
|
### FieldConfig
|
|
5645
5794
|
|
|
5646
5795
|
**Kind:** `interface`
|
|
@@ -5903,6 +6052,38 @@ onChange={e => control.setValue(e.target.value)}
|
|
|
5903
6052
|
|
|
5904
6053
|
_Source: src/platforms/react/hooks/types.ts_
|
|
5905
6054
|
|
|
6055
|
+
### FieldHandle
|
|
6056
|
+
|
|
6057
|
+
**Kind:** `interface`
|
|
6058
|
+
|
|
6059
|
+
Базовый императивный контракт поля формы.
|
|
6060
|
+
|
|
6061
|
+
Обёртка поля (`FormField.Control` из `@reformer/cdk`, рендерер `@reformer/renderer-react`)
|
|
6062
|
+
строит его из DOM-узла контрола. Композиты со своим handle (`open`/`close`/`reload`/…) отдают
|
|
6063
|
+
его как есть — он расширяет этот контракт.
|
|
6064
|
+
|
|
6065
|
+
Достаётся из render-схемы по селектору:
|
|
6066
|
+
`schema.node(sel).getRef<FieldHandle>().current?.focus()`.
|
|
6067
|
+
|
|
6068
|
+
Покрывает ТОЛЬКО императивные действия. Реактивное состояние (value / disabled / options /
|
|
6069
|
+
validation) живёт в behaviors и через handle не дублируется.
|
|
6070
|
+
|
|
6071
|
+
**Signature:**
|
|
6072
|
+
```typescript
|
|
6073
|
+
export interface FieldHandle {
|
|
6074
|
+
/** Сфокусировать поле (делегирует на DOM-элемент контрола). */
|
|
6075
|
+
focus(): void;
|
|
6076
|
+
/** Снять фокус. */
|
|
6077
|
+
blur(): void;
|
|
6078
|
+
/** Проскроллить поле в область видимости. */
|
|
6079
|
+
scrollIntoView(opts?: ScrollIntoViewOptions): void;
|
|
6080
|
+
/** Живой DOM-элемент поля (или `null` до монтирования / для размонтированной ноды). */
|
|
6081
|
+
getElement(): HTMLElement | null;
|
|
6082
|
+
}
|
|
6083
|
+
```
|
|
6084
|
+
|
|
6085
|
+
_Source: src/platforms/react/field-binding/field-handle.ts_
|
|
6086
|
+
|
|
5906
6087
|
### FieldNode
|
|
5907
6088
|
|
|
5908
6089
|
**Kind:** `class`
|
|
@@ -5923,7 +6104,7 @@ export class FieldNode<T> extends FormNode<T> {
|
|
|
5923
6104
|
```typescript
|
|
5924
6105
|
const field = new FieldNode({
|
|
5925
6106
|
value: '',
|
|
5926
|
-
component:
|
|
6107
|
+
component: Input,
|
|
5927
6108
|
validators: [required, email],
|
|
5928
6109
|
});
|
|
5929
6110
|
|
|
@@ -5934,6 +6115,23 @@ console.log(field.valid.value); // true
|
|
|
5934
6115
|
|
|
5935
6116
|
_Source: src/form/nodes/field-node.ts_
|
|
5936
6117
|
|
|
6118
|
+
### FieldSeam
|
|
6119
|
+
|
|
6120
|
+
**Kind:** `interface`
|
|
6121
|
+
|
|
6122
|
+
Seam поля: текущее значение и value-based колбэки.
|
|
6123
|
+
|
|
6124
|
+
**Signature:**
|
|
6125
|
+
```typescript
|
|
6126
|
+
export interface FieldSeam {
|
|
6127
|
+
value: unknown;
|
|
6128
|
+
onChange: (value: unknown) => void;
|
|
6129
|
+
onBlur: () => void;
|
|
6130
|
+
}
|
|
6131
|
+
```
|
|
6132
|
+
|
|
6133
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
6134
|
+
|
|
5937
6135
|
### FieldStatus
|
|
5938
6136
|
|
|
5939
6137
|
**Kind:** `type`
|
|
@@ -6374,14 +6572,14 @@ interface Form {
|
|
|
6374
6572
|
}
|
|
6375
6573
|
|
|
6376
6574
|
const schema: FormSchema<Form> = {
|
|
6377
|
-
name: { value: '', component:
|
|
6575
|
+
name: { value: '', component: Input },
|
|
6378
6576
|
address: {
|
|
6379
|
-
city: { value: '', component:
|
|
6380
|
-
street: { value: '', component:
|
|
6577
|
+
city: { value: '', component: Input },
|
|
6578
|
+
street: { value: '', component: Input },
|
|
6381
6579
|
},
|
|
6382
6580
|
items: [{
|
|
6383
|
-
title: { value: '', component:
|
|
6384
|
-
price: { value: 0, component:
|
|
6581
|
+
title: { value: '', component: Input },
|
|
6582
|
+
price: { value: 0, component: Input },
|
|
6385
6583
|
}],
|
|
6386
6584
|
};
|
|
6387
6585
|
```
|
|
@@ -6642,6 +6840,26 @@ validate(model.$.appointmentDate, [required(), futureDate({ message: 'Дата
|
|
|
6642
6840
|
|
|
6643
6841
|
_Source: src/form/validators/future-date.ts_
|
|
6644
6842
|
|
|
6843
|
+
### getFieldAdapter
|
|
6844
|
+
|
|
6845
|
+
**Kind:** `function`
|
|
6846
|
+
|
|
6847
|
+
Адаптер, объявленный компонентом статикой `reformerAdapter`, либо `undefined`.
|
|
6848
|
+
|
|
6849
|
+
**Signature:**
|
|
6850
|
+
```typescript
|
|
6851
|
+
export function getFieldAdapter(component: unknown): FieldAdapter | undefined
|
|
6852
|
+
```
|
|
6853
|
+
|
|
6854
|
+
**Examples:**
|
|
6855
|
+
|
|
6856
|
+
```ts
|
|
6857
|
+
Checkbox.reformerAdapter = checkedAdapter;
|
|
6858
|
+
getFieldAdapter(Checkbox); // → checkedAdapter
|
|
6859
|
+
```
|
|
6860
|
+
|
|
6861
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
6862
|
+
|
|
6645
6863
|
### getNodeForSignal
|
|
6646
6864
|
|
|
6647
6865
|
**Kind:** `function`
|
|
@@ -6738,8 +6956,8 @@ export class GroupNode<T> extends FormNode<T> {
|
|
|
6738
6956
|
|
|
6739
6957
|
```typescript
|
|
6740
6958
|
const form = new GroupNode({
|
|
6741
|
-
email: { valueSignal: model.$.email, component:
|
|
6742
|
-
password: { valueSignal: model.$.password, component:
|
|
6959
|
+
email: { valueSignal: model.$.email, component: Input },
|
|
6960
|
+
password: { valueSignal: model.$.password, component: Input },
|
|
6743
6961
|
});
|
|
6744
6962
|
|
|
6745
6963
|
// Прямой доступ к полям через Proxy
|
|
@@ -7075,6 +7293,20 @@ validate(model.$.amount, [required(), isNumber({ message: 'Введите чис
|
|
|
7075
7293
|
|
|
7076
7294
|
_Source: src/form/validators/is-number.ts_
|
|
7077
7295
|
|
|
7296
|
+
### makeElementFieldHandle
|
|
7297
|
+
|
|
7298
|
+
**Kind:** `function`
|
|
7299
|
+
|
|
7300
|
+
Собирает базовый {@link FieldHandle}, делегирующий на DOM-элемент по ссылке `el`.
|
|
7301
|
+
Все вызовы null-safe: до монтирования (`el.current === null`) — no-op, без исключений.
|
|
7302
|
+
|
|
7303
|
+
**Signature:**
|
|
7304
|
+
```typescript
|
|
7305
|
+
export function makeElementFieldHandle(el: RefObject<HTMLElement | null>): FieldHandle
|
|
7306
|
+
```
|
|
7307
|
+
|
|
7308
|
+
_Source: src/platforms/react/field-binding/field-handle.ts_
|
|
7309
|
+
|
|
7078
7310
|
### markDerived
|
|
7079
7311
|
|
|
7080
7312
|
**Kind:** `function`
|
|
@@ -7796,8 +8028,8 @@ export class ModelArrayNode<T extends object> extends FormNode<T[]> { /* … */
|
|
|
7796
8028
|
const model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });
|
|
7797
8029
|
|
|
7798
8030
|
const rowItem = (item: FormModel<{ name: string; qty: number }>) => ({
|
|
7799
|
-
name: { value: item.$.name, component:
|
|
7800
|
-
qty: { value: item.$.qty, component:
|
|
8031
|
+
name: { value: item.$.name, component: Input },
|
|
8032
|
+
qty: { value: item.$.qty, component: Input },
|
|
7801
8033
|
});
|
|
7802
8034
|
|
|
7803
8035
|
const form = createForm({
|
|
@@ -7980,25 +8212,25 @@ export class NodeFactory {
|
|
|
7980
8212
|
* // FieldNode
|
|
7981
8213
|
* const field = factory.createNode({
|
|
7982
8214
|
* value: 'test@mail.com',
|
|
7983
|
-
* component:
|
|
8215
|
+
* component: Input,
|
|
7984
8216
|
* validators: [required, email]
|
|
7985
8217
|
* });
|
|
7986
8218
|
*
|
|
7987
8219
|
* // GroupNode
|
|
7988
8220
|
* const group = factory.createNode({
|
|
7989
|
-
* email: { value: '', component:
|
|
7990
|
-
* password: { value: '', component:
|
|
8221
|
+
* email: { value: '', component: Input },
|
|
8222
|
+
* password: { value: '', component: Input }
|
|
7991
8223
|
* });
|
|
7992
8224
|
*
|
|
7993
8225
|
* // ArrayNode (объект)
|
|
7994
8226
|
* const array = factory.createNode({
|
|
7995
|
-
* schema: { title: { value: '', component:
|
|
8227
|
+
* schema: { title: { value: '', component: Input } },
|
|
7996
8228
|
* initialItems: [{ title: 'Item 1' }]
|
|
7997
8229
|
* });
|
|
7998
8230
|
*
|
|
7999
8231
|
* // ArrayNode (массив) - новый формат
|
|
8000
8232
|
* const array2 = factory.createNode([
|
|
8001
|
-
* { title: { value: '', component:
|
|
8233
|
+
* { title: { value: '', component: Input } }, // schema
|
|
8002
8234
|
* { title: 'Item 1' }, // initial item 1
|
|
8003
8235
|
* { title: 'Item 2' } // initial item 2
|
|
8004
8236
|
* ]);
|
|
@@ -8012,7 +8244,7 @@ export class NodeFactory {
|
|
|
8012
8244
|
import { NodeFactory } from '@reformer/core';
|
|
8013
8245
|
|
|
8014
8246
|
const factory = new NodeFactory();
|
|
8015
|
-
const node = factory.createNode({ value: '', component:
|
|
8247
|
+
const node = factory.createNode({ value: '', component: Input }); // → FieldNode<string>
|
|
8016
8248
|
```
|
|
8017
8249
|
|
|
8018
8250
|
_Source: src/form/factories/node-factory.ts_
|
|
@@ -8901,6 +9133,33 @@ function ArrayRenderer({ arrayNode }) {
|
|
|
8901
9133
|
|
|
8902
9134
|
_Source: src/platforms/react/hooks/useArrayLength.ts_
|
|
8903
9135
|
|
|
9136
|
+
### useFieldHandleRef
|
|
9137
|
+
|
|
9138
|
+
**Kind:** `function`
|
|
9139
|
+
|
|
9140
|
+
Ref для контрола поля + императивный handle для потребителя.
|
|
9141
|
+
|
|
9142
|
+
Возвращённый ref вешается на контрол. На `outerRef` публикуется:
|
|
9143
|
+
- handle самого контрола, если тот его реализует (`useImperativeHandle` композита);
|
|
9144
|
+
- иначе — базовый {@link FieldHandle}, построенный из DOM-узла контрола.
|
|
9145
|
+
|
|
9146
|
+
Хук вызывается безусловно; вешать ref на контрол стоит только при заданном `outerRef` —
|
|
9147
|
+
так function-компонент без ref-поддержки не получает лишний ref.
|
|
9148
|
+
|
|
9149
|
+
**Signature:**
|
|
9150
|
+
```typescript
|
|
9151
|
+
export function useFieldHandleRef<H = FieldHandle>(
|
|
9152
|
+
outerRef: Ref<H> | undefined
|
|
9153
|
+
): RefObject<unknown>
|
|
9154
|
+
```
|
|
9155
|
+
|
|
9156
|
+
**Parameters:**
|
|
9157
|
+
- `outerRef` — - ref потребителя (из `forwardRef` или `schema.node(sel).getRef()`).
|
|
9158
|
+
|
|
9159
|
+
**Returns:** ref, который нужно передать контролу.
|
|
9160
|
+
|
|
9161
|
+
_Source: src/platforms/react/field-binding/field-handle.ts_
|
|
9162
|
+
|
|
8904
9163
|
### useFormBundle
|
|
8905
9164
|
|
|
8906
9165
|
**Kind:** `function`
|