@reformer/core 11.0.0-beta.5 → 11.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/behaviors.js +124 -124
- package/dist/create-model-B8U-COv-.js +103 -0
- package/dist/en-CjAqn7h0.js +41 -0
- package/dist/form/behaviors/internals.d.ts +9 -0
- package/dist/form/create-form.d.ts +6 -6
- package/dist/form/factories/node-factory.d.ts +16 -16
- package/dist/form/nodes/array-node.d.ts +3 -3
- package/dist/form/nodes/field-node.d.ts +3 -3
- package/dist/form/nodes/group-node.d.ts +7 -7
- package/dist/form/nodes/model-array-node.d.ts +2 -2
- package/dist/form/types/contracts.d.ts +6 -0
- package/dist/form/types/deep-schema.d.ts +5 -5
- package/dist/form/types/validation-schema.d.ts +13 -1
- package/dist/form/validation/operators.d.ts +6 -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 +548 -601
- 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 +3 -3
- package/dist/model/model-signals-proxy.d.ts +25 -0
- package/dist/model/types.d.ts +19 -5
- package/dist/model-signals-proxy-2vqS243J.js +250 -0
- package/dist/model.js +18 -16
- package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
- package/dist/platforms/react/field-binding/field-handle.d.ts +50 -0
- package/dist/platforms/react/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 +4 -0
- package/dist/{strategy-DA3JwxsR.js → strategy-BXS_gBfz.js} +7 -7
- package/dist/validation-error-B-U3RsV0.js +11 -0
- package/dist/validation.js +1 -1
- 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/02-quick-start.md +7 -7
- package/docs/llms/03-api-signatures.md +11 -5
- package/docs/llms/05-common-mistakes.md +1 -1
- package/docs/llms/09-formschema.md +12 -12
- package/docs/llms/10-arrays.md +34 -4
- package/docs/llms/14-extended-mistakes.md +1 -1
- package/docs/llms/15-project-structure.md +15 -13
- package/docs/llms/16-ui-components.md +36 -9
- package/docs/llms/17-nonexistent-api.md +16 -2
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/30-type-safety-recipes.md +1 -1
- package/docs/llms/34-i18n.md +272 -0
- package/llms-index.json +1 -1
- package/llms.txt +1925 -175
- package/package.json +22 -3
- package/dist/create-model-Dxh9cfLp.js +0 -97
- package/dist/model-signals-proxy-mV3WGdlO.js +0 -233
package/llms.txt
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
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 — локаль, загрузка словарей и провайдер
|
|
41
42
|
- API Reference (auto-generated from JSDoc)
|
|
42
43
|
|
|
43
44
|
## 1. 1. API Reference
|
|
@@ -128,7 +129,7 @@ const { value, errors, disabled } = useFormControl(control.loanType);
|
|
|
128
129
|
import { createCoreForm, createModel, useFormBundle, type FormProxy } from '@reformer/core';
|
|
129
130
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
130
131
|
import { required, email } from '@reformer/core/validators';
|
|
131
|
-
import { FormField,
|
|
132
|
+
import { FormField, Input, Button } from '@reformer/ui-kit';
|
|
132
133
|
|
|
133
134
|
// 1. Define form type as `type` alias (not `interface` — see Recipe 2)
|
|
134
135
|
type ContactForm = {
|
|
@@ -144,12 +145,12 @@ const model = createModel<ContactForm>({ name: '', email: '' });
|
|
|
144
145
|
const schema = {
|
|
145
146
|
name: {
|
|
146
147
|
value: model.$.name,
|
|
147
|
-
component:
|
|
148
|
+
component: Input,
|
|
148
149
|
componentProps: { label: 'Name', placeholder: 'Your name' },
|
|
149
150
|
},
|
|
150
151
|
email: {
|
|
151
152
|
value: model.$.email,
|
|
152
|
-
component:
|
|
153
|
+
component: Input,
|
|
153
154
|
componentProps: { label: 'Email', type: 'email' },
|
|
154
155
|
},
|
|
155
156
|
};
|
|
@@ -223,14 +224,14 @@ const model = createModel<MyForm>({ properties: [] });
|
|
|
223
224
|
const propertyItem = (item: FormModel<PropertyItem>) => ({
|
|
224
225
|
type: {
|
|
225
226
|
value: item.$.type,
|
|
226
|
-
component:
|
|
227
|
+
component: SelectAsync,
|
|
227
228
|
componentProps: { label: 'Тип', options: [/* ... */] },
|
|
228
229
|
},
|
|
229
|
-
description: { value: item.$.description, component:
|
|
230
|
+
description: { value: item.$.description, component: Textarea, componentProps: { label: 'Описание' } },
|
|
230
231
|
estimatedValue: {
|
|
231
232
|
value: item.$.estimatedValue,
|
|
232
|
-
component:
|
|
233
|
-
componentProps: { label: 'Стоимость'
|
|
233
|
+
component: InputNumber,
|
|
234
|
+
componentProps: { label: 'Стоимость' },
|
|
234
235
|
},
|
|
235
236
|
});
|
|
236
237
|
|
|
@@ -320,6 +321,12 @@ minAge(years: number, options?: { message?: string })
|
|
|
320
321
|
maxAge(years: number, options?: { message?: string })
|
|
321
322
|
```
|
|
322
323
|
|
|
324
|
+
Кроме `message`, каждое правило принимает `messageKey` (ключ словаря приложения) и `params`.
|
|
325
|
+
Без `message` правило кладёт в ошибку пустую строку, а текст при отображении берётся из словаря
|
|
326
|
+
локали по коду (`validation.<code>`): «Обязательное поле» под русской локалью, английская фраза без
|
|
327
|
+
провайдера. Явное `message` важнее словаря, но языком не переключается; для локализуемого текста —
|
|
328
|
+
`messageKey`. Подробно — [34-i18n.md](./34-i18n.md).
|
|
329
|
+
|
|
323
330
|
Использование: layout-дерево `createForm` привязывает поля к сигналам, правила живут отдельной
|
|
324
331
|
`ValidationSchema` и гоняются раннером `validateModel` по требованию.
|
|
325
332
|
|
|
@@ -328,16 +335,16 @@ import { createModel, createForm } from '@reformer/core';
|
|
|
328
335
|
import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
|
|
329
336
|
import { required, min, max, email } from '@reformer/core/validators';
|
|
330
337
|
|
|
331
|
-
type Loan = { email: string; age: number; amount: number };
|
|
332
|
-
const model = createModel<Loan>({ email: '', age:
|
|
338
|
+
type Loan = { email: string; age: number | null; amount: number | null };
|
|
339
|
+
const model = createModel<Loan>({ email: '', age: null, amount: null });
|
|
333
340
|
|
|
334
341
|
// layout-схема createForm НЕ несёт validators — только привязка поля к сигналу + компонент
|
|
335
342
|
const form = createForm({
|
|
336
343
|
model,
|
|
337
344
|
schema: {
|
|
338
|
-
email: { value: model.$.email, component:
|
|
339
|
-
age: { value: model.$.age, component:
|
|
340
|
-
amount: { value: model.$.amount, component:
|
|
345
|
+
email: { value: model.$.email, component: Input },
|
|
346
|
+
age: { value: model.$.age, component: InputNumber },
|
|
347
|
+
amount: { value: model.$.amount, component: InputNumber },
|
|
341
348
|
},
|
|
342
349
|
});
|
|
343
350
|
|
|
@@ -837,7 +844,7 @@ computeFrom([model.$.price, model.$.quantity], model.$.total, (price, qty) => pr
|
|
|
837
844
|
```typescript
|
|
838
845
|
// ❌ WRONG - дерево { value, validators } и позиционная строка удалены
|
|
839
846
|
const schema = {
|
|
840
|
-
email: { value: model.$.email, component:
|
|
847
|
+
email: { value: model.$.email, component: Input, validators: [required(), email()] },
|
|
841
848
|
};
|
|
842
849
|
validate(path.email, required(), 'Email is required'); // старая сигнатура validate() — удалена
|
|
843
850
|
|
|
@@ -1090,7 +1097,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
1090
1097
|
```typescript
|
|
1091
1098
|
{
|
|
1092
1099
|
value: model.$.fieldName, // сигнал модели (PathAwareSignal) — обязателен
|
|
1093
|
-
component:
|
|
1100
|
+
component: Input, // React-компонент
|
|
1094
1101
|
componentProps?: object, // пропсы (label, placeholder, options, type, ...)
|
|
1095
1102
|
disabled?: boolean,
|
|
1096
1103
|
updateOn?: 'change' | 'blur' | 'submit',
|
|
@@ -1108,7 +1115,7 @@ layout-схеме НЕТ — правила живут в отдельной с
|
|
|
1108
1115
|
import { createModel, createForm } from '@reformer/core';
|
|
1109
1116
|
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
1110
1117
|
import { required } from '@reformer/core/validators';
|
|
1111
|
-
import {
|
|
1118
|
+
import { Input, InputNumber, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
1112
1119
|
|
|
1113
1120
|
const model = createModel<MyForm>({ name: '', age: null, agree: false, status: 'active' });
|
|
1114
1121
|
|
|
@@ -1120,22 +1127,22 @@ const myValidation = defineValidationSchema<MyForm>(({ model }) => {
|
|
|
1120
1127
|
const schema = {
|
|
1121
1128
|
name: {
|
|
1122
1129
|
value: model.$.name,
|
|
1123
|
-
component:
|
|
1130
|
+
component: Input,
|
|
1124
1131
|
componentProps: { label: 'Name', placeholder: 'Enter name' },
|
|
1125
1132
|
},
|
|
1126
1133
|
age: {
|
|
1127
1134
|
value: model.$.age,
|
|
1128
|
-
component:
|
|
1129
|
-
componentProps: {
|
|
1135
|
+
component: InputNumber,
|
|
1136
|
+
componentProps: { label: 'Age' },
|
|
1130
1137
|
},
|
|
1131
1138
|
agree: {
|
|
1132
1139
|
value: model.$.agree,
|
|
1133
|
-
component:
|
|
1140
|
+
component: CheckboxWithLabel,
|
|
1134
1141
|
componentProps: { label: 'I agree to terms' },
|
|
1135
1142
|
},
|
|
1136
1143
|
status: {
|
|
1137
1144
|
value: model.$.status,
|
|
1138
|
-
component:
|
|
1145
|
+
component: SelectAsync,
|
|
1139
1146
|
componentProps: {
|
|
1140
1147
|
label: 'Status',
|
|
1141
1148
|
options: [
|
|
@@ -1159,9 +1166,9 @@ const form = createForm<MyForm>({ model, schema });
|
|
|
1159
1166
|
import type { FormModel } from '@reformer/core';
|
|
1160
1167
|
|
|
1161
1168
|
const addressNodes = (m: FormModel<Address>) => ({
|
|
1162
|
-
street: { value: m.$.street, component:
|
|
1163
|
-
city: { value: m.$.city, component:
|
|
1164
|
-
zip: { value: m.$.zip, component:
|
|
1169
|
+
street: { value: m.$.street, component: Input, componentProps: { label: 'Street' } },
|
|
1170
|
+
city: { value: m.$.city, component: Input, componentProps: { label: 'City' } },
|
|
1171
|
+
zip: { value: m.$.zip, component: Input, componentProps: { label: 'ZIP' } },
|
|
1165
1172
|
});
|
|
1166
1173
|
|
|
1167
1174
|
const schema = {
|
|
@@ -1178,8 +1185,8 @@ const schema = {
|
|
|
1178
1185
|
import type { FormModel } from '@reformer/core';
|
|
1179
1186
|
|
|
1180
1187
|
const itemSchema = (item: FormModel<Item>) => ({
|
|
1181
|
-
id: { value: item.$.id, component:
|
|
1182
|
-
name: { value: item.$.name, component:
|
|
1188
|
+
id: { value: item.$.id, component: Input, componentProps: { label: 'ID' } },
|
|
1189
|
+
name: { value: item.$.name, component: Input, componentProps: { label: 'Name' } },
|
|
1183
1190
|
});
|
|
1184
1191
|
|
|
1185
1192
|
const schema = {
|
|
@@ -1233,7 +1240,7 @@ if (isFieldNode(node)) { /* ... */ }
|
|
|
1233
1240
|
|
|
1234
1241
|
```typescript
|
|
1235
1242
|
import { createModel, createForm, type FormModel } from '@reformer/core';
|
|
1236
|
-
import {
|
|
1243
|
+
import { Input, InputNumber } from '@reformer/ui-kit';
|
|
1237
1244
|
|
|
1238
1245
|
type Item = { id: string; name: string; price: number };
|
|
1239
1246
|
type MyForm = { items: Item[] };
|
|
@@ -1242,9 +1249,9 @@ const model = createModel<MyForm>({ items: [] });
|
|
|
1242
1249
|
|
|
1243
1250
|
// под-схема одного элемента (item.$.field — сигнал под-модели)
|
|
1244
1251
|
const itemSchema = (item: FormModel<Item>) => ({
|
|
1245
|
-
id: { value: item.$.id, component:
|
|
1246
|
-
name: { value: item.$.name, component:
|
|
1247
|
-
price: { value: item.$.price, component:
|
|
1252
|
+
id: { value: item.$.id, component: Input },
|
|
1253
|
+
name: { value: item.$.name, component: Input },
|
|
1254
|
+
price: { value: item.$.price, component: InputNumber },
|
|
1248
1255
|
});
|
|
1249
1256
|
|
|
1250
1257
|
const schema = {
|
|
@@ -1258,6 +1265,36 @@ const form = createForm<MyForm>({ model, schema });
|
|
|
1258
1265
|
> он не совместим с `Record<string, FormValue>` и `ArrayNode<Item>` его отвергнет.
|
|
1259
1266
|
> См. `30-type-safety-recipes.md`.
|
|
1260
1267
|
|
|
1268
|
+
### Массив как ОДНО значение поля
|
|
1269
|
+
|
|
1270
|
+
Массив бывает не набором под-форм, а одним значением: мультивыбор, теги, список файлов. Чем он
|
|
1271
|
+
является, решает **схема**, а не данные: `{ array, item }` — набор под-форм, а узел поля
|
|
1272
|
+
`{ value: model.$.<путь>, component }` — одно значение. Массив, к которому в схеме ничего не
|
|
1273
|
+
привязано, в форму не попадает.
|
|
1274
|
+
|
|
1275
|
+
```typescript
|
|
1276
|
+
type MyForm = { tags: string[] };
|
|
1277
|
+
|
|
1278
|
+
const model = createModel<MyForm>({ tags: [] });
|
|
1279
|
+
|
|
1280
|
+
const schema = {
|
|
1281
|
+
tags: { value: model.$.tags, component: SelectMulti }, // поле над массивом целиком
|
|
1282
|
+
};
|
|
1283
|
+
const form = createForm<MyForm>({ model, schema });
|
|
1284
|
+
|
|
1285
|
+
form.tags.setValue(['a', 'b']); // FieldNode<string[]>
|
|
1286
|
+
model.$.tags.value = ['c']; // ручка значения: заменяет массив целиком
|
|
1287
|
+
validate(model.$.tags, [required(), maxLength(3)]); // правило получает массив
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
- `model.$.<массив>` — записываемая ручка значения (`PathAwareSignal`): подходит для `value:` в
|
|
1291
|
+
схеме, `validate`/`cross`, `enableWhen`/`copyFrom` и для `model.signalAt(path)`.
|
|
1292
|
+
- Узел-массив хранит только массив: запись `null`/`undefined` даёт `[]`. Нужно отличать «не
|
|
1293
|
+
выбирали» от «выбрали ничего» — объявляй поле `T[] | null` с начальным `null`: в рантайме это
|
|
1294
|
+
лист, и привязывается он так же.
|
|
1295
|
+
- Запись в узел-ГРУППУ (`model.$.<группа>.value = …`) — ошибка: группа пишется по полям или через
|
|
1296
|
+
`model.patch(...)`.
|
|
1297
|
+
|
|
1261
1298
|
### Один массив — три слоя (три разных движка)
|
|
1262
1299
|
|
|
1263
1300
|
Одна и та же коллекция описывается **тремя разными формами** — по одной на движок. Их легко
|
|
@@ -1645,7 +1682,7 @@ import { revalidateWhen, type ValidationError } from '@reformer/core';
|
|
|
1645
1682
|
// ❌ старое (УДАЛЕНО): дерево { value, validators }, ModelValidator (value, scope, root), validateFormModel
|
|
1646
1683
|
const legacyRule: ModelValidator<number, unknown, Form> = (_value, _scope, root) =>
|
|
1647
1684
|
root.field1 > root.field2 ? { code: 'error', message: 'Invalid' } : null;
|
|
1648
|
-
const legacySchema = { field1: { value: model.$.field1, component:
|
|
1685
|
+
const legacySchema = { field1: { value: model.$.field1, component: Input, validators: [legacyRule] } };
|
|
1649
1686
|
validateFormModel(model, legacySchema);
|
|
1650
1687
|
|
|
1651
1688
|
// ✅ новое: cross-правило — обычная функция над снапшотом (model.get()), не читает scope/root
|
|
@@ -1677,11 +1714,13 @@ revalidateWhen([model.$.field2], () => void validateModel(model, schema));
|
|
|
1677
1714
|
|
|
1678
1715
|
One form is one module folder. **Default layout — flat minimalist:** every concern is a single
|
|
1679
1716
|
file at the module root, and the whole form (entry + all wizard steps inline) lives in one
|
|
1680
|
-
`index.tsx`.
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1717
|
+
`index.tsx`. Naming rule: **`form.<role>` is a form artefact, the suffix names its role** —
|
|
1718
|
+
`form.schema.*` (layout), `form.behavior.ts` (model behavior), `form.render.ts` (render-layer
|
|
1719
|
+
behavior, renderer targets only), `form.validation.ts` (validation rules); every other file has
|
|
1720
|
+
no prefix. A wizard may instead keep each
|
|
1721
|
+
step's code in `steps/<slug>/` (same file names inside) with a `steps/index.ts` aggregator. The old
|
|
1722
|
+
`renderer.schema.*` / `renderer.behavior.ts` / `renderer.wizard.tsx` / `validation.ts` names still
|
|
1723
|
+
work with a warning. No `lib/` / `schema/` / `components/` nesting until the form grows (see "Scaling up" below). Arrays are declared in `form.schema.ts`
|
|
1685
1724
|
and rendered with `FormArraySection` — no per-step component files.
|
|
1686
1725
|
|
|
1687
1726
|
```
|
|
@@ -1695,13 +1734,13 @@ src/
|
|
|
1695
1734
|
│ ├── model.ts # createModel + initial values + empty-array-element factories
|
|
1696
1735
|
│ ├── form.schema.ts # FormSchema: { value: model.$.x, component, componentProps }
|
|
1697
1736
|
│ ├── form.behavior.ts # defineFormBehavior: compute / enableWhen / hideWhen / copyFrom / onChange
|
|
1698
|
-
│ ├── validation.ts
|
|
1737
|
+
│ ├── form.validation.ts # ALL validation → { validateStep, validateAll }
|
|
1699
1738
|
│ ├── data-sources.ts # options + async loaders (dataSources)
|
|
1700
1739
|
│ └── api.ts # submit + prefill/load
|
|
1701
1740
|
```
|
|
1702
1741
|
|
|
1703
1742
|
Rule of thumb: **one concern → one file at the module root; the whole component tree (all steps) →
|
|
1704
|
-
`index.tsx`; validation is a single `validation.ts`.** This is the working default for
|
|
1743
|
+
`index.tsx`; validation is a single `form.validation.ts`.** This is the working default for
|
|
1705
1744
|
almost every form — reach for folders only when a file stops fitting on a screen.
|
|
1706
1745
|
|
|
1707
1746
|
### Key Files
|
|
@@ -1722,7 +1761,7 @@ export const createCreditApplicationModel = (): FormModel<CreditApplicationForm>
|
|
|
1722
1761
|
// forms/credit-application/form.schema.ts
|
|
1723
1762
|
import type { FormModel } from '@reformer/core';
|
|
1724
1763
|
export const creditApplicationSchema = (model: FormModel<CreditApplicationForm>) => ({
|
|
1725
|
-
loanType: { value: model.$.loanType, component:
|
|
1764
|
+
loanType: { value: model.$.loanType, component: SelectAsync, componentProps: { /* ... */ } },
|
|
1726
1765
|
personalData: personalDataNodes(model.$.personalData),
|
|
1727
1766
|
properties: { array: model.properties, item: propertyItem },
|
|
1728
1767
|
});
|
|
@@ -1770,7 +1809,7 @@ forms/
|
|
|
1770
1809
|
├── schema/ # The form definition
|
|
1771
1810
|
│ ├── model.ts # createModel factory + initial values + array-element factories
|
|
1772
1811
|
│ ├── form.schema.ts # schema builder (model) => tree ({ value: model.$.x, component })
|
|
1773
|
-
│ ├── validation.ts
|
|
1812
|
+
│ ├── form.validation.ts # defineValidationSchema + validateModel config ({ validateStep, validateAll })
|
|
1774
1813
|
│ ├── form.behavior.ts # defineFormBehavior(...)
|
|
1775
1814
|
│ └── create-form.ts # Assembly: createCoreForm({ model, schema, behavior, validation })
|
|
1776
1815
|
│
|
|
@@ -1782,7 +1821,7 @@ forms/
|
|
|
1782
1821
|
|
|
1783
1822
|
Rule of thumb for this layout: **domain raw material → `lib/`; anything describing the form →
|
|
1784
1823
|
`schema/`; React layout → `components/`; root = entry + `index.ts`.** In very large forms you
|
|
1785
|
-
may co-locate each step's `form.schema.ts` / `validation.ts` / `form.behavior.ts` inside its
|
|
1824
|
+
may co-locate each step's `form.schema.ts` / `form.validation.ts` / `form.behavior.ts` inside its
|
|
1786
1825
|
`steps/[Step]/` folder, keeping only the shared `model` and cross-step rules in `schema/` — see
|
|
1787
1826
|
the guide below.
|
|
1788
1827
|
|
|
@@ -1798,9 +1837,9 @@ the guide below.
|
|
|
1798
1837
|
> when registering the MCP server to choose which structure the generators lead with. Default
|
|
1799
1838
|
> is `minimalist`.
|
|
1800
1839
|
|
|
1801
|
-
> Cross-target variants (renderer-react `
|
|
1802
|
-
> renderer-json `
|
|
1803
|
-
> `
|
|
1840
|
+
> Cross-target variants (renderer-react `form.schema.ts` + `form.render.ts` /
|
|
1841
|
+
> renderer-json `form.schema.ts` + `form.render.ts` + `registry.ts`, plus an optional
|
|
1842
|
+
> `wizard.tsx` shim), the `.tsx` / `.json` schema variants, the centralized-vs-co-located
|
|
1804
1843
|
> choice, and the full reuse map live in the **form-directory-layout** guide (`@reformer/mcp`) —
|
|
1805
1844
|
> `find_recipe directory-layout`.
|
|
1806
1845
|
|
|
@@ -1810,12 +1849,13 @@ the guide below.
|
|
|
1810
1849
|
|
|
1811
1850
|
> **Default rule (read first)**: для UI используй `FormField` из
|
|
1812
1851
|
> [`@reformer/ui-kit`](../../reformer-ui-kit/) — он покрывает 95% случаев одной
|
|
1813
|
-
> строкой `<FormField control={form.x} />`. Свои
|
|
1852
|
+
> строкой `<FormField control={form.x} />`. Свои обёртки поля пиши ТОЛЬКО если
|
|
1814
1853
|
> ui-kit не подходит (другая design system, особый low-level input).
|
|
1815
1854
|
>
|
|
1816
1855
|
> Канонический schema-driven подход:
|
|
1817
1856
|
>
|
|
1818
|
-
> - **компонент** объявляется в схеме как `component:
|
|
1857
|
+
> - **компонент** объявляется в схеме как `component: Input` (или `SelectAsync`, `CheckboxWithLabel`, …) —
|
|
1858
|
+
> сам компонент кита, отдельных «field-версий» нет: связывает его с полем обёртка (`FormField`)
|
|
1819
1859
|
> - **пропсы** компонента — в `componentProps: { label, placeholder, options, type, ... }`
|
|
1820
1860
|
> - **JSX рендерит**: `<FormField control={form.x} />` БЕЗ дополнительных props
|
|
1821
1861
|
>
|
|
@@ -1827,7 +1867,7 @@ the guide below.
|
|
|
1827
1867
|
```tsx
|
|
1828
1868
|
import { useMemo } from 'react';
|
|
1829
1869
|
import { createModel, createForm } from '@reformer/core';
|
|
1830
|
-
import { FormField,
|
|
1870
|
+
import { FormField, Input, SelectAsync, CheckboxWithLabel, Button } from '@reformer/ui-kit';
|
|
1831
1871
|
|
|
1832
1872
|
type RegistrationForm = {
|
|
1833
1873
|
email: string;
|
|
@@ -1841,12 +1881,12 @@ function RegistrationPage() {
|
|
|
1841
1881
|
const schema = {
|
|
1842
1882
|
email: {
|
|
1843
1883
|
value: model.$.email,
|
|
1844
|
-
component:
|
|
1884
|
+
component: Input,
|
|
1845
1885
|
componentProps: { label: 'Email', type: 'email', placeholder: 'you@example.com' },
|
|
1846
1886
|
},
|
|
1847
1887
|
country: {
|
|
1848
1888
|
value: model.$.country,
|
|
1849
|
-
component:
|
|
1889
|
+
component: SelectAsync,
|
|
1850
1890
|
componentProps: {
|
|
1851
1891
|
label: 'Country',
|
|
1852
1892
|
options: [
|
|
@@ -1857,7 +1897,7 @@ function RegistrationPage() {
|
|
|
1857
1897
|
},
|
|
1858
1898
|
agree: {
|
|
1859
1899
|
value: model.$.agree,
|
|
1860
|
-
component:
|
|
1900
|
+
component: CheckboxWithLabel,
|
|
1861
1901
|
componentProps: { label: 'I agree to terms' },
|
|
1862
1902
|
},
|
|
1863
1903
|
};
|
|
@@ -1900,7 +1940,7 @@ function RegistrationPage() {
|
|
|
1900
1940
|
✅ Всё это в схеме:
|
|
1901
1941
|
|
|
1902
1942
|
```ts
|
|
1903
|
-
{ email: { component:
|
|
1943
|
+
{ email: { component: Input, componentProps: { label: 'Email' } } }
|
|
1904
1944
|
```
|
|
1905
1945
|
|
|
1906
1946
|
```tsx
|
|
@@ -1921,7 +1961,8 @@ import { InputMask } from 'react-input-mask';
|
|
|
1921
1961
|
```
|
|
1922
1962
|
|
|
1923
1963
|
`children` оборачивается в `CdkFormField.Control asChild` и получает все нужные
|
|
1924
|
-
props (`value`, `onChange`, `onBlur`, `aria-invalid`)
|
|
1964
|
+
props (`value`, `onChange`, `onBlur`, `aria-invalid`) — в диалекте ребёнка, если тот объявил
|
|
1965
|
+
статику `reformerAdapter` (`defineFieldControl` из `@reformer/ui-kit/fields`), иначе value-based.
|
|
1925
1966
|
|
|
1926
1967
|
### Advanced — write your own from scratch (rare)
|
|
1927
1968
|
|
|
@@ -1963,12 +2004,37 @@ function MyFormField<T>({ control }: MyFormFieldProps<T>) {
|
|
|
1963
2004
|
<MyFormField control={form.email} /> // ← без label-prop
|
|
1964
2005
|
```
|
|
1965
2006
|
|
|
2007
|
+
### Связывание поля с контролом — `FieldAdapter` (`@reformer/core`)
|
|
2008
|
+
|
|
2009
|
+
Контролы говорят на разных диалектах (`checked` + `onCheckedChange`, `onValueChange`, DOM-событие
|
|
2010
|
+
в `onChange`), форма — на value-based seam (`value` + `onChange(value)` + `onBlur`). Перевод
|
|
2011
|
+
описывает `FieldAdapter` (`valueProp`, `changeProp`, `fromEmit`, `toValue`, `bindBlur`, `strip`,
|
|
2012
|
+
`passControl` — все необязательны). Компонент объявляет его статикой `reformerAdapter`, а обёртка
|
|
2013
|
+
поля (`FormField.Control` из `@reformer/cdk`, рендерер `@reformer/renderer-react`) читает статику
|
|
2014
|
+
и связывает поле сама — отдельные «field-версии» компонентов не нужны.
|
|
2015
|
+
|
|
2016
|
+
| Символ | Назначение |
|
|
2017
|
+
| ----------------------------- | --------------------------------------------------------------------------- |
|
|
2018
|
+
| `FieldAdapter` | Тип адаптера: диалект контрола |
|
|
2019
|
+
| `getFieldAdapter(component)` | Статика `component.reformerAdapter` либо `undefined` |
|
|
2020
|
+
| `bindFieldProps(adapter, seam, props)` | Props контрола под адаптер (без адаптера — seam как есть) |
|
|
2021
|
+
| `FieldHandle` | Императивный handle поля: `focus` / `blur` / `scrollIntoView` / `getElement` |
|
|
2022
|
+
| `makeElementFieldHandle(ref)` | Базовый `FieldHandle` из DOM-узла |
|
|
2023
|
+
| `useFieldHandleRef(outerRef)` | Ref для контрола + публикация handle потребителю (строит ОБЁРТКА поля) |
|
|
2024
|
+
|
|
2025
|
+
В приложении обычно достаточно `defineFieldControl(MyControl, { adapter })` из
|
|
2026
|
+
`@reformer/ui-kit/fields` (плюс готовые пресеты `checkedAdapter`, `valueChangeAdapter`, …); для
|
|
2027
|
+
компонентов чужой библиотеки без статики — `RendererSettings.resolveFieldAdapter`.
|
|
2028
|
+
|
|
1966
2029
|
### Integration with UI libraries (shadcn etc.)
|
|
1967
2030
|
|
|
1968
2031
|
> **Через рендерер — без обёртки.** Если ты рендеришь форму через `@reformer/renderer-react` /
|
|
1969
2032
|
> `@reformer/renderer-json`, вместо ручной обёртки на каждый контрол можно зарегистрировать сырой
|
|
1970
2033
|
> компонент и передать `settings.resolveFieldAdapter` — рендерер сам сведёт value-seam к диалекту
|
|
1971
|
-
> контрола.
|
|
2034
|
+
> контрола. Свой компонент можно подготовить и один раз — статикой
|
|
2035
|
+
> `defineFieldControl(MyInput, { adapter })` из `@reformer/ui-kit/fields`: его поймут и рендерер,
|
|
2036
|
+
> и `FormField.Control` из `@reformer/cdk`. Ручная обёртка на `useFormControl` (ниже) нужна для
|
|
2037
|
+
> прямого JSX без `FormField` и рендерера.
|
|
1972
2038
|
|
|
1973
2039
|
Если есть существующая design system — оборачивай её компоненты в один
|
|
1974
2040
|
`MyFormField` (как выше) и используй один прop `control`. Не множь обёртки на
|
|
@@ -2026,6 +2092,20 @@ function ShadcnFormField({ control }: { control: FieldNode<string> }) {
|
|
|
2026
2092
|
| `FormProvider`, `control` prop, `register()` | `<Component form={form} />`, `useFormControl(form.field)` | Форма передаётся через props |
|
|
2027
2093
|
| `getFieldValue()` | `model.field` / `useFormControlValue(form.field)`| Не существует |
|
|
2028
2094
|
|
|
2095
|
+
### UI: удалённые field-версии компонентов (`@reformer/ui-kit`)
|
|
2096
|
+
|
|
2097
|
+
Отдельных «field-версий» у компонентов кита больше нет: в `component` поля кладётся сам
|
|
2098
|
+
компонент, диалект он объявляет статикой `reformerAdapter`, связывает поле обёртка
|
|
2099
|
+
(`FormField.Control` / рендерер).
|
|
2100
|
+
|
|
2101
|
+
| Wrong | Correct |
|
|
2102
|
+
| ---------------------------------------------- | --------------------------------------------------------- |
|
|
2103
|
+
| `InputField`, `SelectField`, `CheckboxField`, … (любой `*Field`) | `Input`, `SelectAsync`, `CheckboxWithLabel`, … — таблица в ui-kit `01-overview.md` |
|
|
2104
|
+
| `InputField` + `type: 'number'` | `InputNumber` |
|
|
2105
|
+
| `InputField` + `suggestions` | `InputSuggest` |
|
|
2106
|
+
| `FileUploadField` + `variant: 'dropzone' \| 'input'` | `FileUploadDropzone` / `FileUploadInput` (без `variant`) |
|
|
2107
|
+
| `withFormControl(MyControl, adapter)` | `defineFieldControl(MyControl, { adapter })` из `@reformer/ui-kit/fields` |
|
|
2108
|
+
|
|
2029
2109
|
### Common Import Errors
|
|
2030
2110
|
|
|
2031
2111
|
```typescript
|
|
@@ -2064,8 +2144,8 @@ const schema = {
|
|
|
2064
2144
|
|
|
2065
2145
|
// CORRECT - layout только связывает поле с сигналом модели...
|
|
2066
2146
|
const layout = {
|
|
2067
|
-
name: { value: model.$.name, component:
|
|
2068
|
-
email: { value: model.$.email, component:
|
|
2147
|
+
name: { value: model.$.name, component: Input, componentProps: { label: 'Name' } },
|
|
2148
|
+
email: { value: model.$.email, component: Input, componentProps: { label: 'Email' } },
|
|
2069
2149
|
};
|
|
2070
2150
|
|
|
2071
2151
|
// ...а правила — отдельная ambient-схема (прогоняется раннером validateModel):
|
|
@@ -3656,16 +3736,16 @@ Initial values задаются в `createModel(initial)`; `reset()` возвр
|
|
|
3656
3736
|
|
|
3657
3737
|
```typescript
|
|
3658
3738
|
import { createModel, createForm } from '@reformer/core';
|
|
3659
|
-
import {
|
|
3739
|
+
import { Input, SelectAsync, CheckboxWithLabel } from '@reformer/ui-kit';
|
|
3660
3740
|
|
|
3661
3741
|
type ProfileForm = { username: string; language: 'ru' | 'en'; marketing: boolean };
|
|
3662
3742
|
|
|
3663
3743
|
const model = createModel<ProfileForm>({ username: '', language: 'ru', marketing: true });
|
|
3664
3744
|
const schema = {
|
|
3665
|
-
username: { value: model.$.username, component:
|
|
3745
|
+
username: { value: model.$.username, component: Input, componentProps: { label: 'Username' } },
|
|
3666
3746
|
language: {
|
|
3667
3747
|
value: model.$.language,
|
|
3668
|
-
component:
|
|
3748
|
+
component: SelectAsync,
|
|
3669
3749
|
componentProps: {
|
|
3670
3750
|
label: 'Язык',
|
|
3671
3751
|
options: [
|
|
@@ -3674,7 +3754,7 @@ const schema = {
|
|
|
3674
3754
|
],
|
|
3675
3755
|
},
|
|
3676
3756
|
},
|
|
3677
|
-
marketing: { value: model.$.marketing, component:
|
|
3757
|
+
marketing: { value: model.$.marketing, component: CheckboxWithLabel, componentProps: { label: 'Рассылка' } },
|
|
3678
3758
|
};
|
|
3679
3759
|
const form = createForm({ model, schema });
|
|
3680
3760
|
// model.get() === { username: '', language: 'ru', marketing: true }
|
|
@@ -3880,7 +3960,7 @@ Layout-схема БЕЗ валидаторов — правила живут в
|
|
|
3880
3960
|
const model = createModel<CreditApplicationForm>(initial);
|
|
3881
3961
|
|
|
3882
3962
|
const schema = {
|
|
3883
|
-
loanAmount: { value: model.$.loanAmount, component:
|
|
3963
|
+
loanAmount: { value: model.$.loanAmount, component: InputNumber },
|
|
3884
3964
|
// вложенная группа — builder, принимающий ModelSignals<Sub>
|
|
3885
3965
|
personalData: personalDataNodes(model.$.personalData),
|
|
3886
3966
|
// массив — { array, item }
|
|
@@ -4422,7 +4502,280 @@ if (typeof window !== 'undefined') ctrl.start();
|
|
|
4422
4502
|
- [31-async-validator-debounce.md](./31-async-validator-debounce.md) — async-правила и отмена устаревших прогонов
|
|
4423
4503
|
- [13-multi-step.md](./13-multi-step.md) — per-step / полная валидация wizard (`validateStep` / `validateAll`)
|
|
4424
4504
|
|
|
4425
|
-
## 82.
|
|
4505
|
+
## 82. Purpose
|
|
4506
|
+
|
|
4507
|
+
**i18n — локаль, загрузка словарей и провайдер**
|
|
4508
|
+
|
|
4509
|
+
Подпуть `@reformer/core/i18n` — общий рантайм локализации для всех пакетов `@reformer/*`: объект
|
|
4510
|
+
локали, загрузка словарей, React-провайдер, перевод и форматирование. Язык задаётся провайдером,
|
|
4511
|
+
подгружается динамически и переключается на лету; узлы формы при этом не пересоздаются.
|
|
4512
|
+
|
|
4513
|
+
Без провайдера всё работает на английском: у каждого пакета есть встроенная английская таблица.
|
|
4514
|
+
|
|
4515
|
+
> Импорт всегда из `@reformer/core/i18n`. Из главной бочки `@reformer/core` этот модуль не
|
|
4516
|
+
> реэкспортируется: React-контекст локализации обязан существовать в одном экземпляре.
|
|
4517
|
+
|
|
4518
|
+
## 83. Локаль — это данные
|
|
4519
|
+
|
|
4520
|
+
```typescript
|
|
4521
|
+
interface FormLocale {
|
|
4522
|
+
code: string; // BCP 47: 'en', 'ru', 'pt-BR' — по нему работают Intl и plural
|
|
4523
|
+
messages: Record<string, string>; // плоский словарь: ключ → сообщение ICU
|
|
4524
|
+
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
4525
|
+
dateLocale?: unknown; // необязательно: объект локали react-day-picker / date-fns
|
|
4526
|
+
dateFormat?: string; // необязательно: токены date-fns для DatePicker
|
|
4527
|
+
}
|
|
4528
|
+
```
|
|
4529
|
+
|
|
4530
|
+
Функций в локали нет, поэтому её можно положить JSON-файлом на сервер и получить по сети:
|
|
4531
|
+
|
|
4532
|
+
```json
|
|
4533
|
+
{ "code": "ru", "weekStartsOn": 1, "messages": { "profile.title": "Профиль" } }
|
|
4534
|
+
```
|
|
4535
|
+
|
|
4536
|
+
`extendLocale(base, patch)` возвращает новую локаль: словари сливаются (ключи из `patch` важнее),
|
|
4537
|
+
остальные поля заменяются.
|
|
4538
|
+
|
|
4539
|
+
## 84. Формат сообщений — подмножество ICU
|
|
4540
|
+
|
|
4541
|
+
| Конструкция | Пример |
|
|
4542
|
+
| ------------------- | ------------------------------------------------------------------------- |
|
|
4543
|
+
| подстановка | `Открыт файл {name}` |
|
|
4544
|
+
| множественные формы | `{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}` |
|
|
4545
|
+
| точное значение | `{count, plural, =0{файлов нет} other{# файлов}}` |
|
|
4546
|
+
| выбор | `{gender, select, male{он} female{она} other{оно}}` |
|
|
4547
|
+
| экранирование | `'{'` → `{`, `'}'` → `}`, `''` → `'` |
|
|
4548
|
+
|
|
4549
|
+
- Ветка `other` обязательна. `#` внутри `plural` — число, отформатированное по языку.
|
|
4550
|
+
- Типы `number`, `date`, `time`, `selectordinal` не поддерживаются: числа и даты форматируйте
|
|
4551
|
+
отдельно (`useI18n().number`, `.date`) и передавайте готовой строкой.
|
|
4552
|
+
- Категории (`one`/`few`/`many`) берутся из `Intl.PluralRules` языка локали.
|
|
4553
|
+
|
|
4554
|
+
## 85. Загрузка локалей
|
|
4555
|
+
|
|
4556
|
+
Откуда брать словарь, решает приложение: источник — функция `(code) => локаль | словарь`.
|
|
4557
|
+
|
|
4558
|
+
```typescript
|
|
4559
|
+
import { createLocaleLoader, fetchMessages } from '@reformer/core/i18n';
|
|
4560
|
+
import { loadCoreLocale } from '@reformer/core/locale';
|
|
4561
|
+
|
|
4562
|
+
export const loadLocale = createLocaleLoader([
|
|
4563
|
+
loadCoreLocale, // встроенные словари ядра — чанк нужного языка
|
|
4564
|
+
fetchMessages((code) => `/locales/${code}.json`), // ключи приложения и правки — с сервера
|
|
4565
|
+
]);
|
|
4566
|
+
```
|
|
4567
|
+
|
|
4568
|
+
- Источники опрашиваются параллельно, сливаются по порядку: поздний перекрывает ранний.
|
|
4569
|
+
- Источник отдаёт локаль целиком (`{ code, messages, … }`) или голый словарь (`{ "a.b": "…" }`);
|
|
4570
|
+
`null` означает «для этого языка у меня ничего нет» и ошибкой не считается.
|
|
4571
|
+
- Результат кэшируется по коду языка, идущий запрос не дублируется.
|
|
4572
|
+
- Отказ любого источника — отказ загрузки. Источник, которому можно промахнуться, оборачивайте:
|
|
4573
|
+
`(code) => source(code).catch(() => null)`.
|
|
4574
|
+
- `loadLocale.peek(code)` — уже загруженная локаль или `undefined`; `loadLocale.preload(code)` — то
|
|
4575
|
+
же, что вызов загрузчика.
|
|
4576
|
+
|
|
4577
|
+
Ядро отдаёт свои локали в трёх видах:
|
|
4578
|
+
|
|
4579
|
+
| Вид | Импорт | Для чего |
|
|
4580
|
+
| ----------------- | ------------------------------------------- | ----------------------------------------------- |
|
|
4581
|
+
| загрузчик | `loadCoreLocale` из `@reformer/core/locale` | основной путь: чанк языка по запросу |
|
|
4582
|
+
| JSON-файл | `@reformer/core/locale/ru.json` | выложить на свой сервер, править без пересборки |
|
|
4583
|
+
| синхронный модуль | `ru` из `@reformer/core/locale/ru` | SSR и тесты |
|
|
4584
|
+
|
|
4585
|
+
Встроенные языки ядра — `en` и `ru`. Словарь ядра несёт тексты ошибок валидации
|
|
4586
|
+
(`validation.<code>`) и единицы размера файла (`format.fileSize.b|kb|mb|gb|tb`).
|
|
4587
|
+
|
|
4588
|
+
Пакеты выше отдают локаль накопительно и в тех же трёх видах, поэтому источник встроенных словарей
|
|
4589
|
+
в загрузчике всегда один — самого верхнего из используемых пакетов:
|
|
4590
|
+
|
|
4591
|
+
| Пакеты приложения | Источник | Что в локали |
|
|
4592
|
+
| ------------------------------- | -------------------------------------------- | --------------------------------- |
|
|
4593
|
+
| только ядро | `loadCoreLocale` из `@reformer/core/locale` | ошибки валидации, размер файла |
|
|
4594
|
+
| ядро + `@reformer/cdk` | `loadCdkLocale` из `@reformer/cdk/locale` | то же плюс строки cdk (`cdk.*`) |
|
|
4595
|
+
| ядро + cdk + `@reformer/ui-kit` | `loadKitLocale` из `@reformer/ui-kit/locale` | то же плюс подписи кита (`kit.*`) |
|
|
4596
|
+
|
|
4597
|
+
## 86. Провайдер
|
|
4598
|
+
|
|
4599
|
+
```tsx
|
|
4600
|
+
import { I18nProvider } from '@reformer/core/i18n';
|
|
4601
|
+
|
|
4602
|
+
function App() {
|
|
4603
|
+
const [lang, setLang] = useState('ru');
|
|
4604
|
+
return (
|
|
4605
|
+
<I18nProvider lang={lang} load={loadLocale}>
|
|
4606
|
+
<LangSwitch value={lang} onChange={setLang} />
|
|
4607
|
+
<CreditForm />
|
|
4608
|
+
</I18nProvider>
|
|
4609
|
+
);
|
|
4610
|
+
}
|
|
4611
|
+
```
|
|
4612
|
+
|
|
4613
|
+
| Проп | Назначение |
|
|
4614
|
+
| --------------- | ------------------------------------------------------------------------ |
|
|
4615
|
+
| `lang` + `load` | провайдер грузит локаль сам |
|
|
4616
|
+
| `fallback` | что показать, пока не загружена самая первая локаль; по умолчанию ничего |
|
|
4617
|
+
| `onError` | отказ загрузки `(error, lang)` |
|
|
4618
|
+
| `locale` | готовый объект вместо `lang` + `load` — SSR, тесты |
|
|
4619
|
+
|
|
4620
|
+
Поведение при `lang` + `load`:
|
|
4621
|
+
|
|
4622
|
+
- при смене `lang` на экране остаётся прежний язык, пока новый не загрузится;
|
|
4623
|
+
`useI18n().pending` — для индикатора;
|
|
4624
|
+
- при быстром переключении применяется только последний запрос;
|
|
4625
|
+
- ошибка загрузки язык не меняет и попадает в `useI18n().error` и `onError`;
|
|
4626
|
+
- если локаль уже в кэше (`await loadLocale.preload(lang)` до монтирования), первый рендер
|
|
4627
|
+
синхронный и `fallback` не показывается.
|
|
4628
|
+
|
|
4629
|
+
SSR: эффекты на сервере не выполняются, поэтому там передавайте готовый объект —
|
|
4630
|
+
`<I18nProvider locale={ru}>` — либо заранее вызовите `preload`.
|
|
4631
|
+
|
|
4632
|
+
Вложенный провайдер заменяет локаль для своего поддерева целиком.
|
|
4633
|
+
|
|
4634
|
+
## 87. Хуки
|
|
4635
|
+
|
|
4636
|
+
```typescript
|
|
4637
|
+
const { code, locale, pending, error, t, has, number, date, fileSize } = useI18n();
|
|
4638
|
+
|
|
4639
|
+
t('profile.title'); // ключ приложения
|
|
4640
|
+
t('cart.items', { count: 3 }); // с подстановками
|
|
4641
|
+
t('cart.empty', undefined, 'Your cart is empty'); // английский текст на случай промаха
|
|
4642
|
+
number(1234.5); // '1 234,5' в ru
|
|
4643
|
+
date(new Date(), { dateStyle: 'long' }); // '15 января 2024 г.' в ru
|
|
4644
|
+
fileSize(1536); // '1,5 КБ' в ru, '1.5 KB' без провайдера
|
|
4645
|
+
```
|
|
4646
|
+
|
|
4647
|
+
`useMessages(builtinEn)` — для авторов пакетов и китов: ищет ключ в локали провайдера, а при
|
|
4648
|
+
промахе берёт встроенную английскую таблицу пакета. Тип ключа выводится из таблицы.
|
|
4649
|
+
|
|
4650
|
+
```tsx
|
|
4651
|
+
import en from './locale/en.json';
|
|
4652
|
+
|
|
4653
|
+
function ClearButton() {
|
|
4654
|
+
const t = useMessages(en);
|
|
4655
|
+
return <button aria-label={t('kit.select.clear')} />;
|
|
4656
|
+
}
|
|
4657
|
+
```
|
|
4658
|
+
|
|
4659
|
+
## 88. Тексты ошибок валидации
|
|
4660
|
+
|
|
4661
|
+
Правила из `@reformer/core/validators` несут только `code` и `params`; готового текста у них нет
|
|
4662
|
+
(`message` по умолчанию — пустая строка). Строку даёт резолвер при отображении ошибки:
|
|
4663
|
+
|
|
4664
|
+
1. `messageKey` автора правила — если такой ключ есть в активной локали;
|
|
4665
|
+
2. явное `message` автора правила;
|
|
4666
|
+
3. `validation.<code>` из словаря локали;
|
|
4667
|
+
4. `validation.<code>` из встроенной английской таблицы ядра;
|
|
4668
|
+
5. сам код — для своих кодов, которых нет ни в одном словаре.
|
|
4669
|
+
|
|
4670
|
+
```typescript
|
|
4671
|
+
validate(model.$.email, [
|
|
4672
|
+
required(), // «Обязательное поле» в ru, английская фраза без провайдера
|
|
4673
|
+
minLength(5, { message: 'Слишком коротко' }), // явный текст: важнее словаря, но языком не переключается
|
|
4674
|
+
email({ messageKey: 'profile.email.invalid' }), // ключ словаря приложения: переключается вместе с языком
|
|
4675
|
+
]);
|
|
4676
|
+
```
|
|
4677
|
+
|
|
4678
|
+
- Текст получается при отображении, а не при проверке: смена языка переводит уже показанные
|
|
4679
|
+
ошибки без повторной валидации.
|
|
4680
|
+
- `FormField` (`@reformer/cdk`, `@reformer/ui-kit`) резолвит ошибку сам. В своём компоненте —
|
|
4681
|
+
`useValidationMessage()`; вне React — `resolveValidationError(error, createI18n(locale))`.
|
|
4682
|
+
- Свой код ошибки локализуется ключом `validation.<свой_код>` в словаре приложения либо
|
|
4683
|
+
`messageKey` в самом правиле.
|
|
4684
|
+
- Встроенный текст меняется тем же ключом в словаре приложения:
|
|
4685
|
+
`extendLocale(ru, { messages: { 'validation.required': 'Заполните поле' } })`.
|
|
4686
|
+
- Параметры готовятся до подстановки: `Date` — по языку (`15 янв. 2030 г.`), массив — перечнем,
|
|
4687
|
+
размеры в байтах (`maxFileSize`, `minFileSize`, `actualSize`, `maxTotalFileSize`, `actualTotal`) —
|
|
4688
|
+
строкой с единицей (`5 МБ`). Числа остаются числами, поэтому в сообщении работает `plural`.
|
|
4689
|
+
|
|
4690
|
+
```tsx
|
|
4691
|
+
function FieldErrors({ control }: { control: FieldNode<string> }) {
|
|
4692
|
+
const message = useValidationMessage();
|
|
4693
|
+
const { errors, shouldShowError } = useFormControl(control);
|
|
4694
|
+
return shouldShowError ? <span role="alert">{message(errors[0])}</span> : null;
|
|
4695
|
+
}
|
|
4696
|
+
```
|
|
4697
|
+
|
|
4698
|
+
Смонтированный `ValidationMessagesProvider` из `@reformer/cdk` заменяет этот резолвер целиком:
|
|
4699
|
+
локаль для ошибок в его поддереве не участвует.
|
|
4700
|
+
|
|
4701
|
+
## 89. Что показывается, когда ключа нет
|
|
4702
|
+
|
|
4703
|
+
| Ключ | Нет в локали | Сообщение не разбирается |
|
|
4704
|
+
| -------------------------- | --------------------------------------------------------------- | ----------------------------------------------------- |
|
|
4705
|
+
| пакета (`useMessages`) | встроенный английский пакета | встроенный английский, `console.error` вне production |
|
|
4706
|
+
| приложения (`useI18n().t`) | `defaultMessage`, затем сам ключ; `console.warn` вне production | то же, `console.error` |
|
|
4707
|
+
|
|
4708
|
+
Пропущенная подстановка выводится как `{name}`. Форма не падает ни в одном из случаев: неполный
|
|
4709
|
+
словарь, пришедший по сети, — законный сценарий.
|
|
4710
|
+
|
|
4711
|
+
## 90. Проверка словаря в тестах
|
|
4712
|
+
|
|
4713
|
+
```typescript
|
|
4714
|
+
import { validateLocale } from '@reformer/core/i18n';
|
|
4715
|
+
|
|
4716
|
+
expect(validateLocale(ruJson, enJson)).toEqual([]);
|
|
4717
|
+
// находит: syntax (не разбирается), missing / extra (ключи), arguments (потерянная подстановка)
|
|
4718
|
+
```
|
|
4719
|
+
|
|
4720
|
+
## 91. Описатели сообщений (низкий уровень)
|
|
4721
|
+
|
|
4722
|
+
`msg(key, values?, defaultMessage?)` создаёт описатель сообщения, `defineMessages(appEn)` — `msg` с
|
|
4723
|
+
типизированными ключами, `resolveLocalized(value, i18n)` раскрывает описатели в объекте пропсов.
|
|
4724
|
+
|
|
4725
|
+
> ⚠️ Рендереры и `FormField` описатели сами пока НЕ раскрывают. Не кладите `msg(...)` в
|
|
4726
|
+
> `componentProps` схемы: компонент получит объект вместо строки. До появления раскрытия в
|
|
4727
|
+
> рендерерах вызывайте `resolveLocalized` вручную или используйте `useI18n().t`.
|
|
4728
|
+
|
|
4729
|
+
## 92. Anti-patterns
|
|
4730
|
+
|
|
4731
|
+
```typescript
|
|
4732
|
+
// ❌ Импорт из главной бочки — i18n там нет, а второй путь импорта дал бы вторую копию контекста
|
|
4733
|
+
import { I18nProvider } from '@reformer/core';
|
|
4734
|
+
|
|
4735
|
+
// ✅ Единственный путь импорта — подпуть i18n
|
|
4736
|
+
import { I18nProvider } from '@reformer/core/i18n';
|
|
4737
|
+
```
|
|
4738
|
+
|
|
4739
|
+
```tsx
|
|
4740
|
+
// ❌ Новый загрузчик на каждый рендер: кэш теряется, локаль грузится заново, первый кадр — fallback
|
|
4741
|
+
<I18nProvider lang={lang} load={createLocaleLoader([loadCoreLocale])}>
|
|
4742
|
+
|
|
4743
|
+
// ✅ Загрузчик — константа модуля: один кэш на приложение
|
|
4744
|
+
const loadLocale = createLocaleLoader([loadCoreLocale]);
|
|
4745
|
+
<I18nProvider lang={lang} load={loadLocale}>
|
|
4746
|
+
```
|
|
4747
|
+
|
|
4748
|
+
```typescript
|
|
4749
|
+
// ❌ Число или дата типами ICU внутри сообщения — не поддерживается, сообщение не разберётся
|
|
4750
|
+
const message = 'Итого: {total, number}';
|
|
4751
|
+
|
|
4752
|
+
// ✅ Отформатировать отдельно и передать готовой строкой
|
|
4753
|
+
t('cart.total', { total: number(total, { style: 'currency', currency: 'RUB' }) });
|
|
4754
|
+
```
|
|
4755
|
+
|
|
4756
|
+
```tsx
|
|
4757
|
+
// ❌ Ошибка выводится полем message: у правил без своего текста оно пустое — на экране ничего
|
|
4758
|
+
<span>{errors[0].message}</span>;
|
|
4759
|
+
|
|
4760
|
+
// ✅ Текст ошибки — через резолвер: он знает словарь локали, messageKey и явное сообщение
|
|
4761
|
+
const message = useValidationMessage();
|
|
4762
|
+
<span>{message(errors[0])}</span>;
|
|
4763
|
+
```
|
|
4764
|
+
|
|
4765
|
+
```typescript
|
|
4766
|
+
// ❌ Настройки локали без словаря: объект без поля messages принимается за словарь сообщений
|
|
4767
|
+
const source = () => ({ weekStartsOn: 1 });
|
|
4768
|
+
|
|
4769
|
+
// ✅ Часть локали узнаётся по полю messages — отдавайте его хотя бы пустым
|
|
4770
|
+
const source = () => ({ weekStartsOn: 1, messages: {} });
|
|
4771
|
+
```
|
|
4772
|
+
|
|
4773
|
+
## 93. See also
|
|
4774
|
+
|
|
4775
|
+
- [28-submit-and-reset.md](./28-submit-and-reset.md) — жизненный цикл формы; смена языка его не затрагивает
|
|
4776
|
+
- [33-validation-strategy.md](./33-validation-strategy.md) — когда запускается валидация
|
|
4777
|
+
|
|
4778
|
+
## 94. API Reference
|
|
4426
4779
|
|
|
4427
4780
|
_Auto-generated from JSDoc on public exports._
|
|
4428
4781
|
|
|
@@ -4693,8 +5046,8 @@ export class ArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
4693
5046
|
|
|
4694
5047
|
```typescript
|
|
4695
5048
|
const array = new ArrayNode({
|
|
4696
|
-
title: { value: '', component:
|
|
4697
|
-
price: { value: 0, component:
|
|
5049
|
+
title: { value: '', component: Input },
|
|
5050
|
+
price: { value: 0, component: Input },
|
|
4698
5051
|
});
|
|
4699
5052
|
|
|
4700
5053
|
array.push({ title: 'Item 1', price: 100 });
|
|
@@ -4823,6 +5176,35 @@ export type BehaviorScope<T> = { model: FormModel<T>; form: FormProxy<T> };
|
|
|
4823
5176
|
|
|
4824
5177
|
_Source: src/form/behaviors/types.ts_
|
|
4825
5178
|
|
|
5179
|
+
### bindFieldProps
|
|
5180
|
+
|
|
5181
|
+
**Kind:** `function`
|
|
5182
|
+
|
|
5183
|
+
Props контрола под адаптер: срезает служебные ключи обёртки и `strip` адаптера, кладёт значение
|
|
5184
|
+
в `valueProp` (через `toValue`), вешает `changeProp`, который прогоняет эмит контрола через
|
|
5185
|
+
`fromEmit` в value-based `onChange` поля, и пробрасывает blur (`bindBlur` либо `onBlur`).
|
|
5186
|
+
Без адаптера seam проходит как есть (`value` / `onChange` / `onBlur`).
|
|
5187
|
+
|
|
5188
|
+
`componentProps` спредятся ПЕРВЫМИ — seam их перекрывает при совпадении ключей.
|
|
5189
|
+
|
|
5190
|
+
**Signature:**
|
|
5191
|
+
```typescript
|
|
5192
|
+
export function bindFieldProps(
|
|
5193
|
+
adapter: FieldAdapter | undefined,
|
|
5194
|
+
seam: FieldSeam,
|
|
5195
|
+
componentProps: Record<string, unknown> = {}
|
|
5196
|
+
): Record<string, unknown>
|
|
5197
|
+
```
|
|
5198
|
+
|
|
5199
|
+
**Parameters:**
|
|
5200
|
+
- `adapter` — - Адаптер контрола или `undefined` (value-based контрол).
|
|
5201
|
+
- `seam` — - Значение поля и его колбэки.
|
|
5202
|
+
- `componentProps` — - Прочие props контрола.
|
|
5203
|
+
|
|
5204
|
+
**Returns:** Плоский объект props для спреда в контрол.
|
|
5205
|
+
|
|
5206
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
5207
|
+
|
|
4826
5208
|
### buildValidation
|
|
4827
5209
|
|
|
4828
5210
|
**Kind:** `function`
|
|
@@ -4860,6 +5242,35 @@ await v!.validateAll(); // все шаги + extras
|
|
|
4860
5242
|
|
|
4861
5243
|
_Source: src/form/validation/config.ts_
|
|
4862
5244
|
|
|
5245
|
+
### bundledLocaleSource
|
|
5246
|
+
|
|
5247
|
+
**Kind:** `function`
|
|
5248
|
+
|
|
5249
|
+
Источник встроенных локалей пакета: по чанку на язык. Язык ищется точно, затем по основной части
|
|
5250
|
+
кода (`ru-RU` → `ru`). Языка нет — источник отдаёт `null`: подписи пакета останутся английскими,
|
|
5251
|
+
пока приложение не даст их своим источником.
|
|
5252
|
+
|
|
5253
|
+
**Signature:**
|
|
5254
|
+
```typescript
|
|
5255
|
+
export function bundledLocaleSource(
|
|
5256
|
+
loaders: Readonly<Record<string, () => Promise<FormLocale>>>
|
|
5257
|
+
): LocaleSource
|
|
5258
|
+
```
|
|
5259
|
+
|
|
5260
|
+
**Parameters:**
|
|
5261
|
+
- `loaders` — - Код языка → ленивый импорт его локали.
|
|
5262
|
+
|
|
5263
|
+
**Examples:**
|
|
5264
|
+
|
|
5265
|
+
```ts
|
|
5266
|
+
export const loadKitLocale = bundledLocaleSource({
|
|
5267
|
+
en: () => import('./en').then((m) => m.en),
|
|
5268
|
+
ru: () => import('./ru').then((m) => m.ru),
|
|
5269
|
+
});
|
|
5270
|
+
```
|
|
5271
|
+
|
|
5272
|
+
_Source: src/i18n/loader.ts_
|
|
5273
|
+
|
|
4863
5274
|
### ChangeContext
|
|
4864
5275
|
|
|
4865
5276
|
**Kind:** `interface`
|
|
@@ -4977,6 +5388,19 @@ copyFrom(model.$.email, model.$.emailAdditional, { when: () => model.sameEmail }
|
|
|
4977
5388
|
|
|
4978
5389
|
_Source: src/model/behaviors-value.ts_
|
|
4979
5390
|
|
|
5391
|
+
### CORE_LOCALES
|
|
5392
|
+
|
|
5393
|
+
**Kind:** `const`
|
|
5394
|
+
|
|
5395
|
+
Языки, которые ядро поставляет само.
|
|
5396
|
+
|
|
5397
|
+
**Signature:**
|
|
5398
|
+
```typescript
|
|
5399
|
+
export const CORE_LOCALES: readonly string[]
|
|
5400
|
+
```
|
|
5401
|
+
|
|
5402
|
+
_Source: src/locale/index.ts_
|
|
5403
|
+
|
|
4980
5404
|
### CORE_RUNTIME_TOKEN
|
|
4981
5405
|
|
|
4982
5406
|
**Kind:** `const`
|
|
@@ -5114,8 +5538,8 @@ const model = createModel<UserForm>({ email: '', password: '' });
|
|
|
5114
5538
|
// Layout-схема НЕ несёт validators — только component/componentProps.
|
|
5115
5539
|
const schema = {
|
|
5116
5540
|
children: [
|
|
5117
|
-
{ value: model.$.email, component:
|
|
5118
|
-
{ value: model.$.password, component:
|
|
5541
|
+
{ value: model.$.email, component: Input },
|
|
5542
|
+
{ value: model.$.password, component: Input },
|
|
5119
5543
|
],
|
|
5120
5544
|
};
|
|
5121
5545
|
|
|
@@ -5207,9 +5631,9 @@ const schema = {
|
|
|
5207
5631
|
component: Section,
|
|
5208
5632
|
children: [
|
|
5209
5633
|
// Layout несёт только component/componentProps; правила — в отдельной ValidationSchema.
|
|
5210
|
-
{ value: model.$.email, component:
|
|
5634
|
+
{ value: model.$.email, component: Input },
|
|
5211
5635
|
// вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)
|
|
5212
|
-
{ value: model.$.profile.name, component:
|
|
5636
|
+
{ value: model.$.profile.name, component: Input },
|
|
5213
5637
|
],
|
|
5214
5638
|
};
|
|
5215
5639
|
|
|
@@ -5287,6 +5711,122 @@ dispose();
|
|
|
5287
5711
|
|
|
5288
5712
|
_Source: src/form/validation/strategy.ts_
|
|
5289
5713
|
|
|
5714
|
+
### createI18n
|
|
5715
|
+
|
|
5716
|
+
**Kind:** `function`
|
|
5717
|
+
|
|
5718
|
+
Ручка для локали. Для одного и того же объекта локали возвращается один и тот же объект —
|
|
5719
|
+
ручку можно держать в зависимостях хуков.
|
|
5720
|
+
|
|
5721
|
+
**Signature:**
|
|
5722
|
+
```typescript
|
|
5723
|
+
export function createI18n(locale: FormLocale): I18nHandle
|
|
5724
|
+
```
|
|
5725
|
+
|
|
5726
|
+
**Parameters:**
|
|
5727
|
+
- `locale` — - Локаль; см. {@link FormLocale}.
|
|
5728
|
+
|
|
5729
|
+
**Examples:**
|
|
5730
|
+
|
|
5731
|
+
```ts
|
|
5732
|
+
const i18n = createI18n({ code: 'ru', messages: { 'cart.items': '{count, plural, one{# товар} few{# товара} other{# товаров}}' } });
|
|
5733
|
+
i18n.t('cart.items', { count: 3 }); // '3 товара'
|
|
5734
|
+
i18n.fileSize(1536); // '1,5 КБ'
|
|
5735
|
+
```
|
|
5736
|
+
|
|
5737
|
+
_Source: src/i18n/translator.ts_
|
|
5738
|
+
|
|
5739
|
+
### createLocaleLoader
|
|
5740
|
+
|
|
5741
|
+
**Kind:** `function`
|
|
5742
|
+
|
|
5743
|
+
Собирает загрузчик из источников. Источники опрашиваются параллельно, а сливаются по порядку:
|
|
5744
|
+
поздний перекрывает ранний. Результат кэшируется по коду языка, идущий запрос не дублируется.
|
|
5745
|
+
Отказ любого источника — отказ загрузки; из кэша такой запрос удаляется, и следующая попытка
|
|
5746
|
+
пойдёт заново. Источник, которому можно промахнуться, приложение оборачивает само:
|
|
5747
|
+
`(code) => source(code).catch(() => null)`.
|
|
5748
|
+
|
|
5749
|
+
Код языка у результата — всегда запрошенный: по нему работает `Intl`, и он не должен зависеть
|
|
5750
|
+
от того, что написано внутри файла.
|
|
5751
|
+
|
|
5752
|
+
**Signature:**
|
|
5753
|
+
```typescript
|
|
5754
|
+
export function createLocaleLoader(sources: readonly LocaleSource[]): LocaleLoader
|
|
5755
|
+
```
|
|
5756
|
+
|
|
5757
|
+
**Parameters:**
|
|
5758
|
+
- `sources` — - Источники в порядке возрастания приоритета.
|
|
5759
|
+
|
|
5760
|
+
**Examples:**
|
|
5761
|
+
|
|
5762
|
+
```ts
|
|
5763
|
+
import { createLocaleLoader, fetchMessages } from '@reformer/core/i18n';
|
|
5764
|
+
import { loadKitLocale } from '@reformer/ui-kit/locale';
|
|
5765
|
+
|
|
5766
|
+
export const loadLocale = createLocaleLoader([
|
|
5767
|
+
loadKitLocale, // подписи кита — чанк нужного языка
|
|
5768
|
+
fetchMessages((code) => `/locales/${code}.json`), // ключи приложения и правки — с сервера
|
|
5769
|
+
]);
|
|
5770
|
+
```
|
|
5771
|
+
|
|
5772
|
+
_Source: src/i18n/loader.ts_
|
|
5773
|
+
|
|
5774
|
+
### createLocaleStore
|
|
5775
|
+
|
|
5776
|
+
**Kind:** `function`
|
|
5777
|
+
|
|
5778
|
+
Создаёт хранилище. Загрузка при создании НЕ начинается: начальное состояние считается чисто —
|
|
5779
|
+
из кэша загрузчика, если локаль там уже есть, — а запрос уходит на первый {@link LocaleStore.setLang}.
|
|
5780
|
+
Это позволяет создавать хранилище прямо в рендере.
|
|
5781
|
+
|
|
5782
|
+
**Signature:**
|
|
5783
|
+
```typescript
|
|
5784
|
+
export function createLocaleStore(
|
|
5785
|
+
load: LocaleLoad,
|
|
5786
|
+
lang: string,
|
|
5787
|
+
options: LocaleStoreOptions = {}
|
|
5788
|
+
): LocaleStore
|
|
5789
|
+
```
|
|
5790
|
+
|
|
5791
|
+
**Parameters:**
|
|
5792
|
+
- `load` — - Загрузчик локалей.
|
|
5793
|
+
- `lang` — - Начальный язык.
|
|
5794
|
+
- `options` — - См. {@link LocaleStoreOptions}.
|
|
5795
|
+
|
|
5796
|
+
_Source: src/i18n/locale-store.ts_
|
|
5797
|
+
|
|
5798
|
+
### createMessageFormatter
|
|
5799
|
+
|
|
5800
|
+
**Kind:** `function`
|
|
5801
|
+
|
|
5802
|
+
Форматтер одной локали с кэшем разбора: сообщение разбирается один раз, на первый вызов.
|
|
5803
|
+
Это внутренность `I18nService.t()` — сам `t()` добавляет сверху поиск ключа в словаре, маркер
|
|
5804
|
+
пропущенного ключа и откат на резервную локаль.
|
|
5805
|
+
|
|
5806
|
+
**Signature:**
|
|
5807
|
+
```typescript
|
|
5808
|
+
export function createMessageFormatter(
|
|
5809
|
+
locale: string,
|
|
5810
|
+
options: MessageFormatOptions = {}
|
|
5811
|
+
): (source: string, values?: MessageValues) => string
|
|
5812
|
+
```
|
|
5813
|
+
|
|
5814
|
+
**Parameters:**
|
|
5815
|
+
- `locale` — - Локаль форматтера.
|
|
5816
|
+
- `options` — - Настройки; см. {@link MessageFormatOptions}.
|
|
5817
|
+
|
|
5818
|
+
**Returns:** Функция `(сообщение, значения) => строка`.
|
|
5819
|
+
|
|
5820
|
+
**Examples:**
|
|
5821
|
+
|
|
5822
|
+
```ts
|
|
5823
|
+
const format = createMessageFormatter('ru');
|
|
5824
|
+
format('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}', { count: 5 });
|
|
5825
|
+
// '5 файлов'
|
|
5826
|
+
```
|
|
5827
|
+
|
|
5828
|
+
_Source: src/i18n/message-format.ts_
|
|
5829
|
+
|
|
5290
5830
|
### createModel
|
|
5291
5831
|
|
|
5292
5832
|
**Kind:** `function`
|
|
@@ -5341,6 +5881,20 @@ export function cross<TSnapshot>(
|
|
|
5341
5881
|
|
|
5342
5882
|
_Source: src/form/validation/operators.ts_
|
|
5343
5883
|
|
|
5884
|
+
### DEFAULT_LOCALE
|
|
5885
|
+
|
|
5886
|
+
**Kind:** `const`
|
|
5887
|
+
|
|
5888
|
+
Локаль «провайдера нет»: английский, пустой словарь. Каждый пакет при промахе по словарю берёт
|
|
5889
|
+
свою встроенную английскую таблицу, поэтому пустой словарь означает «всё по-английски».
|
|
5890
|
+
|
|
5891
|
+
**Signature:**
|
|
5892
|
+
```typescript
|
|
5893
|
+
export const DEFAULT_LOCALE: FormLocale
|
|
5894
|
+
```
|
|
5895
|
+
|
|
5896
|
+
_Source: src/i18n/locale.ts_
|
|
5897
|
+
|
|
5344
5898
|
### defer
|
|
5345
5899
|
|
|
5346
5900
|
**Kind:** `function`
|
|
@@ -5377,6 +5931,37 @@ export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
|
5377
5931
|
|
|
5378
5932
|
_Source: src/form/behaviors/context.ts_
|
|
5379
5933
|
|
|
5934
|
+
### defineMessages
|
|
5935
|
+
|
|
5936
|
+
**Kind:** `function`
|
|
5937
|
+
|
|
5938
|
+
`msg` с типизированными ключами: принимает английский словарь приложения, проверяет ключ при
|
|
5939
|
+
компиляции и подставляет английский текст как `defaultMessage` — без провайдера и при промахе
|
|
5940
|
+
по локали форма покажет его, а не ключ.
|
|
5941
|
+
|
|
5942
|
+
**Signature:**
|
|
5943
|
+
```typescript
|
|
5944
|
+
export function defineMessages<D extends Messages>(
|
|
5945
|
+
defaults: D
|
|
5946
|
+
): {
|
|
5947
|
+
msg(key: keyof D & string, values?: Readonly<Record<string, unknown>>): MessageDescriptor;
|
|
5948
|
+
}
|
|
5949
|
+
```
|
|
5950
|
+
|
|
5951
|
+
**Parameters:**
|
|
5952
|
+
- `defaults` — - Английский словарь приложения.
|
|
5953
|
+
|
|
5954
|
+
**Examples:**
|
|
5955
|
+
|
|
5956
|
+
```ts
|
|
5957
|
+
import appEn from './locales/en.json';
|
|
5958
|
+
|
|
5959
|
+
export const { msg } = defineMessages(appEn);
|
|
5960
|
+
msg('profile.email.label'); // опечатка в ключе — ошибка компиляции
|
|
5961
|
+
```
|
|
5962
|
+
|
|
5963
|
+
_Source: src/i18n/descriptor.ts_
|
|
5964
|
+
|
|
5380
5965
|
### defineValidationSchema
|
|
5381
5966
|
|
|
5382
5967
|
**Kind:** `function`
|
|
@@ -5435,7 +6020,8 @@ _Source: src/form/behaviors/node.ts_
|
|
|
5435
6020
|
**Kind:** `function`
|
|
5436
6021
|
|
|
5437
6022
|
Применить под-правила к КАЖДОМУ элементу текущего массива модели.
|
|
5438
|
-
`U extends object` — элементы должны быть под-моделями (объектами);
|
|
6023
|
+
`U extends object` — элементы должны быть под-моделями (объектами); массив примитивов — одно
|
|
6024
|
+
значение поля, его проверяют целиком: `validate(model.$.<массив>, [...])`.
|
|
5439
6025
|
|
|
5440
6026
|
**Signature:**
|
|
5441
6027
|
```typescript
|
|
@@ -5454,9 +6040,11 @@ _Source: src/form/validation/operators.ts_
|
|
|
5454
6040
|
Обойти ВСЕ листовые сигналы модели (включая элементы массивов), вызвав `visit` на каждом.
|
|
5455
6041
|
|
|
5456
6042
|
Внутри реактивного `effect` служит подпиской «любое поле изменилось»: `visit(sig => void sig.value)`
|
|
5457
|
-
подписывает на значения листьев, а обход массивов через `items.value` — на их состав.
|
|
5458
|
-
|
|
5459
|
-
|
|
6043
|
+
подписывает на значения листьев, а обход массивов через `items.value` — на их состав. Тот же
|
|
6044
|
+
паттерн, что `revalidateWhen`, но без ручного перечисления зависимостей.
|
|
6045
|
+
|
|
6046
|
+
Сами массивы обход не посещает — только их элементы. Поле, привязанное к массиву целиком
|
|
6047
|
+
(мультивыбор), живёт на сигнале массива; чтобы увидеть и его, берите {@link eachValueSignal}.
|
|
5460
6048
|
|
|
5461
6049
|
**Signature:**
|
|
5462
6050
|
```typescript
|
|
@@ -5468,18 +6056,40 @@ export function eachLeafSignal<T>(
|
|
|
5468
6056
|
|
|
5469
6057
|
_Source: src/model/create-model.ts_
|
|
5470
6058
|
|
|
5471
|
-
###
|
|
6059
|
+
### eachValueSignal
|
|
5472
6060
|
|
|
5473
6061
|
**Kind:** `function`
|
|
5474
6062
|
|
|
5475
|
-
|
|
6063
|
+
Обойти ВСЕ ручки значений модели: листья (включая элементы массивов) И сами массивы.
|
|
6064
|
+
|
|
6065
|
+
Отличие от {@link eachLeafSignal} — узлы-массивы: массив бывает одним значением поля
|
|
6066
|
+
(`{ value: model.$.tags, component }`), и нода такого поля привязана к сигналу массива, а не
|
|
6067
|
+
к сигналам элементов. Обходчику, которому нужна именно НОДА поля (стратегия `blur` подписывается
|
|
6068
|
+
на её `touched`), одних листьев мало — он не увидел бы мультивыбор. Так строятся триггеры
|
|
6069
|
+
`change`/`blur` стратегий валидации (см. `createFormValidation`).
|
|
5476
6070
|
|
|
5477
6071
|
**Signature:**
|
|
5478
6072
|
```typescript
|
|
5479
|
-
export function
|
|
6073
|
+
export function eachValueSignal<T>(
|
|
6074
|
+
model: FormModel<T>,
|
|
6075
|
+
visit: (signal: PathAwareSignal<unknown>) => void
|
|
6076
|
+
): void
|
|
5480
6077
|
```
|
|
5481
6078
|
|
|
5482
|
-
_Source: src/
|
|
6079
|
+
_Source: src/model/create-model.ts_
|
|
6080
|
+
|
|
6081
|
+
### effect
|
|
6082
|
+
|
|
6083
|
+
**Kind:** `function`
|
|
6084
|
+
|
|
6085
|
+
Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup.
|
|
6086
|
+
|
|
6087
|
+
**Signature:**
|
|
6088
|
+
```typescript
|
|
6089
|
+
export function effect(fn: () => void | (() => void)): void
|
|
6090
|
+
```
|
|
6091
|
+
|
|
6092
|
+
_Source: src/form/behaviors/context.ts_
|
|
5483
6093
|
|
|
5484
6094
|
### email
|
|
5485
6095
|
|
|
@@ -5641,6 +6251,136 @@ exclusiveFlag(model.$.contacts, (row) => row.$.primary);
|
|
|
5641
6251
|
|
|
5642
6252
|
_Source: src/form/behaviors/collections.ts_
|
|
5643
6253
|
|
|
6254
|
+
### extendLocale
|
|
6255
|
+
|
|
6256
|
+
**Kind:** `function`
|
|
6257
|
+
|
|
6258
|
+
Новая локаль на основе существующей: словари сливаются (ключи из `patch` важнее), остальные
|
|
6259
|
+
поля заменяются. Исходный объект не меняется.
|
|
6260
|
+
|
|
6261
|
+
**Signature:**
|
|
6262
|
+
```typescript
|
|
6263
|
+
export function extendLocale(base: FormLocale, patch: Partial<FormLocale>): FormLocale
|
|
6264
|
+
```
|
|
6265
|
+
|
|
6266
|
+
**Parameters:**
|
|
6267
|
+
- `base` — - Локаль-основа, например поставляемая китом.
|
|
6268
|
+
- `patch` — - Свои ключи, переопределения китовых подписей и поля локали.
|
|
6269
|
+
|
|
6270
|
+
**Examples:**
|
|
6271
|
+
|
|
6272
|
+
```ts
|
|
6273
|
+
import { ru } from '@reformer/ui-kit/locale/ru';
|
|
6274
|
+
|
|
6275
|
+
const appRu = extendLocale(ru, {
|
|
6276
|
+
messages: { 'profile.title': 'Профиль', 'kit.formWizard.submit': 'Отправить' },
|
|
6277
|
+
});
|
|
6278
|
+
```
|
|
6279
|
+
|
|
6280
|
+
_Source: src/i18n/locale.ts_
|
|
6281
|
+
|
|
6282
|
+
### fetchMessages
|
|
6283
|
+
|
|
6284
|
+
**Kind:** `function`
|
|
6285
|
+
|
|
6286
|
+
Источник локали по сети: забирает JSON по адресу, построенному из кода языка. Файл может быть
|
|
6287
|
+
словарём (`{ "profile.title": "Профиль" }`) или локалью целиком (`{ "code": "ru", "messages": … }`).
|
|
6288
|
+
Ответ не `2xx` — ошибка загрузки.
|
|
6289
|
+
|
|
6290
|
+
**Signature:**
|
|
6291
|
+
```typescript
|
|
6292
|
+
export function fetchMessages(url: (code: string) => string, init?: RequestInit): LocaleSource
|
|
6293
|
+
```
|
|
6294
|
+
|
|
6295
|
+
**Parameters:**
|
|
6296
|
+
- `url` — - Адрес файла для языка.
|
|
6297
|
+
- `init` — - Параметры запроса, передаются в `fetch` как есть.
|
|
6298
|
+
|
|
6299
|
+
**Examples:**
|
|
6300
|
+
|
|
6301
|
+
```ts
|
|
6302
|
+
fetchMessages((code) => `/locales/${code}.json`);
|
|
6303
|
+
```
|
|
6304
|
+
|
|
6305
|
+
_Source: src/i18n/loader.ts_
|
|
6306
|
+
|
|
6307
|
+
### FIELD_WRAPPER_ONLY_PROPS
|
|
6308
|
+
|
|
6309
|
+
**Kind:** `const`
|
|
6310
|
+
|
|
6311
|
+
Props, которые адресованы обёртке поля, а не контролу: `labelTooltip` рисует `FormField` у
|
|
6312
|
+
подписи. Они приходят в общем мешке `componentProps`, и без среза утекли бы в DOM контрола.
|
|
6313
|
+
|
|
6314
|
+
**Signature:**
|
|
6315
|
+
```typescript
|
|
6316
|
+
export const FIELD_WRAPPER_ONLY_PROPS: readonly string[]
|
|
6317
|
+
```
|
|
6318
|
+
|
|
6319
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
6320
|
+
|
|
6321
|
+
### FieldAdapter
|
|
6322
|
+
|
|
6323
|
+
**Kind:** `interface`
|
|
6324
|
+
|
|
6325
|
+
Адаптер поля: описывает диалект контрола (`checked`/`onCheckedChange`, `value`/`onValueChange`,
|
|
6326
|
+
DOM-событие в `onChange`, …). Все ключи необязательны — по умолчанию seam проходит как есть.
|
|
6327
|
+
|
|
6328
|
+
**Signature:**
|
|
6329
|
+
```typescript
|
|
6330
|
+
export interface FieldAdapter {
|
|
6331
|
+
/** Проп, из которого контрол читает значение (по умолчанию `'value'`). */
|
|
6332
|
+
valueProp?: string;
|
|
6333
|
+
/** Колбэк, через который контрол эмитит изменение (по умолчанию `'onChange'`). */
|
|
6334
|
+
changeProp?: string;
|
|
6335
|
+
/**
|
|
6336
|
+
* Эмит контрола → значение поля (по умолчанию — как есть). `rest` — прочие props контрола
|
|
6337
|
+
* (например, чтобы достать `options` при резолве значения).
|
|
6338
|
+
*/
|
|
6339
|
+
fromEmit?: (arg: unknown, rest: Record<string, unknown>) => unknown;
|
|
6340
|
+
/** Значение поля → `valueProp` контрола (коэрсия `null`/`undefined`; по умолчанию — как есть). */
|
|
6341
|
+
toValue?: (value: unknown) => unknown;
|
|
6342
|
+
/** Проброс blur нестандартным каналом (по умолчанию — `onBlur`). */
|
|
6343
|
+
bindBlur?: (onBlur: () => void) => Record<string, unknown>;
|
|
6344
|
+
/** Ключи, которые убрать из `componentProps` перед спредом в контрол. */
|
|
6345
|
+
strip?: string[];
|
|
6346
|
+
/**
|
|
6347
|
+
* Передавать ли контролу ноду формы пропом `control` (только рендерер). По умолчанию `false`:
|
|
6348
|
+
* errors/touched обслуживает обёртка поля. Ставь `true`, если контрол сам вызывает
|
|
6349
|
+
* `useFormControl(control)`.
|
|
6350
|
+
*/
|
|
6351
|
+
passControl?: boolean;
|
|
6352
|
+
}
|
|
6353
|
+
```
|
|
6354
|
+
|
|
6355
|
+
**Examples:**
|
|
6356
|
+
|
|
6357
|
+
Radix Checkbox
|
|
6358
|
+
```ts
|
|
6359
|
+
const checkedAdapter: FieldAdapter = {
|
|
6360
|
+
valueProp: 'checked',
|
|
6361
|
+
changeProp: 'onCheckedChange',
|
|
6362
|
+
fromEmit: (c) => c === true,
|
|
6363
|
+
toValue: (v) => v ?? false,
|
|
6364
|
+
};
|
|
6365
|
+
```
|
|
6366
|
+
|
|
6367
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
6368
|
+
|
|
6369
|
+
### FieldAdapterCarrier
|
|
6370
|
+
|
|
6371
|
+
**Kind:** `interface`
|
|
6372
|
+
|
|
6373
|
+
Компонент, объявивший свой диалект статикой.
|
|
6374
|
+
|
|
6375
|
+
**Signature:**
|
|
6376
|
+
```typescript
|
|
6377
|
+
export interface FieldAdapterCarrier {
|
|
6378
|
+
reformerAdapter?: FieldAdapter;
|
|
6379
|
+
}
|
|
6380
|
+
```
|
|
6381
|
+
|
|
6382
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
6383
|
+
|
|
5644
6384
|
### FieldConfig
|
|
5645
6385
|
|
|
5646
6386
|
**Kind:** `interface`
|
|
@@ -5746,11 +6486,12 @@ export interface FieldControlState<T> {
|
|
|
5746
6486
|
* @example
|
|
5747
6487
|
* ```tsx
|
|
5748
6488
|
* const { errors } = useFormControl(field);
|
|
6489
|
+
* const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
5749
6490
|
*
|
|
5750
6491
|
* return (
|
|
5751
6492
|
* <ul className="error-list">
|
|
5752
6493
|
* {errors.map((error, i) => (
|
|
5753
|
-
* <li key={i}>{error
|
|
6494
|
+
* <li key={i}>{message(error)}</li>
|
|
5754
6495
|
* ))}
|
|
5755
6496
|
* </ul>
|
|
5756
6497
|
* );
|
|
@@ -5813,12 +6554,13 @@ export interface FieldControlState<T> {
|
|
|
5813
6554
|
* @example
|
|
5814
6555
|
* ```tsx
|
|
5815
6556
|
* const { shouldShowError, errors } = useFormControl(field);
|
|
6557
|
+
* const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
5816
6558
|
*
|
|
5817
6559
|
* return (
|
|
5818
6560
|
* <div>
|
|
5819
6561
|
* <input {...props} />
|
|
5820
6562
|
* {shouldShowError && (
|
|
5821
|
-
* <span className="error">{errors[0]
|
|
6563
|
+
* <span className="error">{message(errors[0])}</span>
|
|
5822
6564
|
* )}
|
|
5823
6565
|
* </div>
|
|
5824
6566
|
* );
|
|
@@ -5881,6 +6623,7 @@ control: FieldNode<string>;
|
|
|
5881
6623
|
|
|
5882
6624
|
function TextField({ control }: Props) {
|
|
5883
6625
|
const state = useFormControl(control);
|
|
6626
|
+
const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
5884
6627
|
|
|
5885
6628
|
return (
|
|
5886
6629
|
<div>
|
|
@@ -5890,7 +6633,7 @@ disabled={state.disabled}
|
|
|
5890
6633
|
onChange={e => control.setValue(e.target.value)}
|
|
5891
6634
|
/>
|
|
5892
6635
|
{state.shouldShowError && state.errors[0] && (
|
|
5893
|
-
<span className="error">{state.errors[0]
|
|
6636
|
+
<span className="error">{message(state.errors[0])}</span>
|
|
5894
6637
|
)}
|
|
5895
6638
|
</div>
|
|
5896
6639
|
);
|
|
@@ -5903,6 +6646,38 @@ onChange={e => control.setValue(e.target.value)}
|
|
|
5903
6646
|
|
|
5904
6647
|
_Source: src/platforms/react/hooks/types.ts_
|
|
5905
6648
|
|
|
6649
|
+
### FieldHandle
|
|
6650
|
+
|
|
6651
|
+
**Kind:** `interface`
|
|
6652
|
+
|
|
6653
|
+
Базовый императивный контракт поля формы.
|
|
6654
|
+
|
|
6655
|
+
Обёртка поля (`FormField.Control` из `@reformer/cdk`, рендерер `@reformer/renderer-react`)
|
|
6656
|
+
строит его из DOM-узла контрола. Композиты со своим handle (`open`/`close`/`reload`/…) отдают
|
|
6657
|
+
его как есть — он расширяет этот контракт.
|
|
6658
|
+
|
|
6659
|
+
Достаётся из render-схемы по селектору:
|
|
6660
|
+
`schema.node(sel).getRef<FieldHandle>().current?.focus()`.
|
|
6661
|
+
|
|
6662
|
+
Покрывает ТОЛЬКО императивные действия. Реактивное состояние (value / disabled / options /
|
|
6663
|
+
validation) живёт в behaviors и через handle не дублируется.
|
|
6664
|
+
|
|
6665
|
+
**Signature:**
|
|
6666
|
+
```typescript
|
|
6667
|
+
export interface FieldHandle {
|
|
6668
|
+
/** Сфокусировать поле (делегирует на DOM-элемент контрола). */
|
|
6669
|
+
focus(): void;
|
|
6670
|
+
/** Снять фокус. */
|
|
6671
|
+
blur(): void;
|
|
6672
|
+
/** Проскроллить поле в область видимости. */
|
|
6673
|
+
scrollIntoView(opts?: ScrollIntoViewOptions): void;
|
|
6674
|
+
/** Живой DOM-элемент поля (или `null` до монтирования / для размонтированной ноды). */
|
|
6675
|
+
getElement(): HTMLElement | null;
|
|
6676
|
+
}
|
|
6677
|
+
```
|
|
6678
|
+
|
|
6679
|
+
_Source: src/platforms/react/field-binding/field-handle.ts_
|
|
6680
|
+
|
|
5906
6681
|
### FieldNode
|
|
5907
6682
|
|
|
5908
6683
|
**Kind:** `class`
|
|
@@ -5923,7 +6698,7 @@ export class FieldNode<T> extends FormNode<T> {
|
|
|
5923
6698
|
```typescript
|
|
5924
6699
|
const field = new FieldNode({
|
|
5925
6700
|
value: '',
|
|
5926
|
-
component:
|
|
6701
|
+
component: Input,
|
|
5927
6702
|
validators: [required, email],
|
|
5928
6703
|
});
|
|
5929
6704
|
|
|
@@ -5934,6 +6709,23 @@ console.log(field.valid.value); // true
|
|
|
5934
6709
|
|
|
5935
6710
|
_Source: src/form/nodes/field-node.ts_
|
|
5936
6711
|
|
|
6712
|
+
### FieldSeam
|
|
6713
|
+
|
|
6714
|
+
**Kind:** `interface`
|
|
6715
|
+
|
|
6716
|
+
Seam поля: текущее значение и value-based колбэки.
|
|
6717
|
+
|
|
6718
|
+
**Signature:**
|
|
6719
|
+
```typescript
|
|
6720
|
+
export interface FieldSeam {
|
|
6721
|
+
value: unknown;
|
|
6722
|
+
onChange: (value: unknown) => void;
|
|
6723
|
+
onBlur: () => void;
|
|
6724
|
+
}
|
|
6725
|
+
```
|
|
6726
|
+
|
|
6727
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
6728
|
+
|
|
5937
6729
|
### FieldStatus
|
|
5938
6730
|
|
|
5939
6731
|
**Kind:** `type`
|
|
@@ -6064,6 +6856,33 @@ todos.forEach((item, i) => {
|
|
|
6064
6856
|
|
|
6065
6857
|
_Source: src/form/types/form-proxy.ts_
|
|
6066
6858
|
|
|
6859
|
+
### formatPattern
|
|
6860
|
+
|
|
6861
|
+
**Kind:** `function`
|
|
6862
|
+
|
|
6863
|
+
Собирает строку из разобранного сообщения. Значения подставляются как текст и повторно
|
|
6864
|
+
не разбираются.
|
|
6865
|
+
|
|
6866
|
+
**Signature:**
|
|
6867
|
+
```typescript
|
|
6868
|
+
export function formatPattern(
|
|
6869
|
+
pattern: MessagePattern,
|
|
6870
|
+
values: MessageValues,
|
|
6871
|
+
locale: string,
|
|
6872
|
+
options: MessageFormatOptions = {}
|
|
6873
|
+
): string
|
|
6874
|
+
```
|
|
6875
|
+
|
|
6876
|
+
**Parameters:**
|
|
6877
|
+
- `pattern` — - Результат {@link parseMessage}.
|
|
6878
|
+
- `values` — - Значения аргументов.
|
|
6879
|
+
- `locale` — - Локаль для {@link Intl.PluralRules} и {@link Intl.NumberFormat} (`ru`, `en`, …).
|
|
6880
|
+
- `options` — - Настройки; см. {@link MessageFormatOptions}.
|
|
6881
|
+
|
|
6882
|
+
**Returns:** Готовая строка интерфейса.
|
|
6883
|
+
|
|
6884
|
+
_Source: src/i18n/message-format.ts_
|
|
6885
|
+
|
|
6067
6886
|
### FormBehavior
|
|
6068
6887
|
|
|
6069
6888
|
**Kind:** `interface`
|
|
@@ -6209,6 +7028,43 @@ try {
|
|
|
6209
7028
|
|
|
6210
7029
|
_Source: src/form/validation/error-handler.ts_
|
|
6211
7030
|
|
|
7031
|
+
### FormLocale
|
|
7032
|
+
|
|
7033
|
+
**Kind:** `interface`
|
|
7034
|
+
|
|
7035
|
+
Локаль формы.
|
|
7036
|
+
|
|
7037
|
+
**Signature:**
|
|
7038
|
+
```typescript
|
|
7039
|
+
export interface FormLocale {
|
|
7040
|
+
/** Код языка BCP 47 (`en`, `ru`, `pt-BR`): по нему работают `Intl` и множественные формы. */
|
|
7041
|
+
readonly code: string;
|
|
7042
|
+
/** Словарь сообщений. Ключа нет — пакет подставит встроенный английский. */
|
|
7043
|
+
readonly messages: Messages;
|
|
7044
|
+
/** Первый день недели календаря. По умолчанию — воскресенье. */
|
|
7045
|
+
readonly weekStartsOn?: WeekStart;
|
|
7046
|
+
/**
|
|
7047
|
+
* Необязательное переопределение: объект локали `react-day-picker` / `date-fns`. Для ядра
|
|
7048
|
+
* непрозрачен. Без него даты форматируются через `Intl` по {@link FormLocale.code}.
|
|
7049
|
+
*/
|
|
7050
|
+
readonly dateLocale?: unknown;
|
|
7051
|
+
/** Необязательное переопределение: формат даты в токенах `date-fns` для DatePicker. */
|
|
7052
|
+
readonly dateFormat?: string;
|
|
7053
|
+
}
|
|
7054
|
+
```
|
|
7055
|
+
|
|
7056
|
+
**Examples:**
|
|
7057
|
+
|
|
7058
|
+
```ts
|
|
7059
|
+
const ru: FormLocale = {
|
|
7060
|
+
code: 'ru',
|
|
7061
|
+
weekStartsOn: 1,
|
|
7062
|
+
messages: { 'kit.select.selected': 'Выбрано: {count}' },
|
|
7063
|
+
};
|
|
7064
|
+
```
|
|
7065
|
+
|
|
7066
|
+
_Source: src/i18n/locale.ts_
|
|
7067
|
+
|
|
6212
7068
|
### FormModel
|
|
6213
7069
|
|
|
6214
7070
|
**Kind:** `type`
|
|
@@ -6374,14 +7230,14 @@ interface Form {
|
|
|
6374
7230
|
}
|
|
6375
7231
|
|
|
6376
7232
|
const schema: FormSchema<Form> = {
|
|
6377
|
-
name: { value: '', component:
|
|
7233
|
+
name: { value: '', component: Input },
|
|
6378
7234
|
address: {
|
|
6379
|
-
city: { value: '', component:
|
|
6380
|
-
street: { value: '', component:
|
|
7235
|
+
city: { value: '', component: Input },
|
|
7236
|
+
street: { value: '', component: Input },
|
|
6381
7237
|
},
|
|
6382
7238
|
items: [{
|
|
6383
|
-
title: { value: '', component:
|
|
6384
|
-
price: { value: 0, component:
|
|
7239
|
+
title: { value: '', component: Input },
|
|
7240
|
+
price: { value: 0, component: Input },
|
|
6385
7241
|
}],
|
|
6386
7242
|
};
|
|
6387
7243
|
```
|
|
@@ -6642,6 +7498,26 @@ validate(model.$.appointmentDate, [required(), futureDate({ message: 'Дата
|
|
|
6642
7498
|
|
|
6643
7499
|
_Source: src/form/validators/future-date.ts_
|
|
6644
7500
|
|
|
7501
|
+
### getFieldAdapter
|
|
7502
|
+
|
|
7503
|
+
**Kind:** `function`
|
|
7504
|
+
|
|
7505
|
+
Адаптер, объявленный компонентом статикой `reformerAdapter`, либо `undefined`.
|
|
7506
|
+
|
|
7507
|
+
**Signature:**
|
|
7508
|
+
```typescript
|
|
7509
|
+
export function getFieldAdapter(component: unknown): FieldAdapter | undefined
|
|
7510
|
+
```
|
|
7511
|
+
|
|
7512
|
+
**Examples:**
|
|
7513
|
+
|
|
7514
|
+
```ts
|
|
7515
|
+
Checkbox.reformerAdapter = checkedAdapter;
|
|
7516
|
+
getFieldAdapter(Checkbox); // → checkedAdapter
|
|
7517
|
+
```
|
|
7518
|
+
|
|
7519
|
+
_Source: src/platforms/react/field-binding/field-adapter.ts_
|
|
7520
|
+
|
|
6645
7521
|
### getNodeForSignal
|
|
6646
7522
|
|
|
6647
7523
|
**Kind:** `function`
|
|
@@ -6738,8 +7614,8 @@ export class GroupNode<T> extends FormNode<T> {
|
|
|
6738
7614
|
|
|
6739
7615
|
```typescript
|
|
6740
7616
|
const form = new GroupNode({
|
|
6741
|
-
email: { valueSignal: model.$.email, component:
|
|
6742
|
-
password: { valueSignal: model.$.password, component:
|
|
7617
|
+
email: { valueSignal: model.$.email, component: Input },
|
|
7618
|
+
password: { valueSignal: model.$.password, component: Input },
|
|
6743
7619
|
});
|
|
6744
7620
|
|
|
6745
7621
|
// Прямой доступ к полям через Proxy
|
|
@@ -6770,6 +7646,163 @@ export interface GroupNodeConfig<T> {
|
|
|
6770
7646
|
|
|
6771
7647
|
_Source: src/form/types/index.ts_
|
|
6772
7648
|
|
|
7649
|
+
### I18N_CONTEXT_MARKER
|
|
7650
|
+
|
|
7651
|
+
**Kind:** `const`
|
|
7652
|
+
|
|
7653
|
+
Имя контекста и одновременно маркер, по которому страж ищет его копии в сборках.
|
|
7654
|
+
|
|
7655
|
+
**Signature:**
|
|
7656
|
+
```typescript
|
|
7657
|
+
export const I18N_CONTEXT_MARKER
|
|
7658
|
+
```
|
|
7659
|
+
|
|
7660
|
+
_Source: src/platforms/react/i18n/context.ts_
|
|
7661
|
+
|
|
7662
|
+
### I18nContextValue
|
|
7663
|
+
|
|
7664
|
+
**Kind:** `interface`
|
|
7665
|
+
|
|
7666
|
+
Что видит компонент: ручка активной локали и состояние её загрузки.
|
|
7667
|
+
|
|
7668
|
+
**Signature:**
|
|
7669
|
+
```typescript
|
|
7670
|
+
export interface I18nContextValue extends I18nHandle {
|
|
7671
|
+
/** Идёт загрузка другого языка; на экране пока прежний. Без загрузчика всегда `false`. */
|
|
7672
|
+
readonly pending: boolean;
|
|
7673
|
+
/** Ошибка последней загрузки языка. */
|
|
7674
|
+
readonly error: unknown;
|
|
7675
|
+
}
|
|
7676
|
+
```
|
|
7677
|
+
|
|
7678
|
+
_Source: src/platforms/react/i18n/context.ts_
|
|
7679
|
+
|
|
7680
|
+
### I18nHandle
|
|
7681
|
+
|
|
7682
|
+
**Kind:** `interface`
|
|
7683
|
+
|
|
7684
|
+
Ручка локали: перевод и форматирование по одному языку.
|
|
7685
|
+
|
|
7686
|
+
**Signature:**
|
|
7687
|
+
```typescript
|
|
7688
|
+
export interface I18nHandle {
|
|
7689
|
+
/** Локаль, по которой работает ручка. */
|
|
7690
|
+
readonly locale: FormLocale;
|
|
7691
|
+
/** Код языка — то же, что `locale.code`. */
|
|
7692
|
+
readonly code: string;
|
|
7693
|
+
/** Есть ли ключ в словаре локали (встроенные английские таблицы не учитываются). */
|
|
7694
|
+
has(key: string): boolean;
|
|
7695
|
+
/**
|
|
7696
|
+
* Перевод ключа приложения: словарь локали → `defaultMessage` → встроенная таблица ядра → ключ.
|
|
7697
|
+
*
|
|
7698
|
+
* @param key - Ключ сообщения.
|
|
7699
|
+
* @param values - Значения подстановок.
|
|
7700
|
+
* @param defaultMessage - Английский текст на случай, если ключа в локали нет.
|
|
7701
|
+
*/
|
|
7702
|
+
t(key: string, values?: MessageValues, defaultMessage?: string): string;
|
|
7703
|
+
/** Число по правилам языка. */
|
|
7704
|
+
number(value: number, options?: Intl.NumberFormatOptions): string;
|
|
7705
|
+
/** Дата по правилам языка. */
|
|
7706
|
+
date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
|
|
7707
|
+
/** Размер файла: `1536` → `1.5 KB` (`1,5 КБ` в русской локали). База — 1024. */
|
|
7708
|
+
fileSize(bytes: number): string;
|
|
7709
|
+
}
|
|
7710
|
+
```
|
|
7711
|
+
|
|
7712
|
+
_Source: src/i18n/translator.ts_
|
|
7713
|
+
|
|
7714
|
+
### I18nLoadingProviderProps
|
|
7715
|
+
|
|
7716
|
+
**Kind:** `interface`
|
|
7717
|
+
|
|
7718
|
+
Режим «провайдер грузит сам».
|
|
7719
|
+
|
|
7720
|
+
**Signature:**
|
|
7721
|
+
```typescript
|
|
7722
|
+
export interface I18nLoadingProviderProps {
|
|
7723
|
+
/** Код языка. Смена кода запускает загрузку; до её конца на экране прежний язык. */
|
|
7724
|
+
lang: string;
|
|
7725
|
+
/** Загрузчик локалей — обычно результат `createLocaleLoader`. */
|
|
7726
|
+
load: LocaleLoad;
|
|
7727
|
+
/**
|
|
7728
|
+
* Что показать, пока не загружена самая первая локаль. По умолчанию — ничего: иначе на кадр
|
|
7729
|
+
* мелькнул бы встроенный английский. Если локаль уже в кэше загрузчика (`load.preload(lang)`
|
|
7730
|
+
* до монтирования), первый рендер синхронный и `fallback` не показывается.
|
|
7731
|
+
*/
|
|
7732
|
+
fallback?: ReactNode;
|
|
7733
|
+
/** Отказ загрузки. Язык на экране при этом не меняется. */
|
|
7734
|
+
onError?: (error: unknown, lang: string) => void;
|
|
7735
|
+
children?: ReactNode;
|
|
7736
|
+
}
|
|
7737
|
+
```
|
|
7738
|
+
|
|
7739
|
+
_Source: src/platforms/react/i18n/provider.ts_
|
|
7740
|
+
|
|
7741
|
+
### I18nProvider
|
|
7742
|
+
|
|
7743
|
+
**Kind:** `function`
|
|
7744
|
+
|
|
7745
|
+
Провайдер локализации для подписей компонентов, ошибок валидации и авторских подписей формы.
|
|
7746
|
+
Без провайдера компоненты говорят встроенным английским.
|
|
7747
|
+
|
|
7748
|
+
При смене `lang` на экране остаётся прежний язык, пока новый не загрузится (`useI18n().pending`
|
|
7749
|
+
— для индикатора). При быстром переключении применяется только последний запрос. Вложенный
|
|
7750
|
+
провайдер заменяет локаль для своего поддерева целиком.
|
|
7751
|
+
|
|
7752
|
+
**Signature:**
|
|
7753
|
+
```typescript
|
|
7754
|
+
export function I18nProvider(props: I18nProviderProps): ReactElement | null
|
|
7755
|
+
```
|
|
7756
|
+
|
|
7757
|
+
**Examples:**
|
|
7758
|
+
|
|
7759
|
+
Провайдер грузит локаль сам
|
|
7760
|
+
```tsx
|
|
7761
|
+
const [lang, setLang] = useState('ru');
|
|
7762
|
+
|
|
7763
|
+
<I18nProvider lang={lang} load={loadLocale}>
|
|
7764
|
+
<LangSwitch value={lang} onChange={setLang} />
|
|
7765
|
+
<CreditForm />
|
|
7766
|
+
</I18nProvider>
|
|
7767
|
+
```
|
|
7768
|
+
|
|
7769
|
+
Готовая локаль — SSR и тесты
|
|
7770
|
+
```tsx
|
|
7771
|
+
import { ru } from '@reformer/ui-kit/locale/ru';
|
|
7772
|
+
|
|
7773
|
+
<I18nProvider locale={ru}><CreditForm /></I18nProvider>
|
|
7774
|
+
```
|
|
7775
|
+
|
|
7776
|
+
_Source: src/platforms/react/i18n/provider.ts_
|
|
7777
|
+
|
|
7778
|
+
### I18nProviderProps
|
|
7779
|
+
|
|
7780
|
+
**Kind:** `type`
|
|
7781
|
+
|
|
7782
|
+
**Signature:**
|
|
7783
|
+
```typescript
|
|
7784
|
+
export type I18nProviderProps = I18nStaticProviderProps | I18nLoadingProviderProps;
|
|
7785
|
+
```
|
|
7786
|
+
|
|
7787
|
+
_Source: src/platforms/react/i18n/provider.ts_
|
|
7788
|
+
|
|
7789
|
+
### I18nStaticProviderProps
|
|
7790
|
+
|
|
7791
|
+
**Kind:** `interface`
|
|
7792
|
+
|
|
7793
|
+
Режим «готовая локаль».
|
|
7794
|
+
|
|
7795
|
+
**Signature:**
|
|
7796
|
+
```typescript
|
|
7797
|
+
export interface I18nStaticProviderProps {
|
|
7798
|
+
/** Локаль. Смена объекта переключает язык сразу. */
|
|
7799
|
+
locale: FormLocale;
|
|
7800
|
+
children?: ReactNode;
|
|
7801
|
+
}
|
|
7802
|
+
```
|
|
7803
|
+
|
|
7804
|
+
_Source: src/platforms/react/i18n/provider.ts_
|
|
7805
|
+
|
|
6773
7806
|
### integer
|
|
6774
7807
|
|
|
6775
7808
|
**Kind:** `function`
|
|
@@ -7015,6 +8048,19 @@ if (isGroupNode(node)) {
|
|
|
7015
8048
|
|
|
7016
8049
|
_Source: src/form/type-guards.ts_
|
|
7017
8050
|
|
|
8051
|
+
### isMessageDescriptor
|
|
8052
|
+
|
|
8053
|
+
**Kind:** `function`
|
|
8054
|
+
|
|
8055
|
+
Является ли значение описателем сообщения.
|
|
8056
|
+
|
|
8057
|
+
**Signature:**
|
|
8058
|
+
```typescript
|
|
8059
|
+
export function isMessageDescriptor(value: unknown): value is MessageDescriptor
|
|
8060
|
+
```
|
|
8061
|
+
|
|
8062
|
+
_Source: src/i18n/descriptor.ts_
|
|
8063
|
+
|
|
7018
8064
|
### isModelContainerSignal
|
|
7019
8065
|
|
|
7020
8066
|
**Kind:** `function`
|
|
@@ -7075,25 +8121,240 @@ validate(model.$.amount, [required(), isNumber({ message: 'Введите чис
|
|
|
7075
8121
|
|
|
7076
8122
|
_Source: src/form/validators/is-number.ts_
|
|
7077
8123
|
|
|
7078
|
-
###
|
|
8124
|
+
### isValueSignal
|
|
7079
8125
|
|
|
7080
8126
|
**Kind:** `function`
|
|
7081
8127
|
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
Вызывается операторами {@link computeFrom}/`compute` для их сигнала-цели. После этого
|
|
7085
|
-
bulk-сеттеры (`model.set`/`model.patch`, `patchValue`/`setValue`) пропускают это поле, чтобы
|
|
7086
|
-
значение из payload не затирало вычисляемое. Прикладной код напрямую обычно не вызывает.
|
|
8128
|
+
«Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив
|
|
8129
|
+
дерева `model.$` (значение — массив целиком, `.value` читается и пишется).
|
|
7087
8130
|
|
|
7088
|
-
|
|
7089
|
-
|
|
8131
|
+
Группа сюда не входит: она читается целиком, но не пишется и полем не бывает. Этой проверкой
|
|
8132
|
+
`createForm` и рендереры отличают узел-поле схемы (`{ value, component }`) от контейнера —
|
|
8133
|
+
вместо голого `value instanceof Signal`, который узел-массив не проходит.
|
|
7090
8134
|
|
|
7091
8135
|
**Signature:**
|
|
7092
8136
|
```typescript
|
|
7093
|
-
export function
|
|
8137
|
+
export function isValueSignal(value: unknown): value is Signal<unknown>
|
|
7094
8138
|
```
|
|
7095
8139
|
|
|
7096
|
-
**
|
|
8140
|
+
**Examples:**
|
|
8141
|
+
|
|
8142
|
+
```typescript
|
|
8143
|
+
const model = createModel({ name: '', tags: [] as string[], address: { city: '' } });
|
|
8144
|
+
isValueSignal(model.$.name); // true — лист
|
|
8145
|
+
isValueSignal(model.$.tags); // true — массив как значение
|
|
8146
|
+
isValueSignal(model.$.address); // false — группа
|
|
8147
|
+
```
|
|
8148
|
+
|
|
8149
|
+
_Source: src/model/model-signals-proxy.ts_
|
|
8150
|
+
|
|
8151
|
+
### loadCoreLocale
|
|
8152
|
+
|
|
8153
|
+
**Kind:** `const`
|
|
8154
|
+
|
|
8155
|
+
Источник локалей ядра для `createLocaleLoader`: чанк нужного языка по запросу.
|
|
8156
|
+
|
|
8157
|
+
**Signature:**
|
|
8158
|
+
```typescript
|
|
8159
|
+
export const loadCoreLocale
|
|
8160
|
+
```
|
|
8161
|
+
|
|
8162
|
+
**Examples:**
|
|
8163
|
+
|
|
8164
|
+
```ts
|
|
8165
|
+
const loadLocale = createLocaleLoader([loadCoreLocale, fetchMessages((code) => `/locales/${code}.json`)]);
|
|
8166
|
+
```
|
|
8167
|
+
|
|
8168
|
+
_Source: src/locale/index.ts_
|
|
8169
|
+
|
|
8170
|
+
### LocaleIssue
|
|
8171
|
+
|
|
8172
|
+
**Kind:** `interface`
|
|
8173
|
+
|
|
8174
|
+
Что не так со словарём.
|
|
8175
|
+
|
|
8176
|
+
**Signature:**
|
|
8177
|
+
```typescript
|
|
8178
|
+
export interface LocaleIssue {
|
|
8179
|
+
readonly key: string;
|
|
8180
|
+
/**
|
|
8181
|
+
* - `syntax` — сообщение не разбирается;
|
|
8182
|
+
* - `missing` — ключ есть в образце, но его нет в словаре;
|
|
8183
|
+
* - `extra` — ключа нет в образце;
|
|
8184
|
+
* - `arguments` — набор подстановок не совпадает с образцом.
|
|
8185
|
+
*/
|
|
8186
|
+
readonly kind: 'syntax' | 'missing' | 'extra' | 'arguments';
|
|
8187
|
+
readonly message: string;
|
|
8188
|
+
}
|
|
8189
|
+
```
|
|
8190
|
+
|
|
8191
|
+
_Source: src/i18n/locale.ts_
|
|
8192
|
+
|
|
8193
|
+
### LocaleLoad
|
|
8194
|
+
|
|
8195
|
+
**Kind:** `interface`
|
|
8196
|
+
|
|
8197
|
+
Функция загрузки локали. `peek` — необязательный синхронный кэш (есть у `createLocaleLoader`).
|
|
8198
|
+
|
|
8199
|
+
**Signature:**
|
|
8200
|
+
```typescript
|
|
8201
|
+
export interface LocaleLoad {
|
|
8202
|
+
(code: string): Promise<FormLocale>;
|
|
8203
|
+
peek?(code: string): FormLocale | undefined;
|
|
8204
|
+
}
|
|
8205
|
+
```
|
|
8206
|
+
|
|
8207
|
+
_Source: src/i18n/locale-store.ts_
|
|
8208
|
+
|
|
8209
|
+
### LocaleLoader
|
|
8210
|
+
|
|
8211
|
+
**Kind:** `interface`
|
|
8212
|
+
|
|
8213
|
+
Загрузчик локалей с кэшем.
|
|
8214
|
+
|
|
8215
|
+
**Signature:**
|
|
8216
|
+
```typescript
|
|
8217
|
+
export interface LocaleLoader {
|
|
8218
|
+
/** Загрузить локаль языка. Повторный вызов возвращает тот же промис и ту же локаль. */
|
|
8219
|
+
(code: string): Promise<FormLocale>;
|
|
8220
|
+
/** Уже загруженная локаль или `undefined`. Позволяет провайдеру отрисоваться синхронно. */
|
|
8221
|
+
peek(code: string): FormLocale | undefined;
|
|
8222
|
+
/** То же, что вызов загрузчика; имя для чтения в коде старта приложения. */
|
|
8223
|
+
preload(code: string): Promise<FormLocale>;
|
|
8224
|
+
}
|
|
8225
|
+
```
|
|
8226
|
+
|
|
8227
|
+
_Source: src/i18n/loader.ts_
|
|
8228
|
+
|
|
8229
|
+
### LocaleSource
|
|
8230
|
+
|
|
8231
|
+
**Kind:** `type`
|
|
8232
|
+
|
|
8233
|
+
Источник локали. Получает код языка, отдаёт локаль или словарь — сразу или промисом.
|
|
8234
|
+
`null`/`undefined` означает «для этого языка у меня ничего нет» и ошибкой не считается.
|
|
8235
|
+
|
|
8236
|
+
**Signature:**
|
|
8237
|
+
```typescript
|
|
8238
|
+
export type LocaleSource = (code: string) => LocaleSourceResult | Promise<LocaleSourceResult>;
|
|
8239
|
+
```
|
|
8240
|
+
|
|
8241
|
+
_Source: src/i18n/loader.ts_
|
|
8242
|
+
|
|
8243
|
+
### LocaleSourceResult
|
|
8244
|
+
|
|
8245
|
+
**Kind:** `type`
|
|
8246
|
+
|
|
8247
|
+
Что может вернуть источник: локаль целиком, её часть, голый словарь или ничего.
|
|
8248
|
+
|
|
8249
|
+
**Signature:**
|
|
8250
|
+
```typescript
|
|
8251
|
+
export type LocaleSourceResult = Partial<FormLocale> | Messages | null | undefined;
|
|
8252
|
+
```
|
|
8253
|
+
|
|
8254
|
+
_Source: src/i18n/loader.ts_
|
|
8255
|
+
|
|
8256
|
+
### LocaleStore
|
|
8257
|
+
|
|
8258
|
+
**Kind:** `interface`
|
|
8259
|
+
|
|
8260
|
+
Хранилище активной локали.
|
|
8261
|
+
|
|
8262
|
+
**Signature:**
|
|
8263
|
+
```typescript
|
|
8264
|
+
export interface LocaleStore {
|
|
8265
|
+
getState(): LocaleStoreState;
|
|
8266
|
+
subscribe(listener: () => void): () => void;
|
|
8267
|
+
/** Запросить язык. Повторный запрос того же языка, пока он грузится или уже показан, — no-op. */
|
|
8268
|
+
setLang(lang: string): void;
|
|
8269
|
+
}
|
|
8270
|
+
```
|
|
8271
|
+
|
|
8272
|
+
_Source: src/i18n/locale-store.ts_
|
|
8273
|
+
|
|
8274
|
+
### LocaleStoreOptions
|
|
8275
|
+
|
|
8276
|
+
**Kind:** `interface`
|
|
8277
|
+
|
|
8278
|
+
Настройки хранилища.
|
|
8279
|
+
|
|
8280
|
+
**Signature:**
|
|
8281
|
+
```typescript
|
|
8282
|
+
export interface LocaleStoreOptions {
|
|
8283
|
+
/** Вызывается при отказе загрузки языка, который всё ещё запрошен. */
|
|
8284
|
+
readonly onError?: (error: unknown, lang: string) => void;
|
|
8285
|
+
}
|
|
8286
|
+
```
|
|
8287
|
+
|
|
8288
|
+
_Source: src/i18n/locale-store.ts_
|
|
8289
|
+
|
|
8290
|
+
### LocaleStoreState
|
|
8291
|
+
|
|
8292
|
+
**Kind:** `interface`
|
|
8293
|
+
|
|
8294
|
+
Состояние хранилища. Объект неизменяемый: новая ссылка — новое состояние.
|
|
8295
|
+
|
|
8296
|
+
**Signature:**
|
|
8297
|
+
```typescript
|
|
8298
|
+
export interface LocaleStoreState {
|
|
8299
|
+
/** Запрошенный язык. Пока `pending`, может отличаться от языка `locale`. */
|
|
8300
|
+
readonly lang: string;
|
|
8301
|
+
/** Локаль, которая сейчас на экране; `null` — ни одна ещё не загружена. */
|
|
8302
|
+
readonly locale: FormLocale | null;
|
|
8303
|
+
/** Идёт загрузка запрошенного языка. */
|
|
8304
|
+
readonly pending: boolean;
|
|
8305
|
+
/** Ошибка последней загрузки; сбрасывается следующей удачной. */
|
|
8306
|
+
readonly error: unknown;
|
|
8307
|
+
}
|
|
8308
|
+
```
|
|
8309
|
+
|
|
8310
|
+
_Source: src/i18n/locale-store.ts_
|
|
8311
|
+
|
|
8312
|
+
### LocalizableText
|
|
8313
|
+
|
|
8314
|
+
**Kind:** `type`
|
|
8315
|
+
|
|
8316
|
+
Текст, который можно задать строкой или описателем.
|
|
8317
|
+
|
|
8318
|
+
**Signature:**
|
|
8319
|
+
```typescript
|
|
8320
|
+
export type LocalizableText = string | MessageDescriptor;
|
|
8321
|
+
```
|
|
8322
|
+
|
|
8323
|
+
_Source: src/i18n/descriptor.ts_
|
|
8324
|
+
|
|
8325
|
+
### makeElementFieldHandle
|
|
8326
|
+
|
|
8327
|
+
**Kind:** `function`
|
|
8328
|
+
|
|
8329
|
+
Собирает базовый {@link FieldHandle}, делегирующий на DOM-элемент по ссылке `el`.
|
|
8330
|
+
Все вызовы null-safe: до монтирования (`el.current === null`) — no-op, без исключений.
|
|
8331
|
+
|
|
8332
|
+
**Signature:**
|
|
8333
|
+
```typescript
|
|
8334
|
+
export function makeElementFieldHandle(el: RefObject<HTMLElement | null>): FieldHandle
|
|
8335
|
+
```
|
|
8336
|
+
|
|
8337
|
+
_Source: src/platforms/react/field-binding/field-handle.ts_
|
|
8338
|
+
|
|
8339
|
+
### markDerived
|
|
8340
|
+
|
|
8341
|
+
**Kind:** `function`
|
|
8342
|
+
|
|
8343
|
+
Пометить сигнал производным (вычисляемым).
|
|
8344
|
+
|
|
8345
|
+
Вызывается операторами {@link computeFrom}/`compute` для их сигнала-цели. После этого
|
|
8346
|
+
bulk-сеттеры (`model.set`/`model.patch`, `patchValue`/`setValue`) пропускают это поле, чтобы
|
|
8347
|
+
значение из payload не затирало вычисляемое. Прикладной код напрямую обычно не вызывает.
|
|
8348
|
+
|
|
8349
|
+
Идемпотентна на уровне флага: повторные вызовы наращивают счётчик ссылок, чтобы каждый оператор,
|
|
8350
|
+
пишущий в этот сигнал, был сбалансирован своим {@link unmarkDerived} при dispose.
|
|
8351
|
+
|
|
8352
|
+
**Signature:**
|
|
8353
|
+
```typescript
|
|
8354
|
+
export function markDerived(signal: Signal<any>): void
|
|
8355
|
+
```
|
|
8356
|
+
|
|
8357
|
+
**Parameters:**
|
|
7097
8358
|
- `signal` — - Сигнал значения из {@link FormModel}, которым владеет compute
|
|
7098
8359
|
|
|
7099
8360
|
**Examples:**
|
|
@@ -7317,103 +8578,261 @@ export function maxFileSize<TForm = unknown, TField = unknown>(
|
|
|
7317
8578
|
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7318
8579
|
попадают `maxFileSize`, `fileName` и `actualSize`.
|
|
7319
8580
|
|
|
7320
|
-
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
8581
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
8582
|
+
|
|
8583
|
+
**Examples:**
|
|
8584
|
+
|
|
8585
|
+
Ограничение размера вложений
|
|
8586
|
+
```typescript
|
|
8587
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8588
|
+
import { maxFileSize } from '@reformer/core/validators';
|
|
8589
|
+
|
|
8590
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8591
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8592
|
+
validate(model.$.documents, [maxFileSize(5 * 1024 * 1024, { message: 'Файл больше 5 МБ' })]);
|
|
8593
|
+
});
|
|
8594
|
+
```
|
|
8595
|
+
|
|
8596
|
+
_Source: src/form/validators/max-file-size.ts_
|
|
8597
|
+
|
|
8598
|
+
### maxLength
|
|
8599
|
+
|
|
8600
|
+
**Kind:** `function`
|
|
8601
|
+
|
|
8602
|
+
Фабрика валидатора максимальной длины строки или массива.
|
|
8603
|
+
|
|
8604
|
+
Работает со строкой или массивом (проверяется `value.length`). Пустые значения
|
|
8605
|
+
(`null`/`undefined`/`''`) и значения без числового `length` пропускаются
|
|
8606
|
+
(используйте {@link required} для обязательности).
|
|
8607
|
+
|
|
8608
|
+
**Signature:**
|
|
8609
|
+
```typescript
|
|
8610
|
+
export function maxLength<TForm = unknown, TField = unknown>(
|
|
8611
|
+
maxLen: number,
|
|
8612
|
+
options?: ValidateOptions
|
|
8613
|
+
): Validator<TForm, TField>
|
|
8614
|
+
```
|
|
8615
|
+
|
|
8616
|
+
**Parameters:**
|
|
8617
|
+
- `maxLen` — - Максимально допустимая длина (включительно)
|
|
8618
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
8619
|
+
попадают `maxLength` и `actualLength`.
|
|
8620
|
+
|
|
8621
|
+
**Returns:** Чистый валидатор {@link Validator} для строки или массива
|
|
8622
|
+
|
|
8623
|
+
**Examples:**
|
|
8624
|
+
|
|
8625
|
+
Максимальная длина строки
|
|
8626
|
+
```typescript
|
|
8627
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8628
|
+
import { maxLength } from '@reformer/core/validators';
|
|
8629
|
+
|
|
8630
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8631
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8632
|
+
validate(model.$.name, [maxLength(50)]);
|
|
8633
|
+
validate(model.$.bio, [maxLength(500, { message: 'Максимум 500 символов' })]);
|
|
8634
|
+
});
|
|
8635
|
+
```
|
|
8636
|
+
|
|
8637
|
+
_Source: src/form/validators/max-length.ts_
|
|
8638
|
+
|
|
8639
|
+
### maxTotalFileSize
|
|
8640
|
+
|
|
8641
|
+
**Kind:** `function`
|
|
8642
|
+
|
|
8643
|
+
Фабрика валидатора суммарного размера всех файлов.
|
|
8644
|
+
|
|
8645
|
+
Складывает `size` всех файлов значения (элементы без числового `size` в сумму
|
|
8646
|
+
не входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются
|
|
8647
|
+
(используйте {@link required} для обязательности).
|
|
8648
|
+
|
|
8649
|
+
**Signature:**
|
|
8650
|
+
```typescript
|
|
8651
|
+
export function maxTotalFileSize<TForm = unknown, TField = unknown>(
|
|
8652
|
+
maxTotal: number,
|
|
8653
|
+
options?: ValidateOptions
|
|
8654
|
+
): Validator<TForm, TField>
|
|
8655
|
+
```
|
|
8656
|
+
|
|
8657
|
+
**Parameters:**
|
|
8658
|
+
- `maxTotal` — - Максимально допустимый суммарный размер в байтах (включительно)
|
|
8659
|
+
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
8660
|
+
попадают `maxTotalFileSize` и `actualTotal`.
|
|
8661
|
+
|
|
8662
|
+
**Returns:** Чистый валидатор {@link Validator} для файла или массива файлов
|
|
8663
|
+
|
|
8664
|
+
**Examples:**
|
|
8665
|
+
|
|
8666
|
+
Суммарно не более 20 МБ
|
|
8667
|
+
```typescript
|
|
8668
|
+
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
8669
|
+
import { maxTotalFileSize } from '@reformer/core/validators';
|
|
8670
|
+
|
|
8671
|
+
// Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
|
|
8672
|
+
const validation = defineValidationSchema<MyForm>(({ model }) => {
|
|
8673
|
+
validate(model.$.documents, [maxTotalFileSize(20 * 1024 * 1024, { message: 'Суммарно не более 20 МБ' })]);
|
|
8674
|
+
});
|
|
8675
|
+
```
|
|
8676
|
+
|
|
8677
|
+
_Source: src/form/validators/max-total-file-size.ts_
|
|
8678
|
+
|
|
8679
|
+
### MESSAGE_DESCRIPTOR
|
|
8680
|
+
|
|
8681
|
+
**Kind:** `const`
|
|
8682
|
+
|
|
8683
|
+
Бренд описателя. `Symbol.for` — чтобы описатель узнавался и через границу двух копий модуля
|
|
8684
|
+
(разные чанки, разные версии пакета на странице).
|
|
8685
|
+
|
|
8686
|
+
**Signature:**
|
|
8687
|
+
```typescript
|
|
8688
|
+
export const MESSAGE_DESCRIPTOR: unique symbol
|
|
8689
|
+
```
|
|
8690
|
+
|
|
8691
|
+
_Source: src/i18n/descriptor.ts_
|
|
8692
|
+
|
|
8693
|
+
### messageArguments
|
|
8694
|
+
|
|
8695
|
+
**Kind:** `function`
|
|
8696
|
+
|
|
8697
|
+
Имена аргументов сообщения, включая те, что встречаются только внутри веток `plural`/`select`.
|
|
8698
|
+
|
|
8699
|
+
**Signature:**
|
|
8700
|
+
```typescript
|
|
8701
|
+
export function messageArguments(
|
|
8702
|
+
pattern: MessagePattern,
|
|
8703
|
+
out: Set<string> = new Set()
|
|
8704
|
+
): Set<string>
|
|
8705
|
+
```
|
|
8706
|
+
|
|
8707
|
+
**Parameters:**
|
|
8708
|
+
- `pattern` — - Результат `parseMessage`.
|
|
8709
|
+
|
|
8710
|
+
_Source: src/i18n/locale.ts_
|
|
8711
|
+
|
|
8712
|
+
### MessageBranches
|
|
8713
|
+
|
|
8714
|
+
**Kind:** `type`
|
|
8715
|
+
|
|
8716
|
+
Ветки `plural`/`select`: категория CLDR (`one`, `few`, …), точное совпадение (`=0`) или значение `select`.
|
|
8717
|
+
|
|
8718
|
+
**Signature:**
|
|
8719
|
+
```typescript
|
|
8720
|
+
export type MessageBranches = ReadonlyMap<string, MessagePattern>;
|
|
8721
|
+
```
|
|
8722
|
+
|
|
8723
|
+
_Source: src/i18n/message-format.ts_
|
|
8724
|
+
|
|
8725
|
+
### MessageDescriptor
|
|
8726
|
+
|
|
8727
|
+
**Kind:** `interface`
|
|
8728
|
+
|
|
8729
|
+
Описатель сообщения. Намеренно без ключей `value`, `array`, `item`, `component`: обход схемы в
|
|
8730
|
+
`createForm` узнаёт по ним узел, а описатель узлом не является.
|
|
8731
|
+
|
|
8732
|
+
**Signature:**
|
|
8733
|
+
```typescript
|
|
8734
|
+
export interface MessageDescriptor {
|
|
8735
|
+
readonly [MESSAGE_DESCRIPTOR]: true;
|
|
8736
|
+
/** Ключ сообщения в словаре приложения. */
|
|
8737
|
+
readonly key: string;
|
|
8738
|
+
/** Значения подстановок. */
|
|
8739
|
+
readonly values?: Readonly<Record<string, unknown>>;
|
|
8740
|
+
/** Английский текст на случай, если ключа в локали нет. */
|
|
8741
|
+
readonly defaultMessage?: string;
|
|
8742
|
+
}
|
|
8743
|
+
```
|
|
8744
|
+
|
|
8745
|
+
_Source: src/i18n/descriptor.ts_
|
|
8746
|
+
|
|
8747
|
+
### MessageFormatOptions
|
|
8748
|
+
|
|
8749
|
+
**Kind:** `interface`
|
|
8750
|
+
|
|
8751
|
+
Настройки форматирования.
|
|
8752
|
+
|
|
8753
|
+
**Signature:**
|
|
8754
|
+
```typescript
|
|
8755
|
+
export interface MessageFormatOptions {
|
|
8756
|
+
/**
|
|
8757
|
+
* Что подставить вместо аргумента, которого нет в значениях (или чьё значение не число там, где
|
|
8758
|
+
* ожидалось число). По умолчанию — заметный маркер `⟦name⟧`, тот же приём, что и для пропущенного
|
|
8759
|
+
* ключа: пропуск должен попадаться разработчику, а не пользователю.
|
|
8760
|
+
*/
|
|
8761
|
+
readonly onMissingArgument?: (name: string) => string;
|
|
8762
|
+
}
|
|
8763
|
+
```
|
|
8764
|
+
|
|
8765
|
+
_Source: src/i18n/message-format.ts_
|
|
8766
|
+
|
|
8767
|
+
### MessageNode
|
|
8768
|
+
|
|
8769
|
+
**Kind:** `type`
|
|
7321
8770
|
|
|
7322
|
-
|
|
8771
|
+
Узел разобранного сообщения.
|
|
7323
8772
|
|
|
7324
|
-
|
|
8773
|
+
**Signature:**
|
|
7325
8774
|
```typescript
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
});
|
|
8775
|
+
export type MessageNode =
|
|
8776
|
+
| { readonly kind: 'text'; readonly value: string }
|
|
8777
|
+
| { readonly kind: 'arg'; readonly name: string }
|
|
8778
|
+
| { readonly kind: 'number'; readonly name: string }
|
|
8779
|
+
| { readonly kind: 'plural'; readonly name: string; readonly branches: MessageBranches }
|
|
8780
|
+
| { readonly kind: 'select'; readonly name: string; readonly branches: MessageBranches };
|
|
7333
8781
|
```
|
|
7334
8782
|
|
|
7335
|
-
_Source: src/
|
|
7336
|
-
|
|
7337
|
-
### maxLength
|
|
8783
|
+
_Source: src/i18n/message-format.ts_
|
|
7338
8784
|
|
|
7339
|
-
|
|
8785
|
+
### MessagePattern
|
|
7340
8786
|
|
|
7341
|
-
|
|
8787
|
+
**Kind:** `type`
|
|
7342
8788
|
|
|
7343
|
-
|
|
7344
|
-
(`null`/`undefined`/`''`) и значения без числового `length` пропускаются
|
|
7345
|
-
(используйте {@link required} для обязательности).
|
|
8789
|
+
Разобранное сообщение — плоская последовательность узлов.
|
|
7346
8790
|
|
|
7347
8791
|
**Signature:**
|
|
7348
8792
|
```typescript
|
|
7349
|
-
export
|
|
7350
|
-
maxLen: number,
|
|
7351
|
-
options?: ValidateOptions
|
|
7352
|
-
): Validator<TForm, TField>
|
|
8793
|
+
export type MessagePattern = readonly MessageNode[];
|
|
7353
8794
|
```
|
|
7354
8795
|
|
|
7355
|
-
|
|
7356
|
-
- `maxLen` — - Максимально допустимая длина (включительно)
|
|
7357
|
-
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7358
|
-
попадают `maxLength` и `actualLength`.
|
|
8796
|
+
_Source: src/i18n/message-format.ts_
|
|
7359
8797
|
|
|
7360
|
-
|
|
8798
|
+
### Messages
|
|
7361
8799
|
|
|
7362
|
-
**
|
|
8800
|
+
**Kind:** `type`
|
|
7363
8801
|
|
|
7364
|
-
|
|
7365
|
-
```typescript
|
|
7366
|
-
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7367
|
-
import { maxLength } from '@reformer/core/validators';
|
|
8802
|
+
Плоский словарь: ключ → сообщение в формате ICU (см. `message-format.ts`).
|
|
7368
8803
|
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
validate(model.$.bio, [maxLength(500, { message: 'Максимум 500 символов' })]);
|
|
7373
|
-
});
|
|
8804
|
+
**Signature:**
|
|
8805
|
+
```typescript
|
|
8806
|
+
export type Messages = Readonly<Record<string, string>>;
|
|
7374
8807
|
```
|
|
7375
8808
|
|
|
7376
|
-
_Source: src/
|
|
7377
|
-
|
|
7378
|
-
### maxTotalFileSize
|
|
8809
|
+
_Source: src/i18n/locale.ts_
|
|
7379
8810
|
|
|
7380
|
-
|
|
8811
|
+
### MessageSyntaxError
|
|
7381
8812
|
|
|
7382
|
-
|
|
8813
|
+
**Kind:** `class`
|
|
7383
8814
|
|
|
7384
|
-
|
|
7385
|
-
не входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются
|
|
7386
|
-
(используйте {@link required} для обязательности).
|
|
8815
|
+
Отказ разбора сообщения: синтаксис или отсутствие обязательной ветки `other`.
|
|
7387
8816
|
|
|
7388
8817
|
**Signature:**
|
|
7389
8818
|
```typescript
|
|
7390
|
-
export
|
|
7391
|
-
maxTotal: number,
|
|
7392
|
-
options?: ValidateOptions
|
|
7393
|
-
): Validator<TForm, TField>
|
|
8819
|
+
export class MessageSyntaxError extends Error { /* … */ }
|
|
7394
8820
|
```
|
|
7395
8821
|
|
|
7396
|
-
|
|
7397
|
-
- `maxTotal` — - Максимально допустимый суммарный размер в байтах (включительно)
|
|
7398
|
-
- `options` — - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
|
|
7399
|
-
попадают `maxTotalFileSize` и `actualTotal`.
|
|
8822
|
+
_Source: src/i18n/message-format.ts_
|
|
7400
8823
|
|
|
7401
|
-
|
|
8824
|
+
### MessageValues
|
|
7402
8825
|
|
|
7403
|
-
**
|
|
8826
|
+
**Kind:** `type`
|
|
7404
8827
|
|
|
7405
|
-
|
|
7406
|
-
```typescript
|
|
7407
|
-
import { defineValidationSchema, validate } from '@reformer/core/validation';
|
|
7408
|
-
import { maxTotalFileSize } from '@reformer/core/validators';
|
|
8828
|
+
Значения аргументов сообщения.
|
|
7409
8829
|
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
});
|
|
8830
|
+
**Signature:**
|
|
8831
|
+
```typescript
|
|
8832
|
+
export type MessageValues = Readonly<Record<string, unknown>>;
|
|
7414
8833
|
```
|
|
7415
8834
|
|
|
7416
|
-
_Source: src/
|
|
8835
|
+
_Source: src/i18n/message-format.ts_
|
|
7417
8836
|
|
|
7418
8837
|
### min
|
|
7419
8838
|
|
|
@@ -7691,7 +9110,10 @@ export interface ModelApi<T> {
|
|
|
7691
9110
|
reset(): void;
|
|
7692
9111
|
/** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */
|
|
7693
9112
|
captureInitial(): void;
|
|
7694
|
-
/**
|
|
9113
|
+
/**
|
|
9114
|
+
* Резолв строкового пути в сигнал (для error-routing/мостов): лист либо массив целиком
|
|
9115
|
+
* (ручка значения массива — см. {@link ModelArraySignals}). Для пути группы — `undefined`.
|
|
9116
|
+
*/
|
|
7695
9117
|
signalAt(path: string): PathAwareSignal<unknown> | undefined;
|
|
7696
9118
|
}
|
|
7697
9119
|
```
|
|
@@ -7796,8 +9218,8 @@ export class ModelArrayNode<T extends object> extends FormNode<T[]> { /* … */
|
|
|
7796
9218
|
const model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });
|
|
7797
9219
|
|
|
7798
9220
|
const rowItem = (item: FormModel<{ name: string; qty: number }>) => ({
|
|
7799
|
-
name: { value: item.$.name, component:
|
|
7800
|
-
qty: { value: item.$.qty, component:
|
|
9221
|
+
name: { value: item.$.name, component: Input },
|
|
9222
|
+
qty: { value: item.$.qty, component: Input },
|
|
7801
9223
|
});
|
|
7802
9224
|
|
|
7803
9225
|
const form = createForm({
|
|
@@ -7820,12 +9242,23 @@ _Source: src/form/nodes/model-array-node.ts_
|
|
|
7820
9242
|
**Kind:** `type`
|
|
7821
9243
|
|
|
7822
9244
|
Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`
|
|
7823
|
-
и
|
|
7824
|
-
|
|
9245
|
+
и сигнал значения массива целиком (реагирует и на правку элемента, и на изменение состава —
|
|
9246
|
+
push/removeAt/move).
|
|
9247
|
+
|
|
9248
|
+
В отличие от группы, узел ЗАПИСЫВАЕМ: `model.$.tags.value = ['a']` заменяет массив целиком. Это
|
|
9249
|
+
делает его {@link PathAwareSignal} — к нему привязывают поле (`{ value: model.$.tags, component }`),
|
|
9250
|
+
правило (`validate(model.$.tags, …)`) и поведение, как к листу. Так массив служит ОДНИМ значением
|
|
9251
|
+
поля: мультивыбор, теги, список файлов.
|
|
9252
|
+
|
|
9253
|
+
`V` — объявленный тип поля, включая `null`/`undefined`: поле `string[] | null` с начальным `null`
|
|
9254
|
+
в рантайме — лист с тем же `.value`/`peek`/`subscribe`, поэтому общая часть честна для обоих
|
|
9255
|
+
случаев. `length` и индексы есть только у настоящего узла-массива (начальное значение — массив).
|
|
7825
9256
|
|
|
7826
9257
|
**Signature:**
|
|
7827
9258
|
```typescript
|
|
7828
|
-
export type ModelArraySignals<U, V> =
|
|
9259
|
+
export type ModelArraySignals<U, V> = Signal<V> & {
|
|
9260
|
+
/** Путь массива в модели (dot-нотация). */
|
|
9261
|
+
readonly __path: string;
|
|
7829
9262
|
readonly length: number;
|
|
7830
9263
|
readonly [index: number]: ModelSignalNode<U>;
|
|
7831
9264
|
};
|
|
@@ -7908,6 +9341,34 @@ export type ModelValue<V> =
|
|
|
7908
9341
|
|
|
7909
9342
|
_Source: src/model/types.ts_
|
|
7910
9343
|
|
|
9344
|
+
### msg
|
|
9345
|
+
|
|
9346
|
+
**Kind:** `function`
|
|
9347
|
+
|
|
9348
|
+
Создаёт описатель сообщения.
|
|
9349
|
+
|
|
9350
|
+
**Signature:**
|
|
9351
|
+
```typescript
|
|
9352
|
+
export function msg(
|
|
9353
|
+
key: string,
|
|
9354
|
+
values?: Readonly<Record<string, unknown>>,
|
|
9355
|
+
defaultMessage?: string
|
|
9356
|
+
): MessageDescriptor
|
|
9357
|
+
```
|
|
9358
|
+
|
|
9359
|
+
**Parameters:**
|
|
9360
|
+
- `key` — - Ключ в словаре приложения.
|
|
9361
|
+
- `values` — - Значения подстановок.
|
|
9362
|
+
- `defaultMessage` — - Английский текст на случай, если ключа в локали нет.
|
|
9363
|
+
|
|
9364
|
+
**Examples:**
|
|
9365
|
+
|
|
9366
|
+
```ts
|
|
9367
|
+
{ value: model.$.email, component: Input, componentProps: { label: msg('profile.email.label') } }
|
|
9368
|
+
```
|
|
9369
|
+
|
|
9370
|
+
_Source: src/i18n/descriptor.ts_
|
|
9371
|
+
|
|
7911
9372
|
### multipleOf
|
|
7912
9373
|
|
|
7913
9374
|
**Kind:** `function`
|
|
@@ -7980,25 +9441,25 @@ export class NodeFactory {
|
|
|
7980
9441
|
* // FieldNode
|
|
7981
9442
|
* const field = factory.createNode({
|
|
7982
9443
|
* value: 'test@mail.com',
|
|
7983
|
-
* component:
|
|
9444
|
+
* component: Input,
|
|
7984
9445
|
* validators: [required, email]
|
|
7985
9446
|
* });
|
|
7986
9447
|
*
|
|
7987
9448
|
* // GroupNode
|
|
7988
9449
|
* const group = factory.createNode({
|
|
7989
|
-
* email: { value: '', component:
|
|
7990
|
-
* password: { value: '', component:
|
|
9450
|
+
* email: { value: '', component: Input },
|
|
9451
|
+
* password: { value: '', component: Input }
|
|
7991
9452
|
* });
|
|
7992
9453
|
*
|
|
7993
9454
|
* // ArrayNode (объект)
|
|
7994
9455
|
* const array = factory.createNode({
|
|
7995
|
-
* schema: { title: { value: '', component:
|
|
9456
|
+
* schema: { title: { value: '', component: Input } },
|
|
7996
9457
|
* initialItems: [{ title: 'Item 1' }]
|
|
7997
9458
|
* });
|
|
7998
9459
|
*
|
|
7999
9460
|
* // ArrayNode (массив) - новый формат
|
|
8000
9461
|
* const array2 = factory.createNode([
|
|
8001
|
-
* { title: { value: '', component:
|
|
9462
|
+
* { title: { value: '', component: Input } }, // schema
|
|
8002
9463
|
* { title: 'Item 1' }, // initial item 1
|
|
8003
9464
|
* { title: 'Item 2' } // initial item 2
|
|
8004
9465
|
* ]);
|
|
@@ -8012,7 +9473,7 @@ export class NodeFactory {
|
|
|
8012
9473
|
import { NodeFactory } from '@reformer/core';
|
|
8013
9474
|
|
|
8014
9475
|
const factory = new NodeFactory();
|
|
8015
|
-
const node = factory.createNode({ value: '', component:
|
|
9476
|
+
const node = factory.createNode({ value: '', component: Input }); // → FieldNode<string>
|
|
8016
9477
|
```
|
|
8017
9478
|
|
|
8018
9479
|
_Source: src/form/factories/node-factory.ts_
|
|
@@ -8124,6 +9585,31 @@ export function onDispose(cleanup: BehaviorCleanup): void
|
|
|
8124
9585
|
|
|
8125
9586
|
_Source: src/form/behaviors/context.ts_
|
|
8126
9587
|
|
|
9588
|
+
### parseMessage
|
|
9589
|
+
|
|
9590
|
+
**Kind:** `function`
|
|
9591
|
+
|
|
9592
|
+
Разбирает сообщение в дерево. Бросает {@link MessageSyntaxError} на ошибке синтаксиса и на
|
|
9593
|
+
`plural`/`select` без ветки `other`.
|
|
9594
|
+
|
|
9595
|
+
**Signature:**
|
|
9596
|
+
```typescript
|
|
9597
|
+
export function parseMessage(source: string): MessagePattern
|
|
9598
|
+
```
|
|
9599
|
+
|
|
9600
|
+
**Parameters:**
|
|
9601
|
+
- `source` — - Текст сообщения из словаря.
|
|
9602
|
+
|
|
9603
|
+
**Returns:** Разобранное сообщение для {@link formatPattern}.
|
|
9604
|
+
|
|
9605
|
+
**Examples:**
|
|
9606
|
+
|
|
9607
|
+
```ts
|
|
9608
|
+
parseMessage('{count, plural, one{# файл} few{# файла} many{# файлов} other{# файла}}');
|
|
9609
|
+
```
|
|
9610
|
+
|
|
9611
|
+
_Source: src/i18n/message-format.ts_
|
|
9612
|
+
|
|
8127
9613
|
### pastDate
|
|
8128
9614
|
|
|
8129
9615
|
**Kind:** `function`
|
|
@@ -8388,6 +9874,60 @@ resetWhen(model.$.cardNumber, () => model.paymentType !== 'card', { resetValue:
|
|
|
8388
9874
|
|
|
8389
9875
|
_Source: src/model/behaviors-value.ts_
|
|
8390
9876
|
|
|
9877
|
+
### resolveLocalized
|
|
9878
|
+
|
|
9879
|
+
**Kind:** `function`
|
|
9880
|
+
|
|
9881
|
+
Заменяет описатели сообщений на строки по активной локали. Обходит массивы и простые
|
|
9882
|
+
объекты-литералы — подписи опций, заголовки шагов; сигналы, React-элементы, функции и
|
|
9883
|
+
экземпляры классов (узлы формы) возвращает как есть. Поддерево без описателей сохраняет ссылку:
|
|
9884
|
+
если раскрывать было нечего, возвращается тот же объект.
|
|
9885
|
+
|
|
9886
|
+
**Signature:**
|
|
9887
|
+
```typescript
|
|
9888
|
+
export function resolveLocalized<T>(value: T, i18n: I18nHandle): T
|
|
9889
|
+
```
|
|
9890
|
+
|
|
9891
|
+
**Parameters:**
|
|
9892
|
+
- `value` — - Пропсы, массив или одиночное значение.
|
|
9893
|
+
- `i18n` — - Ручка активной локали (`createI18n` / `useI18n`).
|
|
9894
|
+
|
|
9895
|
+
**Examples:**
|
|
9896
|
+
|
|
9897
|
+
```ts
|
|
9898
|
+
resolveLocalized({ label: msg('profile.email.label'), options: [{ value: 'a', label: msg('opt.a') }] }, i18n);
|
|
9899
|
+
// { label: 'Почта', options: [{ value: 'a', label: 'Вариант А' }] }
|
|
9900
|
+
```
|
|
9901
|
+
|
|
9902
|
+
_Source: src/i18n/resolve-localized.ts_
|
|
9903
|
+
|
|
9904
|
+
### resolveValidationError
|
|
9905
|
+
|
|
9906
|
+
**Kind:** `function`
|
|
9907
|
+
|
|
9908
|
+
Отображаемый текст ошибки валидации. Порядок источников — в шапке модуля.
|
|
9909
|
+
|
|
9910
|
+
**Signature:**
|
|
9911
|
+
```typescript
|
|
9912
|
+
export function resolveValidationError(error: ValidationError, i18n: I18nHandle): string
|
|
9913
|
+
```
|
|
9914
|
+
|
|
9915
|
+
**Parameters:**
|
|
9916
|
+
- `error` — - Ошибка от правила: `code`, `message`, `messageKey`, `params`.
|
|
9917
|
+
- `i18n` — - Ручка активной локали (`createI18n` / `useI18n`).
|
|
9918
|
+
|
|
9919
|
+
**Examples:**
|
|
9920
|
+
|
|
9921
|
+
```ts
|
|
9922
|
+
resolveValidationError({ code: 'minLength', message: '', params: { minLength: 8 } }, i18n);
|
|
9923
|
+
// 'Enter at least 8 characters' · в русской локали — 'Не меньше 8 символов'
|
|
9924
|
+
|
|
9925
|
+
resolveValidationError({ code: 'required', message: 'Укажите телефон' }, i18n);
|
|
9926
|
+
// 'Укажите телефон' — явное сообщение автора важнее словаря
|
|
9927
|
+
```
|
|
9928
|
+
|
|
9929
|
+
_Source: src/i18n/validation-message.ts_
|
|
9930
|
+
|
|
8391
9931
|
### revalidateWhen
|
|
8392
9932
|
|
|
8393
9933
|
**Kind:** `function`
|
|
@@ -8752,6 +10292,32 @@ transformValue(model.$.promoCode, (v) => (v ?? '').toUpperCase());
|
|
|
8752
10292
|
|
|
8753
10293
|
_Source: src/model/behaviors-value.ts_
|
|
8754
10294
|
|
|
10295
|
+
### translateBuiltin
|
|
10296
|
+
|
|
10297
|
+
**Kind:** `function`
|
|
10298
|
+
|
|
10299
|
+
Перевод ключа пакета: словарь локали, затем встроенная английская таблица пакета, затем сам
|
|
10300
|
+
ключ. Английская таблица форматируется по английским правилам — её множественные формы и
|
|
10301
|
+
группировка чисел не зависят от активного языка.
|
|
10302
|
+
|
|
10303
|
+
**Signature:**
|
|
10304
|
+
```typescript
|
|
10305
|
+
export function translateBuiltin(
|
|
10306
|
+
locale: FormLocale,
|
|
10307
|
+
builtin: Messages,
|
|
10308
|
+
key: string,
|
|
10309
|
+
values?: MessageValues
|
|
10310
|
+
): string
|
|
10311
|
+
```
|
|
10312
|
+
|
|
10313
|
+
**Parameters:**
|
|
10314
|
+
- `locale` — - Активная локаль.
|
|
10315
|
+
- `builtin` — - Встроенная английская таблица пакета-владельца ключа.
|
|
10316
|
+
- `key` — - Ключ сообщения.
|
|
10317
|
+
- `values` — - Значения подстановок.
|
|
10318
|
+
|
|
10319
|
+
_Source: src/i18n/translator.ts_
|
|
10320
|
+
|
|
8755
10321
|
### UnknownRecord
|
|
8756
10322
|
|
|
8757
10323
|
**Kind:** `type`
|
|
@@ -8901,6 +10467,33 @@ function ArrayRenderer({ arrayNode }) {
|
|
|
8901
10467
|
|
|
8902
10468
|
_Source: src/platforms/react/hooks/useArrayLength.ts_
|
|
8903
10469
|
|
|
10470
|
+
### useFieldHandleRef
|
|
10471
|
+
|
|
10472
|
+
**Kind:** `function`
|
|
10473
|
+
|
|
10474
|
+
Ref для контрола поля + императивный handle для потребителя.
|
|
10475
|
+
|
|
10476
|
+
Возвращённый ref вешается на контрол. На `outerRef` публикуется:
|
|
10477
|
+
- handle самого контрола, если тот его реализует (`useImperativeHandle` композита);
|
|
10478
|
+
- иначе — базовый {@link FieldHandle}, построенный из DOM-узла контрола.
|
|
10479
|
+
|
|
10480
|
+
Хук вызывается безусловно; вешать ref на контрол стоит только при заданном `outerRef` —
|
|
10481
|
+
так function-компонент без ref-поддержки не получает лишний ref.
|
|
10482
|
+
|
|
10483
|
+
**Signature:**
|
|
10484
|
+
```typescript
|
|
10485
|
+
export function useFieldHandleRef<H = FieldHandle>(
|
|
10486
|
+
outerRef: Ref<H> | undefined
|
|
10487
|
+
): RefObject<unknown>
|
|
10488
|
+
```
|
|
10489
|
+
|
|
10490
|
+
**Parameters:**
|
|
10491
|
+
- `outerRef` — - ref потребителя (из `forwardRef` или `schema.node(sel).getRef()`).
|
|
10492
|
+
|
|
10493
|
+
**Returns:** ref, который нужно передать контролу.
|
|
10494
|
+
|
|
10495
|
+
_Source: src/platforms/react/field-binding/field-handle.ts_
|
|
10496
|
+
|
|
8904
10497
|
### useFormBundle
|
|
8905
10498
|
|
|
8906
10499
|
**Kind:** `function`
|
|
@@ -8968,6 +10561,7 @@ export function useFormControl(
|
|
|
8968
10561
|
Текстовое поле с валидацией
|
|
8969
10562
|
```tsx
|
|
8970
10563
|
import { useFormControl } from '@reformer/core';
|
|
10564
|
+
import { useValidationMessage } from '@reformer/core/i18n';
|
|
8971
10565
|
import type { FieldNode } from '@reformer/core';
|
|
8972
10566
|
|
|
8973
10567
|
interface TextFieldProps {
|
|
@@ -8983,6 +10577,8 @@ shouldShowError,
|
|
|
8983
10577
|
errors,
|
|
8984
10578
|
pending
|
|
8985
10579
|
} = useFormControl(control);
|
|
10580
|
+
// Текст ошибки даёт резолвер: у правил без своего сообщения `message` пуст.
|
|
10581
|
+
const message = useValidationMessage();
|
|
8986
10582
|
|
|
8987
10583
|
return (
|
|
8988
10584
|
<div className="field">
|
|
@@ -9002,7 +10598,7 @@ return (
|
|
|
9002
10598
|
|
|
9003
10599
|
{shouldShowError && errors[0] && (
|
|
9004
10600
|
<span className="error" role="alert">
|
|
9005
|
-
{errors[0]
|
|
10601
|
+
{message(errors[0])}
|
|
9006
10602
|
</span>
|
|
9007
10603
|
)}
|
|
9008
10604
|
</div>
|
|
@@ -9050,6 +10646,7 @@ control: FieldNode<string>;
|
|
|
9050
10646
|
|
|
9051
10647
|
function Select({ control }: SelectProps) {
|
|
9052
10648
|
const { value, disabled, componentProps, shouldShowError, errors } = useFormControl(control);
|
|
10649
|
+
const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
9053
10650
|
const options = componentProps.options as Array<{ value: string; label: string }>;
|
|
9054
10651
|
|
|
9055
10652
|
return (
|
|
@@ -9067,7 +10664,7 @@ onBlur={() => control.markAsTouched()}
|
|
|
9067
10664
|
</option>
|
|
9068
10665
|
))}
|
|
9069
10666
|
</select>
|
|
9070
|
-
{shouldShowError && <span className="error">{errors[0]
|
|
10667
|
+
{shouldShowError && <span className="error">{message(errors[0])}</span>}
|
|
9071
10668
|
</div>
|
|
9072
10669
|
);
|
|
9073
10670
|
}
|
|
@@ -9305,6 +10902,7 @@ dangerouslySetInnerHTML={{ __html: html }}
|
|
|
9305
10902
|
// Основной редактор использует useFormControl для полного состояния
|
|
9306
10903
|
function MarkdownEditor({ control }: MarkdownEditorProps) {
|
|
9307
10904
|
const { value, shouldShowError, errors } = useFormControl(control);
|
|
10905
|
+
const message = useValidationMessage(); // из '@reformer/core/i18n'
|
|
9308
10906
|
|
|
9309
10907
|
return (
|
|
9310
10908
|
<div className="editor-container">
|
|
@@ -9312,7 +10910,7 @@ return (
|
|
|
9312
10910
|
value={value}
|
|
9313
10911
|
onChange={e => control.setValue(e.target.value)}
|
|
9314
10912
|
/>
|
|
9315
|
-
{shouldShowError && <span className="error">{errors[0]
|
|
10913
|
+
{shouldShowError && <span className="error">{message(errors[0])}</span>}
|
|
9316
10914
|
|
|
9317
10915
|
{/* Preview обновляется только при изменении value *}
|
|
9318
10916
|
<MarkdownPreview control={control} />
|
|
@@ -9416,12 +11014,104 @@ export interface UseFormValidationResult {
|
|
|
9416
11014
|
|
|
9417
11015
|
_Source: src/platforms/react/hooks/use-form-validation.ts_
|
|
9418
11016
|
|
|
11017
|
+
### useI18n
|
|
11018
|
+
|
|
11019
|
+
**Kind:** `function`
|
|
11020
|
+
|
|
11021
|
+
Активная локаль: перевод ключей приложения, форматирование чисел, дат и размеров, состояние
|
|
11022
|
+
загрузки языка. Без провайдера — английский с пустым словарём.
|
|
11023
|
+
|
|
11024
|
+
**Signature:**
|
|
11025
|
+
```typescript
|
|
11026
|
+
export function useI18n(): I18nContextValue
|
|
11027
|
+
```
|
|
11028
|
+
|
|
11029
|
+
**Examples:**
|
|
11030
|
+
|
|
11031
|
+
```tsx
|
|
11032
|
+
function LangBadge() {
|
|
11033
|
+
const { code, pending, t } = useI18n();
|
|
11034
|
+
return <span aria-busy={pending}>{t('app.language')}: {code}</span>;
|
|
11035
|
+
}
|
|
11036
|
+
```
|
|
11037
|
+
|
|
11038
|
+
_Source: src/platforms/react/i18n/hooks.ts_
|
|
11039
|
+
|
|
11040
|
+
### useMessages
|
|
11041
|
+
|
|
11042
|
+
**Kind:** `function`
|
|
11043
|
+
|
|
11044
|
+
Переводчик пакета: ищет ключ в локали провайдера, а при промахе берёт встроенную английскую
|
|
11045
|
+
таблицу этого пакета. Так пакет говорит по-английски без провайдера и не зависит от того, знает
|
|
11046
|
+
ли локаль его ключи. Тип ключа выводится из таблицы — опечатка в ключе не скомпилируется.
|
|
11047
|
+
|
|
11048
|
+
`defaults` должен быть стабильной ссылкой (импорт JSON-модуля), иначе переводчик будет
|
|
11049
|
+
пересоздаваться на каждый рендер.
|
|
11050
|
+
|
|
11051
|
+
**Signature:**
|
|
11052
|
+
```typescript
|
|
11053
|
+
export function useMessages<D extends Messages>(
|
|
11054
|
+
defaults: D
|
|
11055
|
+
): (key: keyof D & string, values?: MessageValues) => string
|
|
11056
|
+
```
|
|
11057
|
+
|
|
11058
|
+
**Parameters:**
|
|
11059
|
+
- `defaults` — - Встроенная английская таблица пакета.
|
|
11060
|
+
|
|
11061
|
+
**Returns:** Функция `(ключ, значения?) => строка`; её идентичность меняется вместе с локалью.
|
|
11062
|
+
|
|
11063
|
+
**Examples:**
|
|
11064
|
+
|
|
11065
|
+
```tsx
|
|
11066
|
+
import en from './locale/en.json';
|
|
11067
|
+
|
|
11068
|
+
const useKitMessages = () => useMessages(en);
|
|
11069
|
+
|
|
11070
|
+
function ClearButton() {
|
|
11071
|
+
const t = useKitMessages();
|
|
11072
|
+
return <button aria-label={t('kit.select.clear')} />;
|
|
11073
|
+
}
|
|
11074
|
+
```
|
|
11075
|
+
|
|
11076
|
+
_Source: src/platforms/react/i18n/hooks.ts_
|
|
11077
|
+
|
|
11078
|
+
### useValidationMessage
|
|
11079
|
+
|
|
11080
|
+
**Kind:** `function`
|
|
11081
|
+
|
|
11082
|
+
Резолвер текста ошибки валидации по активной локали: `messageKey` автора → явное `message` →
|
|
11083
|
+
словарь локали по коду → встроенный английский → код. Идентичность функции меняется вместе с
|
|
11084
|
+
локалью, поэтому показанные ошибки переводятся при смене языка без повторной валидации.
|
|
11085
|
+
|
|
11086
|
+
`FormField` из `@reformer/cdk` использует его сам; вызывайте напрямую, если рисуете ошибки
|
|
11087
|
+
своим компонентом.
|
|
11088
|
+
|
|
11089
|
+
**Signature:**
|
|
11090
|
+
```typescript
|
|
11091
|
+
export function useValidationMessage(): (error: ValidationError) => string
|
|
11092
|
+
```
|
|
11093
|
+
|
|
11094
|
+
**Examples:**
|
|
11095
|
+
|
|
11096
|
+
```tsx
|
|
11097
|
+
function FieldErrors({ control }: { control: FieldNode<string> }) {
|
|
11098
|
+
const message = useValidationMessage();
|
|
11099
|
+
const { errors } = useFormControl(control);
|
|
11100
|
+
return <>{errors.map((error) => <span key={error.code}>{message(error)}</span>)}</>;
|
|
11101
|
+
}
|
|
11102
|
+
```
|
|
11103
|
+
|
|
11104
|
+
_Source: src/platforms/react/i18n/hooks.ts_
|
|
11105
|
+
|
|
9419
11106
|
### validate
|
|
9420
11107
|
|
|
9421
11108
|
**Kind:** `function`
|
|
9422
11109
|
|
|
9423
11110
|
Синхронные правила поля.
|
|
9424
11111
|
|
|
11112
|
+
Поле — лист либо массив целиком: `model.$.<массив>` — такая же ручка значения, и правило
|
|
11113
|
+
получает массив (мультивыбор, теги, список файлов).
|
|
11114
|
+
|
|
9425
11115
|
**Signature:**
|
|
9426
11116
|
```typescript
|
|
9427
11117
|
export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void
|
|
@@ -9431,6 +11121,7 @@ export function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TFiel
|
|
|
9431
11121
|
|
|
9432
11122
|
```ts
|
|
9433
11123
|
validate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);
|
|
11124
|
+
validate(model.$.tags, [required(), maxLength(3)]); // tags: string[] — массив как значение
|
|
9434
11125
|
```
|
|
9435
11126
|
|
|
9436
11127
|
_Source: src/form/validation/operators.ts_
|
|
@@ -9461,6 +11152,34 @@ validateAsync(model.$.username, [async (v, { signal }) => {
|
|
|
9461
11152
|
|
|
9462
11153
|
_Source: src/form/validation/operators.ts_
|
|
9463
11154
|
|
|
11155
|
+
### validateLocale
|
|
11156
|
+
|
|
11157
|
+
**Kind:** `function`
|
|
11158
|
+
|
|
11159
|
+
Проверяет словарь: каждое сообщение разбирается, а при заданном образце — набор ключей и имена
|
|
11160
|
+
подстановок совпадают с ним. Нужна там, где компилятор не поможет: словарь пришёл по сети или
|
|
11161
|
+
написан вне репозитория. В рантайме вызывать не обязательно — битое или пропущенное сообщение
|
|
11162
|
+
и так откатится на встроенный английский; проверка нужна тестам и CI.
|
|
11163
|
+
|
|
11164
|
+
**Signature:**
|
|
11165
|
+
```typescript
|
|
11166
|
+
export function validateLocale(messages: Messages, reference?: Messages): LocaleIssue[]
|
|
11167
|
+
```
|
|
11168
|
+
|
|
11169
|
+
**Parameters:**
|
|
11170
|
+
- `messages` — - Проверяемый словарь.
|
|
11171
|
+
- `reference` — - Образец, обычно английский словарь того же пакета.
|
|
11172
|
+
|
|
11173
|
+
**Returns:** Список расхождений; пустой — словарь в порядке.
|
|
11174
|
+
|
|
11175
|
+
**Examples:**
|
|
11176
|
+
|
|
11177
|
+
```ts
|
|
11178
|
+
expect(validateLocale(ruJson, enJson)).toEqual([]);
|
|
11179
|
+
```
|
|
11180
|
+
|
|
11181
|
+
_Source: src/i18n/locale.ts_
|
|
11182
|
+
|
|
9464
11183
|
### validateModel
|
|
9465
11184
|
|
|
9466
11185
|
**Kind:** `function`
|
|
@@ -9517,8 +11236,20 @@ _Source: src/form/validation/run.ts_
|
|
|
9517
11236
|
```typescript
|
|
9518
11237
|
export interface ValidateOptions {
|
|
9519
11238
|
/** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст
|
|
9520
|
-
*
|
|
11239
|
+
* берётся из словаря локали по `code` (`validation.<code>`; см. `resolveValidationError` в
|
|
11240
|
+
* `@reformer/core/i18n`). Явное сообщение важнее словаря, но не переключается вместе с языком —
|
|
11241
|
+
* для локализуемого текста используйте {@link ValidateOptions.messageKey}. */
|
|
9521
11242
|
message?: string;
|
|
11243
|
+
/**
|
|
11244
|
+
* Ключ сообщения в словаре приложения — локализуемый текст автора правила. Если ключ есть в
|
|
11245
|
+
* активной локали, он важнее и `message`, и текста по `code`; если нет — в ход идёт `message`.
|
|
11246
|
+
*
|
|
11247
|
+
* @example
|
|
11248
|
+
* ```ts
|
|
11249
|
+
* required({ messageKey: 'profile.name.required', message: 'Name is required' });
|
|
11250
|
+
* ```
|
|
11251
|
+
*/
|
|
11252
|
+
messageKey?: string;
|
|
9522
11253
|
/** Параметры ошибки (подстановка в шаблон сообщения / i18n). */
|
|
9523
11254
|
params?: Record<string, FormValue>;
|
|
9524
11255
|
}
|
|
@@ -9550,7 +11281,13 @@ _Source: src/form/validation/operators.ts_
|
|
|
9550
11281
|
```typescript
|
|
9551
11282
|
export interface ValidationError {
|
|
9552
11283
|
code: string;
|
|
11284
|
+
/**
|
|
11285
|
+
* Готовый текст ошибки. Пустая строка — «автор правила текста не задавал»: отображаемую строку
|
|
11286
|
+
* тогда даёт резолвер по `code` (словарь локали `validation.<code>`).
|
|
11287
|
+
*/
|
|
9553
11288
|
message: string;
|
|
11289
|
+
/** Ключ сообщения в словаре приложения; при наличии в активной локали важнее `message`. */
|
|
11290
|
+
messageKey?: string;
|
|
9554
11291
|
params?: Record<string, FormValue>;
|
|
9555
11292
|
/** Severity level: 'error' (default) blocks submission, 'warning' shows message but allows submission */
|
|
9556
11293
|
severity?: 'error' | 'warning';
|
|
@@ -9670,3 +11407,16 @@ watchField(model.$.country, async (country) => {
|
|
|
9670
11407
|
```
|
|
9671
11408
|
|
|
9672
11409
|
_Source: src/model/behaviors-value.ts_
|
|
11410
|
+
|
|
11411
|
+
### WeekStart
|
|
11412
|
+
|
|
11413
|
+
**Kind:** `type`
|
|
11414
|
+
|
|
11415
|
+
День недели, с которого начинается календарь: `0` — воскресенье, `1` — понедельник.
|
|
11416
|
+
|
|
11417
|
+
**Signature:**
|
|
11418
|
+
```typescript
|
|
11419
|
+
export type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
11420
|
+
```
|
|
11421
|
+
|
|
11422
|
+
_Source: src/i18n/locale.ts_
|