@reformer/core 11.0.0-beta.6 → 11.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/behaviors.js +216 -179
- package/dist/create-model-BBRHhHwK.js +30 -0
- package/dist/en-CjAqn7h0.js +41 -0
- package/dist/form/behaviors/collections.d.ts +14 -1
- package/dist/form/behaviors/context.d.ts +18 -2
- package/dist/form/behaviors/index.d.ts +3 -1
- package/dist/form/behaviors/internals.d.ts +9 -1
- package/dist/form/behaviors/schema-operators.d.ts +76 -0
- package/dist/form/behaviors/types.d.ts +15 -3
- package/dist/form/create-core-form.d.ts +5 -3
- package/dist/form/create-form.d.ts +28 -53
- package/dist/form/form-bundle.d.ts +136 -0
- package/dist/form/index.d.ts +6 -1
- package/dist/form/nodes/field-node.d.ts +2 -2
- package/dist/form/nodes/group-node.d.ts +1 -1
- package/dist/form/nodes/model-array-node.d.ts +10 -5
- package/dist/form/schema-controller.d.ts +137 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +22 -16
- package/dist/form/types/contracts.d.ts +6 -0
- package/dist/form/types/deep-schema.d.ts +2 -2
- package/dist/form/types/form-proxy.d.ts +1 -1
- package/dist/form/types/schema-node.d.ts +37 -12
- package/dist/form/types/validation-schema.d.ts +13 -1
- package/dist/form/validation/context.d.ts +6 -1
- package/dist/form/validation/index.d.ts +1 -1
- package/dist/form/validation/operators.d.ts +74 -7
- package/dist/form/validation/types.d.ts +2 -1
- package/dist/form/validators/validation-error.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +54 -0
- package/dist/i18n/en.json.d.ts +45 -0
- package/dist/i18n/loader.d.ts +71 -0
- package/dist/i18n/locale-store.d.ts +39 -0
- package/dist/i18n/locale.d.ts +90 -0
- package/dist/i18n/message-format.d.ts +116 -0
- package/dist/i18n/resolve-localized.d.ts +17 -0
- package/dist/i18n/ru.json.d.ts +45 -0
- package/dist/i18n/translator.d.ts +50 -0
- package/dist/i18n/validation-message.d.ts +18 -0
- package/dist/i18n.d.ts +31 -0
- package/dist/i18n.js +303 -0
- package/dist/index-BrNkQj1y.js +105 -0
- package/dist/index.js +737 -743
- package/dist/loader-Dv_Hl7JC.js +216 -0
- package/dist/locale/en.d.ts +2 -0
- package/dist/locale/en.js +5 -0
- package/dist/locale/en.json +42 -0
- package/dist/locale/index.d.ts +18 -0
- package/dist/locale/ru.d.ts +2 -0
- package/dist/locale/ru.js +41 -0
- package/dist/locale/ru.json +42 -0
- package/dist/locale.d.ts +2 -0
- package/dist/locale.js +9 -0
- package/dist/model/create-model.d.ts +17 -3
- package/dist/model/index.d.ts +8 -3
- package/dist/model/model-nodes.d.ts +67 -4
- package/dist/model/model-signals-proxy.d.ts +33 -1
- package/dist/model/model-value-proxy.d.ts +76 -0
- package/dist/model/types.d.ts +26 -9
- package/dist/model-value-proxy-caTmAiUd.js +419 -0
- package/dist/model.js +21 -17
- package/dist/node-BeV_lV5i.js +100 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/platforms/react/hooks/types.d.ts +6 -3
- package/dist/platforms/react/hooks/useFormControlValue.d.ts +2 -1
- package/dist/platforms/react/i18n/context.d.ts +13 -0
- package/dist/platforms/react/i18n/hooks.d.ts +59 -0
- package/dist/platforms/react/i18n/provider.d.ts +52 -0
- package/dist/platforms/react/index.d.ts +1 -0
- package/dist/strategy-DgzELWno.js +204 -0
- package/dist/validation-error-B-U3RsV0.js +11 -0
- package/dist/validation.js +7 -6
- package/dist/validators/email.js +5 -8
- package/dist/validators/future-date.js +8 -11
- package/dist/validators/integer.js +4 -7
- package/dist/validators/is-date.js +5 -8
- package/dist/validators/is-number.js +4 -7
- package/dist/validators/max-age.js +8 -11
- package/dist/validators/max-date.js +8 -11
- package/dist/validators/max-length.js +5 -8
- package/dist/validators/max.js +4 -7
- package/dist/validators/min-age.js +8 -11
- package/dist/validators/min-date.js +8 -11
- package/dist/validators/min-length.js +5 -8
- package/dist/validators/min.js +4 -7
- package/dist/validators/multiple-of.js +5 -8
- package/dist/validators/non-negative.js +4 -7
- package/dist/validators/non-zero.js +4 -7
- package/dist/validators/past-date.js +8 -11
- package/dist/validators/pattern.js +5 -8
- package/dist/validators/phone.js +6 -9
- package/dist/validators/required.js +4 -7
- package/dist/validators/url.js +9 -14
- package/dist/validators.js +84 -106
- package/docs/llms/03-api-signatures.md +10 -4
- package/docs/llms/09-formschema.md +5 -5
- package/docs/llms/10-arrays.md +52 -3
- package/docs/llms/16-ui-components.md +2 -2
- package/docs/llms/18-conditional-fields.md +1 -1
- package/docs/llms/21-array-operations.md +26 -0
- package/docs/llms/28-submit-and-reset.md +3 -3
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/33-validation-strategy.md +2 -2
- package/docs/llms/34-i18n.md +272 -0
- package/docs/llms/35-unified-contract.md +246 -0
- package/llms-index.json +1 -1
- package/llms.txt +2746 -229
- package/package.json +22 -3
- package/dist/create-model-Dxh9cfLp.js +0 -97
- package/dist/model-signals-proxy-mV3WGdlO.js +0 -233
- package/dist/node-DGEFMTQu.js +0 -18
- package/dist/strategy-DA3JwxsR.js +0 -170
package/llms.txt
CHANGED
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
- 31-async-validator-debounce.md — async-validator-debounce
|
|
39
39
|
- 32-async-options-loading.md — async-options-loading
|
|
40
40
|
- 33-validation-strategy.md — useFormValidation — Единый выбор стратегии валидации
|
|
41
|
+
- 34-i18n.md — i18n — локаль, загрузка словарей и провайдер
|
|
42
|
+
- 35-unified-contract.md — Единый контракт формы — одна схема, одна сборка, одно поведение
|
|
41
43
|
- API Reference (auto-generated from JSDoc)
|
|
42
44
|
|
|
43
45
|
## 1. 1. API Reference
|
|
@@ -274,7 +276,7 @@ createModel<T extends object>(initial: T): FormModel<T>
|
|
|
274
276
|
// model.arrayField → ModelArray<Item> (push/removeAt/insertAt/move/swap/clear/at/map/length)
|
|
275
277
|
|
|
276
278
|
// Форма (ноды поверх сигналов модели)
|
|
277
|
-
|
|
279
|
+
createFormFromModel<T>({ model, schema, behavior? }): FormProxy<T>
|
|
278
280
|
// form.<field> → FieldNode / GroupNode / FormArrayProxy
|
|
279
281
|
// form.<field>.setValue(v) / .value.value / .errors.value / .disabled.value
|
|
280
282
|
// form.<field>.enable() / .disable() / .reset() / .markAsTouched() / .setErrors([...])
|
|
@@ -320,11 +322,17 @@ minAge(years: number, options?: { message?: string })
|
|
|
320
322
|
maxAge(years: number, options?: { message?: string })
|
|
321
323
|
```
|
|
322
324
|
|
|
325
|
+
Кроме `message`, каждое правило принимает `messageKey` (ключ словаря приложения) и `params`.
|
|
326
|
+
Без `message` правило кладёт в ошибку пустую строку, а текст при отображении берётся из словаря
|
|
327
|
+
локали по коду (`validation.<code>`): «Обязательное поле» под русской локалью, английская фраза без
|
|
328
|
+
провайдера. Явное `message` важнее словаря, но языком не переключается; для локализуемого текста —
|
|
329
|
+
`messageKey`. Подробно — [34-i18n.md](./34-i18n.md).
|
|
330
|
+
|
|
323
331
|
Использование: layout-дерево `createForm` привязывает поля к сигналам, правила живут отдельной
|
|
324
332
|
`ValidationSchema` и гоняются раннером `validateModel` по требованию.
|
|
325
333
|
|
|
326
334
|
```typescript
|
|
327
|
-
import { createModel, createForm } from '@reformer/core';
|
|
335
|
+
import { createModel, createForm, createFormFromModel } from '@reformer/core';
|
|
328
336
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
329
337
|
import { required, min, max, email } from '@reformer/core/validators';
|
|
330
338
|
|
|
@@ -332,7 +340,7 @@ type Loan = { email: string; age: number | null; amount: number | null };
|
|
|
332
340
|
const model = createModel<Loan>({ email: '', age: null, amount: null });
|
|
333
341
|
|
|
334
342
|
// layout-схема createForm НЕ несёт validators — только привязка поля к сигналу + компонент
|
|
335
|
-
const form =
|
|
343
|
+
const form = createFormFromModel({
|
|
336
344
|
model,
|
|
337
345
|
schema: {
|
|
338
346
|
email: { value: model.$.email, component: Input },
|
|
@@ -519,7 +527,7 @@ const behavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
|
519
527
|
});
|
|
520
528
|
});
|
|
521
529
|
|
|
522
|
-
const form =
|
|
530
|
+
const form = createFormFromModel({ model, schema, behavior });
|
|
523
531
|
```
|
|
524
532
|
|
|
525
533
|
DSL-операторы: `compute` (auto-tracking, без явного списка источников), `computeFrom`, `copyFrom`,
|
|
@@ -1105,7 +1113,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
1105
1113
|
### Primitive Fields
|
|
1106
1114
|
|
|
1107
1115
|
```typescript
|
|
1108
|
-
import { createModel,
|
|
1116
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
1109
1117
|
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
1110
1118
|
import { required } from '@reformer/core/validators';
|
|
1111
1119
|
import { Input, InputNumber, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
@@ -1146,7 +1154,7 @@ const schema = {
|
|
|
1146
1154
|
},
|
|
1147
1155
|
};
|
|
1148
1156
|
|
|
1149
|
-
const form =
|
|
1157
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
1150
1158
|
```
|
|
1151
1159
|
|
|
1152
1160
|
### Nested Objects
|
|
@@ -1191,7 +1199,7 @@ const schema = {
|
|
|
1191
1199
|
|
|
1192
1200
|
```typescript
|
|
1193
1201
|
// M1: данные из модели + схема (+ опциональный декларативный behavior)
|
|
1194
|
-
const form =
|
|
1202
|
+
const form = createFormFromModel<MyForm>({
|
|
1195
1203
|
model, // FormModel<MyForm> — обязателен
|
|
1196
1204
|
schema, // дерево узлов, привязанных к сигналам
|
|
1197
1205
|
behavior: myBehavior, // опционально: defineFormBehavior(...) из @reformer/core/behaviors
|
|
@@ -1206,12 +1214,12 @@ model.items.push({ id: '1', name: 'Item' }); // операции над масс
|
|
|
1206
1214
|
> **Тип `schema` в M1 — `unknown`, не `FormSchema<T>`.** Это осознанно: интерпретатор обходит
|
|
1207
1215
|
> произвольную структуру и собирает листья `{ value: signal, component?, ... }`, поэтому обёртки
|
|
1208
1216
|
> (`{ children: [...] }`, wizard/steps) компилируются. Строгий `FormSchema<T>` (keyed-map по полям
|
|
1209
|
-
> `T`) применяется только на legacy-пути `
|
|
1217
|
+
> `T`) применяется только на legacy-пути `createLegacyForm(schema)` без модели.
|
|
1210
1218
|
|
|
1211
1219
|
### createForm Returns a Proxy
|
|
1212
1220
|
|
|
1213
1221
|
```typescript
|
|
1214
|
-
const form =
|
|
1222
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
1215
1223
|
|
|
1216
1224
|
form.email; // FieldNode<string> — TypeScript знает тип
|
|
1217
1225
|
form.address.city; // FieldNode<string> — вложенный доступ
|
|
@@ -1232,7 +1240,7 @@ if (isFieldNode(node)) { /* ... */ }
|
|
|
1232
1240
|
элемента из его под-модели (`FormModel<Item>`).
|
|
1233
1241
|
|
|
1234
1242
|
```typescript
|
|
1235
|
-
import { createModel,
|
|
1243
|
+
import { createModel, createFormFromModel, type FormModel } from '@reformer/core';
|
|
1236
1244
|
import { Input, InputNumber } from '@reformer/ui-kit';
|
|
1237
1245
|
|
|
1238
1246
|
type Item = { id: string; name: string; price: number };
|
|
@@ -1251,13 +1259,62 @@ const schema = {
|
|
|
1251
1259
|
items: { array: model.items, item: itemSchema },
|
|
1252
1260
|
};
|
|
1253
1261
|
|
|
1254
|
-
const form =
|
|
1262
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
1255
1263
|
```
|
|
1256
1264
|
|
|
1257
1265
|
> **Type constraint:** тип элемента `Item` объявляй через `type`-alias (не `interface`) — иначе
|
|
1258
1266
|
> он не совместим с `Record<string, FormValue>` и `ArrayNode<Item>` его отвергнет.
|
|
1259
1267
|
> См. `30-type-safety-recipes.md`.
|
|
1260
1268
|
|
|
1269
|
+
### Массив как ОДНО значение поля
|
|
1270
|
+
|
|
1271
|
+
Массив бывает не набором под-форм, а одним значением: мультивыбор, теги, список файлов. Чем он
|
|
1272
|
+
является, решает **схема**, а не данные: `{ array, item }` — набор под-форм, а узел поля
|
|
1273
|
+
`{ value: model.$.<путь>, component }` — одно значение. Массив, к которому в схеме ничего не
|
|
1274
|
+
привязано, в форму не попадает.
|
|
1275
|
+
|
|
1276
|
+
```typescript
|
|
1277
|
+
type MyForm = { tags: string[] };
|
|
1278
|
+
|
|
1279
|
+
const model = createModel<MyForm>({ tags: [] });
|
|
1280
|
+
|
|
1281
|
+
const schema = {
|
|
1282
|
+
tags: { value: model.$.tags, component: SelectMulti }, // поле над массивом целиком
|
|
1283
|
+
};
|
|
1284
|
+
const form = createFormFromModel<MyForm>({ model, schema });
|
|
1285
|
+
|
|
1286
|
+
form.tags.setValue(['a', 'b']); // FieldNode<string[]>
|
|
1287
|
+
model.$.tags.value = ['c']; // ручка значения: заменяет массив целиком
|
|
1288
|
+
validate(model.$.tags, [required(), maxLength(3)]); // правило получает массив
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
- `model.$.<массив>` — записываемая ручка значения (`PathAwareSignal`): подходит для `value:` в
|
|
1292
|
+
схеме, `validate`/`cross`, `enableWhen`/`copyFrom` и для `model.signalAt(path)`.
|
|
1293
|
+
- Узел-массив хранит только массив: запись `null`/`undefined` даёт `[]`. Нужно отличать «не
|
|
1294
|
+
выбирали» от «выбрали ничего» — объявляй поле `T[] | null` с начальным `null`: в рантайме это
|
|
1295
|
+
лист, и привязывается он так же.
|
|
1296
|
+
- Запись в узел-ГРУППУ (`model.$.<группа>.value = …`) — ошибка: группа пишется по полям или через
|
|
1297
|
+
`model.patch(...)`.
|
|
1298
|
+
|
|
1299
|
+
### Массив в группе и в строке другого массива
|
|
1300
|
+
|
|
1301
|
+
Узел `{ array, item }` стоит на любой глубине: массив в группе получает ноду `form.<группа>.<массив>`,
|
|
1302
|
+
массив в строке — `form.<массив>.at(i).<массив>`. Под-схема строки объявляет свои массивы так же.
|
|
1303
|
+
|
|
1304
|
+
```typescript
|
|
1305
|
+
const contactItem = (contact: FormModel<Contact>) => ({
|
|
1306
|
+
name: { value: contact.$.name, component: Input },
|
|
1307
|
+
phones: { array: contact.phones, item: phoneItem }, // массив в строке массива
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
const schema = {
|
|
1311
|
+
hotlines: { array: model.details.hotlines, item: phoneItem }, // массив в группе
|
|
1312
|
+
contacts: { array: model.contacts, item: contactItem },
|
|
1313
|
+
};
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
Поведение строк вложенного массива — `applyEach` внутри схемы строки, см. `21-array-operations.md`.
|
|
1317
|
+
|
|
1261
1318
|
### Один массив — три слоя (три разных движка)
|
|
1262
1319
|
|
|
1263
1320
|
Одна и та же коллекция описывается **тремя разными формами** — по одной на движок. Их легко
|
|
@@ -1269,7 +1326,7 @@ const form = createForm<MyForm>({ model, schema });
|
|
|
1269
1326
|
`model.$.properties`.
|
|
1270
1327
|
|
|
1271
1328
|
```typescript
|
|
1272
|
-
// узел схемы для
|
|
1329
|
+
// узел схемы для createFormFromModel({ model, schema })
|
|
1273
1330
|
properties: { array: model.properties, item: propertyItem },
|
|
1274
1331
|
```
|
|
1275
1332
|
|
|
@@ -1829,7 +1886,7 @@ the guide below.
|
|
|
1829
1886
|
|
|
1830
1887
|
```tsx
|
|
1831
1888
|
import { useMemo } from 'react';
|
|
1832
|
-
import { createModel,
|
|
1889
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
1833
1890
|
import { FormField, Input, SelectAsync, CheckboxWithLabel, Button } from '@reformer/ui-kit';
|
|
1834
1891
|
|
|
1835
1892
|
type RegistrationForm = {
|
|
@@ -1864,7 +1921,7 @@ function RegistrationPage() {
|
|
|
1864
1921
|
componentProps: { label: 'I agree to terms' },
|
|
1865
1922
|
},
|
|
1866
1923
|
};
|
|
1867
|
-
return
|
|
1924
|
+
return createFormFromModel<RegistrationForm>({ model, schema });
|
|
1868
1925
|
}, []);
|
|
1869
1926
|
|
|
1870
1927
|
return (
|
|
@@ -2193,7 +2250,7 @@ export const creditBehavior = defineFormBehavior<CreditForm>(({ model }) => {
|
|
|
2193
2250
|
- **Все поля должны быть материализованы в модели** (`createModel`), даже те, что показываются
|
|
2194
2251
|
только под условием. `enableWhen`/`compute`/`copyFrom` принимают сигналы `model.$.field`, а не
|
|
2195
2252
|
строковые пути; сигнала для несуществующего поля нет.
|
|
2196
|
-
- Поведение подключается к форме через `
|
|
2253
|
+
- Поведение подключается к форме через `createFormFromModel({ model, schema, behavior })` — форма владеет
|
|
2197
2254
|
жизненным циклом, DSL-операторы регистрируют свой cleanup сами (ручной массив cleanup'ов не нужен).
|
|
2198
2255
|
- Таргетом может быть и **группа**: `enableWhen(model.$.residenceAddress, () => model.sameAsRegistration === false)`
|
|
2199
2256
|
проходит по поддереву; без `resetOnDisable` значение группы сохраняется (удобно, когда оно копируется
|
|
@@ -2640,6 +2697,32 @@ const behavior = defineFormBehavior<MyForm>(({ model }) => {
|
|
|
2640
2697
|
});
|
|
2641
2698
|
```
|
|
2642
2699
|
|
|
2700
|
+
### Вложенные области — массив в группе и в строке массива
|
|
2701
|
+
|
|
2702
|
+
Узел `{ array, item }` и операторы `applyEach` / `apply` / `enableWhen` работают на любой глубине:
|
|
2703
|
+
цель оператора — **ручка** `model.$.…`, под-модель и нода находятся по её идентичности, а не по
|
|
2704
|
+
пути.
|
|
2705
|
+
|
|
2706
|
+
```typescript
|
|
2707
|
+
const phoneBehavior = defineFormBehavior<Phone>(({ model: phone }) => {
|
|
2708
|
+
enableWhen(phone.$.number, () => phone.primary);
|
|
2709
|
+
});
|
|
2710
|
+
|
|
2711
|
+
const contactBehavior = defineFormBehavior<Contact>(({ model: contact }) => {
|
|
2712
|
+
applyEach(contact.$.phones, phoneBehavior); // массив внутри строки
|
|
2713
|
+
enableWhen(contact.$.address, () => contact.quantity > 0); // группа внутри строки
|
|
2714
|
+
apply(contact.$.address, addressBehavior); // под-схема получает настоящую под-модель
|
|
2715
|
+
});
|
|
2716
|
+
|
|
2717
|
+
const behavior = defineFormBehavior<MyForm>(({ model }) => {
|
|
2718
|
+
applyEach(model.$.contacts, contactBehavior);
|
|
2719
|
+
});
|
|
2720
|
+
```
|
|
2721
|
+
|
|
2722
|
+
Ручка → value-фасад: `modelOf(model.$.address)` — под-модель (`get` / `set` / `patch`),
|
|
2723
|
+
`modelOf(model.$.phones)` — фасад массива (`push` / `removeAt` / `at`). Нода формы по ручке —
|
|
2724
|
+
`getNodeForSignal(model.$.address)`.
|
|
2725
|
+
|
|
2643
2726
|
### Aggregate write — aggregateInto
|
|
2644
2727
|
|
|
2645
2728
|
Агрегатная запись в строки (например, «последняя строка = 100 − Σ остальных»):
|
|
@@ -3456,7 +3539,7 @@ form.<field>.clearErrors();
|
|
|
3456
3539
|
|
|
3457
3540
|
```tsx
|
|
3458
3541
|
import { useMemo } from 'react';
|
|
3459
|
-
import { createModel, createForm } from '@reformer/core';
|
|
3542
|
+
import { createModel, createForm, createFormFromModel } from '@reformer/core';
|
|
3460
3543
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
3461
3544
|
import { required, email, minLength } from '@reformer/core/validators';
|
|
3462
3545
|
|
|
@@ -3474,7 +3557,7 @@ function RegistrationForm() {
|
|
|
3474
3557
|
const { model, form } = useMemo(() => {
|
|
3475
3558
|
const m = createModel<RegistrationFormData>({ username: '', email: '', password: '' });
|
|
3476
3559
|
// schema здесь — layout-дерево нод (компоненты/раскладка), без валидаторов.
|
|
3477
|
-
return { model: m, form:
|
|
3560
|
+
return { model: m, form: createFormFromModel({ model: m, schema: buildLayout(m) }) };
|
|
3478
3561
|
}, []);
|
|
3479
3562
|
|
|
3480
3563
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
@@ -3606,7 +3689,7 @@ const handleSubmit = async (e) => {
|
|
|
3606
3689
|
|
|
3607
3690
|
```typescript
|
|
3608
3691
|
// ❌ Валидаторы в layout-дереве, отдаваемом в createForm — layout не несёт правил
|
|
3609
|
-
|
|
3692
|
+
createFormFromModel({ model, schema: { username: { value: '', validators: [required()] } } });
|
|
3610
3693
|
|
|
3611
3694
|
// ✅ Валидация — отдельная схема, прогоняется раннером
|
|
3612
3695
|
const schema = defineValidationSchema<T>(({ model }) => {
|
|
@@ -3698,7 +3781,7 @@ Initial values задаются в `createModel(initial)`; `reset()` возвр
|
|
|
3698
3781
|
### Initial values в модели
|
|
3699
3782
|
|
|
3700
3783
|
```typescript
|
|
3701
|
-
import { createModel,
|
|
3784
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
3702
3785
|
import { Input, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
3703
3786
|
|
|
3704
3787
|
type ProfileForm = { username: string; language: 'ru' | 'en'; marketing: boolean };
|
|
@@ -3719,7 +3802,7 @@ const schema = {
|
|
|
3719
3802
|
},
|
|
3720
3803
|
marketing: { value: model.$.marketing, component: CheckboxWithLabel, componentProps: { label: 'Рассылка' } },
|
|
3721
3804
|
};
|
|
3722
|
-
const form =
|
|
3805
|
+
const form = createFormFromModel({ model, schema });
|
|
3723
3806
|
// model.get() === { username: '', language: 'ru', marketing: true }
|
|
3724
3807
|
// после правок: model.reset() возвращает к этим значениям
|
|
3725
3808
|
```
|
|
@@ -3806,14 +3889,14 @@ export const addressBehavior = defineFormBehavior<AddressForm>(({ model, form })
|
|
|
3806
3889
|
// ❌ model/form пересоздаются на каждый рендер → preload запускается каждый раз
|
|
3807
3890
|
function MyForm() {
|
|
3808
3891
|
const model = createModel<T>(initial); // КАЖДЫЙ рендер!
|
|
3809
|
-
const form =
|
|
3892
|
+
const form = createFormFromModel({ model, schema });
|
|
3810
3893
|
}
|
|
3811
3894
|
|
|
3812
3895
|
// ✅ Стабильные ссылки через useMemo
|
|
3813
3896
|
function MyForm() {
|
|
3814
3897
|
const { model, form } = useMemo(() => {
|
|
3815
3898
|
const m = createModel<T>(initial);
|
|
3816
|
-
return { model: m, form:
|
|
3899
|
+
return { model: m, form: createFormFromModel({ model: m, schema: buildSchema(m) }) };
|
|
3817
3900
|
}, []);
|
|
3818
3901
|
}
|
|
3819
3902
|
```
|
|
@@ -4345,7 +4428,7 @@ function createFormValidation<T>(
|
|
|
4345
4428
|
`markAsTouched` не нужен.
|
|
4346
4429
|
|
|
4347
4430
|
```tsx
|
|
4348
|
-
import { createModel,
|
|
4431
|
+
import { createModel, createFormFromModel, useFormValidation } from '@reformer/core';
|
|
4349
4432
|
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
4350
4433
|
import { required, email, minLength } from '@reformer/core/validators';
|
|
4351
4434
|
|
|
@@ -4360,7 +4443,7 @@ const registrationValidation = defineValidationSchema<RegistrationData>(({ model
|
|
|
4360
4443
|
|
|
4361
4444
|
function RegistrationForm() {
|
|
4362
4445
|
const model = useMemo(() => createModel<RegistrationData>({ username: '', email: '', password: '' }), []);
|
|
4363
|
-
const form = useMemo(() =>
|
|
4446
|
+
const form = useMemo(() => createFormFromModel({ model }), [model]);
|
|
4364
4447
|
|
|
4365
4448
|
const { submit, isValidating } = useFormValidation({
|
|
4366
4449
|
model,
|
|
@@ -4465,7 +4548,527 @@ if (typeof window !== 'undefined') ctrl.start();
|
|
|
4465
4548
|
- [31-async-validator-debounce.md](./31-async-validator-debounce.md) — async-правила и отмена устаревших прогонов
|
|
4466
4549
|
- [13-multi-step.md](./13-multi-step.md) — per-step / полная валидация wizard (`validateStep` / `validateAll`)
|
|
4467
4550
|
|
|
4468
|
-
## 82.
|
|
4551
|
+
## 82. Purpose
|
|
4552
|
+
|
|
4553
|
+
**i18n — локаль, загрузка словарей и провайдер**
|
|
4554
|
+
|
|
4555
|
+
Подпуть `@reformer/core/i18n` — общий рантайм локализации для всех пакетов `@reformer/*`: объект
|
|
4556
|
+
локали, загрузка словарей, React-провайдер, перевод и форматирование. Язык задаётся провайдером,
|
|
4557
|
+
подгружается динамически и переключается на лету; узлы формы при этом не пересоздаются.
|
|
4558
|
+
|
|
4559
|
+
Без провайдера всё работает на английском: у каждого пакета есть встроенная английская таблица.
|
|
4560
|
+
|
|
4561
|
+
> Импорт всегда из `@reformer/core/i18n`. Из главной бочки `@reformer/core` этот модуль не
|
|
4562
|
+
> реэкспортируется: React-контекст локализации обязан существовать в одном экземпляре.
|
|
4563
|
+
|
|
4564
|
+
## 83. Локаль — это данные
|
|
4565
|
+
|
|
4566
|
+
```typescript
|
|
4567
|
+
interface FormLocale {
|
|
4568
|
+
code: string; // BCP 47: 'en', 'ru', 'pt-BR' — по нему работают Intl и plural
|
|
4569
|
+
messages: Record<string, string>; // плоский словарь: ключ → сообщение ICU
|
|
4570
|
+
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
4571
|
+
dateLocale?: unknown; // необязательно: объект локали react-day-picker / date-fns
|
|
4572
|
+
dateFormat?: string; // необязательно: токены date-fns для DatePicker
|
|
4573
|
+
}
|
|
4574
|
+
```
|
|
4575
|
+
|
|
4576
|
+
Функций в локали нет, поэтому её можно положить JSON-файлом на сервер и получить по сети:
|
|
4577
|
+
|
|
4578
|
+
```json
|
|
4579
|
+
{ "code": "ru", "weekStartsOn": 1, "messages": { "profile.title": "Профиль" } }
|
|
4580
|
+
```
|
|
4581
|
+
|
|
4582
|
+
`extendLocale(base, patch)` возвращает новую локаль: словари сливаются (ключи из `patch` важнее),
|
|
4583
|
+
остальные поля заменяются.
|
|
4584
|
+
|
|
4585
|
+
## 84. Формат сообщений — подмножество ICU
|
|
4586
|
+
|
|
4587
|
+
| Конструкция | Пример |
|
|
4588
|
+
| ------------------- | ------------------------------------------------------------------------- |
|
|
4589
|
+
| подстановка | `Открыт файл {name}` |
|
|
4590
|
+
| множественные формы | `{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}` |
|
|
4591
|
+
| точное значение | `{count, plural, =0{файлов нет} other{# файлов}}` |
|
|
4592
|
+
| выбор | `{gender, select, male{он} female{она} other{оно}}` |
|
|
4593
|
+
| экранирование | `'{'` → `{`, `'}'` → `}`, `''` → `'` |
|
|
4594
|
+
|
|
4595
|
+
- Ветка `other` обязательна. `#` внутри `plural` — число, отформатированное по языку.
|
|
4596
|
+
- Типы `number`, `date`, `time`, `selectordinal` не поддерживаются: числа и даты форматируйте
|
|
4597
|
+
отдельно (`useI18n().number`, `.date`) и передавайте готовой строкой.
|
|
4598
|
+
- Категории (`one`/`few`/`many`) берутся из `Intl.PluralRules` языка локали.
|
|
4599
|
+
|
|
4600
|
+
## 85. Загрузка локалей
|
|
4601
|
+
|
|
4602
|
+
Откуда брать словарь, решает приложение: источник — функция `(code) => локаль | словарь`.
|
|
4603
|
+
|
|
4604
|
+
```typescript
|
|
4605
|
+
import { createLocaleLoader, fetchMessages } from '@reformer/core/i18n';
|
|
4606
|
+
import { loadCoreLocale } from '@reformer/core/locale';
|
|
4607
|
+
|
|
4608
|
+
export const loadLocale = createLocaleLoader([
|
|
4609
|
+
loadCoreLocale, // встроенные словари ядра — чанк нужного языка
|
|
4610
|
+
fetchMessages((code) => `/locales/${code}.json`), // ключи приложения и правки — с сервера
|
|
4611
|
+
]);
|
|
4612
|
+
```
|
|
4613
|
+
|
|
4614
|
+
- Источники опрашиваются параллельно, сливаются по порядку: поздний перекрывает ранний.
|
|
4615
|
+
- Источник отдаёт локаль целиком (`{ code, messages, … }`) или голый словарь (`{ "a.b": "…" }`);
|
|
4616
|
+
`null` означает «для этого языка у меня ничего нет» и ошибкой не считается.
|
|
4617
|
+
- Результат кэшируется по коду языка, идущий запрос не дублируется.
|
|
4618
|
+
- Отказ любого источника — отказ загрузки. Источник, которому можно промахнуться, оборачивайте:
|
|
4619
|
+
`(code) => source(code).catch(() => null)`.
|
|
4620
|
+
- `loadLocale.peek(code)` — уже загруженная локаль или `undefined`; `loadLocale.preload(code)` — то
|
|
4621
|
+
же, что вызов загрузчика.
|
|
4622
|
+
|
|
4623
|
+
Ядро отдаёт свои локали в трёх видах:
|
|
4624
|
+
|
|
4625
|
+
| Вид | Импорт | Для чего |
|
|
4626
|
+
| ----------------- | ------------------------------------------- | ----------------------------------------------- |
|
|
4627
|
+
| загрузчик | `loadCoreLocale` из `@reformer/core/locale` | основной путь: чанк языка по запросу |
|
|
4628
|
+
| JSON-файл | `@reformer/core/locale/ru.json` | выложить на свой сервер, править без пересборки |
|
|
4629
|
+
| синхронный модуль | `ru` из `@reformer/core/locale/ru` | SSR и тесты |
|
|
4630
|
+
|
|
4631
|
+
Встроенные языки ядра — `en` и `ru`. Словарь ядра несёт тексты ошибок валидации
|
|
4632
|
+
(`validation.<code>`) и единицы размера файла (`format.fileSize.b|kb|mb|gb|tb`).
|
|
4633
|
+
|
|
4634
|
+
Пакеты выше отдают локаль накопительно и в тех же трёх видах, поэтому источник встроенных словарей
|
|
4635
|
+
в загрузчике всегда один — самого верхнего из используемых пакетов:
|
|
4636
|
+
|
|
4637
|
+
| Пакеты приложения | Источник | Что в локали |
|
|
4638
|
+
| ------------------------------- | -------------------------------------------- | --------------------------------- |
|
|
4639
|
+
| только ядро | `loadCoreLocale` из `@reformer/core/locale` | ошибки валидации, размер файла |
|
|
4640
|
+
| ядро + `@reformer/cdk` | `loadCdkLocale` из `@reformer/cdk/locale` | то же плюс строки cdk (`cdk.*`) |
|
|
4641
|
+
| ядро + cdk + `@reformer/ui-kit` | `loadKitLocale` из `@reformer/ui-kit/locale` | то же плюс подписи кита (`kit.*`) |
|
|
4642
|
+
|
|
4643
|
+
## 86. Провайдер
|
|
4644
|
+
|
|
4645
|
+
```tsx
|
|
4646
|
+
import { I18nProvider } from '@reformer/core/i18n';
|
|
4647
|
+
|
|
4648
|
+
function App() {
|
|
4649
|
+
const [lang, setLang] = useState('ru');
|
|
4650
|
+
return (
|
|
4651
|
+
<I18nProvider lang={lang} load={loadLocale}>
|
|
4652
|
+
<LangSwitch value={lang} onChange={setLang} />
|
|
4653
|
+
<CreditForm />
|
|
4654
|
+
</I18nProvider>
|
|
4655
|
+
);
|
|
4656
|
+
}
|
|
4657
|
+
```
|
|
4658
|
+
|
|
4659
|
+
| Проп | Назначение |
|
|
4660
|
+
| --------------- | ------------------------------------------------------------------------ |
|
|
4661
|
+
| `lang` + `load` | провайдер грузит локаль сам |
|
|
4662
|
+
| `fallback` | что показать, пока не загружена самая первая локаль; по умолчанию ничего |
|
|
4663
|
+
| `onError` | отказ загрузки `(error, lang)` |
|
|
4664
|
+
| `locale` | готовый объект вместо `lang` + `load` — SSR, тесты |
|
|
4665
|
+
|
|
4666
|
+
Поведение при `lang` + `load`:
|
|
4667
|
+
|
|
4668
|
+
- при смене `lang` на экране остаётся прежний язык, пока новый не загрузится;
|
|
4669
|
+
`useI18n().pending` — для индикатора;
|
|
4670
|
+
- при быстром переключении применяется только последний запрос;
|
|
4671
|
+
- ошибка загрузки язык не меняет и попадает в `useI18n().error` и `onError`;
|
|
4672
|
+
- если локаль уже в кэше (`await loadLocale.preload(lang)` до монтирования), первый рендер
|
|
4673
|
+
синхронный и `fallback` не показывается.
|
|
4674
|
+
|
|
4675
|
+
SSR: эффекты на сервере не выполняются, поэтому там передавайте готовый объект —
|
|
4676
|
+
`<I18nProvider locale={ru}>` — либо заранее вызовите `preload`.
|
|
4677
|
+
|
|
4678
|
+
Вложенный провайдер заменяет локаль для своего поддерева целиком.
|
|
4679
|
+
|
|
4680
|
+
## 87. Хуки
|
|
4681
|
+
|
|
4682
|
+
```typescript
|
|
4683
|
+
const { code, locale, pending, error, t, has, number, date, fileSize } = useI18n();
|
|
4684
|
+
|
|
4685
|
+
t('profile.title'); // ключ приложения
|
|
4686
|
+
t('cart.items', { count: 3 }); // с подстановками
|
|
4687
|
+
t('cart.empty', undefined, 'Your cart is empty'); // английский текст на случай промаха
|
|
4688
|
+
number(1234.5); // '1 234,5' в ru
|
|
4689
|
+
date(new Date(), { dateStyle: 'long' }); // '15 января 2024 г.' в ru
|
|
4690
|
+
fileSize(1536); // '1,5 КБ' в ru, '1.5 KB' без провайдера
|
|
4691
|
+
```
|
|
4692
|
+
|
|
4693
|
+
`useMessages(builtinEn)` — для авторов пакетов и китов: ищет ключ в локали провайдера, а при
|
|
4694
|
+
промахе берёт встроенную английскую таблицу пакета. Тип ключа выводится из таблицы.
|
|
4695
|
+
|
|
4696
|
+
```tsx
|
|
4697
|
+
import en from './locale/en.json';
|
|
4698
|
+
|
|
4699
|
+
function ClearButton() {
|
|
4700
|
+
const t = useMessages(en);
|
|
4701
|
+
return <button aria-label={t('kit.select.clear')} />;
|
|
4702
|
+
}
|
|
4703
|
+
```
|
|
4704
|
+
|
|
4705
|
+
## 88. Тексты ошибок валидации
|
|
4706
|
+
|
|
4707
|
+
Правила из `@reformer/core/validators` несут только `code` и `params`; готового текста у них нет
|
|
4708
|
+
(`message` по умолчанию — пустая строка). Строку даёт резолвер при отображении ошибки:
|
|
4709
|
+
|
|
4710
|
+
1. `messageKey` автора правила — если такой ключ есть в активной локали;
|
|
4711
|
+
2. явное `message` автора правила;
|
|
4712
|
+
3. `validation.<code>` из словаря локали;
|
|
4713
|
+
4. `validation.<code>` из встроенной английской таблицы ядра;
|
|
4714
|
+
5. сам код — для своих кодов, которых нет ни в одном словаре.
|
|
4715
|
+
|
|
4716
|
+
```typescript
|
|
4717
|
+
validate(model.$.email, [
|
|
4718
|
+
required(), // «Обязательное поле» в ru, английская фраза без провайдера
|
|
4719
|
+
minLength(5, { message: 'Слишком коротко' }), // явный текст: важнее словаря, но языком не переключается
|
|
4720
|
+
email({ messageKey: 'profile.email.invalid' }), // ключ словаря приложения: переключается вместе с языком
|
|
4721
|
+
]);
|
|
4722
|
+
```
|
|
4723
|
+
|
|
4724
|
+
- Текст получается при отображении, а не при проверке: смена языка переводит уже показанные
|
|
4725
|
+
ошибки без повторной валидации.
|
|
4726
|
+
- `FormField` (`@reformer/cdk`, `@reformer/ui-kit`) резолвит ошибку сам. В своём компоненте —
|
|
4727
|
+
`useValidationMessage()`; вне React — `resolveValidationError(error, createI18n(locale))`.
|
|
4728
|
+
- Свой код ошибки локализуется ключом `validation.<свой_код>` в словаре приложения либо
|
|
4729
|
+
`messageKey` в самом правиле.
|
|
4730
|
+
- Встроенный текст меняется тем же ключом в словаре приложения:
|
|
4731
|
+
`extendLocale(ru, { messages: { 'validation.required': 'Заполните поле' } })`.
|
|
4732
|
+
- Параметры готовятся до подстановки: `Date` — по языку (`15 янв. 2030 г.`), массив — перечнем,
|
|
4733
|
+
размеры в байтах (`maxFileSize`, `minFileSize`, `actualSize`, `maxTotalFileSize`, `actualTotal`) —
|
|
4734
|
+
строкой с единицей (`5 МБ`). Числа остаются числами, поэтому в сообщении работает `plural`.
|
|
4735
|
+
|
|
4736
|
+
```tsx
|
|
4737
|
+
function FieldErrors({ control }: { control: FieldNode<string> }) {
|
|
4738
|
+
const message = useValidationMessage();
|
|
4739
|
+
const { errors, shouldShowError } = useFormControl(control);
|
|
4740
|
+
return shouldShowError ? <span role="alert">{message(errors[0])}</span> : null;
|
|
4741
|
+
}
|
|
4742
|
+
```
|
|
4743
|
+
|
|
4744
|
+
Смонтированный `ValidationMessagesProvider` из `@reformer/cdk` заменяет этот резолвер целиком:
|
|
4745
|
+
локаль для ошибок в его поддереве не участвует.
|
|
4746
|
+
|
|
4747
|
+
## 89. Что показывается, когда ключа нет
|
|
4748
|
+
|
|
4749
|
+
| Ключ | Нет в локали | Сообщение не разбирается |
|
|
4750
|
+
| -------------------------- | --------------------------------------------------------------- | ----------------------------------------------------- |
|
|
4751
|
+
| пакета (`useMessages`) | встроенный английский пакета | встроенный английский, `console.error` вне production |
|
|
4752
|
+
| приложения (`useI18n().t`) | `defaultMessage`, затем сам ключ; `console.warn` вне production | то же, `console.error` |
|
|
4753
|
+
|
|
4754
|
+
Пропущенная подстановка выводится как `{name}`. Форма не падает ни в одном из случаев: неполный
|
|
4755
|
+
словарь, пришедший по сети, — законный сценарий.
|
|
4756
|
+
|
|
4757
|
+
## 90. Проверка словаря в тестах
|
|
4758
|
+
|
|
4759
|
+
```typescript
|
|
4760
|
+
import { validateLocale } from '@reformer/core/i18n';
|
|
4761
|
+
|
|
4762
|
+
expect(validateLocale(ruJson, enJson)).toEqual([]);
|
|
4763
|
+
// находит: syntax (не разбирается), missing / extra (ключи), arguments (потерянная подстановка)
|
|
4764
|
+
```
|
|
4765
|
+
|
|
4766
|
+
## 91. Описатели сообщений (низкий уровень)
|
|
4767
|
+
|
|
4768
|
+
`msg(key, values?, defaultMessage?)` создаёт описатель сообщения, `defineMessages(appEn)` — `msg` с
|
|
4769
|
+
типизированными ключами, `resolveLocalized(value, i18n)` раскрывает описатели в объекте пропсов.
|
|
4770
|
+
|
|
4771
|
+
> ⚠️ Рендереры и `FormField` описатели сами пока НЕ раскрывают. Не кладите `msg(...)` в
|
|
4772
|
+
> `componentProps` схемы: компонент получит объект вместо строки. До появления раскрытия в
|
|
4773
|
+
> рендерерах вызывайте `resolveLocalized` вручную или используйте `useI18n().t`.
|
|
4774
|
+
|
|
4775
|
+
## 92. Anti-patterns
|
|
4776
|
+
|
|
4777
|
+
```typescript
|
|
4778
|
+
// ❌ Импорт из главной бочки — i18n там нет, а второй путь импорта дал бы вторую копию контекста
|
|
4779
|
+
import { I18nProvider } from '@reformer/core';
|
|
4780
|
+
|
|
4781
|
+
// ✅ Единственный путь импорта — подпуть i18n
|
|
4782
|
+
import { I18nProvider } from '@reformer/core/i18n';
|
|
4783
|
+
```
|
|
4784
|
+
|
|
4785
|
+
```tsx
|
|
4786
|
+
// ❌ Новый загрузчик на каждый рендер: кэш теряется, локаль грузится заново, первый кадр — fallback
|
|
4787
|
+
<I18nProvider lang={lang} load={createLocaleLoader([loadCoreLocale])}>
|
|
4788
|
+
|
|
4789
|
+
// ✅ Загрузчик — константа модуля: один кэш на приложение
|
|
4790
|
+
const loadLocale = createLocaleLoader([loadCoreLocale]);
|
|
4791
|
+
<I18nProvider lang={lang} load={loadLocale}>
|
|
4792
|
+
```
|
|
4793
|
+
|
|
4794
|
+
```typescript
|
|
4795
|
+
// ❌ Число или дата типами ICU внутри сообщения — не поддерживается, сообщение не разберётся
|
|
4796
|
+
const message = 'Итого: {total, number}';
|
|
4797
|
+
|
|
4798
|
+
// ✅ Отформатировать отдельно и передать готовой строкой
|
|
4799
|
+
t('cart.total', { total: number(total, { style: 'currency', currency: 'RUB' }) });
|
|
4800
|
+
```
|
|
4801
|
+
|
|
4802
|
+
```tsx
|
|
4803
|
+
// ❌ Ошибка выводится полем message: у правил без своего текста оно пустое — на экране ничего
|
|
4804
|
+
<span>{errors[0].message}</span>;
|
|
4805
|
+
|
|
4806
|
+
// ✅ Текст ошибки — через резолвер: он знает словарь локали, messageKey и явное сообщение
|
|
4807
|
+
const message = useValidationMessage();
|
|
4808
|
+
<span>{message(errors[0])}</span>;
|
|
4809
|
+
```
|
|
4810
|
+
|
|
4811
|
+
```typescript
|
|
4812
|
+
// ❌ Настройки локали без словаря: объект без поля messages принимается за словарь сообщений
|
|
4813
|
+
const source = () => ({ weekStartsOn: 1 });
|
|
4814
|
+
|
|
4815
|
+
// ✅ Часть локали узнаётся по полю messages — отдавайте его хотя бы пустым
|
|
4816
|
+
const source = () => ({ weekStartsOn: 1, messages: {} });
|
|
4817
|
+
```
|
|
4818
|
+
|
|
4819
|
+
## 93. See also
|
|
4820
|
+
|
|
4821
|
+
- [28-submit-and-reset.md](./28-submit-and-reset.md) — жизненный цикл формы; смена языка его не затрагивает
|
|
4822
|
+
- [33-validation-strategy.md](./33-validation-strategy.md) — когда запускается валидация
|
|
4823
|
+
|
|
4824
|
+
## 94. Purpose
|
|
4825
|
+
|
|
4826
|
+
**Единый контракт формы — одна схема, одна сборка, одно поведение**
|
|
4827
|
+
|
|
4828
|
+
Форма описывается одинаково для всех способов реализации — разметка в JSX, рендерер по TS-схеме,
|
|
4829
|
+
рендерер по JSON. Схема одна (дерево узлов), привязка одна (ручка `model.$.…`), сборка одна
|
|
4830
|
+
(`createForm`), поведение одно (`defineFormBehavior`). Способы отличаются только видом схемы
|
|
4831
|
+
(билдер или документ) и тем, кто рисует результат.
|
|
4832
|
+
|
|
4833
|
+
Прежние записи — ключи `value` / `array`, оператор `each`, `createCoreForm` — пока принимаются.
|
|
4834
|
+
Новый код пишется так, как описано здесь.
|
|
4835
|
+
|
|
4836
|
+
| Слой | поле | массив под-форм | подформа |
|
|
4837
|
+
| --------- | --------------------------------- | ----------------------------------------- | ------------------------------------- |
|
|
4838
|
+
| схема | `{ model: model.$.x, component }` | `{ model: model.$.items, item }` | `{ model: model.$.group, part }` |
|
|
4839
|
+
| валидация | `validate(model.$.x, rules)` | `applyEach(model.$.items, itemRules)` | `apply(model.$.group, groupRules)` |
|
|
4840
|
+
| поведение | `compute(model.$.x, …)` | `applyEach(model.$.items, itemBehavior)` | `apply(model.$.group, groupBehavior)` |
|
|
4841
|
+
|
|
4842
|
+
Правило привязки: `model.$.…` — привязка (схема, правило, оператор). Без `$` — чтение значения
|
|
4843
|
+
(`model.loanType === 'mortgage'`), мутация (`model.items.push()`) и под-модель как область.
|
|
4844
|
+
|
|
4845
|
+
## 95. Модель — начальные значения и шаблоны элементов
|
|
4846
|
+
|
|
4847
|
+
```typescript
|
|
4848
|
+
import { arrayOf, createModel } from '@reformer/core';
|
|
4849
|
+
|
|
4850
|
+
const blankPhone = (): Phone => ({ number: '' });
|
|
4851
|
+
const blankCoBorrower = (): CoBorrower => ({ name: '', phones: arrayOf(blankPhone) });
|
|
4852
|
+
|
|
4853
|
+
export const createCreditModel = () =>
|
|
4854
|
+
createModel<CreditForm>({
|
|
4855
|
+
loanType: 'consumer',
|
|
4856
|
+
registrationAddress: { city: '', street: '' },
|
|
4857
|
+
coBorrowers: arrayOf(blankCoBorrower), // пустой массив + шаблон нового элемента
|
|
4858
|
+
tags: [], // массив как одно значение поля — шаблон не нужен
|
|
4859
|
+
});
|
|
4860
|
+
```
|
|
4861
|
+
|
|
4862
|
+
`arrayOf(blank, items?)` возвращает обычный массив `U[]` и запоминает фабрику нового элемента.
|
|
4863
|
+
После этого `push()` и `insertAt(i)` без значения кладут элемент по шаблону — в модели, в ноде
|
|
4864
|
+
массива формы и в кнопке «Добавить» секции массива.
|
|
4865
|
+
|
|
4866
|
+
- Шаблон переживает `set` / `patch` / `reset` модели.
|
|
4867
|
+
- Вложенный массив объявляется внутри шаблона элемента; элементы, пришедшие обычными данными
|
|
4868
|
+
(загрузка с сервера), получают его шаблон из шаблона родителя.
|
|
4869
|
+
- У массива без шаблона `push()` без значения бросает ошибку с подсказкой.
|
|
4870
|
+
- Шаблон привязан к самому массиву: до `createModel` его нельзя копировать. `structuredClone`,
|
|
4871
|
+
`JSON.parse(JSON.stringify(…))` и `[...array]` отдают массив без шаблона. Начальные значения с
|
|
4872
|
+
`arrayOf` держат в фабрике, а не в константе, которую клонируют.
|
|
4873
|
+
|
|
4874
|
+
## 96. Схема — одно дерево
|
|
4875
|
+
|
|
4876
|
+
```typescript
|
|
4877
|
+
import type { FormModel } from '@reformer/core';
|
|
4878
|
+
|
|
4879
|
+
// подформа: объявлена один раз, получает под-модель
|
|
4880
|
+
const address = (model: FormModel<Address>) => ({
|
|
4881
|
+
component: Box,
|
|
4882
|
+
children: [
|
|
4883
|
+
{ model: model.$.city, component: Input, componentProps: { label: 'Город' } },
|
|
4884
|
+
{ model: model.$.street, component: Input, componentProps: { label: 'Улица' } },
|
|
4885
|
+
],
|
|
4886
|
+
});
|
|
4887
|
+
|
|
4888
|
+
// строка массива — такая же функция от под-модели
|
|
4889
|
+
const coBorrower = (model: FormModel<CoBorrower>) => ({
|
|
4890
|
+
selector: 'co-borrower',
|
|
4891
|
+
component: Box,
|
|
4892
|
+
children: [{ model: model.$.name, component: Input, componentProps: { label: 'ФИО' } }],
|
|
4893
|
+
});
|
|
4894
|
+
|
|
4895
|
+
export const creditSchema = (model: FormModel<CreditForm>) => ({
|
|
4896
|
+
component: Box,
|
|
4897
|
+
children: [
|
|
4898
|
+
{ model: model.$.loanType, component: SelectAsync, componentProps: { label: 'Тип' } }, // поле
|
|
4899
|
+
{ model: model.$.tags, component: SelectMulti }, // массив как одно значение
|
|
4900
|
+
{
|
|
4901
|
+
selector: 'registration',
|
|
4902
|
+
component: Section,
|
|
4903
|
+
componentProps: { title: 'Адрес регистрации' },
|
|
4904
|
+
children: [{ model: model.$.registrationAddress, part: address }], // подформа
|
|
4905
|
+
},
|
|
4906
|
+
{ model: model.$.coBorrowers, component: FormArray, item: coBorrower }, // массив под-форм
|
|
4907
|
+
],
|
|
4908
|
+
});
|
|
4909
|
+
```
|
|
4910
|
+
|
|
4911
|
+
Чем узел является, решают ручка и соседние ключи:
|
|
4912
|
+
|
|
4913
|
+
| Узел | Как отличить |
|
|
4914
|
+
| --------------- | --------------------------------------------- |
|
|
4915
|
+
| поле | `model` — лист или массив; нет `item`, `part` |
|
|
4916
|
+
| массив под-форм | `model` — массив, `item` — функция |
|
|
4917
|
+
| подформа | `model` — группа, `part` — функция |
|
|
4918
|
+
| контейнер | есть `children`, нет `model` |
|
|
4919
|
+
|
|
4920
|
+
- Узел узнаётся по ЗНАЧЕНИЮ: в записи «имя поля → узел» поле данных может называться `model`,
|
|
4921
|
+
`item`, `part` или `value` — под таким ключом лежит обычный вложенный узел.
|
|
4922
|
+
- Поля подформы принадлежат той же форме: `form.registrationAddress.city`.
|
|
4923
|
+
- Билдер `item` / `part` вызывается один раз на под-модель; сборка и рендерер получают одно и то
|
|
4924
|
+
же поддерево (`schemaSubtree(builder, subModel)`).
|
|
4925
|
+
- `initialValue` узла-массива — запасной шаблон нового элемента для форм, чья модель создаётся из
|
|
4926
|
+
данных без кода. Шаблон модели (`arrayOf`) главнее.
|
|
4927
|
+
|
|
4928
|
+
## 97. Валидация — подформа и массив подключаются привязкой
|
|
4929
|
+
|
|
4930
|
+
```typescript
|
|
4931
|
+
import {
|
|
4932
|
+
apply,
|
|
4933
|
+
applyEach,
|
|
4934
|
+
cross,
|
|
4935
|
+
defineValidationSchema,
|
|
4936
|
+
validate,
|
|
4937
|
+
validateWhen,
|
|
4938
|
+
} from '@reformer/core/validation';
|
|
4939
|
+
|
|
4940
|
+
const addressRules = defineValidationSchema<Address>(({ model }) => {
|
|
4941
|
+
validate(model.$.city, [required()]);
|
|
4942
|
+
cross<Address>(model.$.street, (address) =>
|
|
4943
|
+
address.city !== '' && address.street === '' ? streetRequired : null
|
|
4944
|
+
);
|
|
4945
|
+
});
|
|
4946
|
+
|
|
4947
|
+
const coBorrowerRules = defineValidationSchema<CoBorrower>(({ model }) => {
|
|
4948
|
+
validate(model.$.name, [required()]);
|
|
4949
|
+
});
|
|
4950
|
+
|
|
4951
|
+
export const contactsRules = defineValidationSchema<CreditForm>(({ model }) => {
|
|
4952
|
+
apply(model.$.registrationAddress, addressRules);
|
|
4953
|
+
validateWhen(
|
|
4954
|
+
() => !model.sameAsRegistration,
|
|
4955
|
+
() => apply(model.$.residenceAddress, addressRules)
|
|
4956
|
+
);
|
|
4957
|
+
applyEach(model.$.coBorrowers, coBorrowerRules);
|
|
4958
|
+
});
|
|
4959
|
+
```
|
|
4960
|
+
|
|
4961
|
+
- У схемы, подключённой через `apply(ручка, схема)` / `applyEach`, своя область: `model` — под-модель,
|
|
4962
|
+
`cross` получает её снапшот. Захватывать снапшот в замыкание больше не нужно.
|
|
4963
|
+
- Одна и та же схема подключается и к группе, и к элементам массива.
|
|
4964
|
+
- `apply(model.$.a, rules)` принимает и массив ручек: `apply([model.$.a, model.$.b], rules)`.
|
|
4965
|
+
- `apply(schemaA, schemaB)` — композиция схем над той же моделью — остаётся.
|
|
4966
|
+
- Правило получает `(value, scope, root)`: модель области и корень прогона.
|
|
4967
|
+
- `each(model.items, (item) => …)` — прежняя запись `applyEach`: принимает фасад и ручку, области
|
|
4968
|
+
не создаёт.
|
|
4969
|
+
|
|
4970
|
+
## 98. Поведение — модель, форма и схема
|
|
4971
|
+
|
|
4972
|
+
```typescript
|
|
4973
|
+
import {
|
|
4974
|
+
apply,
|
|
4975
|
+
applyEach,
|
|
4976
|
+
defineFormBehavior,
|
|
4977
|
+
enableWhen,
|
|
4978
|
+
hideWhen,
|
|
4979
|
+
onComponentEvent,
|
|
4980
|
+
onMount,
|
|
4981
|
+
} from '@reformer/core/behaviors';
|
|
4982
|
+
|
|
4983
|
+
const addressBehavior = defineFormBehavior<Address>(({ model, schema }) => {
|
|
4984
|
+
hideWhen(schema.node('street'), () => model.city === ''); // узел ищется внутри части
|
|
4985
|
+
});
|
|
4986
|
+
|
|
4987
|
+
export const creditBehavior = defineFormBehavior<CreditForm>(({ model, form, schema }) => {
|
|
4988
|
+
const isMortgage = () => model.loanType === 'mortgage';
|
|
4989
|
+
|
|
4990
|
+
enableWhen(model.$.propertyValue, isMortgage, { resetOnDisable: true });
|
|
4991
|
+
hideWhen(schema.node('mortgage'), () => !isMortgage());
|
|
4992
|
+
onComponentEvent(schema.node('wizard'), 'onSubmit', () => submitApplication(model.get()));
|
|
4993
|
+
onMount(schema.node('data-boundary'), () => void loadApplication(model));
|
|
4994
|
+
|
|
4995
|
+
apply([model.$.registrationAddress, model.$.residenceAddress], addressBehavior);
|
|
4996
|
+
applyEach(model.$.coBorrowers, coBorrowerBehavior);
|
|
4997
|
+
});
|
|
4998
|
+
```
|
|
4999
|
+
|
|
5000
|
+
Операторы узлов схемы:
|
|
5001
|
+
|
|
5002
|
+
| Оператор | Что записывает |
|
|
5003
|
+
| ----------------------------------------- | ---------------------------------------------------------- |
|
|
5004
|
+
| `hideWhen(node, condition)` | узел скрыт, пока условие истинно |
|
|
5005
|
+
| `onComponentEvent(node, event, handler)` | обработчик пропа-события компонента |
|
|
5006
|
+
| `onMount(node, fn)` / `onUnmount(node, fn)` | хуки монтирования узла |
|
|
5007
|
+
| `onInit(node, fn)` | `fn` вызывается сразу, до первого рендера |
|
|
5008
|
+
| `renderEffect(schema, fn)` | реактивный эффект, пока схема области смонтирована |
|
|
5009
|
+
| `schema.node(sel).setHidden / patchProps` | принудительная видимость и пропсы узла |
|
|
5010
|
+
| `schema.node(sel).getRef()` | ref на компонент узла — объект `{ current }` |
|
|
5011
|
+
|
|
5012
|
+
- **Области изолированы.** `schema.node(selector)` ищет узел только в своей области: корневое
|
|
5013
|
+
поведение — в корневом дереве, поведение подформы и строки массива — в своём поддереве. До узла
|
|
5014
|
+
строки или части корень дотягивается через `applyEach` / `apply`. Два монтирования одной части
|
|
5015
|
+
не конфликтуют.
|
|
5016
|
+
- **Исключение — ref по пути модели**: `schema.node('phones.0.number').getRef()` ищется от корня.
|
|
5017
|
+
- **Операторы узлов только записывают правило — исполняет его рендерер.** При разметке в JSX они
|
|
5018
|
+
ничего не делают: видимость, отправка и загрузка данных остаются в JSX.
|
|
5019
|
+
- Правило на селектор, которого нет в дереве, ничего не делает; в dev — предупреждение.
|
|
5020
|
+
|
|
5021
|
+
## 99. Сборка — `createForm`
|
|
5022
|
+
|
|
5023
|
+
```tsx
|
|
5024
|
+
import { createForm, useFormBundle } from '@reformer/core';
|
|
5025
|
+
|
|
5026
|
+
const credit = useFormBundle(() =>
|
|
5027
|
+
createForm<CreditForm>({
|
|
5028
|
+
model: createCreditModel(), // либо initial: { … }
|
|
5029
|
+
schema: creditSchema, // (model) => узел; для JSON — документ + registry
|
|
5030
|
+
behavior: creditBehavior,
|
|
5031
|
+
validation: { steps: { loan: loanRules, contacts: contactsRules }, extras: crossRules },
|
|
5032
|
+
})
|
|
5033
|
+
);
|
|
5034
|
+
// credit = { model, form, validation, render }
|
|
5035
|
+
|
|
5036
|
+
<FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />; // рендерер
|
|
5037
|
+
<FormField control={credit.form.loanType} />; // разметка в JSX — та же сборка
|
|
5038
|
+
```
|
|
5039
|
+
|
|
5040
|
+
- Порядок: модель → `seed` → дерево схемы → ноды формы → поведение → валидация → `setup`. Дерево
|
|
5041
|
+
строится один раз.
|
|
5042
|
+
- `validation` в бандле типизирована по конфигу: правила переданы — поле есть всегда.
|
|
5043
|
+
- `render` — `{ tree, controller, node(selector) }`: готовое дерево и схема-контроллер сборки.
|
|
5044
|
+
- JSON: `createForm({ model, schema: document, registry })` — дерево собирает
|
|
5045
|
+
`registry.resolveSchema`. Ядро документ не разбирает.
|
|
5046
|
+
- Внутри рендерера бандл доступен компонентам через `useFormBundleContext()` — так визард берёт
|
|
5047
|
+
форму и валидацию сам.
|
|
5048
|
+
|
|
5049
|
+
### Низкоуровневые фабрики
|
|
5050
|
+
|
|
5051
|
+
| Задача | Фабрика |
|
|
5052
|
+
| ---------------------------------------------- | ---------------------------------------- |
|
|
5053
|
+
| форма из модели и готового дерева, без бандла | `createFormFromModel({ model, schema })` |
|
|
5054
|
+
| форма без модели — плоская схема полей | `createLegacyForm(schema)` |
|
|
5055
|
+
|
|
5056
|
+
`createForm` с готовым деревом вместо билдера или без модели бросает ошибку с отсылкой к нужной
|
|
5057
|
+
фабрике.
|
|
5058
|
+
|
|
5059
|
+
## 100. Частые ошибки
|
|
5060
|
+
|
|
5061
|
+
| Ошибка | Как правильно |
|
|
5062
|
+
| -------------------------------------------------- | ------------------------------------------------------- |
|
|
5063
|
+
| `model: model.address` (под-модель у поля) | `model: model.$.address.city` — привязка всегда с `$` |
|
|
5064
|
+
| `{ model: model.$.address, component }` без `part` | группа полем не бывает — нужен `part` |
|
|
5065
|
+
| `createForm({ model, schema: дерево })` | `schema` — билдер `(model) => узел` |
|
|
5066
|
+
| `hideWhen(schema.node('row-узел'), …)` из корня | внутри `applyEach(model.$.items, itemBehavior)` |
|
|
5067
|
+
| `push({})` для новой строки | `arrayOf(blank)` в модели и `push()` без значения |
|
|
5068
|
+
| `createModel(structuredClone(INITIAL))` с `arrayOf` | фабрика начальных значений — клон теряет шаблон массива |
|
|
5069
|
+
| `cross` в подформе читает корень | `apply(model.$.group, rules)` — область подформы |
|
|
5070
|
+
|
|
5071
|
+
## 101. API Reference
|
|
4469
5072
|
|
|
4470
5073
|
_Auto-generated from JSDoc on public exports._
|
|
4471
5074
|
|
|
@@ -4515,14 +5118,22 @@ _Source: src/form/types/index.ts_
|
|
|
4515
5118
|
|
|
4516
5119
|
**Kind:** `function`
|
|
4517
5120
|
|
|
4518
|
-
|
|
5121
|
+
Применить под-схему к одному или нескольким полям-группам (переиспользование).
|
|
5122
|
+
|
|
5123
|
+
Под-схема получает scope группы: `model` — настоящая под-модель (`model.$.field`, чтение и
|
|
5124
|
+
запись значений, `get`/`set`/`patch`), `form` — нода группы. Цель — ручка группы
|
|
5125
|
+
(`model.$.address`); работает и в корневой схеме, и в схеме строки массива.
|
|
4519
5126
|
|
|
4520
5127
|
**Signature:**
|
|
4521
5128
|
```typescript
|
|
4522
|
-
export function apply<
|
|
5129
|
+
export function apply<TField>(targets: object | object[], subSchema: FormBehavior<TField>): void
|
|
4523
5130
|
```
|
|
4524
5131
|
|
|
4525
|
-
|
|
5132
|
+
**Examples:**
|
|
5133
|
+
|
|
5134
|
+
apply([model.$.registrationAddress, model.$.residenceAddress], addressBehavior);
|
|
5135
|
+
|
|
5136
|
+
_Source: src/form/behaviors/collections.ts_
|
|
4526
5137
|
|
|
4527
5138
|
### applyEach
|
|
4528
5139
|
|
|
@@ -4538,6 +5149,9 @@ _Source: src/form/validation/operators.ts_
|
|
|
4538
5149
|
рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
|
|
4539
5150
|
к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
|
|
4540
5151
|
|
|
5152
|
+
Работает на любой глубине: массив в корне, в группе и в строке другого массива — в схеме строки
|
|
5153
|
+
можно снова вызвать `applyEach`.
|
|
5154
|
+
|
|
4541
5155
|
**Signature:**
|
|
4542
5156
|
```typescript
|
|
4543
5157
|
export function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void
|
|
@@ -4548,6 +5162,7 @@ export function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>)
|
|
|
4548
5162
|
applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
|
|
4549
5163
|
compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
|
|
4550
5164
|
enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
|
|
5165
|
+
applyEach(row.$.phones, phoneBehavior); // массив внутри строки
|
|
4551
5166
|
}));
|
|
4552
5167
|
|
|
4553
5168
|
_Source: src/form/behaviors/collections.ts_
|
|
@@ -4764,6 +5379,52 @@ export interface ArrayNodeLike {
|
|
|
4764
5379
|
|
|
4765
5380
|
_Source: src/form/types/index.ts_
|
|
4766
5381
|
|
|
5382
|
+
### arrayOf
|
|
5383
|
+
|
|
5384
|
+
**Kind:** `function`
|
|
5385
|
+
|
|
5386
|
+
Массив модели с шаблоном нового элемента.
|
|
5387
|
+
|
|
5388
|
+
Шаблон — фабрика значения, которое получает `push()` / `insertAt(i)` без аргумента: кнопка
|
|
5389
|
+
«Добавить» секции массива, `model.items.push()` в поведении. Объявляется там же, где остальные
|
|
5390
|
+
начальные значения, — в модели; схеме и JSX знать его не нужно.
|
|
5391
|
+
|
|
5392
|
+
Возвращает обычный массив: тип поля остаётся `U[]`. Шаблон переживает `set` / `patch` / `reset`
|
|
5393
|
+
модели. Вложенные массивы объявляются внутри шаблона элемента — тогда и элементы, пришедшие
|
|
5394
|
+
обычными данными (загрузка с сервера), получают их шаблоны.
|
|
5395
|
+
|
|
5396
|
+
Шаблон привязан к самому массиву, поэтому до `createModel` его нельзя копировать:
|
|
5397
|
+
`structuredClone`, `JSON.parse(JSON.stringify(…))` и `[...array]` отдают массив без шаблона, и
|
|
5398
|
+
`push()` без значения бросит ошибку. Начальные значения с `arrayOf` держат в фабрике
|
|
5399
|
+
(`const initial = () => ({ items: arrayOf(blank) })`), а не в константе, которую клонируют.
|
|
5400
|
+
|
|
5401
|
+
**Signature:**
|
|
5402
|
+
```typescript
|
|
5403
|
+
export function arrayOf<U>(blank: () => U, items: readonly U[] = []): U[]
|
|
5404
|
+
```
|
|
5405
|
+
|
|
5406
|
+
**Parameters:**
|
|
5407
|
+
- `blank` — - Фабрика нового элемента. Вызывается на каждое добавление — значение не делится
|
|
5408
|
+
между элементами.
|
|
5409
|
+
- `items` — - Начальные элементы. По умолчанию массив пуст.
|
|
5410
|
+
|
|
5411
|
+
**Returns:** Массив начальных значений с шаблоном.
|
|
5412
|
+
|
|
5413
|
+
**Examples:**
|
|
5414
|
+
|
|
5415
|
+
```typescript
|
|
5416
|
+
const blankPhone = () => ({ number: '' });
|
|
5417
|
+
const blankCoBorrower = () => ({ name: '', phones: arrayOf(blankPhone) });
|
|
5418
|
+
|
|
5419
|
+
const model = createModel({ coBorrowers: arrayOf(blankCoBorrower) });
|
|
5420
|
+
|
|
5421
|
+
model.coBorrowers.push(); // { name: '', phones: [] }
|
|
5422
|
+
model.coBorrowers.at(0).phones.push(); // { number: '' }
|
|
5423
|
+
model.coBorrowers.push({ name: 'Анна', phones: [] }); // значение целиком — как раньше
|
|
5424
|
+
```
|
|
5425
|
+
|
|
5426
|
+
_Source: src/model/model-nodes.ts_
|
|
5427
|
+
|
|
4767
5428
|
### AsyncRule
|
|
4768
5429
|
|
|
4769
5430
|
**Kind:** `type`
|
|
@@ -4857,13 +5518,21 @@ _Source: src/model/behaviors-value.ts_
|
|
|
4857
5518
|
|
|
4858
5519
|
**Kind:** `type`
|
|
4859
5520
|
|
|
4860
|
-
|
|
5521
|
+
Область схемы поведения: модель (значения и сигналы), форма (ноды) и схема (узлы разметки).
|
|
5522
|
+
|
|
5523
|
+
`schema` адресует узлы СВОЕЙ области: в корневом поведении — корневое дерево, в поведении
|
|
5524
|
+
подформы (`apply`) и строки массива (`applyEach`) — поддерево этой части. Правила узлов
|
|
5525
|
+
(`hideWhen`, `onComponentEvent`, `onMount`) исполняет рендерер; без него они ничего не делают.
|
|
4861
5526
|
|
|
4862
5527
|
**Signature:**
|
|
4863
5528
|
```typescript
|
|
4864
|
-
export type BehaviorScope<T> = {
|
|
4865
|
-
|
|
4866
|
-
|
|
5529
|
+
export type BehaviorScope<T> = {
|
|
5530
|
+
model: FormModel<T>;
|
|
5531
|
+
form: FormProxy<T>;
|
|
5532
|
+
schema: SchemaScope;
|
|
5533
|
+
};
|
|
5534
|
+
```
|
|
5535
|
+
|
|
4867
5536
|
_Source: src/form/behaviors/types.ts_
|
|
4868
5537
|
|
|
4869
5538
|
### bindFieldProps
|
|
@@ -4932,6 +5601,35 @@ await v!.validateAll(); // все шаги + extras
|
|
|
4932
5601
|
|
|
4933
5602
|
_Source: src/form/validation/config.ts_
|
|
4934
5603
|
|
|
5604
|
+
### bundledLocaleSource
|
|
5605
|
+
|
|
5606
|
+
**Kind:** `function`
|
|
5607
|
+
|
|
5608
|
+
Источник встроенных локалей пакета: по чанку на язык. Язык ищется точно, затем по основной части
|
|
5609
|
+
кода (`ru-RU` → `ru`). Языка нет — источник отдаёт `null`: подписи пакета останутся английскими,
|
|
5610
|
+
пока приложение не даст их своим источником.
|
|
5611
|
+
|
|
5612
|
+
**Signature:**
|
|
5613
|
+
```typescript
|
|
5614
|
+
export function bundledLocaleSource(
|
|
5615
|
+
loaders: Readonly<Record<string, () => Promise<FormLocale>>>
|
|
5616
|
+
): LocaleSource
|
|
5617
|
+
```
|
|
5618
|
+
|
|
5619
|
+
**Parameters:**
|
|
5620
|
+
- `loaders` — - Код языка → ленивый импорт его локали.
|
|
5621
|
+
|
|
5622
|
+
**Examples:**
|
|
5623
|
+
|
|
5624
|
+
```ts
|
|
5625
|
+
export const loadKitLocale = bundledLocaleSource({
|
|
5626
|
+
en: () => import('./en').then((m) => m.en),
|
|
5627
|
+
ru: () => import('./ru').then((m) => m.ru),
|
|
5628
|
+
});
|
|
5629
|
+
```
|
|
5630
|
+
|
|
5631
|
+
_Source: src/i18n/loader.ts_
|
|
5632
|
+
|
|
4935
5633
|
### ChangeContext
|
|
4936
5634
|
|
|
4937
5635
|
**Kind:** `interface`
|
|
@@ -5049,6 +5747,19 @@ copyFrom(model.$.email, model.$.emailAdditional, { when: () => model.sameEmail }
|
|
|
5049
5747
|
|
|
5050
5748
|
_Source: src/model/behaviors-value.ts_
|
|
5051
5749
|
|
|
5750
|
+
### CORE_LOCALES
|
|
5751
|
+
|
|
5752
|
+
**Kind:** `const`
|
|
5753
|
+
|
|
5754
|
+
Языки, которые ядро поставляет само.
|
|
5755
|
+
|
|
5756
|
+
**Signature:**
|
|
5757
|
+
```typescript
|
|
5758
|
+
export const CORE_LOCALES: readonly string[]
|
|
5759
|
+
```
|
|
5760
|
+
|
|
5761
|
+
_Source: src/locale/index.ts_
|
|
5762
|
+
|
|
5052
5763
|
### CORE_RUNTIME_TOKEN
|
|
5053
5764
|
|
|
5054
5765
|
**Kind:** `const`
|
|
@@ -5131,6 +5842,8 @@ const credit = useFormBundle(() =>
|
|
|
5131
5842
|
return <FormWizard form={credit.form} config={credit.validation} steps={STEPS} />;
|
|
5132
5843
|
```
|
|
5133
5844
|
|
|
5845
|
+
**Deprecated:** Пишите `createForm` — та же сборка, в бандле вдобавок дерево для рендерера.
|
|
5846
|
+
|
|
5134
5847
|
_Source: src/form/create-core-form.ts_
|
|
5135
5848
|
|
|
5136
5849
|
### CreateCoreFormConfig
|
|
@@ -5143,8 +5856,8 @@ _Source: src/form/create-core-form.ts_
|
|
|
5143
5856
|
```typescript
|
|
5144
5857
|
export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {
|
|
5145
5858
|
/**
|
|
5146
|
-
* Билдер схемы формы. Именно функция, а не готовое дерево:
|
|
5147
|
-
*
|
|
5859
|
+
* Билдер схемы формы. Именно функция, а не готовое дерево: узлы схемы держат ручки модели
|
|
5860
|
+
* (`model: model.$.email`), поэтому построить дерево до модели нечем.
|
|
5148
5861
|
*/
|
|
5149
5862
|
schema?: (model: FormModel<T>) => FormSchemaNode;
|
|
5150
5863
|
}
|
|
@@ -5156,53 +5869,89 @@ _Source: src/form/create-core-form.ts_
|
|
|
5156
5869
|
|
|
5157
5870
|
**Kind:** `function`
|
|
5158
5871
|
|
|
5159
|
-
|
|
5872
|
+
Собрать модель, форму, валидацию и дерево для рендера за один проход.
|
|
5160
5873
|
|
|
5161
5874
|
**Signature:**
|
|
5162
5875
|
```typescript
|
|
5163
|
-
export function createForm<T>(
|
|
5876
|
+
export function createForm<T extends object>(
|
|
5877
|
+
config: CreateFormConfig<T> & { validation: FormValidation<T> | ValidationSchema<T> }
|
|
5878
|
+
): ValidatedFormBundle<T>;
|
|
5164
5879
|
```
|
|
5165
5880
|
|
|
5166
5881
|
**Parameters:**
|
|
5167
|
-
- `
|
|
5882
|
+
- `config` — - {@link CreateFormConfig}: (`initial` | `model`) + опц. `schema`, `registry`,
|
|
5883
|
+
`behavior`, `validation`, `seed`, `setup`.
|
|
5168
5884
|
|
|
5169
|
-
**Returns:**
|
|
5885
|
+
**Returns:**
|
|
5170
5886
|
|
|
5171
5887
|
**Examples:**
|
|
5172
5888
|
|
|
5173
|
-
|
|
5174
|
-
```
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5889
|
+
Рендерер по TS-схеме
|
|
5890
|
+
```tsx
|
|
5891
|
+
const credit = useFormBundle(() =>
|
|
5892
|
+
createForm<CreditForm>({
|
|
5893
|
+
model: createCreditModel(),
|
|
5894
|
+
schema: creditSchema, // (model) => узел
|
|
5895
|
+
behavior: creditBehavior,
|
|
5896
|
+
validation: { steps: { loan: loanRules }, extras: crossRules },
|
|
5897
|
+
})
|
|
5898
|
+
);
|
|
5899
|
+
return <FormRenderer form={credit} settings={{ fieldWrapper: FormField }} />;
|
|
5900
|
+
```
|
|
5178
5901
|
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
}
|
|
5902
|
+
React-руками — та же сборка, разметка в JSX
|
|
5903
|
+
```tsx
|
|
5904
|
+
const credit = useFormBundle(() => createForm<CreditForm>({ initial, schema: creditSchema }));
|
|
5905
|
+
return <FormField control={credit.form.loanType} />;
|
|
5906
|
+
```
|
|
5907
|
+
|
|
5908
|
+
JSON — документ схемы и реестр
|
|
5909
|
+
```tsx
|
|
5910
|
+
const credit = useFormBundle(() =>
|
|
5911
|
+
createForm<CreditForm>({ model: createCreditModel(), schema: creditJson, registry })
|
|
5912
|
+
);
|
|
5913
|
+
return <FormRenderer form={credit} />;
|
|
5914
|
+
```
|
|
5183
5915
|
|
|
5184
|
-
|
|
5916
|
+
_Source: src/form/form-bundle.ts_
|
|
5185
5917
|
|
|
5186
|
-
|
|
5187
|
-
const schema = {
|
|
5188
|
-
children: [
|
|
5189
|
-
{ value: model.$.email, component: Input },
|
|
5190
|
-
{ value: model.$.password, component: Input },
|
|
5191
|
-
],
|
|
5192
|
-
};
|
|
5918
|
+
### CreateFormConfig
|
|
5193
5919
|
|
|
5194
|
-
|
|
5195
|
-
form.email.setValue('test@mail.com');
|
|
5920
|
+
**Kind:** `interface`
|
|
5196
5921
|
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5922
|
+
Конфиг {@link createForm}.
|
|
5923
|
+
|
|
5924
|
+
**Signature:**
|
|
5925
|
+
```typescript
|
|
5926
|
+
export interface CreateFormConfig<T> {
|
|
5927
|
+
/** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
|
|
5928
|
+
initial?: T;
|
|
5929
|
+
/** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
|
|
5930
|
+
model?: FormModel<T>;
|
|
5931
|
+
/**
|
|
5932
|
+
* Схема формы: билдер дерева `(model) => узел` либо документ (JSON) — тогда нужен `registry`.
|
|
5933
|
+
* Билдер, а не готовое дерево: узлы держат ручки модели (`model: model.$.email`), и построить
|
|
5934
|
+
* дерево до модели нечем.
|
|
5935
|
+
*/
|
|
5936
|
+
schema?: ((model: FormModel<T>) => FormSchemaNode) | object;
|
|
5937
|
+
/** Реестр JSON-варианта: превращает документ схемы в дерево. */
|
|
5938
|
+
registry?: SchemaResolver;
|
|
5939
|
+
/** Поведение формы: значения, состояние нод и правила узлов схемы. */
|
|
5940
|
+
behavior?: FormBehavior<T>;
|
|
5941
|
+
/** Правила валидации: готовая схема либо {@link FormValidation} со стратегией и шагами. */
|
|
5942
|
+
validation?: FormValidation<T> | ValidationSchema<T>;
|
|
5943
|
+
/**
|
|
5944
|
+
* Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
|
|
5945
|
+
* построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
|
|
5946
|
+
* инициализации не срабатывают).
|
|
5947
|
+
*/
|
|
5948
|
+
seed?: (model: FormModel<T>) => void;
|
|
5949
|
+
/** Донастройка ПОСЛЕ сборки: получает готовый бандл. */
|
|
5950
|
+
setup?: (bundle: FormBundle<T>) => void;
|
|
5951
|
+
}
|
|
5203
5952
|
```
|
|
5204
5953
|
|
|
5205
|
-
_Source: src/form/
|
|
5954
|
+
_Source: src/form/form-bundle.ts_
|
|
5206
5955
|
|
|
5207
5956
|
### CreateFormConfigBase
|
|
5208
5957
|
|
|
@@ -5229,7 +5978,7 @@ export interface CreateFormConfigBase<T, B> {
|
|
|
5229
5978
|
seed?: (model: FormModel<T>) => void;
|
|
5230
5979
|
/**
|
|
5231
5980
|
* Донастройка ПОСЛЕ сборки. Единственное место для правок, которые обязаны идти следом за
|
|
5232
|
-
*
|
|
5981
|
+
* сборкой формы: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),
|
|
5233
5982
|
* поэтому такой префилл выполняется здесь.
|
|
5234
5983
|
*/
|
|
5235
5984
|
setup?: (bundle: B) => void;
|
|
@@ -5246,12 +5995,14 @@ _Source: src/form/create-core-form.ts_
|
|
|
5246
5995
|
|
|
5247
5996
|
Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
|
|
5248
5997
|
ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
|
|
5249
|
-
модели, привязывает конфиг поля (component/componentProps) из схемы, материализует
|
|
5250
|
-
|
|
5251
|
-
роутинга ошибок) и, при наличии, запускает декларативное
|
|
5998
|
+
модели, привязывает конфиг поля (component/componentProps) из схемы, материализует массивы
|
|
5999
|
+
под-форм как {@link ModelArrayNode} (на любой глубине), заполняет реестр ручка→нода (для
|
|
6000
|
+
`enableWhen`, `apply`/`applyEach` и роутинга ошибок) и, при наличии, запускает декларативное
|
|
6001
|
+
поведение (cleanup живёт на форме).
|
|
5252
6002
|
|
|
5253
|
-
Обычно вызывается
|
|
5254
|
-
нужен
|
|
6003
|
+
Обычно вызывается сборкой одним вызовом (`createForm`): она строит дерево схемы, зовёт эту
|
|
6004
|
+
фабрику и собирает валидацию. Прямой вызов нужен там, где бандл не нужен, — форма элемента
|
|
6005
|
+
массива, тесты, свой слой сборки.
|
|
5255
6006
|
|
|
5256
6007
|
**Signature:**
|
|
5257
6008
|
```typescript
|
|
@@ -5265,7 +6016,7 @@ export function createFormFromModel<T>(args: CreateFormFromModelArgs<T>): FormPr
|
|
|
5265
6016
|
|
|
5266
6017
|
**Examples:**
|
|
5267
6018
|
|
|
5268
|
-
Форма из модели + схемы (эквивалент `
|
|
6019
|
+
Форма из модели + схемы (эквивалент `createFormFromModel({ model, schema })`)
|
|
5269
6020
|
```typescript
|
|
5270
6021
|
import { createModel, createFormFromModel } from '@reformer/core';
|
|
5271
6022
|
|
|
@@ -5293,7 +6044,7 @@ console.log(model.email); // 'user@mail.com'
|
|
|
5293
6044
|
```
|
|
5294
6045
|
|
|
5295
6046
|
**See also:**
|
|
5296
|
-
-
|
|
6047
|
+
- `createForm` — сборка одним вызовом: модель, форма, валидация и дерево для рендера
|
|
5297
6048
|
|
|
5298
6049
|
_Source: src/form/create-form.ts_
|
|
5299
6050
|
|
|
@@ -5319,6 +6070,12 @@ export interface CreateFormFromModelArgs<T> {
|
|
|
5319
6070
|
* заполнения реестра сигнал→нода; cleanup живёт на форме и вызывается в `form.dispose()`.
|
|
5320
6071
|
*/
|
|
5321
6072
|
behavior?: FormBehavior<T>;
|
|
6073
|
+
/**
|
|
6074
|
+
* Схема-контроллер сборки: через него поведение получает `schema` своей области. Его передаёт
|
|
6075
|
+
* сборка одним вызовом, чтобы правила узлов (`hideWhen`, `onComponentEvent`) дошли до рендерера.
|
|
6076
|
+
* Без него поведение пишет правила в собственный контроллер, который никто не читает.
|
|
6077
|
+
*/
|
|
6078
|
+
schemaController?: SchemaController;
|
|
5322
6079
|
}
|
|
5323
6080
|
```
|
|
5324
6081
|
|
|
@@ -5359,6 +6116,153 @@ dispose();
|
|
|
5359
6116
|
|
|
5360
6117
|
_Source: src/form/validation/strategy.ts_
|
|
5361
6118
|
|
|
6119
|
+
### createI18n
|
|
6120
|
+
|
|
6121
|
+
**Kind:** `function`
|
|
6122
|
+
|
|
6123
|
+
Ручка для локали. Для одного и того же объекта локали возвращается один и тот же объект —
|
|
6124
|
+
ручку можно держать в зависимостях хуков.
|
|
6125
|
+
|
|
6126
|
+
**Signature:**
|
|
6127
|
+
```typescript
|
|
6128
|
+
export function createI18n(locale: FormLocale): I18nHandle
|
|
6129
|
+
```
|
|
6130
|
+
|
|
6131
|
+
**Parameters:**
|
|
6132
|
+
- `locale` — - Локаль; см. {@link FormLocale}.
|
|
6133
|
+
|
|
6134
|
+
**Examples:**
|
|
6135
|
+
|
|
6136
|
+
```ts
|
|
6137
|
+
const i18n = createI18n({ code: 'ru', messages: { 'cart.items': '{count, plural, one{# товар} few{# товара} other{# товаров}}' } });
|
|
6138
|
+
i18n.t('cart.items', { count: 3 }); // '3 товара'
|
|
6139
|
+
i18n.fileSize(1536); // '1,5 КБ'
|
|
6140
|
+
```
|
|
6141
|
+
|
|
6142
|
+
_Source: src/i18n/translator.ts_
|
|
6143
|
+
|
|
6144
|
+
### createLegacyForm
|
|
6145
|
+
|
|
6146
|
+
**Kind:** `function`
|
|
6147
|
+
|
|
6148
|
+
Создать форму из плоской схемы полей с инлайн-значениями (`value: ''`) — путь ДО архитектуры M1.
|
|
6149
|
+
|
|
6150
|
+
Значения живут в нодах, модели нет: поведение (`defineFormBehavior`) и внешняя валидация
|
|
6151
|
+
(`validateModel`) к такой форме не подключаются. Для нового кода — {@link createFormFromModel}
|
|
6152
|
+
либо сборка одним вызовом.
|
|
6153
|
+
|
|
6154
|
+
**Signature:**
|
|
6155
|
+
```typescript
|
|
6156
|
+
export function createLegacyForm<T>(schema: FormSchema<T>): FormProxy<T>;
|
|
6157
|
+
```
|
|
6158
|
+
|
|
6159
|
+
**Parameters:**
|
|
6160
|
+
- `schema` — - Схема полей формы
|
|
6161
|
+
|
|
6162
|
+
**Returns:** Типизированная форма с Proxy-доступом к полям
|
|
6163
|
+
|
|
6164
|
+
**Examples:**
|
|
6165
|
+
|
|
6166
|
+
```typescript
|
|
6167
|
+
const form = createLegacyForm<UserForm>({
|
|
6168
|
+
email: { value: '', component: Input },
|
|
6169
|
+
password: { value: '', component: Input },
|
|
6170
|
+
});
|
|
6171
|
+
```
|
|
6172
|
+
|
|
6173
|
+
_Source: src/form/create-form.ts_
|
|
6174
|
+
|
|
6175
|
+
### createLocaleLoader
|
|
6176
|
+
|
|
6177
|
+
**Kind:** `function`
|
|
6178
|
+
|
|
6179
|
+
Собирает загрузчик из источников. Источники опрашиваются параллельно, а сливаются по порядку:
|
|
6180
|
+
поздний перекрывает ранний. Результат кэшируется по коду языка, идущий запрос не дублируется.
|
|
6181
|
+
Отказ любого источника — отказ загрузки; из кэша такой запрос удаляется, и следующая попытка
|
|
6182
|
+
пойдёт заново. Источник, которому можно промахнуться, приложение оборачивает само:
|
|
6183
|
+
`(code) => source(code).catch(() => null)`.
|
|
6184
|
+
|
|
6185
|
+
Код языка у результата — всегда запрошенный: по нему работает `Intl`, и он не должен зависеть
|
|
6186
|
+
от того, что написано внутри файла.
|
|
6187
|
+
|
|
6188
|
+
**Signature:**
|
|
6189
|
+
```typescript
|
|
6190
|
+
export function createLocaleLoader(sources: readonly LocaleSource[]): LocaleLoader
|
|
6191
|
+
```
|
|
6192
|
+
|
|
6193
|
+
**Parameters:**
|
|
6194
|
+
- `sources` — - Источники в порядке возрастания приоритета.
|
|
6195
|
+
|
|
6196
|
+
**Examples:**
|
|
6197
|
+
|
|
6198
|
+
```ts
|
|
6199
|
+
import { createLocaleLoader, fetchMessages } from '@reformer/core/i18n';
|
|
6200
|
+
import { loadKitLocale } from '@reformer/ui-kit/locale';
|
|
6201
|
+
|
|
6202
|
+
export const loadLocale = createLocaleLoader([
|
|
6203
|
+
loadKitLocale, // подписи кита — чанк нужного языка
|
|
6204
|
+
fetchMessages((code) => `/locales/${code}.json`), // ключи приложения и правки — с сервера
|
|
6205
|
+
]);
|
|
6206
|
+
```
|
|
6207
|
+
|
|
6208
|
+
_Source: src/i18n/loader.ts_
|
|
6209
|
+
|
|
6210
|
+
### createLocaleStore
|
|
6211
|
+
|
|
6212
|
+
**Kind:** `function`
|
|
6213
|
+
|
|
6214
|
+
Создаёт хранилище. Загрузка при создании НЕ начинается: начальное состояние считается чисто —
|
|
6215
|
+
из кэша загрузчика, если локаль там уже есть, — а запрос уходит на первый {@link LocaleStore.setLang}.
|
|
6216
|
+
Это позволяет создавать хранилище прямо в рендере.
|
|
6217
|
+
|
|
6218
|
+
**Signature:**
|
|
6219
|
+
```typescript
|
|
6220
|
+
export function createLocaleStore(
|
|
6221
|
+
load: LocaleLoad,
|
|
6222
|
+
lang: string,
|
|
6223
|
+
options: LocaleStoreOptions = {}
|
|
6224
|
+
): LocaleStore
|
|
6225
|
+
```
|
|
6226
|
+
|
|
6227
|
+
**Parameters:**
|
|
6228
|
+
- `load` — - Загрузчик локалей.
|
|
6229
|
+
- `lang` — - Начальный язык.
|
|
6230
|
+
- `options` — - См. {@link LocaleStoreOptions}.
|
|
6231
|
+
|
|
6232
|
+
_Source: src/i18n/locale-store.ts_
|
|
6233
|
+
|
|
6234
|
+
### createMessageFormatter
|
|
6235
|
+
|
|
6236
|
+
**Kind:** `function`
|
|
6237
|
+
|
|
6238
|
+
Форматтер одной локали с кэшем разбора: сообщение разбирается один раз, на первый вызов.
|
|
6239
|
+
Это внутренность `I18nService.t()` — сам `t()` добавляет сверху поиск ключа в словаре, маркер
|
|
6240
|
+
пропущенного ключа и откат на резервную локаль.
|
|
6241
|
+
|
|
6242
|
+
**Signature:**
|
|
6243
|
+
```typescript
|
|
6244
|
+
export function createMessageFormatter(
|
|
6245
|
+
locale: string,
|
|
6246
|
+
options: MessageFormatOptions = {}
|
|
6247
|
+
): (source: string, values?: MessageValues) => string
|
|
6248
|
+
```
|
|
6249
|
+
|
|
6250
|
+
**Parameters:**
|
|
6251
|
+
- `locale` — - Локаль форматтера.
|
|
6252
|
+
- `options` — - Настройки; см. {@link MessageFormatOptions}.
|
|
6253
|
+
|
|
6254
|
+
**Returns:** Функция `(сообщение, значения) => строка`.
|
|
6255
|
+
|
|
6256
|
+
**Examples:**
|
|
6257
|
+
|
|
6258
|
+
```ts
|
|
6259
|
+
const format = createMessageFormatter('ru');
|
|
6260
|
+
format('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}', { count: 5 });
|
|
6261
|
+
// '5 файлов'
|
|
6262
|
+
```
|
|
6263
|
+
|
|
6264
|
+
_Source: src/i18n/message-format.ts_
|
|
6265
|
+
|
|
5362
6266
|
### createModel
|
|
5363
6267
|
|
|
5364
6268
|
**Kind:** `function`
|
|
@@ -5395,13 +6299,70 @@ model.get(); // { email: 'a@b.c', profile: { name: 'Ada' }, tag
|
|
|
5395
6299
|
|
|
5396
6300
|
_Source: src/model/create-model.ts_
|
|
5397
6301
|
|
|
6302
|
+
### createSchemaController
|
|
6303
|
+
|
|
6304
|
+
**Kind:** `function`
|
|
6305
|
+
|
|
6306
|
+
Схема-контроллер одной сборки формы.
|
|
6307
|
+
|
|
6308
|
+
**Signature:**
|
|
6309
|
+
```typescript
|
|
6310
|
+
export function createSchemaController(): SchemaController
|
|
6311
|
+
```
|
|
6312
|
+
|
|
6313
|
+
**Examples:**
|
|
6314
|
+
|
|
6315
|
+
```typescript
|
|
6316
|
+
const controller = createSchemaController();
|
|
6317
|
+
const root = controller.scopeOf(model);
|
|
6318
|
+
root.node('mortgage').setHidden(true);
|
|
6319
|
+
|
|
6320
|
+
controller.scopeOf(model.items.at(0)); // область строки — свои селекторы
|
|
6321
|
+
```
|
|
6322
|
+
|
|
6323
|
+
_Source: src/form/schema-controller.ts_
|
|
6324
|
+
|
|
6325
|
+
### createSchemaOverrideMaps
|
|
6326
|
+
|
|
6327
|
+
**Kind:** `function`
|
|
6328
|
+
|
|
6329
|
+
Пустое хранилище переопределений области.
|
|
6330
|
+
|
|
6331
|
+
**Signature:**
|
|
6332
|
+
```typescript
|
|
6333
|
+
export function createSchemaOverrideMaps(): SchemaOverrideMaps
|
|
6334
|
+
```
|
|
6335
|
+
|
|
6336
|
+
_Source: src/form/schema-controller.ts_
|
|
6337
|
+
|
|
6338
|
+
### createSchemaScope
|
|
6339
|
+
|
|
6340
|
+
**Kind:** `function`
|
|
6341
|
+
|
|
6342
|
+
Схема области поверх хранилища: `node(selector)` → управление узлом.
|
|
6343
|
+
|
|
6344
|
+
**Signature:**
|
|
6345
|
+
```typescript
|
|
6346
|
+
export function createSchemaScope(
|
|
6347
|
+
maps: SchemaOverrideMaps = createSchemaOverrideMaps()
|
|
6348
|
+
): SchemaScope
|
|
6349
|
+
```
|
|
6350
|
+
|
|
6351
|
+
**Parameters:**
|
|
6352
|
+
- `maps` — - Хранилище области; по умолчанию создаётся новое.
|
|
6353
|
+
|
|
6354
|
+
_Source: src/form/schema-controller.ts_
|
|
6355
|
+
|
|
5398
6356
|
### cross
|
|
5399
6357
|
|
|
5400
6358
|
**Kind:** `function`
|
|
5401
6359
|
|
|
5402
|
-
Cross-field правило: `fn` получает СНАПШОТ модели
|
|
5403
|
-
|
|
5404
|
-
|
|
6360
|
+
Cross-field правило: `fn` получает СНАПШОТ модели текущей области (`model.get()`) и вешает
|
|
6361
|
+
ошибку на `sig`.
|
|
6362
|
+
|
|
6363
|
+
Область — та модель, с которой запущена схема: в корне прогона это вся форма, а в схеме,
|
|
6364
|
+
подключённой через {@link apply} / {@link applyEach}, — под-модель группы или строки массива.
|
|
6365
|
+
Поэтому правила подформы пишутся один раз и не знают, где она стоит.
|
|
5405
6366
|
|
|
5406
6367
|
**Signature:**
|
|
5407
6368
|
```typescript
|
|
@@ -5413,6 +6374,20 @@ export function cross<TSnapshot>(
|
|
|
5413
6374
|
|
|
5414
6375
|
_Source: src/form/validation/operators.ts_
|
|
5415
6376
|
|
|
6377
|
+
### DEFAULT_LOCALE
|
|
6378
|
+
|
|
6379
|
+
**Kind:** `const`
|
|
6380
|
+
|
|
6381
|
+
Локаль «провайдера нет»: английский, пустой словарь. Каждый пакет при промахе по словарю берёт
|
|
6382
|
+
свою встроенную английскую таблицу, поэтому пустой словарь означает «всё по-английски».
|
|
6383
|
+
|
|
6384
|
+
**Signature:**
|
|
6385
|
+
```typescript
|
|
6386
|
+
export const DEFAULT_LOCALE: FormLocale
|
|
6387
|
+
```
|
|
6388
|
+
|
|
6389
|
+
_Source: src/i18n/locale.ts_
|
|
6390
|
+
|
|
5416
6391
|
### defer
|
|
5417
6392
|
|
|
5418
6393
|
**Kind:** `function`
|
|
@@ -5440,15 +6415,47 @@ export function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void):
|
|
|
5440
6415
|
**Examples:**
|
|
5441
6416
|
|
|
5442
6417
|
```ts
|
|
5443
|
-
export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
6418
|
+
export const myBehavior = defineFormBehavior<MyForm>(({ model, form, schema }) => {
|
|
5444
6419
|
compute(model.$.total, () => model.price * model.qty);
|
|
5445
6420
|
enableWhen([model.$.city], () => Boolean(model.country));
|
|
5446
6421
|
onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
|
|
6422
|
+
hideWhen(schema.node('delivery'), () => model.country === '');
|
|
5447
6423
|
});
|
|
5448
6424
|
```
|
|
5449
6425
|
|
|
5450
6426
|
_Source: src/form/behaviors/context.ts_
|
|
5451
6427
|
|
|
6428
|
+
### defineMessages
|
|
6429
|
+
|
|
6430
|
+
**Kind:** `function`
|
|
6431
|
+
|
|
6432
|
+
`msg` с типизированными ключами: принимает английский словарь приложения, проверяет ключ при
|
|
6433
|
+
компиляции и подставляет английский текст как `defaultMessage` — без провайдера и при промахе
|
|
6434
|
+
по локали форма покажет его, а не ключ.
|
|
6435
|
+
|
|
6436
|
+
**Signature:**
|
|
6437
|
+
```typescript
|
|
6438
|
+
export function defineMessages<D extends Messages>(
|
|
6439
|
+
defaults: D
|
|
6440
|
+
): {
|
|
6441
|
+
msg(key: keyof D & string, values?: Readonly<Record<string, unknown>>): MessageDescriptor;
|
|
6442
|
+
}
|
|
6443
|
+
```
|
|
6444
|
+
|
|
6445
|
+
**Parameters:**
|
|
6446
|
+
- `defaults` — - Английский словарь приложения.
|
|
6447
|
+
|
|
6448
|
+
**Examples:**
|
|
6449
|
+
|
|
6450
|
+
```ts
|
|
6451
|
+
import appEn from './locales/en.json';
|
|
6452
|
+
|
|
6453
|
+
export const { msg } = defineMessages(appEn);
|
|
6454
|
+
msg('profile.email.label'); // опечатка в ключе — ошибка компиляции
|
|
6455
|
+
```
|
|
6456
|
+
|
|
6457
|
+
_Source: src/i18n/descriptor.ts_
|
|
6458
|
+
|
|
5452
6459
|
### defineValidationSchema
|
|
5453
6460
|
|
|
5454
6461
|
**Kind:** `function`
|
|
@@ -5506,17 +6513,21 @@ _Source: src/form/behaviors/node.ts_
|
|
|
5506
6513
|
|
|
5507
6514
|
**Kind:** `function`
|
|
5508
6515
|
|
|
5509
|
-
Применить под-правила к КАЖДОМУ элементу
|
|
5510
|
-
|
|
6516
|
+
Применить под-правила к КАЖДОМУ элементу массива — прежняя запись {@link applyEach}.
|
|
6517
|
+
|
|
6518
|
+
Колбэк получает под-модель элемента, но области не создаёт: `cross` внутри него видит снапшот
|
|
6519
|
+
корня прогона.
|
|
5511
6520
|
|
|
5512
6521
|
**Signature:**
|
|
5513
6522
|
```typescript
|
|
5514
6523
|
export function each<U extends object>(
|
|
5515
|
-
arr: ModelArray<U>,
|
|
6524
|
+
arr: ArrayBinding<U> | ModelArray<U>,
|
|
5516
6525
|
itemFn: (item: FormModel<U>) => void
|
|
5517
6526
|
): void
|
|
5518
6527
|
```
|
|
5519
6528
|
|
|
6529
|
+
**Deprecated:** Пишите `applyEach(model.$.<массив>, schema)`.
|
|
6530
|
+
|
|
5520
6531
|
_Source: src/form/validation/operators.ts_
|
|
5521
6532
|
|
|
5522
6533
|
### eachLeafSignal
|
|
@@ -5526,9 +6537,11 @@ _Source: src/form/validation/operators.ts_
|
|
|
5526
6537
|
Обойти ВСЕ листовые сигналы модели (включая элементы массивов), вызвав `visit` на каждом.
|
|
5527
6538
|
|
|
5528
6539
|
Внутри реактивного `effect` служит подпиской «любое поле изменилось»: `visit(sig => void sig.value)`
|
|
5529
|
-
подписывает на значения листьев, а обход массивов через `items.value` — на их состав.
|
|
5530
|
-
|
|
5531
|
-
|
|
6540
|
+
подписывает на значения листьев, а обход массивов через `items.value` — на их состав. Тот же
|
|
6541
|
+
паттерн, что `revalidateWhen`, но без ручного перечисления зависимостей.
|
|
6542
|
+
|
|
6543
|
+
Сами массивы обход не посещает — только их элементы. Поле, привязанное к массиву целиком
|
|
6544
|
+
(мультивыбор), живёт на сигнале массива; чтобы увидеть и его, берите {@link eachValueSignal}.
|
|
5532
6545
|
|
|
5533
6546
|
**Signature:**
|
|
5534
6547
|
```typescript
|
|
@@ -5540,6 +6553,28 @@ export function eachLeafSignal<T>(
|
|
|
5540
6553
|
|
|
5541
6554
|
_Source: src/model/create-model.ts_
|
|
5542
6555
|
|
|
6556
|
+
### eachValueSignal
|
|
6557
|
+
|
|
6558
|
+
**Kind:** `function`
|
|
6559
|
+
|
|
6560
|
+
Обойти ВСЕ ручки значений модели: листья (включая элементы массивов) И сами массивы.
|
|
6561
|
+
|
|
6562
|
+
Отличие от {@link eachLeafSignal} — узлы-массивы: массив бывает одним значением поля
|
|
6563
|
+
(`{ value: model.$.tags, component }`), и нода такого поля привязана к сигналу массива, а не
|
|
6564
|
+
к сигналам элементов. Обходчику, которому нужна именно НОДА поля (стратегия `blur` подписывается
|
|
6565
|
+
на её `touched`), одних листьев мало — он не увидел бы мультивыбор. Так строятся триггеры
|
|
6566
|
+
`change`/`blur` стратегий валидации (см. `createFormValidation`).
|
|
6567
|
+
|
|
6568
|
+
**Signature:**
|
|
6569
|
+
```typescript
|
|
6570
|
+
export function eachValueSignal<T>(
|
|
6571
|
+
model: FormModel<T>,
|
|
6572
|
+
visit: (signal: PathAwareSignal<unknown>) => void
|
|
6573
|
+
): void
|
|
6574
|
+
```
|
|
6575
|
+
|
|
6576
|
+
_Source: src/model/create-model.ts_
|
|
6577
|
+
|
|
5543
6578
|
### effect
|
|
5544
6579
|
|
|
5545
6580
|
**Kind:** `function`
|
|
@@ -5713,23 +6748,76 @@ exclusiveFlag(model.$.contacts, (row) => row.$.primary);
|
|
|
5713
6748
|
|
|
5714
6749
|
_Source: src/form/behaviors/collections.ts_
|
|
5715
6750
|
|
|
5716
|
-
###
|
|
6751
|
+
### extendLocale
|
|
5717
6752
|
|
|
5718
|
-
**Kind:** `
|
|
6753
|
+
**Kind:** `function`
|
|
5719
6754
|
|
|
5720
|
-
|
|
5721
|
-
|
|
6755
|
+
Новая локаль на основе существующей: словари сливаются (ключи из `patch` важнее), остальные
|
|
6756
|
+
поля заменяются. Исходный объект не меняется.
|
|
5722
6757
|
|
|
5723
6758
|
**Signature:**
|
|
5724
6759
|
```typescript
|
|
5725
|
-
export
|
|
6760
|
+
export function extendLocale(base: FormLocale, patch: Partial<FormLocale>): FormLocale
|
|
5726
6761
|
```
|
|
5727
6762
|
|
|
5728
|
-
|
|
6763
|
+
**Parameters:**
|
|
6764
|
+
- `base` — - Локаль-основа, например поставляемая китом.
|
|
6765
|
+
- `patch` — - Свои ключи, переопределения китовых подписей и поля локали.
|
|
5729
6766
|
|
|
5730
|
-
|
|
6767
|
+
**Examples:**
|
|
5731
6768
|
|
|
5732
|
-
|
|
6769
|
+
```ts
|
|
6770
|
+
import { ru } from '@reformer/ui-kit/locale/ru';
|
|
6771
|
+
|
|
6772
|
+
const appRu = extendLocale(ru, {
|
|
6773
|
+
messages: { 'profile.title': 'Профиль', 'kit.formWizard.submit': 'Отправить' },
|
|
6774
|
+
});
|
|
6775
|
+
```
|
|
6776
|
+
|
|
6777
|
+
_Source: src/i18n/locale.ts_
|
|
6778
|
+
|
|
6779
|
+
### fetchMessages
|
|
6780
|
+
|
|
6781
|
+
**Kind:** `function`
|
|
6782
|
+
|
|
6783
|
+
Источник локали по сети: забирает JSON по адресу, построенному из кода языка. Файл может быть
|
|
6784
|
+
словарём (`{ "profile.title": "Профиль" }`) или локалью целиком (`{ "code": "ru", "messages": … }`).
|
|
6785
|
+
Ответ не `2xx` — ошибка загрузки.
|
|
6786
|
+
|
|
6787
|
+
**Signature:**
|
|
6788
|
+
```typescript
|
|
6789
|
+
export function fetchMessages(url: (code: string) => string, init?: RequestInit): LocaleSource
|
|
6790
|
+
```
|
|
6791
|
+
|
|
6792
|
+
**Parameters:**
|
|
6793
|
+
- `url` — - Адрес файла для языка.
|
|
6794
|
+
- `init` — - Параметры запроса, передаются в `fetch` как есть.
|
|
6795
|
+
|
|
6796
|
+
**Examples:**
|
|
6797
|
+
|
|
6798
|
+
```ts
|
|
6799
|
+
fetchMessages((code) => `/locales/${code}.json`);
|
|
6800
|
+
```
|
|
6801
|
+
|
|
6802
|
+
_Source: src/i18n/loader.ts_
|
|
6803
|
+
|
|
6804
|
+
### FIELD_WRAPPER_ONLY_PROPS
|
|
6805
|
+
|
|
6806
|
+
**Kind:** `const`
|
|
6807
|
+
|
|
6808
|
+
Props, которые адресованы обёртке поля, а не контролу: `labelTooltip` рисует `FormField` у
|
|
6809
|
+
подписи. Они приходят в общем мешке `componentProps`, и без среза утекли бы в DOM контрола.
|
|
6810
|
+
|
|
6811
|
+
**Signature:**
|
|
6812
|
+
```typescript
|
|
6813
|
+
export const FIELD_WRAPPER_ONLY_PROPS: readonly string[]
|
|
6814
|
+
```
|
|
6815
|
+
|
|
6816
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
6817
|
+
|
|
6818
|
+
### FieldAdapter
|
|
6819
|
+
|
|
6820
|
+
**Kind:** `interface`
|
|
5733
6821
|
|
|
5734
6822
|
Адаптер поля: описывает диалект контрола (`checked`/`onCheckedChange`, `value`/`onValueChange`,
|
|
5735
6823
|
DOM-событие в `onChange`, …). Все ключи необязательны — по умолчанию seam проходит как есть.
|
|
@@ -5895,11 +6983,12 @@ export interface FieldControlState<T> {
|
|
|
5895
6983
|
* @example
|
|
5896
6984
|
* ```tsx
|
|
5897
6985
|
* const { errors } = useFormControl(field);
|
|
6986
|
+
* const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
5898
6987
|
*
|
|
5899
6988
|
* return (
|
|
5900
6989
|
* <ul className="error-list">
|
|
5901
6990
|
* {errors.map((error, i) => (
|
|
5902
|
-
* <li key={i}>{error
|
|
6991
|
+
* <li key={i}>{message(error)}</li>
|
|
5903
6992
|
* ))}
|
|
5904
6993
|
* </ul>
|
|
5905
6994
|
* );
|
|
@@ -5962,12 +7051,13 @@ export interface FieldControlState<T> {
|
|
|
5962
7051
|
* @example
|
|
5963
7052
|
* ```tsx
|
|
5964
7053
|
* const { shouldShowError, errors } = useFormControl(field);
|
|
7054
|
+
* const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
5965
7055
|
*
|
|
5966
7056
|
* return (
|
|
5967
7057
|
* <div>
|
|
5968
7058
|
* <input {...props} />
|
|
5969
7059
|
* {shouldShowError && (
|
|
5970
|
-
* <span className="error">{errors[0]
|
|
7060
|
+
* <span className="error">{message(errors[0])}</span>
|
|
5971
7061
|
* )}
|
|
5972
7062
|
* </div>
|
|
5973
7063
|
* );
|
|
@@ -6030,6 +7120,7 @@ control: FieldNode<string>;
|
|
|
6030
7120
|
|
|
6031
7121
|
function TextField({ control }: Props) {
|
|
6032
7122
|
const state = useFormControl(control);
|
|
7123
|
+
const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
6033
7124
|
|
|
6034
7125
|
return (
|
|
6035
7126
|
<div>
|
|
@@ -6039,7 +7130,7 @@ disabled={state.disabled}
|
|
|
6039
7130
|
onChange={e => control.setValue(e.target.value)}
|
|
6040
7131
|
/>
|
|
6041
7132
|
{state.shouldShowError && state.errors[0] && (
|
|
6042
|
-
<span className="error">{state.errors[0]
|
|
7133
|
+
<span className="error">{message(state.errors[0])}</span>
|
|
6043
7134
|
)}
|
|
6044
7135
|
</div>
|
|
6045
7136
|
);
|
|
@@ -6262,6 +7353,33 @@ todos.forEach((item, i) => {
|
|
|
6262
7353
|
|
|
6263
7354
|
_Source: src/form/types/form-proxy.ts_
|
|
6264
7355
|
|
|
7356
|
+
### formatPattern
|
|
7357
|
+
|
|
7358
|
+
**Kind:** `function`
|
|
7359
|
+
|
|
7360
|
+
Собирает строку из разобранного сообщения. Значения подставляются как текст и повторно
|
|
7361
|
+
не разбираются.
|
|
7362
|
+
|
|
7363
|
+
**Signature:**
|
|
7364
|
+
```typescript
|
|
7365
|
+
export function formatPattern(
|
|
7366
|
+
pattern: MessagePattern,
|
|
7367
|
+
values: MessageValues,
|
|
7368
|
+
locale: string,
|
|
7369
|
+
options: MessageFormatOptions = {}
|
|
7370
|
+
): string
|
|
7371
|
+
```
|
|
7372
|
+
|
|
7373
|
+
**Parameters:**
|
|
7374
|
+
- `pattern` — - Результат {@link parseMessage}.
|
|
7375
|
+
- `values` — - Значения аргументов.
|
|
7376
|
+
- `locale` — - Локаль для {@link Intl.PluralRules} и {@link Intl.NumberFormat} (`ru`, `en`, …).
|
|
7377
|
+
- `options` — - Настройки; см. {@link MessageFormatOptions}.
|
|
7378
|
+
|
|
7379
|
+
**Returns:** Готовая строка интерфейса.
|
|
7380
|
+
|
|
7381
|
+
_Source: src/i18n/message-format.ts_
|
|
7382
|
+
|
|
6265
7383
|
### FormBehavior
|
|
6266
7384
|
|
|
6267
7385
|
**Kind:** `interface`
|
|
@@ -6271,13 +7389,57 @@ _Source: src/form/types/form-proxy.ts_
|
|
|
6271
7389
|
**Signature:**
|
|
6272
7390
|
```typescript
|
|
6273
7391
|
export interface FormBehavior<T> {
|
|
6274
|
-
/**
|
|
6275
|
-
|
|
7392
|
+
/**
|
|
7393
|
+
* @internal Запускается сборкой формы после построения нод и заполнения реестра сигнал→нода.
|
|
7394
|
+
* `controller` — схема-контроллер сборки; без него поведение получает собственный, который
|
|
7395
|
+
* никто не читает (модель без рендерера).
|
|
7396
|
+
*/
|
|
7397
|
+
__run(model: FormModel<T>, form: FormProxy<T>, controller?: SchemaController): BehaviorCleanup;
|
|
6276
7398
|
}
|
|
6277
7399
|
```
|
|
6278
7400
|
|
|
6279
7401
|
_Source: src/form/behaviors/types.ts_
|
|
6280
7402
|
|
|
7403
|
+
### FormBundle
|
|
7404
|
+
|
|
7405
|
+
**Kind:** `interface`
|
|
7406
|
+
|
|
7407
|
+
Результат {@link createForm}.
|
|
7408
|
+
|
|
7409
|
+
**Signature:**
|
|
7410
|
+
```typescript
|
|
7411
|
+
export interface FormBundle<T> {
|
|
7412
|
+
model: FormModel<T>;
|
|
7413
|
+
form: FormProxy<T>;
|
|
7414
|
+
/** Собранная валидация; есть, если в конфиге заданы правила. */
|
|
7415
|
+
validation?: FormValidationBundle<T>;
|
|
7416
|
+
render: FormRender;
|
|
7417
|
+
}
|
|
7418
|
+
```
|
|
7419
|
+
|
|
7420
|
+
_Source: src/form/form-bundle.ts_
|
|
7421
|
+
|
|
7422
|
+
### FormBundleContext
|
|
7423
|
+
|
|
7424
|
+
**Kind:** `const`
|
|
7425
|
+
|
|
7426
|
+
Контекст сборки. Значение — бандл `createForm`; вне рендерера — `null`.
|
|
7427
|
+
|
|
7428
|
+
**Signature:**
|
|
7429
|
+
```typescript
|
|
7430
|
+
export const FormBundleContext
|
|
7431
|
+
```
|
|
7432
|
+
|
|
7433
|
+
**Examples:**
|
|
7434
|
+
|
|
7435
|
+
```tsx
|
|
7436
|
+
<FormBundleContext.Provider value={bundle}>
|
|
7437
|
+
<MyWizard />
|
|
7438
|
+
</FormBundleContext.Provider>
|
|
7439
|
+
```
|
|
7440
|
+
|
|
7441
|
+
_Source: src/platforms/react/form-bundle-context.ts_
|
|
7442
|
+
|
|
6281
7443
|
### FormBundleLike
|
|
6282
7444
|
|
|
6283
7445
|
**Kind:** `interface`
|
|
@@ -6407,6 +7569,43 @@ try {
|
|
|
6407
7569
|
|
|
6408
7570
|
_Source: src/form/validation/error-handler.ts_
|
|
6409
7571
|
|
|
7572
|
+
### FormLocale
|
|
7573
|
+
|
|
7574
|
+
**Kind:** `interface`
|
|
7575
|
+
|
|
7576
|
+
Локаль формы.
|
|
7577
|
+
|
|
7578
|
+
**Signature:**
|
|
7579
|
+
```typescript
|
|
7580
|
+
export interface FormLocale {
|
|
7581
|
+
/** Код языка BCP 47 (`en`, `ru`, `pt-BR`): по нему работают `Intl` и множественные формы. */
|
|
7582
|
+
readonly code: string;
|
|
7583
|
+
/** Словарь сообщений. Ключа нет — пакет подставит встроенный английский. */
|
|
7584
|
+
readonly messages: Messages;
|
|
7585
|
+
/** Первый день недели календаря. По умолчанию — воскресенье. */
|
|
7586
|
+
readonly weekStartsOn?: WeekStart;
|
|
7587
|
+
/**
|
|
7588
|
+
* Необязательное переопределение: объект локали `react-day-picker` / `date-fns`. Для ядра
|
|
7589
|
+
* непрозрачен. Без него даты форматируются через `Intl` по {@link FormLocale.code}.
|
|
7590
|
+
*/
|
|
7591
|
+
readonly dateLocale?: unknown;
|
|
7592
|
+
/** Необязательное переопределение: формат даты в токенах `date-fns` для DatePicker. */
|
|
7593
|
+
readonly dateFormat?: string;
|
|
7594
|
+
}
|
|
7595
|
+
```
|
|
7596
|
+
|
|
7597
|
+
**Examples:**
|
|
7598
|
+
|
|
7599
|
+
```ts
|
|
7600
|
+
const ru: FormLocale = {
|
|
7601
|
+
code: 'ru',
|
|
7602
|
+
weekStartsOn: 1,
|
|
7603
|
+
messages: { 'kit.select.selected': 'Выбрано: {count}' },
|
|
7604
|
+
};
|
|
7605
|
+
```
|
|
7606
|
+
|
|
7607
|
+
_Source: src/i18n/locale.ts_
|
|
7608
|
+
|
|
6410
7609
|
### FormModel
|
|
6411
7610
|
|
|
6412
7611
|
**Kind:** `type`
|
|
@@ -6507,7 +7706,7 @@ interface UserForm {
|
|
|
6507
7706
|
};
|
|
6508
7707
|
}
|
|
6509
7708
|
|
|
6510
|
-
const form =
|
|
7709
|
+
const form = createLegacyForm<UserForm>(schema);
|
|
6511
7710
|
|
|
6512
7711
|
// Доступ к методам GroupNode
|
|
6513
7712
|
await form.validate();
|
|
@@ -6521,6 +7720,30 @@ form.profile.name.setValue('John');
|
|
|
6521
7720
|
|
|
6522
7721
|
_Source: src/form/types/form-proxy.ts_
|
|
6523
7722
|
|
|
7723
|
+
### FormRender
|
|
7724
|
+
|
|
7725
|
+
**Kind:** `interface`
|
|
7726
|
+
|
|
7727
|
+
Часть бандла для рендерера: готовое дерево и схема-контроллер сборки.
|
|
7728
|
+
|
|
7729
|
+
**Signature:**
|
|
7730
|
+
```typescript
|
|
7731
|
+
export interface FormRender {
|
|
7732
|
+
/** Корневой узел дерева схемы; `undefined`, если схема не задана. */
|
|
7733
|
+
readonly tree: FormSchemaNode | undefined;
|
|
7734
|
+
/** Схема-контроллер сборки: области корня, строк массивов и подформ. */
|
|
7735
|
+
readonly controller: SchemaController;
|
|
7736
|
+
/** Управление узлом КОРНЕВОЙ области по его `selector`. */
|
|
7737
|
+
node(selector: string): SchemaNodeControl;
|
|
7738
|
+
/** Обёртка поля из реестра (JSON-вариант). */
|
|
7739
|
+
readonly fieldWrapper?: unknown;
|
|
7740
|
+
/** Граница ошибок из реестра (JSON-вариант). */
|
|
7741
|
+
readonly errorBoundary?: unknown;
|
|
7742
|
+
}
|
|
7743
|
+
```
|
|
7744
|
+
|
|
7745
|
+
_Source: src/form/form-bundle.ts_
|
|
7746
|
+
|
|
6524
7747
|
### FormSchema
|
|
6525
7748
|
|
|
6526
7749
|
**Kind:** `type`
|
|
@@ -6534,7 +7757,7 @@ _Source: src/form/types/form-proxy.ts_
|
|
|
6534
7757
|
Использует NonNullable для корректной обработки опциональных полей.
|
|
6535
7758
|
|
|
6536
7759
|
⚠️ Не путать с {@link FormSchemaNode} — тот описывает **узел дерева** M1-схемы (лист/массив/
|
|
6537
|
-
контейнер), передаваемой в `
|
|
7760
|
+
контейнер), передаваемой в `createFormFromModel({ model, schema })`. `FormSchema` — это data-shaped конфиг
|
|
6538
7761
|
(ключи = поля данных), а не узел дерева.
|
|
6539
7762
|
|
|
6540
7763
|
**Signature:**
|
|
@@ -6590,12 +7813,13 @@ _Source: src/form/types/deep-schema.ts_
|
|
|
6590
7813
|
|
|
6591
7814
|
**Kind:** `interface`
|
|
6592
7815
|
|
|
6593
|
-
Узел единой схемы M1 — layout-дерево, обходимое `
|
|
7816
|
+
Узел единой схемы M1 — layout-дерево, обходимое `createFormFromModel({ model, schema })`
|
|
6594
7817
|
и рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).
|
|
6595
7818
|
|
|
6596
7819
|
Узел совмещает несколько ролей (различаются рантаймом по форме):
|
|
6597
|
-
- **поле** —
|
|
6598
|
-
-
|
|
7820
|
+
- **поле** — `{ model: model.$.x, component, componentProps }`;
|
|
7821
|
+
- **массив под-форм** — `{ model: model.$.items, item: (model) => узел }`;
|
|
7822
|
+
- **подформа** — `{ model: model.$.group, part: (model) => узел }`;
|
|
6599
7823
|
- **контейнер/ветка** — вложенные узлы (`children`), опц. условие `when`;
|
|
6600
7824
|
- **record-of-fields** — под-узлы под произвольными именованными ключами (индексная сигнатура).
|
|
6601
7825
|
|
|
@@ -6606,9 +7830,20 @@ _Source: src/form/types/deep-schema.ts_
|
|
|
6606
7830
|
```typescript
|
|
6607
7831
|
export interface FormSchemaNode {
|
|
6608
7832
|
/**
|
|
6609
|
-
*
|
|
6610
|
-
*
|
|
6611
|
-
*
|
|
7833
|
+
* Привязка узла к части модели — ручка из дерева `model.$`. Чем узел является, решают ручка и
|
|
7834
|
+
* соседние ключи:
|
|
7835
|
+
* - лист или массив (`model.$.email`, `model.$.tags`) — **поле**;
|
|
7836
|
+
* - массив вместе с {@link FormSchemaNode.item} — **массив под-форм**;
|
|
7837
|
+
* - группа вместе с {@link FormSchemaNode.part} — **подформа**.
|
|
7838
|
+
*
|
|
7839
|
+
* Узел распознаётся по значению, а не по имени ключа: в записи «имя поля → узел» поле данных
|
|
7840
|
+
* может называться `model`, и тогда под этим ключом лежит обычный вложенный узел.
|
|
7841
|
+
*/
|
|
7842
|
+
model?: unknown;
|
|
7843
|
+
/**
|
|
7844
|
+
* Прежняя запись привязки поля — то же, что {@link FormSchemaNode.model}.
|
|
7845
|
+
*
|
|
7846
|
+
* @deprecated Пишите `model: model.$.<path>`.
|
|
6612
7847
|
*/
|
|
6613
7848
|
value?: unknown;
|
|
6614
7849
|
/**
|
|
@@ -6638,14 +7873,27 @@ export interface FormSchemaNode {
|
|
|
6638
7873
|
* выводит на своём месте в порядке следования.
|
|
6639
7874
|
*/
|
|
6640
7875
|
children?: readonly (FormSchemaNode | string | number | Signal<any>)[];
|
|
6641
|
-
/**
|
|
7876
|
+
/**
|
|
7877
|
+
* Прежняя запись привязки массива под-форм — фасад `model.<path>` (вместе с `item`).
|
|
7878
|
+
*
|
|
7879
|
+
* @deprecated Пишите `model: model.$.<path>`.
|
|
7880
|
+
*/
|
|
6642
7881
|
array?: SchemaArrayControl;
|
|
6643
7882
|
/** Схема элемента массива: под-модель элемента → узел поддерева. */
|
|
6644
|
-
item?: (
|
|
7883
|
+
item?: (model: any) => FormSchemaNode;
|
|
7884
|
+
/**
|
|
7885
|
+
* Подформа: под-модель группы → узел поддерева. Часть объявляется один раз и подключается к
|
|
7886
|
+
* любой группе той же формы данных: `{ model: model.$.registrationAddress, part: address }`.
|
|
7887
|
+
* Привязки внутри части идут через `$` полученной под-модели.
|
|
7888
|
+
*/
|
|
7889
|
+
part?: (model: any) => FormSchemaNode;
|
|
6645
7890
|
/**
|
|
6646
|
-
*
|
|
6647
|
-
*
|
|
6648
|
-
*
|
|
7891
|
+
* Шаблон нового элемента массива для кнопки «Добавить»: либо готовое значение, либо фабрика
|
|
7892
|
+
* `() => value`. Запасной путь — для форм, чья модель создаётся из данных без кода: шаблон,
|
|
7893
|
+
* объявленный в модели (`arrayOf(blank)`), главнее.
|
|
7894
|
+
*
|
|
7895
|
+
* Тип не различает варианты (union `unknown | (() => unknown)` схлопывается в `unknown`) —
|
|
7896
|
+
* рантайм различает по `typeof initialValue === 'function'`.
|
|
6649
7897
|
*/
|
|
6650
7898
|
initialValue?: unknown;
|
|
6651
7899
|
/** Свободная вложенность: record-of-fields и произвольные под-узлы. */
|
|
@@ -6864,21 +8112,22 @@ _Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
|
6864
8112
|
|
|
6865
8113
|
**Kind:** `function`
|
|
6866
8114
|
|
|
6867
|
-
Найти ноду формы по
|
|
8115
|
+
Найти ноду формы по ручке модели.
|
|
6868
8116
|
|
|
6869
|
-
Используется state-операциями behavior (`enableWhen`/`disableWhen`)
|
|
6870
|
-
валидации в ноды. Возвращает `undefined`, если форма
|
|
6871
|
-
|
|
8117
|
+
Используется state-операциями behavior (`enableWhen`/`disableWhen`), подключением под-схем
|
|
8118
|
+
(`apply`/`applyEach`) и роутингом ошибок валидации в ноды. Возвращает `undefined`, если форма
|
|
8119
|
+
ещё не построена или эта часть модели в форме не материализована (например, массив без
|
|
8120
|
+
item-схемы).
|
|
6872
8121
|
|
|
6873
8122
|
**Signature:**
|
|
6874
8123
|
```typescript
|
|
6875
|
-
export function getNodeForSignal(signal:
|
|
8124
|
+
export function getNodeForSignal(signal: object): FormNode<any> | undefined
|
|
6876
8125
|
```
|
|
6877
8126
|
|
|
6878
8127
|
**Parameters:**
|
|
6879
|
-
- `signal` — -
|
|
8128
|
+
- `signal` — - Ручка из {@link FormModel}: `model.$.<поле>`, `model.$.<группа>`, `model.$.<массив>`
|
|
6880
8129
|
|
|
6881
|
-
**Returns:** Нода формы
|
|
8130
|
+
**Returns:** Нода формы — поля, группы или массива — либо `undefined`
|
|
6882
8131
|
|
|
6883
8132
|
**Examples:**
|
|
6884
8133
|
|
|
@@ -6890,8 +8139,14 @@ const sig = model.$.email;
|
|
|
6890
8139
|
getNodeForSignal(sig)?.setErrors([{ code: 'required', message: 'Обязательно' }]);
|
|
6891
8140
|
```
|
|
6892
8141
|
|
|
8142
|
+
Нода группы и строки массива
|
|
8143
|
+
```typescript
|
|
8144
|
+
getNodeForSignal(model.$.address)?.disable(); // группа целиком
|
|
8145
|
+
getNodeForSignal(model.$.phones[0])?.markAsTouched(); // форма строки массива
|
|
8146
|
+
```
|
|
8147
|
+
|
|
6893
8148
|
**See also:**
|
|
6894
|
-
- {@link registerSignalNode} - регистрация связи
|
|
8149
|
+
- {@link registerSignalNode} - регистрация связи ручка→нода
|
|
6895
8150
|
|
|
6896
8151
|
_Source: src/form/signal-node-registry.ts_
|
|
6897
8152
|
|
|
@@ -6925,7 +8180,7 @@ _Source: src/form/type-guards.ts_
|
|
|
6925
8180
|
|
|
6926
8181
|
**Kind:** `function`
|
|
6927
8182
|
|
|
6928
|
-
|
|
8183
|
+
Текущая область ({ model, form, schema }) активной схемы (escape hatch для кросс-операторов).
|
|
6929
8184
|
|
|
6930
8185
|
**Signature:**
|
|
6931
8186
|
```typescript
|
|
@@ -6941,7 +8196,7 @@ _Source: src/form/behaviors/context.ts_
|
|
|
6941
8196
|
GroupNode - узел для группы полей
|
|
6942
8197
|
|
|
6943
8198
|
Создаётся из {@link FormSchema} (дерево field-конфигов). Обычно строится через `createForm`
|
|
6944
|
-
(M1: `
|
|
8199
|
+
(M1: `createFormFromModel({ model, schema })`); schema-валидация/behavior живут на слое модели
|
|
6945
8200
|
(`validateModel` из `@reformer/core/validation`, `computeFrom`/`enableWhen`/…), а не на ноде.
|
|
6946
8201
|
|
|
6947
8202
|
**Signature:**
|
|
@@ -6988,6 +8243,185 @@ export interface GroupNodeConfig<T> {
|
|
|
6988
8243
|
|
|
6989
8244
|
_Source: src/form/types/index.ts_
|
|
6990
8245
|
|
|
8246
|
+
### hideWhen
|
|
8247
|
+
|
|
8248
|
+
**Kind:** `function`
|
|
8249
|
+
|
|
8250
|
+
Скрывать узел, пока условие истинно.
|
|
8251
|
+
|
|
8252
|
+
Условие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.
|
|
8253
|
+
Принудительное `schema.node(selector).setHidden(...)` главнее условия.
|
|
8254
|
+
|
|
8255
|
+
**Signature:**
|
|
8256
|
+
```typescript
|
|
8257
|
+
export function hideWhen(node: SchemaNodeControl, condition: () => boolean): void
|
|
8258
|
+
```
|
|
8259
|
+
|
|
8260
|
+
**Examples:**
|
|
8261
|
+
|
|
8262
|
+
```typescript
|
|
8263
|
+
hideWhen(schema.node('mortgage'), () => model.loanType !== 'mortgage');
|
|
8264
|
+
```
|
|
8265
|
+
|
|
8266
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
8267
|
+
|
|
8268
|
+
### I18N_CONTEXT_MARKER
|
|
8269
|
+
|
|
8270
|
+
**Kind:** `const`
|
|
8271
|
+
|
|
8272
|
+
Имя контекста и одновременно маркер, по которому страж ищет его копии в сборках.
|
|
8273
|
+
|
|
8274
|
+
**Signature:**
|
|
8275
|
+
```typescript
|
|
8276
|
+
export const I18N_CONTEXT_MARKER
|
|
8277
|
+
```
|
|
8278
|
+
|
|
8279
|
+
_Source: src/platforms/react/i18n/context.ts_
|
|
8280
|
+
|
|
8281
|
+
### I18nContextValue
|
|
8282
|
+
|
|
8283
|
+
**Kind:** `interface`
|
|
8284
|
+
|
|
8285
|
+
Что видит компонент: ручка активной локали и состояние её загрузки.
|
|
8286
|
+
|
|
8287
|
+
**Signature:**
|
|
8288
|
+
```typescript
|
|
8289
|
+
export interface I18nContextValue extends I18nHandle {
|
|
8290
|
+
/** Идёт загрузка другого языка; на экране пока прежний. Без загрузчика всегда `false`. */
|
|
8291
|
+
readonly pending: boolean;
|
|
8292
|
+
/** Ошибка последней загрузки языка. */
|
|
8293
|
+
readonly error: unknown;
|
|
8294
|
+
}
|
|
8295
|
+
```
|
|
8296
|
+
|
|
8297
|
+
_Source: src/platforms/react/i18n/context.ts_
|
|
8298
|
+
|
|
8299
|
+
### I18nHandle
|
|
8300
|
+
|
|
8301
|
+
**Kind:** `interface`
|
|
8302
|
+
|
|
8303
|
+
Ручка локали: перевод и форматирование по одному языку.
|
|
8304
|
+
|
|
8305
|
+
**Signature:**
|
|
8306
|
+
```typescript
|
|
8307
|
+
export interface I18nHandle {
|
|
8308
|
+
/** Локаль, по которой работает ручка. */
|
|
8309
|
+
readonly locale: FormLocale;
|
|
8310
|
+
/** Код языка — то же, что `locale.code`. */
|
|
8311
|
+
readonly code: string;
|
|
8312
|
+
/** Есть ли ключ в словаре локали (встроенные английские таблицы не учитываются). */
|
|
8313
|
+
has(key: string): boolean;
|
|
8314
|
+
/**
|
|
8315
|
+
* Перевод ключа приложения: словарь локали → `defaultMessage` → встроенная таблица ядра → ключ.
|
|
8316
|
+
*
|
|
8317
|
+
* @param key - Ключ сообщения.
|
|
8318
|
+
* @param values - Значения подстановок.
|
|
8319
|
+
* @param defaultMessage - Английский текст на случай, если ключа в локали нет.
|
|
8320
|
+
*/
|
|
8321
|
+
t(key: string, values?: MessageValues, defaultMessage?: string): string;
|
|
8322
|
+
/** Число по правилам языка. */
|
|
8323
|
+
number(value: number, options?: Intl.NumberFormatOptions): string;
|
|
8324
|
+
/** Дата по правилам языка. */
|
|
8325
|
+
date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
|
|
8326
|
+
/** Размер файла: `1536` → `1.5 KB` (`1,5 КБ` в русской локали). База — 1024. */
|
|
8327
|
+
fileSize(bytes: number): string;
|
|
8328
|
+
}
|
|
8329
|
+
```
|
|
8330
|
+
|
|
8331
|
+
_Source: src/i18n/translator.ts_
|
|
8332
|
+
|
|
8333
|
+
### I18nLoadingProviderProps
|
|
8334
|
+
|
|
8335
|
+
**Kind:** `interface`
|
|
8336
|
+
|
|
8337
|
+
Режим «провайдер грузит сам».
|
|
8338
|
+
|
|
8339
|
+
**Signature:**
|
|
8340
|
+
```typescript
|
|
8341
|
+
export interface I18nLoadingProviderProps {
|
|
8342
|
+
/** Код языка. Смена кода запускает загрузку; до её конца на экране прежний язык. */
|
|
8343
|
+
lang: string;
|
|
8344
|
+
/** Загрузчик локалей — обычно результат `createLocaleLoader`. */
|
|
8345
|
+
load: LocaleLoad;
|
|
8346
|
+
/**
|
|
8347
|
+
* Что показать, пока не загружена самая первая локаль. По умолчанию — ничего: иначе на кадр
|
|
8348
|
+
* мелькнул бы встроенный английский. Если локаль уже в кэше загрузчика (`load.preload(lang)`
|
|
8349
|
+
* до монтирования), первый рендер синхронный и `fallback` не показывается.
|
|
8350
|
+
*/
|
|
8351
|
+
fallback?: ReactNode;
|
|
8352
|
+
/** Отказ загрузки. Язык на экране при этом не меняется. */
|
|
8353
|
+
onError?: (error: unknown, lang: string) => void;
|
|
8354
|
+
children?: ReactNode;
|
|
8355
|
+
}
|
|
8356
|
+
```
|
|
8357
|
+
|
|
8358
|
+
_Source: src/platforms/react/i18n/provider.ts_
|
|
8359
|
+
|
|
8360
|
+
### I18nProvider
|
|
8361
|
+
|
|
8362
|
+
**Kind:** `function`
|
|
8363
|
+
|
|
8364
|
+
Провайдер локализации для подписей компонентов, ошибок валидации и авторских подписей формы.
|
|
8365
|
+
Без провайдера компоненты говорят встроенным английским.
|
|
8366
|
+
|
|
8367
|
+
При смене `lang` на экране остаётся прежний язык, пока новый не загрузится (`useI18n().pending`
|
|
8368
|
+
— для индикатора). При быстром переключении применяется только последний запрос. Вложенный
|
|
8369
|
+
провайдер заменяет локаль для своего поддерева целиком.
|
|
8370
|
+
|
|
8371
|
+
**Signature:**
|
|
8372
|
+
```typescript
|
|
8373
|
+
export function I18nProvider(props: I18nProviderProps): ReactElement | null
|
|
8374
|
+
```
|
|
8375
|
+
|
|
8376
|
+
**Examples:**
|
|
8377
|
+
|
|
8378
|
+
Провайдер грузит локаль сам
|
|
8379
|
+
```tsx
|
|
8380
|
+
const [lang, setLang] = useState('ru');
|
|
8381
|
+
|
|
8382
|
+
<I18nProvider lang={lang} load={loadLocale}>
|
|
8383
|
+
<LangSwitch value={lang} onChange={setLang} />
|
|
8384
|
+
<CreditForm />
|
|
8385
|
+
</I18nProvider>
|
|
8386
|
+
```
|
|
8387
|
+
|
|
8388
|
+
Готовая локаль — SSR и тесты
|
|
8389
|
+
```tsx
|
|
8390
|
+
import { ru } from '@reformer/ui-kit/locale/ru';
|
|
8391
|
+
|
|
8392
|
+
<I18nProvider locale={ru}><CreditForm /></I18nProvider>
|
|
8393
|
+
```
|
|
8394
|
+
|
|
8395
|
+
_Source: src/platforms/react/i18n/provider.ts_
|
|
8396
|
+
|
|
8397
|
+
### I18nProviderProps
|
|
8398
|
+
|
|
8399
|
+
**Kind:** `type`
|
|
8400
|
+
|
|
8401
|
+
**Signature:**
|
|
8402
|
+
```typescript
|
|
8403
|
+
export type I18nProviderProps = I18nStaticProviderProps | I18nLoadingProviderProps;
|
|
8404
|
+
```
|
|
8405
|
+
|
|
8406
|
+
_Source: src/platforms/react/i18n/provider.ts_
|
|
8407
|
+
|
|
8408
|
+
### I18nStaticProviderProps
|
|
8409
|
+
|
|
8410
|
+
**Kind:** `interface`
|
|
8411
|
+
|
|
8412
|
+
Режим «готовая локаль».
|
|
8413
|
+
|
|
8414
|
+
**Signature:**
|
|
8415
|
+
```typescript
|
|
8416
|
+
export interface I18nStaticProviderProps {
|
|
8417
|
+
/** Локаль. Смена объекта переключает язык сразу. */
|
|
8418
|
+
locale: FormLocale;
|
|
8419
|
+
children?: ReactNode;
|
|
8420
|
+
}
|
|
8421
|
+
```
|
|
8422
|
+
|
|
8423
|
+
_Source: src/platforms/react/i18n/provider.ts_
|
|
8424
|
+
|
|
6991
8425
|
### integer
|
|
6992
8426
|
|
|
6993
8427
|
**Kind:** `function`
|
|
@@ -7233,6 +8667,19 @@ if (isGroupNode(node)) {
|
|
|
7233
8667
|
|
|
7234
8668
|
_Source: src/form/type-guards.ts_
|
|
7235
8669
|
|
|
8670
|
+
### isMessageDescriptor
|
|
8671
|
+
|
|
8672
|
+
**Kind:** `function`
|
|
8673
|
+
|
|
8674
|
+
Является ли значение описателем сообщения.
|
|
8675
|
+
|
|
8676
|
+
**Signature:**
|
|
8677
|
+
```typescript
|
|
8678
|
+
export function isMessageDescriptor(value: unknown): value is MessageDescriptor
|
|
8679
|
+
```
|
|
8680
|
+
|
|
8681
|
+
_Source: src/i18n/descriptor.ts_
|
|
8682
|
+
|
|
7236
8683
|
### isModelContainerSignal
|
|
7237
8684
|
|
|
7238
8685
|
**Kind:** `function`
|
|
@@ -7293,6 +8740,207 @@ validate(model.$.amount, [required(), isNumber({ message: 'Введите чис
|
|
|
7293
8740
|
|
|
7294
8741
|
_Source: src/form/validators/is-number.ts_
|
|
7295
8742
|
|
|
8743
|
+
### isValueSignal
|
|
8744
|
+
|
|
8745
|
+
**Kind:** `function`
|
|
8746
|
+
|
|
8747
|
+
«Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив
|
|
8748
|
+
дерева `model.$` (значение — массив целиком, `.value` читается и пишется).
|
|
8749
|
+
|
|
8750
|
+
Группа сюда не входит: она читается целиком, но не пишется и полем не бывает. Этой проверкой
|
|
8751
|
+
`createForm` и рендереры отличают узел-поле схемы (`{ value, component }`) от контейнера —
|
|
8752
|
+
вместо голого `value instanceof Signal`, который узел-массив не проходит.
|
|
8753
|
+
|
|
8754
|
+
**Signature:**
|
|
8755
|
+
```typescript
|
|
8756
|
+
export function isValueSignal(value: unknown): value is Signal<unknown>
|
|
8757
|
+
```
|
|
8758
|
+
|
|
8759
|
+
**Examples:**
|
|
8760
|
+
|
|
8761
|
+
```typescript
|
|
8762
|
+
const model = createModel({ name: '', tags: [] as string[], address: { city: '' } });
|
|
8763
|
+
isValueSignal(model.$.name); // true — лист
|
|
8764
|
+
isValueSignal(model.$.tags); // true — массив как значение
|
|
8765
|
+
isValueSignal(model.$.address); // false — группа
|
|
8766
|
+
```
|
|
8767
|
+
|
|
8768
|
+
_Source: src/model/model-signals-proxy.ts_
|
|
8769
|
+
|
|
8770
|
+
### loadCoreLocale
|
|
8771
|
+
|
|
8772
|
+
**Kind:** `const`
|
|
8773
|
+
|
|
8774
|
+
Источник локалей ядра для `createLocaleLoader`: чанк нужного языка по запросу.
|
|
8775
|
+
|
|
8776
|
+
**Signature:**
|
|
8777
|
+
```typescript
|
|
8778
|
+
export const loadCoreLocale
|
|
8779
|
+
```
|
|
8780
|
+
|
|
8781
|
+
**Examples:**
|
|
8782
|
+
|
|
8783
|
+
```ts
|
|
8784
|
+
const loadLocale = createLocaleLoader([loadCoreLocale, fetchMessages((code) => `/locales/${code}.json`)]);
|
|
8785
|
+
```
|
|
8786
|
+
|
|
8787
|
+
_Source: src/locale/index.ts_
|
|
8788
|
+
|
|
8789
|
+
### LocaleIssue
|
|
8790
|
+
|
|
8791
|
+
**Kind:** `interface`
|
|
8792
|
+
|
|
8793
|
+
Что не так со словарём.
|
|
8794
|
+
|
|
8795
|
+
**Signature:**
|
|
8796
|
+
```typescript
|
|
8797
|
+
export interface LocaleIssue {
|
|
8798
|
+
readonly key: string;
|
|
8799
|
+
/**
|
|
8800
|
+
* - `syntax` — сообщение не разбирается;
|
|
8801
|
+
* - `missing` — ключ есть в образце, но его нет в словаре;
|
|
8802
|
+
* - `extra` — ключа нет в образце;
|
|
8803
|
+
* - `arguments` — набор подстановок не совпадает с образцом.
|
|
8804
|
+
*/
|
|
8805
|
+
readonly kind: 'syntax' | 'missing' | 'extra' | 'arguments';
|
|
8806
|
+
readonly message: string;
|
|
8807
|
+
}
|
|
8808
|
+
```
|
|
8809
|
+
|
|
8810
|
+
_Source: src/i18n/locale.ts_
|
|
8811
|
+
|
|
8812
|
+
### LocaleLoad
|
|
8813
|
+
|
|
8814
|
+
**Kind:** `interface`
|
|
8815
|
+
|
|
8816
|
+
Функция загрузки локали. `peek` — необязательный синхронный кэш (есть у `createLocaleLoader`).
|
|
8817
|
+
|
|
8818
|
+
**Signature:**
|
|
8819
|
+
```typescript
|
|
8820
|
+
export interface LocaleLoad {
|
|
8821
|
+
(code: string): Promise<FormLocale>;
|
|
8822
|
+
peek?(code: string): FormLocale | undefined;
|
|
8823
|
+
}
|
|
8824
|
+
```
|
|
8825
|
+
|
|
8826
|
+
_Source: src/i18n/locale-store.ts_
|
|
8827
|
+
|
|
8828
|
+
### LocaleLoader
|
|
8829
|
+
|
|
8830
|
+
**Kind:** `interface`
|
|
8831
|
+
|
|
8832
|
+
Загрузчик локалей с кэшем.
|
|
8833
|
+
|
|
8834
|
+
**Signature:**
|
|
8835
|
+
```typescript
|
|
8836
|
+
export interface LocaleLoader {
|
|
8837
|
+
/** Загрузить локаль языка. Повторный вызов возвращает тот же промис и ту же локаль. */
|
|
8838
|
+
(code: string): Promise<FormLocale>;
|
|
8839
|
+
/** Уже загруженная локаль или `undefined`. Позволяет провайдеру отрисоваться синхронно. */
|
|
8840
|
+
peek(code: string): FormLocale | undefined;
|
|
8841
|
+
/** То же, что вызов загрузчика; имя для чтения в коде старта приложения. */
|
|
8842
|
+
preload(code: string): Promise<FormLocale>;
|
|
8843
|
+
}
|
|
8844
|
+
```
|
|
8845
|
+
|
|
8846
|
+
_Source: src/i18n/loader.ts_
|
|
8847
|
+
|
|
8848
|
+
### LocaleSource
|
|
8849
|
+
|
|
8850
|
+
**Kind:** `type`
|
|
8851
|
+
|
|
8852
|
+
Источник локали. Получает код языка, отдаёт локаль или словарь — сразу или промисом.
|
|
8853
|
+
`null`/`undefined` означает «для этого языка у меня ничего нет» и ошибкой не считается.
|
|
8854
|
+
|
|
8855
|
+
**Signature:**
|
|
8856
|
+
```typescript
|
|
8857
|
+
export type LocaleSource = (code: string) => LocaleSourceResult | Promise<LocaleSourceResult>;
|
|
8858
|
+
```
|
|
8859
|
+
|
|
8860
|
+
_Source: src/i18n/loader.ts_
|
|
8861
|
+
|
|
8862
|
+
### LocaleSourceResult
|
|
8863
|
+
|
|
8864
|
+
**Kind:** `type`
|
|
8865
|
+
|
|
8866
|
+
Что может вернуть источник: локаль целиком, её часть, голый словарь или ничего.
|
|
8867
|
+
|
|
8868
|
+
**Signature:**
|
|
8869
|
+
```typescript
|
|
8870
|
+
export type LocaleSourceResult = Partial<FormLocale> | Messages | null | undefined;
|
|
8871
|
+
```
|
|
8872
|
+
|
|
8873
|
+
_Source: src/i18n/loader.ts_
|
|
8874
|
+
|
|
8875
|
+
### LocaleStore
|
|
8876
|
+
|
|
8877
|
+
**Kind:** `interface`
|
|
8878
|
+
|
|
8879
|
+
Хранилище активной локали.
|
|
8880
|
+
|
|
8881
|
+
**Signature:**
|
|
8882
|
+
```typescript
|
|
8883
|
+
export interface LocaleStore {
|
|
8884
|
+
getState(): LocaleStoreState;
|
|
8885
|
+
subscribe(listener: () => void): () => void;
|
|
8886
|
+
/** Запросить язык. Повторный запрос того же языка, пока он грузится или уже показан, — no-op. */
|
|
8887
|
+
setLang(lang: string): void;
|
|
8888
|
+
}
|
|
8889
|
+
```
|
|
8890
|
+
|
|
8891
|
+
_Source: src/i18n/locale-store.ts_
|
|
8892
|
+
|
|
8893
|
+
### LocaleStoreOptions
|
|
8894
|
+
|
|
8895
|
+
**Kind:** `interface`
|
|
8896
|
+
|
|
8897
|
+
Настройки хранилища.
|
|
8898
|
+
|
|
8899
|
+
**Signature:**
|
|
8900
|
+
```typescript
|
|
8901
|
+
export interface LocaleStoreOptions {
|
|
8902
|
+
/** Вызывается при отказе загрузки языка, который всё ещё запрошен. */
|
|
8903
|
+
readonly onError?: (error: unknown, lang: string) => void;
|
|
8904
|
+
}
|
|
8905
|
+
```
|
|
8906
|
+
|
|
8907
|
+
_Source: src/i18n/locale-store.ts_
|
|
8908
|
+
|
|
8909
|
+
### LocaleStoreState
|
|
8910
|
+
|
|
8911
|
+
**Kind:** `interface`
|
|
8912
|
+
|
|
8913
|
+
Состояние хранилища. Объект неизменяемый: новая ссылка — новое состояние.
|
|
8914
|
+
|
|
8915
|
+
**Signature:**
|
|
8916
|
+
```typescript
|
|
8917
|
+
export interface LocaleStoreState {
|
|
8918
|
+
/** Запрошенный язык. Пока `pending`, может отличаться от языка `locale`. */
|
|
8919
|
+
readonly lang: string;
|
|
8920
|
+
/** Локаль, которая сейчас на экране; `null` — ни одна ещё не загружена. */
|
|
8921
|
+
readonly locale: FormLocale | null;
|
|
8922
|
+
/** Идёт загрузка запрошенного языка. */
|
|
8923
|
+
readonly pending: boolean;
|
|
8924
|
+
/** Ошибка последней загрузки; сбрасывается следующей удачной. */
|
|
8925
|
+
readonly error: unknown;
|
|
8926
|
+
}
|
|
8927
|
+
```
|
|
8928
|
+
|
|
8929
|
+
_Source: src/i18n/locale-store.ts_
|
|
8930
|
+
|
|
8931
|
+
### LocalizableText
|
|
8932
|
+
|
|
8933
|
+
**Kind:** `type`
|
|
8934
|
+
|
|
8935
|
+
Текст, который можно задать строкой или описателем.
|
|
8936
|
+
|
|
8937
|
+
**Signature:**
|
|
8938
|
+
```typescript
|
|
8939
|
+
export type LocalizableText = string | MessageDescriptor;
|
|
8940
|
+
```
|
|
8941
|
+
|
|
8942
|
+
_Source: src/i18n/descriptor.ts_
|
|
8943
|
+
|
|
7296
8944
|
### makeElementFieldHandle
|
|
7297
8945
|
|
|
7298
8946
|
**Kind:** `function`
|
|
@@ -7530,122 +9178,280 @@ _Source: src/form/validators/max-files.ts_
|
|
|
7530
9178
|
|
|
7531
9179
|
Фабрика валидатора максимального размера файла.
|
|
7532
9180
|
|
|
7533
|
-
Работает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с
|
|
7534
|
-
`name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.
|
|
7535
|
-
Пустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`
|
|
7536
|
-
(например, дескриптор загруженного файла без размера) пропускаются
|
|
7537
|
-
(используйте {@link required} для обязательности).
|
|
9181
|
+
Работает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с
|
|
9182
|
+
`name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.
|
|
9183
|
+
Пустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`
|
|
9184
|
+
(например, дескриптор загруженного файла без размера) пропускаются
|
|
9185
|
+
(используйте {@link required} для обязательности).
|
|
9186
|
+
|
|
9187
|
+
**Signature:**
|
|
9188
|
+
```typescript
|
|
9189
|
+
export function maxFileSize<TForm = unknown, TField = unknown>(
|
|
9190
|
+
maxSize: number,
|
|
9191
|
+
options?: ValidateOptions
|
|
9192
|
+
): Validator<TForm, TField>
|
|
9193
|
+
```
|
|
9194
|
+
|
|
9195
|
+
**Parameters:**
|
|
9196
|
+
- `maxSize` — - Максимально допустимый размер файла в байтах (включительно)
|
|
9197
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
9198
|
+
попадают `maxFileSize`, `fileName` и `actualSize`.
|
|
9199
|
+
|
|
9200
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
9201
|
+
|
|
9202
|
+
**Examples:**
|
|
9203
|
+
|
|
9204
|
+
Ограничение размера вложений
|
|
9205
|
+
```typescript
|
|
9206
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
9207
|
+
import { maxFileSize } from '@reformer/core/validators';
|
|
9208
|
+
|
|
9209
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
9210
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
9211
|
+
validate(model.$.documents, [maxFileSize(5 * 1024 * 1024, { message: 'Файл больше 5 МБ' })]);
|
|
9212
|
+
});
|
|
9213
|
+
```
|
|
9214
|
+
|
|
9215
|
+
_Source: src/form/validators/max-file-size.ts_
|
|
9216
|
+
|
|
9217
|
+
### maxLength
|
|
9218
|
+
|
|
9219
|
+
**Kind:** `function`
|
|
9220
|
+
|
|
9221
|
+
Фабрика валидатора максимальной длины строки или массива.
|
|
9222
|
+
|
|
9223
|
+
Работает со строкой или массивом (проверяется `value.length`). Пустые значения
|
|
9224
|
+
(`null`/`undefined`/`''`) и значения без числового `length` пропускаются
|
|
9225
|
+
(используйте {@link required} для обязательности).
|
|
9226
|
+
|
|
9227
|
+
**Signature:**
|
|
9228
|
+
```typescript
|
|
9229
|
+
export function maxLength<TForm = unknown, TField = unknown>(
|
|
9230
|
+
maxLen: number,
|
|
9231
|
+
options?: ValidateOptions
|
|
9232
|
+
): Validator<TForm, TField>
|
|
9233
|
+
```
|
|
9234
|
+
|
|
9235
|
+
**Parameters:**
|
|
9236
|
+
- `maxLen` — - Максимально допустимая длина (включительно)
|
|
9237
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
9238
|
+
попадают `maxLength` и `actualLength`.
|
|
9239
|
+
|
|
9240
|
+
**Returns:** Чистый валидатор {@link Validator} для строки или массива
|
|
9241
|
+
|
|
9242
|
+
**Examples:**
|
|
9243
|
+
|
|
9244
|
+
Максимальная длина строки
|
|
9245
|
+
```typescript
|
|
9246
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
9247
|
+
import { maxLength } from '@reformer/core/validators';
|
|
9248
|
+
|
|
9249
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
9250
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
9251
|
+
validate(model.$.name, [maxLength(50)]);
|
|
9252
|
+
validate(model.$.bio, [maxLength(500, { message: 'Максимум 500 символов' })]);
|
|
9253
|
+
});
|
|
9254
|
+
```
|
|
9255
|
+
|
|
9256
|
+
_Source: src/form/validators/max-length.ts_
|
|
9257
|
+
|
|
9258
|
+
### maxTotalFileSize
|
|
9259
|
+
|
|
9260
|
+
**Kind:** `function`
|
|
9261
|
+
|
|
9262
|
+
Фабрика валидатора суммарного размера всех файлов.
|
|
9263
|
+
|
|
9264
|
+
Складывает `size` всех файлов значения (элементы без числового `size` в сумму
|
|
9265
|
+
не входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются
|
|
9266
|
+
(используйте {@link required} для обязательности).
|
|
9267
|
+
|
|
9268
|
+
**Signature:**
|
|
9269
|
+
```typescript
|
|
9270
|
+
export function maxTotalFileSize<TForm = unknown, TField = unknown>(
|
|
9271
|
+
maxTotal: number,
|
|
9272
|
+
options?: ValidateOptions
|
|
9273
|
+
): Validator<TForm, TField>
|
|
9274
|
+
```
|
|
9275
|
+
|
|
9276
|
+
**Parameters:**
|
|
9277
|
+
- `maxTotal` — - Максимально допустимый суммарный размер в байтах (включительно)
|
|
9278
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
9279
|
+
попадают `maxTotalFileSize` и `actualTotal`.
|
|
9280
|
+
|
|
9281
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
9282
|
+
|
|
9283
|
+
**Examples:**
|
|
9284
|
+
|
|
9285
|
+
Суммарно не более 20 МБ
|
|
9286
|
+
```typescript
|
|
9287
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
9288
|
+
import { maxTotalFileSize } from '@reformer/core/validators';
|
|
9289
|
+
|
|
9290
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
9291
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
9292
|
+
validate(model.$.documents, [maxTotalFileSize(20 * 1024 * 1024, { message: 'Суммарно не более 20 МБ' })]);
|
|
9293
|
+
});
|
|
9294
|
+
```
|
|
9295
|
+
|
|
9296
|
+
_Source: src/form/validators/max-total-file-size.ts_
|
|
9297
|
+
|
|
9298
|
+
### MESSAGE_DESCRIPTOR
|
|
9299
|
+
|
|
9300
|
+
**Kind:** `const`
|
|
9301
|
+
|
|
9302
|
+
Бренд описателя. `Symbol.for` — чтобы описатель узнавался и через границу двух копий модуля
|
|
9303
|
+
(разные чанки, разные версии пакета на странице).
|
|
9304
|
+
|
|
9305
|
+
**Signature:**
|
|
9306
|
+
```typescript
|
|
9307
|
+
export const MESSAGE_DESCRIPTOR: unique symbol
|
|
9308
|
+
```
|
|
9309
|
+
|
|
9310
|
+
_Source: src/i18n/descriptor.ts_
|
|
9311
|
+
|
|
9312
|
+
### messageArguments
|
|
9313
|
+
|
|
9314
|
+
**Kind:** `function`
|
|
9315
|
+
|
|
9316
|
+
Имена аргументов сообщения, включая те, что встречаются только внутри веток `plural`/`select`.
|
|
9317
|
+
|
|
9318
|
+
**Signature:**
|
|
9319
|
+
```typescript
|
|
9320
|
+
export function messageArguments(
|
|
9321
|
+
pattern: MessagePattern,
|
|
9322
|
+
out: Set<string> = new Set()
|
|
9323
|
+
): Set<string>
|
|
9324
|
+
```
|
|
9325
|
+
|
|
9326
|
+
**Parameters:**
|
|
9327
|
+
- `pattern` — - Результат `parseMessage`.
|
|
9328
|
+
|
|
9329
|
+
_Source: src/i18n/locale.ts_
|
|
9330
|
+
|
|
9331
|
+
### MessageBranches
|
|
9332
|
+
|
|
9333
|
+
**Kind:** `type`
|
|
9334
|
+
|
|
9335
|
+
Ветки `plural`/`select`: категория CLDR (`one`, `few`, …), точное совпадение (`=0`) или значение `select`.
|
|
9336
|
+
|
|
9337
|
+
**Signature:**
|
|
9338
|
+
```typescript
|
|
9339
|
+
export type MessageBranches = ReadonlyMap<string, MessagePattern>;
|
|
9340
|
+
```
|
|
9341
|
+
|
|
9342
|
+
_Source: src/i18n/message-format.ts_
|
|
9343
|
+
|
|
9344
|
+
### MessageDescriptor
|
|
9345
|
+
|
|
9346
|
+
**Kind:** `interface`
|
|
9347
|
+
|
|
9348
|
+
Описатель сообщения. Намеренно без ключей `value`, `array`, `item`, `component`: обход схемы в
|
|
9349
|
+
`createForm` узнаёт по ним узел, а описатель узлом не является.
|
|
9350
|
+
|
|
9351
|
+
**Signature:**
|
|
9352
|
+
```typescript
|
|
9353
|
+
export interface MessageDescriptor {
|
|
9354
|
+
readonly [MESSAGE_DESCRIPTOR]: true;
|
|
9355
|
+
/** Ключ сообщения в словаре приложения. */
|
|
9356
|
+
readonly key: string;
|
|
9357
|
+
/** Значения подстановок. */
|
|
9358
|
+
readonly values?: Readonly<Record<string, unknown>>;
|
|
9359
|
+
/** Английский текст на случай, если ключа в локали нет. */
|
|
9360
|
+
readonly defaultMessage?: string;
|
|
9361
|
+
}
|
|
9362
|
+
```
|
|
9363
|
+
|
|
9364
|
+
_Source: src/i18n/descriptor.ts_
|
|
9365
|
+
|
|
9366
|
+
### MessageFormatOptions
|
|
9367
|
+
|
|
9368
|
+
**Kind:** `interface`
|
|
9369
|
+
|
|
9370
|
+
Настройки форматирования.
|
|
7538
9371
|
|
|
7539
9372
|
**Signature:**
|
|
7540
9373
|
```typescript
|
|
7541
|
-
export
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
)
|
|
9374
|
+
export interface MessageFormatOptions {
|
|
9375
|
+
/**
|
|
9376
|
+
* Что подставить вместо аргумента, которого нет в значениях (или чьё значение не число там, где
|
|
9377
|
+
* ожидалось число). По умолчанию — заметный маркер `⟦name⟧`, тот же приём, что и для пропущенного
|
|
9378
|
+
* ключа: пропуск должен попадаться разработчику, а не пользователю.
|
|
9379
|
+
*/
|
|
9380
|
+
readonly onMissingArgument?: (name: string) => string;
|
|
9381
|
+
}
|
|
7545
9382
|
```
|
|
7546
9383
|
|
|
7547
|
-
|
|
7548
|
-
- `maxSize` — - Максимально допустимый размер файла в байтах (включительно)
|
|
7549
|
-
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7550
|
-
попадают `maxFileSize`, `fileName` и `actualSize`.
|
|
9384
|
+
_Source: src/i18n/message-format.ts_
|
|
7551
9385
|
|
|
7552
|
-
|
|
9386
|
+
### MessageNode
|
|
7553
9387
|
|
|
7554
|
-
**
|
|
9388
|
+
**Kind:** `type`
|
|
7555
9389
|
|
|
7556
|
-
|
|
7557
|
-
```typescript
|
|
7558
|
-
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7559
|
-
import { maxFileSize } from '@reformer/core/validators';
|
|
9390
|
+
Узел разобранного сообщения.
|
|
7560
9391
|
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
}
|
|
9392
|
+
**Signature:**
|
|
9393
|
+
```typescript
|
|
9394
|
+
export type MessageNode =
|
|
9395
|
+
| { readonly kind: 'text'; readonly value: string }
|
|
9396
|
+
| { readonly kind: 'arg'; readonly name: string }
|
|
9397
|
+
| { readonly kind: 'number'; readonly name: string }
|
|
9398
|
+
| { readonly kind: 'plural'; readonly name: string; readonly branches: MessageBranches }
|
|
9399
|
+
| { readonly kind: 'select'; readonly name: string; readonly branches: MessageBranches };
|
|
7565
9400
|
```
|
|
7566
9401
|
|
|
7567
|
-
_Source: src/
|
|
7568
|
-
|
|
7569
|
-
### maxLength
|
|
9402
|
+
_Source: src/i18n/message-format.ts_
|
|
7570
9403
|
|
|
7571
|
-
|
|
9404
|
+
### MessagePattern
|
|
7572
9405
|
|
|
7573
|
-
|
|
9406
|
+
**Kind:** `type`
|
|
7574
9407
|
|
|
7575
|
-
|
|
7576
|
-
(`null`/`undefined`/`''`) и значения без числового `length` пропускаются
|
|
7577
|
-
(используйте {@link required} для обязательности).
|
|
9408
|
+
Разобранное сообщение — плоская последовательность узлов.
|
|
7578
9409
|
|
|
7579
9410
|
**Signature:**
|
|
7580
9411
|
```typescript
|
|
7581
|
-
export
|
|
7582
|
-
maxLen: number,
|
|
7583
|
-
options?: ValidateOptions
|
|
7584
|
-
): Validator<TForm, TField>
|
|
9412
|
+
export type MessagePattern = readonly MessageNode[];
|
|
7585
9413
|
```
|
|
7586
9414
|
|
|
7587
|
-
|
|
7588
|
-
- `maxLen` — - Максимально допустимая длина (включительно)
|
|
7589
|
-
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7590
|
-
попадают `maxLength` и `actualLength`.
|
|
9415
|
+
_Source: src/i18n/message-format.ts_
|
|
7591
9416
|
|
|
7592
|
-
|
|
9417
|
+
### Messages
|
|
7593
9418
|
|
|
7594
|
-
**
|
|
9419
|
+
**Kind:** `type`
|
|
7595
9420
|
|
|
7596
|
-
|
|
7597
|
-
```typescript
|
|
7598
|
-
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7599
|
-
import { maxLength } from '@reformer/core/validators';
|
|
9421
|
+
Плоский словарь: ключ → сообщение в формате ICU (см. `message-format.ts`).
|
|
7600
9422
|
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
validate(model.$.bio, [maxLength(500, { message: 'Максимум 500 символов' })]);
|
|
7605
|
-
});
|
|
9423
|
+
**Signature:**
|
|
9424
|
+
```typescript
|
|
9425
|
+
export type Messages = Readonly<Record<string, string>>;
|
|
7606
9426
|
```
|
|
7607
9427
|
|
|
7608
|
-
_Source: src/
|
|
7609
|
-
|
|
7610
|
-
### maxTotalFileSize
|
|
9428
|
+
_Source: src/i18n/locale.ts_
|
|
7611
9429
|
|
|
7612
|
-
|
|
9430
|
+
### MessageSyntaxError
|
|
7613
9431
|
|
|
7614
|
-
|
|
9432
|
+
**Kind:** `class`
|
|
7615
9433
|
|
|
7616
|
-
|
|
7617
|
-
не входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются
|
|
7618
|
-
(используйте {@link required} для обязательности).
|
|
9434
|
+
Отказ разбора сообщения: синтаксис или отсутствие обязательной ветки `other`.
|
|
7619
9435
|
|
|
7620
9436
|
**Signature:**
|
|
7621
9437
|
```typescript
|
|
7622
|
-
export
|
|
7623
|
-
maxTotal: number,
|
|
7624
|
-
options?: ValidateOptions
|
|
7625
|
-
): Validator<TForm, TField>
|
|
9438
|
+
export class MessageSyntaxError extends Error { /* … */ }
|
|
7626
9439
|
```
|
|
7627
9440
|
|
|
7628
|
-
|
|
7629
|
-
- `maxTotal` — - Максимально допустимый суммарный размер в байтах (включительно)
|
|
7630
|
-
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7631
|
-
попадают `maxTotalFileSize` и `actualTotal`.
|
|
9441
|
+
_Source: src/i18n/message-format.ts_
|
|
7632
9442
|
|
|
7633
|
-
|
|
9443
|
+
### MessageValues
|
|
7634
9444
|
|
|
7635
|
-
**
|
|
9445
|
+
**Kind:** `type`
|
|
7636
9446
|
|
|
7637
|
-
|
|
7638
|
-
```typescript
|
|
7639
|
-
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7640
|
-
import { maxTotalFileSize } from '@reformer/core/validators';
|
|
9447
|
+
Значения аргументов сообщения.
|
|
7641
9448
|
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
});
|
|
9449
|
+
**Signature:**
|
|
9450
|
+
```typescript
|
|
9451
|
+
export type MessageValues = Readonly<Record<string, unknown>>;
|
|
7646
9452
|
```
|
|
7647
9453
|
|
|
7648
|
-
_Source: src/
|
|
9454
|
+
_Source: src/i18n/message-format.ts_
|
|
7649
9455
|
|
|
7650
9456
|
### min
|
|
7651
9457
|
|
|
@@ -7923,7 +9729,10 @@ export interface ModelApi<T> {
|
|
|
7923
9729
|
reset(): void;
|
|
7924
9730
|
/** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */
|
|
7925
9731
|
captureInitial(): void;
|
|
7926
|
-
/**
|
|
9732
|
+
/**
|
|
9733
|
+
* Резолв строкового пути в сигнал (для error-routing/мостов): лист либо массив целиком
|
|
9734
|
+
* (ручка значения массива — см. {@link ModelArraySignals}). Для пути группы — `undefined`.
|
|
9735
|
+
*/
|
|
7927
9736
|
signalAt(path: string): PathAwareSignal<unknown> | undefined;
|
|
7928
9737
|
}
|
|
7929
9738
|
```
|
|
@@ -7947,10 +9756,13 @@ export interface ModelArray<U> {
|
|
|
7947
9756
|
readonly __path: string;
|
|
7948
9757
|
/** Реактивная длина. */
|
|
7949
9758
|
readonly length: number;
|
|
7950
|
-
/**
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
9759
|
+
/**
|
|
9760
|
+
* Добавить элемент в конец (значение элемента целиком). Без значения — элемент по шаблону
|
|
9761
|
+
* массива (`arrayOf(blank)` в модели); шаблона нет — ошибка.
|
|
9762
|
+
*/
|
|
9763
|
+
push(item?: U): void;
|
|
9764
|
+
/** Вставить элемент по индексу. Без значения — элемент по шаблону, как у {@link push}. */
|
|
9765
|
+
insertAt(index: number, item?: U): void;
|
|
7954
9766
|
/** Удалить элемент по индексу. */
|
|
7955
9767
|
removeAt(index: number): void;
|
|
7956
9768
|
/** Переместить элемент. */
|
|
@@ -7987,8 +9799,8 @@ _Source: src/model/types.ts_
|
|
|
7987
9799
|
export interface ModelArrayControl<TItem extends object> {
|
|
7988
9800
|
readonly length: number;
|
|
7989
9801
|
at(index: number): FormModel<TItem> | undefined;
|
|
7990
|
-
push(item
|
|
7991
|
-
insertAt(index: number, item
|
|
9802
|
+
push(item?: TItem): void;
|
|
9803
|
+
insertAt(index: number, item?: TItem): void;
|
|
7992
9804
|
removeAt(index: number): void;
|
|
7993
9805
|
move(from: number, to: number): void;
|
|
7994
9806
|
swap(a: number, b: number): void;
|
|
@@ -8012,7 +9824,7 @@ _Source: src/form/nodes/model-array-node.ts_
|
|
|
8012
9824
|
reorder/повторном рендере не пересоздаются (состояние и валидация сохраняются). Реализует тот же
|
|
8013
9825
|
контракт, что ждут секции массива и `useFormControl` (`length`/`value`/`valid`/`errors`/`at`/`push`/…).
|
|
8014
9826
|
|
|
8015
|
-
Обычно создаётся не напрямую, а `
|
|
9827
|
+
Обычно создаётся не напрямую, а `createFormFromModel({ model, schema })`: когда в схеме встречается узел
|
|
8016
9828
|
массива `{ array: model.<field>, item: (item) => itemSchema }`, форма материализует его как
|
|
8017
9829
|
`ModelArrayNode` и кладёт под `form.<field>` (совместим с `FormArraySection`).
|
|
8018
9830
|
|
|
@@ -8032,7 +9844,7 @@ name: { value: item.$.name, component: Input },
|
|
|
8032
9844
|
qty: { value: item.$.qty, component: Input },
|
|
8033
9845
|
});
|
|
8034
9846
|
|
|
8035
|
-
const form =
|
|
9847
|
+
const form = createFormFromModel({
|
|
8036
9848
|
model,
|
|
8037
9849
|
schema: {
|
|
8038
9850
|
children: [{ array: model.rows, item: rowItem }],
|
|
@@ -8052,12 +9864,23 @@ _Source: src/form/nodes/model-array-node.ts_
|
|
|
8052
9864
|
**Kind:** `type`
|
|
8053
9865
|
|
|
8054
9866
|
Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`
|
|
8055
|
-
и
|
|
8056
|
-
|
|
9867
|
+
и сигнал значения массива целиком (реагирует и на правку элемента, и на изменение состава —
|
|
9868
|
+
push/removeAt/move).
|
|
9869
|
+
|
|
9870
|
+
В отличие от группы, узел ЗАПИСЫВАЕМ: `model.$.tags.value = ['a']` заменяет массив целиком. Это
|
|
9871
|
+
делает его {@link PathAwareSignal} — к нему привязывают поле (`{ value: model.$.tags, component }`),
|
|
9872
|
+
правило (`validate(model.$.tags, …)`) и поведение, как к листу. Так массив служит ОДНИМ значением
|
|
9873
|
+
поля: мультивыбор, теги, список файлов.
|
|
9874
|
+
|
|
9875
|
+
`V` — объявленный тип поля, включая `null`/`undefined`: поле `string[] | null` с начальным `null`
|
|
9876
|
+
в рантайме — лист с тем же `.value`/`peek`/`subscribe`, поэтому общая часть честна для обоих
|
|
9877
|
+
случаев. `length` и индексы есть только у настоящего узла-массива (начальное значение — массив).
|
|
8057
9878
|
|
|
8058
9879
|
**Signature:**
|
|
8059
9880
|
```typescript
|
|
8060
|
-
export type ModelArraySignals<U, V> =
|
|
9881
|
+
export type ModelArraySignals<U, V> = Signal<V> & {
|
|
9882
|
+
/** Путь массива в модели (dot-нотация). */
|
|
9883
|
+
readonly __path: string;
|
|
8061
9884
|
readonly length: number;
|
|
8062
9885
|
readonly [index: number]: ModelSignalNode<U>;
|
|
8063
9886
|
};
|
|
@@ -8099,6 +9922,57 @@ export type ModelObject<T> = {
|
|
|
8099
9922
|
|
|
8100
9923
|
_Source: src/model/types.ts_
|
|
8101
9924
|
|
|
9925
|
+
### modelOf
|
|
9926
|
+
|
|
9927
|
+
**Kind:** `function`
|
|
9928
|
+
|
|
9929
|
+
Value-фасад по ручке дерева `model.$`: группа → под-модель {@link FormModel}, массив →
|
|
9930
|
+
{@link ModelArray}. Обратная операция к `model.$`: `modelOf(model.$.address) === model.address`.
|
|
9931
|
+
|
|
9932
|
+
Ручка находит фасад по ИДЕНТИЧНОСТИ, а не по пути. Путь для этого не годится: он абсолютный
|
|
9933
|
+
(`items.0.phones`) и меняется при перестановке строк, а области поведения и формы строк
|
|
9934
|
+
вложенные — поиск по пути от под-модели строки ничего не находит.
|
|
9935
|
+
|
|
9936
|
+
Лист ручкой контейнера не является — функция бросает. В частности, группа или массив,
|
|
9937
|
+
созданные из начального `null`, в модели — лист: вид узла фиксируется при создании.
|
|
9938
|
+
|
|
9939
|
+
**Signature:**
|
|
9940
|
+
```typescript
|
|
9941
|
+
export function modelOf<V extends object | null | undefined>(handle: { peek(): V }): ModelOf<V>
|
|
9942
|
+
```
|
|
9943
|
+
|
|
9944
|
+
**Parameters:**
|
|
9945
|
+
- `handle` — - Узел-группа или узел-массив дерева `model.$`.
|
|
9946
|
+
|
|
9947
|
+
**Returns:** Под-модель группы либо фасад массива.
|
|
9948
|
+
|
|
9949
|
+
**Examples:**
|
|
9950
|
+
|
|
9951
|
+
```typescript
|
|
9952
|
+
const model = createModel({ address: { city: '' }, phones: [{ number: '' }] });
|
|
9953
|
+
|
|
9954
|
+
modelOf(model.$.address).city = 'Казань'; // под-модель: чтение, запись, get/set/patch
|
|
9955
|
+
modelOf(model.$.phones).push({ number: '+7' }); // фасад массива: мутации и обход
|
|
9956
|
+
modelOf(model.$.phones[0]).number; // строка массива — тоже группа
|
|
9957
|
+
```
|
|
9958
|
+
|
|
9959
|
+
_Source: src/model/model-value-proxy.ts_
|
|
9960
|
+
|
|
9961
|
+
### ModelOf
|
|
9962
|
+
|
|
9963
|
+
**Kind:** `type`
|
|
9964
|
+
|
|
9965
|
+
Value-фасад для ручки {@link modelOf}: массив → {@link ModelArray}, объект → под-модель
|
|
9966
|
+
{@link FormModel}.
|
|
9967
|
+
|
|
9968
|
+
**Signature:**
|
|
9969
|
+
```typescript
|
|
9970
|
+
export type ModelOf<V> =
|
|
9971
|
+
NonNullable<V> extends ReadonlyArray<infer U> ? ModelArray<U> : FormModel<NonNullable<V>>;
|
|
9972
|
+
```
|
|
9973
|
+
|
|
9974
|
+
_Source: src/model/model-value-proxy.ts_
|
|
9975
|
+
|
|
8102
9976
|
### ModelSignals
|
|
8103
9977
|
|
|
8104
9978
|
**Kind:** `type`
|
|
@@ -8140,6 +10014,34 @@ export type ModelValue<V> =
|
|
|
8140
10014
|
|
|
8141
10015
|
_Source: src/model/types.ts_
|
|
8142
10016
|
|
|
10017
|
+
### msg
|
|
10018
|
+
|
|
10019
|
+
**Kind:** `function`
|
|
10020
|
+
|
|
10021
|
+
Создаёт описатель сообщения.
|
|
10022
|
+
|
|
10023
|
+
**Signature:**
|
|
10024
|
+
```typescript
|
|
10025
|
+
export function msg(
|
|
10026
|
+
key: string,
|
|
10027
|
+
values?: Readonly<Record<string, unknown>>,
|
|
10028
|
+
defaultMessage?: string
|
|
10029
|
+
): MessageDescriptor
|
|
10030
|
+
```
|
|
10031
|
+
|
|
10032
|
+
**Parameters:**
|
|
10033
|
+
- `key` — - Ключ в словаре приложения.
|
|
10034
|
+
- `values` — - Значения подстановок.
|
|
10035
|
+
- `defaultMessage` — - Английский текст на случай, если ключа в локали нет.
|
|
10036
|
+
|
|
10037
|
+
**Examples:**
|
|
10038
|
+
|
|
10039
|
+
```ts
|
|
10040
|
+
{ value: model.$.email, component: Input, componentProps: { label: msg('profile.email.label') } }
|
|
10041
|
+
```
|
|
10042
|
+
|
|
10043
|
+
_Source: src/i18n/descriptor.ts_
|
|
10044
|
+
|
|
8143
10045
|
### multipleOf
|
|
8144
10046
|
|
|
8145
10047
|
**Kind:** `function`
|
|
@@ -8249,6 +10151,25 @@ const node = factory.createNode({ value: '', component: Input }); // → FieldNo
|
|
|
8249
10151
|
|
|
8250
10152
|
_Source: src/form/factories/node-factory.ts_
|
|
8251
10153
|
|
|
10154
|
+
### NodeLifecycleHooks
|
|
10155
|
+
|
|
10156
|
+
**Kind:** `interface`
|
|
10157
|
+
|
|
10158
|
+
Хуки жизненного цикла узла схемы. Каждый хук опционален; повторная регистрация перезаписывает
|
|
10159
|
+
предыдущее значение.
|
|
10160
|
+
|
|
10161
|
+
**Signature:**
|
|
10162
|
+
```typescript
|
|
10163
|
+
export interface NodeLifecycleHooks {
|
|
10164
|
+
/** Срабатывает один раз при mount узла. Может вернуть cleanup-функцию. */
|
|
10165
|
+
onMount?: () => void | (() => void);
|
|
10166
|
+
/** Срабатывает один раз при unmount узла. */
|
|
10167
|
+
onUnmount?: () => void;
|
|
10168
|
+
}
|
|
10169
|
+
```
|
|
10170
|
+
|
|
10171
|
+
_Source: src/form/schema-controller.ts_
|
|
10172
|
+
|
|
8252
10173
|
### nonNegative
|
|
8253
10174
|
|
|
8254
10175
|
**Kind:** `function`
|
|
@@ -8343,6 +10264,34 @@ export function onChange<T>(
|
|
|
8343
10264
|
|
|
8344
10265
|
_Source: src/form/behaviors/operators.ts_
|
|
8345
10266
|
|
|
10267
|
+
### onComponentEvent
|
|
10268
|
+
|
|
10269
|
+
**Kind:** `function`
|
|
10270
|
+
|
|
10271
|
+
Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).
|
|
10272
|
+
|
|
10273
|
+
Обработчик получает те же аргументы, что и исходный проп компонента, и перекрывает одноимённый
|
|
10274
|
+
проп из `componentProps`.
|
|
10275
|
+
|
|
10276
|
+
**Signature:**
|
|
10277
|
+
```typescript
|
|
10278
|
+
export function onComponentEvent(
|
|
10279
|
+
node: SchemaNodeControl,
|
|
10280
|
+
event: string,
|
|
10281
|
+
handler: (...args: any[]) => any
|
|
10282
|
+
): void
|
|
10283
|
+
```
|
|
10284
|
+
|
|
10285
|
+
**Examples:**
|
|
10286
|
+
|
|
10287
|
+
```typescript
|
|
10288
|
+
onComponentEvent(schema.node('wizard'), 'onSubmit', async () => {
|
|
10289
|
+
await submitApplication(model.get());
|
|
10290
|
+
});
|
|
10291
|
+
```
|
|
10292
|
+
|
|
10293
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10294
|
+
|
|
8346
10295
|
### onDispose
|
|
8347
10296
|
|
|
8348
10297
|
**Kind:** `function`
|
|
@@ -8356,6 +10305,95 @@ export function onDispose(cleanup: BehaviorCleanup): void
|
|
|
8356
10305
|
|
|
8357
10306
|
_Source: src/form/behaviors/context.ts_
|
|
8358
10307
|
|
|
10308
|
+
### onInit
|
|
10309
|
+
|
|
10310
|
+
**Kind:** `function`
|
|
10311
|
+
|
|
10312
|
+
Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла. Единственный
|
|
10313
|
+
хук, способный повлиять на первый рендер, — внутри можно править пропсы через
|
|
10314
|
+
`schema.node(selector).patchProps({ … })`.
|
|
10315
|
+
|
|
10316
|
+
К монтированию узла не привязан; аргумент `node` принимается ради симметрии с
|
|
10317
|
+
{@link onMount} / {@link onUnmount}.
|
|
10318
|
+
|
|
10319
|
+
**Signature:**
|
|
10320
|
+
```typescript
|
|
10321
|
+
export function onInit(_node: SchemaNodeControl, fn: () => void): void
|
|
10322
|
+
```
|
|
10323
|
+
|
|
10324
|
+
**Examples:**
|
|
10325
|
+
|
|
10326
|
+
```typescript
|
|
10327
|
+
onInit(schema.node('summary'), () => {
|
|
10328
|
+
schema.node('summary').patchProps({ currency: detectCurrency() });
|
|
10329
|
+
});
|
|
10330
|
+
```
|
|
10331
|
+
|
|
10332
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10333
|
+
|
|
10334
|
+
### onMount
|
|
10335
|
+
|
|
10336
|
+
**Kind:** `function`
|
|
10337
|
+
|
|
10338
|
+
Хук после первого монтирования узла. Может вернуть очистку — она выполнится при размонтировании.
|
|
10339
|
+
|
|
10340
|
+
**Signature:**
|
|
10341
|
+
```typescript
|
|
10342
|
+
export function onMount(node: SchemaNodeControl, fn: () => void | (() => void)): void
|
|
10343
|
+
```
|
|
10344
|
+
|
|
10345
|
+
**Examples:**
|
|
10346
|
+
|
|
10347
|
+
```typescript
|
|
10348
|
+
onMount(schema.node('data-boundary'), () => void loadApplication());
|
|
10349
|
+
```
|
|
10350
|
+
|
|
10351
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10352
|
+
|
|
10353
|
+
### onUnmount
|
|
10354
|
+
|
|
10355
|
+
**Kind:** `function`
|
|
10356
|
+
|
|
10357
|
+
Хук перед размонтированием узла.
|
|
10358
|
+
|
|
10359
|
+
**Signature:**
|
|
10360
|
+
```typescript
|
|
10361
|
+
export function onUnmount(node: SchemaNodeControl, fn: () => void): void
|
|
10362
|
+
```
|
|
10363
|
+
|
|
10364
|
+
**Examples:**
|
|
10365
|
+
|
|
10366
|
+
```typescript
|
|
10367
|
+
onUnmount(schema.node('wizard'), () => saveDraft(model.get()));
|
|
10368
|
+
```
|
|
10369
|
+
|
|
10370
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
10371
|
+
|
|
10372
|
+
### parseMessage
|
|
10373
|
+
|
|
10374
|
+
**Kind:** `function`
|
|
10375
|
+
|
|
10376
|
+
Разбирает сообщение в дерево. Бросает {@link MessageSyntaxError} на ошибке синтаксиса и на
|
|
10377
|
+
`plural`/`select` без ветки `other`.
|
|
10378
|
+
|
|
10379
|
+
**Signature:**
|
|
10380
|
+
```typescript
|
|
10381
|
+
export function parseMessage(source: string): MessagePattern
|
|
10382
|
+
```
|
|
10383
|
+
|
|
10384
|
+
**Parameters:**
|
|
10385
|
+
- `source` — - Текст сообщения из словаря.
|
|
10386
|
+
|
|
10387
|
+
**Returns:** Разобранное сообщение для {@link formatPattern}.
|
|
10388
|
+
|
|
10389
|
+
**Examples:**
|
|
10390
|
+
|
|
10391
|
+
```ts
|
|
10392
|
+
parseMessage('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}');
|
|
10393
|
+
```
|
|
10394
|
+
|
|
10395
|
+
_Source: src/i18n/message-format.ts_
|
|
10396
|
+
|
|
8359
10397
|
### pastDate
|
|
8360
10398
|
|
|
8361
10399
|
**Kind:** `function`
|
|
@@ -8526,35 +10564,60 @@ _Source: src/form/validators/phone.ts_
|
|
|
8526
10564
|
|
|
8527
10565
|
**Kind:** `function`
|
|
8528
10566
|
|
|
8529
|
-
Связать
|
|
10567
|
+
Связать ручку модели с её нодой формы.
|
|
8530
10568
|
|
|
8531
|
-
Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого
|
|
8532
|
-
|
|
10569
|
+
Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого поля,
|
|
10570
|
+
группы и массива под-форм. Прикладной код обычно этот реестр не заполняет напрямую.
|
|
8533
10571
|
|
|
8534
10572
|
**Signature:**
|
|
8535
10573
|
```typescript
|
|
8536
|
-
export function registerSignalNode(signal:
|
|
10574
|
+
export function registerSignalNode(signal: object, node: FormNode<any>): void
|
|
8537
10575
|
```
|
|
8538
10576
|
|
|
8539
10577
|
**Parameters:**
|
|
8540
|
-
- `signal` — -
|
|
8541
|
-
- `node` — - Нода формы, отвечающая за
|
|
10578
|
+
- `signal` — - Ручка из {@link FormModel}: сигнал листа, узел-массив или узел-группа дерева `model.$`
|
|
10579
|
+
- `node` — - Нода формы, отвечающая за эту часть модели
|
|
10580
|
+
|
|
10581
|
+
**Examples:**
|
|
10582
|
+
|
|
10583
|
+
Привязка листовых полей при построении формы
|
|
10584
|
+
```typescript
|
|
10585
|
+
import { registerSignalNode } from '@reformer/core';
|
|
10586
|
+
|
|
10587
|
+
const sig = model.signalAt('profile.email');
|
|
10588
|
+
const node = group.getFieldByPath('profile.email');
|
|
10589
|
+
if (sig && node) registerSignalNode(sig, node);
|
|
10590
|
+
```
|
|
10591
|
+
|
|
10592
|
+
**See also:**
|
|
10593
|
+
- {@link getNodeForSignal} - обратный поиск ноды по ручке
|
|
10594
|
+
|
|
10595
|
+
_Source: src/form/signal-node-registry.ts_
|
|
10596
|
+
|
|
10597
|
+
### renderEffect
|
|
10598
|
+
|
|
10599
|
+
**Kind:** `function`
|
|
10600
|
+
|
|
10601
|
+
Реактивный эффект, живущий, пока схема области смонтирована рендерером.
|
|
10602
|
+
|
|
10603
|
+
В отличие от `effect` поведения, запускается после монтирования — внутри доступны ref узлов
|
|
10604
|
+
(`schema.node(selector).getRef().current`). Может вернуть функцию очистки.
|
|
10605
|
+
|
|
10606
|
+
**Signature:**
|
|
10607
|
+
```typescript
|
|
10608
|
+
export function renderEffect(schema: SchemaScope, effectFn: () => void | (() => void)): void
|
|
10609
|
+
```
|
|
8542
10610
|
|
|
8543
10611
|
**Examples:**
|
|
8544
10612
|
|
|
8545
|
-
Привязка листовых полей при построении формы
|
|
8546
10613
|
```typescript
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
if (sig && node) registerSignalNode(sig, node);
|
|
10614
|
+
const wizard = schema.node('wizard').getRef<FormWizardHandle>();
|
|
10615
|
+
renderEffect(schema, () => {
|
|
10616
|
+
if (model.loanType === 'mortgage') wizard.current?.goToStep(1);
|
|
10617
|
+
});
|
|
8552
10618
|
```
|
|
8553
10619
|
|
|
8554
|
-
|
|
8555
|
-
- {@link getNodeForSignal} - обратный поиск ноды по сигналу
|
|
8556
|
-
|
|
8557
|
-
_Source: src/form/signal-node-registry.ts_
|
|
10620
|
+
_Source: src/form/behaviors/schema-operators.ts_
|
|
8558
10621
|
|
|
8559
10622
|
### required
|
|
8560
10623
|
|
|
@@ -8620,6 +10683,81 @@ resetWhen(model.$.cardNumber, () => model.paymentType !== 'card', { resetValue:
|
|
|
8620
10683
|
|
|
8621
10684
|
_Source: src/model/behaviors-value.ts_
|
|
8622
10685
|
|
|
10686
|
+
### ResolvedSchema
|
|
10687
|
+
|
|
10688
|
+
**Kind:** `interface`
|
|
10689
|
+
|
|
10690
|
+
Дерево, собранное реестром из документа схемы (JSON), и то, что реестр хочет донести до
|
|
10691
|
+
рендерера вместе с ним.
|
|
10692
|
+
|
|
10693
|
+
**Signature:**
|
|
10694
|
+
```typescript
|
|
10695
|
+
export interface ResolvedSchema {
|
|
10696
|
+
/** Корневой узел дерева. */
|
|
10697
|
+
tree: FormSchemaNode;
|
|
10698
|
+
/** Обёртка поля — компонент, которым рендерер оборачивает каждое поле. */
|
|
10699
|
+
fieldWrapper?: unknown;
|
|
10700
|
+
/** Граница ошибок — компонент, которым рендерер оборачивает дерево. */
|
|
10701
|
+
errorBoundary?: unknown;
|
|
10702
|
+
}
|
|
10703
|
+
```
|
|
10704
|
+
|
|
10705
|
+
_Source: src/form/form-bundle.ts_
|
|
10706
|
+
|
|
10707
|
+
### resolveLocalized
|
|
10708
|
+
|
|
10709
|
+
**Kind:** `function`
|
|
10710
|
+
|
|
10711
|
+
Заменяет описатели сообщений на строки по активной локали. Обходит массивы и простые
|
|
10712
|
+
объекты-литералы — подписи опций, заголовки шагов; сигналы, React-элементы, функции и
|
|
10713
|
+
экземпляры классов (узлы формы) возвращает как есть. Поддерево без описателей сохраняет ссылку:
|
|
10714
|
+
если раскрывать было нечего, возвращается тот же объект.
|
|
10715
|
+
|
|
10716
|
+
**Signature:**
|
|
10717
|
+
```typescript
|
|
10718
|
+
export function resolveLocalized<T>(value: T, i18n: I18nHandle): T
|
|
10719
|
+
```
|
|
10720
|
+
|
|
10721
|
+
**Parameters:**
|
|
10722
|
+
- `value` — - Пропсы, массив или одиночное значение.
|
|
10723
|
+
- `i18n` — - Ручка активной локали (`createI18n` / `useI18n`).
|
|
10724
|
+
|
|
10725
|
+
**Examples:**
|
|
10726
|
+
|
|
10727
|
+
```ts
|
|
10728
|
+
resolveLocalized({ label: msg('profile.email.label'), options: [{ value: 'a', label: msg('opt.a') }] }, i18n);
|
|
10729
|
+
// { label: 'Почта', options: [{ value: 'a', label: 'Вариант А' }] }
|
|
10730
|
+
```
|
|
10731
|
+
|
|
10732
|
+
_Source: src/i18n/resolve-localized.ts_
|
|
10733
|
+
|
|
10734
|
+
### resolveValidationError
|
|
10735
|
+
|
|
10736
|
+
**Kind:** `function`
|
|
10737
|
+
|
|
10738
|
+
Отображаемый текст ошибки валидации. Порядок источников — в шапке модуля.
|
|
10739
|
+
|
|
10740
|
+
**Signature:**
|
|
10741
|
+
```typescript
|
|
10742
|
+
export function resolveValidationError(error: ValidationError, i18n: I18nHandle): string
|
|
10743
|
+
```
|
|
10744
|
+
|
|
10745
|
+
**Parameters:**
|
|
10746
|
+
- `error` — - Ошибка от правила: `code`, `message`, `messageKey`, `params`.
|
|
10747
|
+
- `i18n` — - Ручка активной локали (`createI18n` / `useI18n`).
|
|
10748
|
+
|
|
10749
|
+
**Examples:**
|
|
10750
|
+
|
|
10751
|
+
```ts
|
|
10752
|
+
resolveValidationError({ code: 'minLength', message: '', params: { minLength: 8 } }, i18n);
|
|
10753
|
+
// 'Enter at least 8 characters' · в русской локали — 'Не меньше 8 символов'
|
|
10754
|
+
|
|
10755
|
+
resolveValidationError({ code: 'required', message: 'Укажите телефон' }, i18n);
|
|
10756
|
+
// 'Укажите телефон' — явное сообщение автора важнее словаря
|
|
10757
|
+
```
|
|
10758
|
+
|
|
10759
|
+
_Source: src/i18n/validation-message.ts_
|
|
10760
|
+
|
|
8623
10761
|
### revalidateWhen
|
|
8624
10762
|
|
|
8625
10763
|
**Kind:** `function`
|
|
@@ -8653,7 +10791,8 @@ _Source: src/model/behaviors-value.ts_
|
|
|
8653
10791
|
(`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`
|
|
8654
10792
|
ОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля
|
|
8655
10793
|
(`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).
|
|
8656
|
-
На вызове раннер приводит правило к callable и передаёт `(value,
|
|
10794
|
+
На вызове раннер приводит правило к callable и передаёт `(value, scope, root)`: модель текущей
|
|
10795
|
+
области (в корне прогона — вся модель) и корень прогона.
|
|
8657
10796
|
|
|
8658
10797
|
**Signature:**
|
|
8659
10798
|
```typescript
|
|
@@ -8789,13 +10928,155 @@ export interface SchemaArrayControl {
|
|
|
8789
10928
|
/** Реактивная длина. */
|
|
8790
10929
|
readonly length: number;
|
|
8791
10930
|
at(index: number): unknown;
|
|
8792
|
-
push(item
|
|
10931
|
+
push(item?: unknown): void;
|
|
8793
10932
|
removeAt(index: number): void;
|
|
8794
10933
|
}
|
|
8795
10934
|
```
|
|
8796
10935
|
|
|
8797
10936
|
_Source: src/form/types/schema-node.ts_
|
|
8798
10937
|
|
|
10938
|
+
### SchemaController
|
|
10939
|
+
|
|
10940
|
+
**Kind:** `interface`
|
|
10941
|
+
|
|
10942
|
+
Схема-контроллер сборки: области по под-моделям.
|
|
10943
|
+
|
|
10944
|
+
**Signature:**
|
|
10945
|
+
```typescript
|
|
10946
|
+
export interface SchemaController {
|
|
10947
|
+
/**
|
|
10948
|
+
* Область под-модели: корня сборки, строки массива или группы подформы. Создаётся при первом
|
|
10949
|
+
* обращении; повторное обращение отдаёт ту же область.
|
|
10950
|
+
*/
|
|
10951
|
+
scopeOf(subModel: object): SchemaScope;
|
|
10952
|
+
}
|
|
10953
|
+
```
|
|
10954
|
+
|
|
10955
|
+
_Source: src/form/schema-controller.ts_
|
|
10956
|
+
|
|
10957
|
+
### SchemaNodeControl
|
|
10958
|
+
|
|
10959
|
+
**Kind:** `interface`
|
|
10960
|
+
|
|
10961
|
+
Управление одним узлом схемы. Получается через `schema.node(selector)`.
|
|
10962
|
+
|
|
10963
|
+
**Signature:**
|
|
10964
|
+
```typescript
|
|
10965
|
+
export interface SchemaNodeControl {
|
|
10966
|
+
/** Принудительно скрыть или показать узел, минуя условие `hideWhen`. */
|
|
10967
|
+
setHidden(value: boolean): this;
|
|
10968
|
+
/** Убрать принудительное значение — снова действует условие `hideWhen`. */
|
|
10969
|
+
resetHidden(): this;
|
|
10970
|
+
/** Подмешать объект в `componentProps` узла. */
|
|
10971
|
+
patchProps(partial: Record<string, unknown>): this;
|
|
10972
|
+
/** Убрать подмешанные пропсы. */
|
|
10973
|
+
resetProps(): this;
|
|
10974
|
+
/**
|
|
10975
|
+
* Ref на компонент узла. Создаётся один раз на селектор; рендерер вешает его на компонент.
|
|
10976
|
+
* Компонент обязан принимать `ref`.
|
|
10977
|
+
*/
|
|
10978
|
+
getRef<H>(): { current: H | null };
|
|
10979
|
+
/** @internal Селектор узла. */
|
|
10980
|
+
__selector: string;
|
|
10981
|
+
/** @internal Хранилище области. */
|
|
10982
|
+
__overrideMaps: SchemaOverrideMaps;
|
|
10983
|
+
}
|
|
10984
|
+
```
|
|
10985
|
+
|
|
10986
|
+
_Source: src/form/schema-controller.ts_
|
|
10987
|
+
|
|
10988
|
+
### SchemaOverrideMaps
|
|
10989
|
+
|
|
10990
|
+
**Kind:** `interface`
|
|
10991
|
+
|
|
10992
|
+
Хранилище переопределений одной области схемы. Ключ каждой карты — `selector` узла.
|
|
10993
|
+
|
|
10994
|
+
**Signature:**
|
|
10995
|
+
```typescript
|
|
10996
|
+
export interface SchemaOverrideMaps {
|
|
10997
|
+
hiddenOverrides: Map<string, boolean | null>;
|
|
10998
|
+
propsOverrides: Map<string, Record<string, unknown> | null>;
|
|
10999
|
+
refRegistry: Map<string, { current: any }>;
|
|
11000
|
+
/** Условия скрытия узлов: selector → реактивное условие. */
|
|
11001
|
+
conditionRegistry: Map<string, () => boolean>;
|
|
11002
|
+
/** Реактивные эффекты, живущие, пока область смонтирована (`renderEffect`). */
|
|
11003
|
+
effectRegistry: Array<() => void | (() => void)>;
|
|
11004
|
+
/** Обработчики событий компонентов: selector → { имя пропа → обработчик }. */
|
|
11005
|
+
callbackRegistry: Map<string, Map<string, (...args: any[]) => any>>;
|
|
11006
|
+
/** Хуки жизненного цикла узла: selector → { onMount, onUnmount }. */
|
|
11007
|
+
lifecycleRegistry: Map<string, NodeLifecycleHooks>;
|
|
11008
|
+
/** Общий счётчик изменений области. Узлы на него не подписываются — см. `versionFor`. */
|
|
11009
|
+
version: Signal<number>;
|
|
11010
|
+
/**
|
|
11011
|
+
* Версия-сигнал одного селектора (создаётся лениво). Узел подписывается только на сигнал СВОЕГО
|
|
11012
|
+
* селектора, поэтому `setHidden` / `patchProps` уведомляют один узел, а не всё дерево.
|
|
11013
|
+
*/
|
|
11014
|
+
versionFor(selector: string): Signal<number>;
|
|
11015
|
+
}
|
|
11016
|
+
```
|
|
11017
|
+
|
|
11018
|
+
_Source: src/form/schema-controller.ts_
|
|
11019
|
+
|
|
11020
|
+
### SchemaResolver
|
|
11021
|
+
|
|
11022
|
+
**Kind:** `interface`
|
|
11023
|
+
|
|
11024
|
+
Реестр, умеющий превратить документ схемы в дерево. Метод необязателен: у интерфейса реестра
|
|
11025
|
+
есть сторонние реализации, и ядро документ само не разбирает.
|
|
11026
|
+
|
|
11027
|
+
**Signature:**
|
|
11028
|
+
```typescript
|
|
11029
|
+
export interface SchemaResolver {
|
|
11030
|
+
resolveSchema?(document: unknown, model: FormModel<never>): ResolvedSchema;
|
|
11031
|
+
}
|
|
11032
|
+
```
|
|
11033
|
+
|
|
11034
|
+
_Source: src/form/form-bundle.ts_
|
|
11035
|
+
|
|
11036
|
+
### SchemaScope
|
|
11037
|
+
|
|
11038
|
+
**Kind:** `interface`
|
|
11039
|
+
|
|
11040
|
+
Схема одной области: адресует узлы по `selector`.
|
|
11041
|
+
|
|
11042
|
+
**Signature:**
|
|
11043
|
+
```typescript
|
|
11044
|
+
export interface SchemaScope {
|
|
11045
|
+
/** Управление узлом области по его `selector`. */
|
|
11046
|
+
node(selector: string): SchemaNodeControl;
|
|
11047
|
+
/** @internal Хранилище области — его читает рендерер. */
|
|
11048
|
+
__overrideMaps: SchemaOverrideMaps;
|
|
11049
|
+
}
|
|
11050
|
+
```
|
|
11051
|
+
|
|
11052
|
+
_Source: src/form/schema-controller.ts_
|
|
11053
|
+
|
|
11054
|
+
### schemaSubtree
|
|
11055
|
+
|
|
11056
|
+
**Kind:** `function`
|
|
11057
|
+
|
|
11058
|
+
Поддерево схемы для под-модели — результат `builder(subModel)`, один на пару.
|
|
11059
|
+
|
|
11060
|
+
**Signature:**
|
|
11061
|
+
```typescript
|
|
11062
|
+
export function schemaSubtree<N>(builder: (subModel: any) => N, subModel: object): N
|
|
11063
|
+
```
|
|
11064
|
+
|
|
11065
|
+
**Parameters:**
|
|
11066
|
+
- `builder` — - Билдер поддерева: `item` узла-массива или `part` узла-подформы.
|
|
11067
|
+
- `subModel` — - Под-модель строки массива или группы.
|
|
11068
|
+
|
|
11069
|
+
**Returns:** Узел поддерева; повторный вызов с той же парой отдаёт тот же объект.
|
|
11070
|
+
|
|
11071
|
+
**Examples:**
|
|
11072
|
+
|
|
11073
|
+
```typescript
|
|
11074
|
+
const row = model.items.at(0);
|
|
11075
|
+
schemaSubtree(itemSchema, row) === schemaSubtree(itemSchema, row); // true
|
|
11076
|
+
```
|
|
11077
|
+
|
|
11078
|
+
_Source: src/form/schema-subtree.ts_
|
|
11079
|
+
|
|
8799
11080
|
### SetValueOptions
|
|
8800
11081
|
|
|
8801
11082
|
**Kind:** `interface`
|
|
@@ -8984,6 +11265,32 @@ transformValue(model.$.promoCode, (v) => (v ?? '').toUpperCase());
|
|
|
8984
11265
|
|
|
8985
11266
|
_Source: src/model/behaviors-value.ts_
|
|
8986
11267
|
|
|
11268
|
+
### translateBuiltin
|
|
11269
|
+
|
|
11270
|
+
**Kind:** `function`
|
|
11271
|
+
|
|
11272
|
+
Перевод ключа пакета: словарь локали, затем встроенная английская таблица пакета, затем сам
|
|
11273
|
+
ключ. Английская таблица форматируется по английским правилам — её множественные формы и
|
|
11274
|
+
группировка чисел не зависят от активного языка.
|
|
11275
|
+
|
|
11276
|
+
**Signature:**
|
|
11277
|
+
```typescript
|
|
11278
|
+
export function translateBuiltin(
|
|
11279
|
+
locale: FormLocale,
|
|
11280
|
+
builtin: Messages,
|
|
11281
|
+
key: string,
|
|
11282
|
+
values?: MessageValues
|
|
11283
|
+
): string
|
|
11284
|
+
```
|
|
11285
|
+
|
|
11286
|
+
**Parameters:**
|
|
11287
|
+
- `locale` — - Активная локаль.
|
|
11288
|
+
- `builtin` — - Встроенная английская таблица пакета-владельца ключа.
|
|
11289
|
+
- `key` — - Ключ сообщения.
|
|
11290
|
+
- `values` — - Значения подстановок.
|
|
11291
|
+
|
|
11292
|
+
_Source: src/i18n/translator.ts_
|
|
11293
|
+
|
|
8987
11294
|
### UnknownRecord
|
|
8988
11295
|
|
|
8989
11296
|
**Kind:** `type`
|
|
@@ -8998,6 +11305,36 @@ export type UnknownRecord = Record<string, unknown>;
|
|
|
8998
11305
|
|
|
8999
11306
|
_Source: src/form/types/index.ts_
|
|
9000
11307
|
|
|
11308
|
+
### unknownSchemaSelectors
|
|
11309
|
+
|
|
11310
|
+
**Kind:** `function`
|
|
11311
|
+
|
|
11312
|
+
Селекторы, на которые в области записаны правила, но которых нет в её дереве. Такое правило
|
|
11313
|
+
ничего не делает — сборка и рендерер в dev предупреждают о нём.
|
|
11314
|
+
|
|
11315
|
+
Ref в проверку не входит: его разрешено брать по абсолютному пути модели
|
|
11316
|
+
(`phones.0.number`), а такой строки при сборке может ещё не быть.
|
|
11317
|
+
|
|
11318
|
+
**Signature:**
|
|
11319
|
+
```typescript
|
|
11320
|
+
export function unknownSchemaSelectors(maps: SchemaOverrideMaps, tree: unknown): string[]
|
|
11321
|
+
```
|
|
11322
|
+
|
|
11323
|
+
**Parameters:**
|
|
11324
|
+
- `maps` — - Хранилище области.
|
|
11325
|
+
- `tree` — - Дерево (поддерево) области.
|
|
11326
|
+
|
|
11327
|
+
**Returns:** Селекторы с правилами, не найденные в дереве.
|
|
11328
|
+
|
|
11329
|
+
**Examples:**
|
|
11330
|
+
|
|
11331
|
+
```typescript
|
|
11332
|
+
const scope = controller.scopeOf(model);
|
|
11333
|
+
unknownSchemaSelectors(scope.__overrideMaps, tree); // ['mortgage'] — узла с таким selector нет
|
|
11334
|
+
```
|
|
11335
|
+
|
|
11336
|
+
_Source: src/form/schema-controller.ts_
|
|
11337
|
+
|
|
9001
11338
|
### unmarkDerived
|
|
9002
11339
|
|
|
9003
11340
|
**Kind:** `function`
|
|
@@ -9185,6 +11522,29 @@ const credit = useFormBundle(() => createCoreForm<CreditForm>({ model: createCre
|
|
|
9185
11522
|
|
|
9186
11523
|
_Source: src/platforms/react/hooks/use-form-bundle.ts_
|
|
9187
11524
|
|
|
11525
|
+
### useFormBundleContext
|
|
11526
|
+
|
|
11527
|
+
**Kind:** `function`
|
|
11528
|
+
|
|
11529
|
+
Бандл сборки, внутри которой отрисован компонент; `null` — компонент стоит вне рендерера
|
|
11530
|
+
(например, визард в разметке React-руками получает форму и валидацию пропсами).
|
|
11531
|
+
|
|
11532
|
+
**Signature:**
|
|
11533
|
+
```typescript
|
|
11534
|
+
export function useFormBundleContext<T = unknown>(): FormBundle<T> | null
|
|
11535
|
+
```
|
|
11536
|
+
|
|
11537
|
+
**Examples:**
|
|
11538
|
+
|
|
11539
|
+
```tsx
|
|
11540
|
+
function StepFooter() {
|
|
11541
|
+
const bundle = useFormBundleContext<CreditForm>();
|
|
11542
|
+
return <button onClick={() => bundle?.validation?.validateAll()}>Проверить</button>;
|
|
11543
|
+
}
|
|
11544
|
+
```
|
|
11545
|
+
|
|
11546
|
+
_Source: src/platforms/react/form-bundle-context.ts_
|
|
11547
|
+
|
|
9188
11548
|
### useFormControl
|
|
9189
11549
|
|
|
9190
11550
|
**Kind:** `function`
|
|
@@ -9227,6 +11587,7 @@ export function useFormControl(
|
|
|
9227
11587
|
Текстовое поле с валидацией
|
|
9228
11588
|
```tsx
|
|
9229
11589
|
import { useFormControl } from '@reformer/core';
|
|
11590
|
+
import { useValidationMessage } from '@reformer/core/i18n';
|
|
9230
11591
|
import type { FieldNode } from '@reformer/core';
|
|
9231
11592
|
|
|
9232
11593
|
interface TextFieldProps {
|
|
@@ -9242,6 +11603,8 @@ shouldShowError,
|
|
|
9242
11603
|
errors,
|
|
9243
11604
|
pending
|
|
9244
11605
|
} = useFormControl(control);
|
|
11606
|
+
// Текст ошибки даёт резолвер: у правил без своего сообщения `message` пуст.
|
|
11607
|
+
const message = useValidationMessage();
|
|
9245
11608
|
|
|
9246
11609
|
return (
|
|
9247
11610
|
<div className="field">
|
|
@@ -9261,7 +11624,7 @@ return (
|
|
|
9261
11624
|
|
|
9262
11625
|
{shouldShowError && errors[0] && (
|
|
9263
11626
|
<span className="error" role="alert">
|
|
9264
|
-
{errors[0]
|
|
11627
|
+
{message(errors[0])}
|
|
9265
11628
|
</span>
|
|
9266
11629
|
)}
|
|
9267
11630
|
</div>
|
|
@@ -9309,6 +11672,7 @@ control: FieldNode<string>;
|
|
|
9309
11672
|
|
|
9310
11673
|
function Select({ control }: SelectProps) {
|
|
9311
11674
|
const { value, disabled, componentProps, shouldShowError, errors } = useFormControl(control);
|
|
11675
|
+
const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
9312
11676
|
const options = componentProps.options as Array<{ value: string; label: string }>;
|
|
9313
11677
|
|
|
9314
11678
|
return (
|
|
@@ -9326,7 +11690,7 @@ onBlur={() => control.markAsTouched()}
|
|
|
9326
11690
|
</option>
|
|
9327
11691
|
))}
|
|
9328
11692
|
</select>
|
|
9329
|
-
{shouldShowError && <span className="error">{errors[0]
|
|
11693
|
+
{shouldShowError && <span className="error">{message(errors[0])}</span>}
|
|
9330
11694
|
</div>
|
|
9331
11695
|
);
|
|
9332
11696
|
}
|
|
@@ -9564,6 +11928,7 @@ dangerouslySetInnerHTML={{ __html: html }}
|
|
|
9564
11928
|
// Основной редактор использует useFormControl для полного состояния
|
|
9565
11929
|
function MarkdownEditor({ control }: MarkdownEditorProps) {
|
|
9566
11930
|
const { value, shouldShowError, errors } = useFormControl(control);
|
|
11931
|
+
const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
9567
11932
|
|
|
9568
11933
|
return (
|
|
9569
11934
|
<div className="editor-container">
|
|
@@ -9571,7 +11936,7 @@ return (
|
|
|
9571
11936
|
value={value}
|
|
9572
11937
|
onChange={e => control.setValue(e.target.value)}
|
|
9573
11938
|
/>
|
|
9574
|
-
{shouldShowError && <span className="error">{errors[0]
|
|
11939
|
+
{shouldShowError && <span className="error">{message(errors[0])}</span>}
|
|
9575
11940
|
|
|
9576
11941
|
{/* Preview обновляется только при изменении value *}
|
|
9577
11942
|
<MarkdownPreview control={control} />
|
|
@@ -9675,12 +12040,104 @@ export interface UseFormValidationResult {
|
|
|
9675
12040
|
|
|
9676
12041
|
_Source: src/platforms/react/hooks/use-form-validation.ts_
|
|
9677
12042
|
|
|
12043
|
+
### useI18n
|
|
12044
|
+
|
|
12045
|
+
**Kind:** `function`
|
|
12046
|
+
|
|
12047
|
+
Активная локаль: перевод ключей приложения, форматирование чисел, дат и размеров, состояние
|
|
12048
|
+
загрузки языка. Без провайдера — английский с пустым словарём.
|
|
12049
|
+
|
|
12050
|
+
**Signature:**
|
|
12051
|
+
```typescript
|
|
12052
|
+
export function useI18n(): I18nContextValue
|
|
12053
|
+
```
|
|
12054
|
+
|
|
12055
|
+
**Examples:**
|
|
12056
|
+
|
|
12057
|
+
```tsx
|
|
12058
|
+
function LangBadge() {
|
|
12059
|
+
const { code, pending, t } = useI18n();
|
|
12060
|
+
return <span aria-busy={pending}>{t('app.language')}: {code}</span>;
|
|
12061
|
+
}
|
|
12062
|
+
```
|
|
12063
|
+
|
|
12064
|
+
_Source: src/platforms/react/i18n/hooks.ts_
|
|
12065
|
+
|
|
12066
|
+
### useMessages
|
|
12067
|
+
|
|
12068
|
+
**Kind:** `function`
|
|
12069
|
+
|
|
12070
|
+
Переводчик пакета: ищет ключ в локали провайдера, а при промахе берёт встроенную английскую
|
|
12071
|
+
таблицу этого пакета. Так пакет говорит по-английски без провайдера и не зависит от того, знает
|
|
12072
|
+
ли локаль его ключи. Тип ключа выводится из таблицы — опечатка в ключе не скомпилируется.
|
|
12073
|
+
|
|
12074
|
+
`defaults` должен быть стабильной ссылкой (импорт JSON-модуля), иначе переводчик будет
|
|
12075
|
+
пересоздаваться на каждый рендер.
|
|
12076
|
+
|
|
12077
|
+
**Signature:**
|
|
12078
|
+
```typescript
|
|
12079
|
+
export function useMessages<D extends Messages>(
|
|
12080
|
+
defaults: D
|
|
12081
|
+
): (key: keyof D & string, values?: MessageValues) => string
|
|
12082
|
+
```
|
|
12083
|
+
|
|
12084
|
+
**Parameters:**
|
|
12085
|
+
- `defaults` — - Встроенная английская таблица пакета.
|
|
12086
|
+
|
|
12087
|
+
**Returns:** Функция `(ключ, значения?) => строка`; её идентичность меняется вместе с локалью.
|
|
12088
|
+
|
|
12089
|
+
**Examples:**
|
|
12090
|
+
|
|
12091
|
+
```tsx
|
|
12092
|
+
import en from './locale/en.json';
|
|
12093
|
+
|
|
12094
|
+
const useKitMessages = () => useMessages(en);
|
|
12095
|
+
|
|
12096
|
+
function ClearButton() {
|
|
12097
|
+
const t = useKitMessages();
|
|
12098
|
+
return <button aria-label={t('kit.select.clear')} />;
|
|
12099
|
+
}
|
|
12100
|
+
```
|
|
12101
|
+
|
|
12102
|
+
_Source: src/platforms/react/i18n/hooks.ts_
|
|
12103
|
+
|
|
12104
|
+
### useValidationMessage
|
|
12105
|
+
|
|
12106
|
+
**Kind:** `function`
|
|
12107
|
+
|
|
12108
|
+
Резолвер текста ошибки валидации по активной локали: `messageKey` автора → явное `message` →
|
|
12109
|
+
словарь локали по коду → встроенный английский → код. Идентичность функции меняется вместе с
|
|
12110
|
+
локалью, поэтому показанные ошибки переводятся при смене языка без повторной валидации.
|
|
12111
|
+
|
|
12112
|
+
`FormField` из `@reformer/cdk` использует его сам; вызывайте напрямую, если рисуете ошибки
|
|
12113
|
+
своим компонентом.
|
|
12114
|
+
|
|
12115
|
+
**Signature:**
|
|
12116
|
+
```typescript
|
|
12117
|
+
export function useValidationMessage(): (error: ValidationError) => string
|
|
12118
|
+
```
|
|
12119
|
+
|
|
12120
|
+
**Examples:**
|
|
12121
|
+
|
|
12122
|
+
```tsx
|
|
12123
|
+
function FieldErrors({ control }: { control: FieldNode<string> }) {
|
|
12124
|
+
const message = useValidationMessage();
|
|
12125
|
+
const { errors } = useFormControl(control);
|
|
12126
|
+
return <>{errors.map((error) => <span key={error.code}>{message(error)}</span>)}</>;
|
|
12127
|
+
}
|
|
12128
|
+
```
|
|
12129
|
+
|
|
12130
|
+
_Source: src/platforms/react/i18n/hooks.ts_
|
|
12131
|
+
|
|
9678
12132
|
### validate
|
|
9679
12133
|
|
|
9680
12134
|
**Kind:** `function`
|
|
9681
12135
|
|
|
9682
12136
|
Синхронные правила поля.
|
|
9683
12137
|
|
|
12138
|
+
Поле — лист либо массив целиком: `model.$.<массив>` — такая же ручка значения, и правило
|
|
12139
|
+
получает массив (мультивыбор, теги, список файлов).
|
|
12140
|
+
|
|
9684
12141
|
**Signature:**
|
|
9685
12142
|
```typescript
|
|
9686
12143
|
export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void
|
|
@@ -9690,6 +12147,7 @@ export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TFiel
|
|
|
9690
12147
|
|
|
9691
12148
|
```ts
|
|
9692
12149
|
validate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);
|
|
12150
|
+
validate(model.$.tags, [required(), maxLength(3)]); // tags: string[] — массив как значение
|
|
9693
12151
|
```
|
|
9694
12152
|
|
|
9695
12153
|
_Source: src/form/validation/operators.ts_
|
|
@@ -9720,6 +12178,34 @@ validateAsync(model.$.username, [async (v, { signal }) => {
|
|
|
9720
12178
|
|
|
9721
12179
|
_Source: src/form/validation/operators.ts_
|
|
9722
12180
|
|
|
12181
|
+
### validateLocale
|
|
12182
|
+
|
|
12183
|
+
**Kind:** `function`
|
|
12184
|
+
|
|
12185
|
+
Проверяет словарь: каждое сообщение разбирается, а при заданном образце — набор ключей и имена
|
|
12186
|
+
подстановок совпадают с ним. Нужна там, где компилятор не поможет: словарь пришёл по сети или
|
|
12187
|
+
написан вне репозитория. В рантайме вызывать не обязательно — битое или пропущенное сообщение
|
|
12188
|
+
и так откатится на встроенный английский; проверка нужна тестам и CI.
|
|
12189
|
+
|
|
12190
|
+
**Signature:**
|
|
12191
|
+
```typescript
|
|
12192
|
+
export function validateLocale(messages: Messages, reference?: Messages): LocaleIssue[]
|
|
12193
|
+
```
|
|
12194
|
+
|
|
12195
|
+
**Parameters:**
|
|
12196
|
+
- `messages` — - Проверяемый словарь.
|
|
12197
|
+
- `reference` — - Образец, обычно английский словарь того же пакета.
|
|
12198
|
+
|
|
12199
|
+
**Returns:** Список расхождений; пустой — словарь в порядке.
|
|
12200
|
+
|
|
12201
|
+
**Examples:**
|
|
12202
|
+
|
|
12203
|
+
```ts
|
|
12204
|
+
expect(validateLocale(ruJson, enJson)).toEqual([]);
|
|
12205
|
+
```
|
|
12206
|
+
|
|
12207
|
+
_Source: src/i18n/locale.ts_
|
|
12208
|
+
|
|
9723
12209
|
### validateModel
|
|
9724
12210
|
|
|
9725
12211
|
**Kind:** `function`
|
|
@@ -9776,8 +12262,20 @@ _Source: src/form/validation/run.ts_
|
|
|
9776
12262
|
```typescript
|
|
9777
12263
|
export interface ValidateOptions {
|
|
9778
12264
|
/** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст
|
|
9779
|
-
*
|
|
12265
|
+
* берётся из словаря локали по `code` (`validation.<code>`; см. `resolveValidationError` в
|
|
12266
|
+
* `@reformer/core/i18n`). Явное сообщение важнее словаря, но не переключается вместе с языком —
|
|
12267
|
+
* для локализуемого текста используйте {@link ValidateOptions.messageKey}. */
|
|
9780
12268
|
message?: string;
|
|
12269
|
+
/**
|
|
12270
|
+
* Ключ сообщения в словаре приложения — локализуемый текст автора правила. Если ключ есть в
|
|
12271
|
+
* активной локали, он важнее и `message`, и текста по `code`; если нет — в ход идёт `message`.
|
|
12272
|
+
*
|
|
12273
|
+
* @example
|
|
12274
|
+
* ```ts
|
|
12275
|
+
* required({ messageKey: 'profile.name.required', message: 'Name is required' });
|
|
12276
|
+
* ```
|
|
12277
|
+
*/
|
|
12278
|
+
messageKey?: string;
|
|
9781
12279
|
/** Параметры ошибки (подстановка в шаблон сообщения / i18n). */
|
|
9782
12280
|
params?: Record<string, FormValue>;
|
|
9783
12281
|
}
|
|
@@ -9809,7 +12307,13 @@ _Source: src/form/validation/operators.ts_
|
|
|
9809
12307
|
```typescript
|
|
9810
12308
|
export interface ValidationError {
|
|
9811
12309
|
code: string;
|
|
12310
|
+
/**
|
|
12311
|
+
* Готовый текст ошибки. Пустая строка — «автор правила текста не задавал»: отображаемую строку
|
|
12312
|
+
* тогда даёт резолвер по `code` (словарь локали `validation.<code>`).
|
|
12313
|
+
*/
|
|
9812
12314
|
message: string;
|
|
12315
|
+
/** Ключ сообщения в словаре приложения; при наличии в активной локали важнее `message`. */
|
|
12316
|
+
messageKey?: string;
|
|
9813
12317
|
params?: Record<string, FormValue>;
|
|
9814
12318
|
/** Severity level: 'error' (default) blocks submission, 'warning' shows message but allows submission */
|
|
9815
12319
|
severity?: 'error' | 'warning';
|
|
@@ -9929,3 +12433,16 @@ watchField(model.$.country, async (country) => {
|
|
|
9929
12433
|
```
|
|
9930
12434
|
|
|
9931
12435
|
_Source: src/model/behaviors-value.ts_
|
|
12436
|
+
|
|
12437
|
+
### WeekStart
|
|
12438
|
+
|
|
12439
|
+
**Kind:** `type`
|
|
12440
|
+
|
|
12441
|
+
День недели, с которого начинается календарь: `0` — воскресенье, `1` — понедельник.
|
|
12442
|
+
|
|
12443
|
+
**Signature:**
|
|
12444
|
+
```typescript
|
|
12445
|
+
export type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
12446
|
+
```
|
|
12447
|
+
|
|
12448
|
+
_Source: src/i18n/locale.ts_
|