@reformer/core 11.0.0-beta.8 → 11.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -73
- package/dist/behaviors-value-HA_Q03hc.js +108 -0
- package/dist/behaviors.js +370 -242
- package/dist/{create-model-BBRHhHwK.js → create-model-BvcbNPgL.js} +6 -6
- package/dist/date-utils-90-qZbse.js +43 -0
- package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
- package/dist/form/aggregate-signals.d.ts +21 -63
- package/dist/form/behaviors/collections.d.ts +28 -7
- package/dist/form/behaviors/context.d.ts +19 -2
- package/dist/form/behaviors/index.d.ts +3 -3
- package/dist/form/behaviors/internals.d.ts +9 -2
- package/dist/form/behaviors/node.d.ts +25 -2
- package/dist/form/behaviors/operators.d.ts +17 -9
- package/dist/form/behaviors/schema-operators.d.ts +6 -4
- package/dist/form/behaviors/types.d.ts +12 -4
- package/dist/form/create-form.d.ts +47 -35
- package/dist/form/flow/flow.d.ts +87 -0
- package/dist/form/form-bundle.d.ts +45 -28
- package/dist/form/form-config-base.d.ts +46 -0
- package/dist/form/form-level.d.ts +121 -0
- package/dist/form/form-proxy-builder.d.ts +6 -22
- package/dist/form/index.d.ts +16 -21
- package/dist/form/nodes/form-array-node.d.ts +100 -0
- package/dist/form/nodes/form-control-node.d.ts +73 -0
- package/dist/form/nodes/form-group-node.d.ts +66 -0
- package/dist/form/nodes/node-kind.d.ts +29 -0
- package/dist/form/nodes/state/availability.d.ts +6 -0
- package/dist/form/nodes/state/component.d.ts +6 -0
- package/dist/form/nodes/state/index.d.ts +12 -0
- package/dist/form/nodes/state/interaction.d.ts +13 -0
- package/dist/form/nodes/state/issues.d.ts +9 -0
- package/dist/form/nodes/state/pending.d.ts +6 -0
- package/dist/form/schema/compile.d.ts +59 -0
- package/dist/form/schema/compiled.d.ts +143 -0
- package/dist/form/schema/row-key.d.ts +11 -0
- package/dist/form/schema-controller.d.ts +30 -14
- package/dist/form/signal-node-registry.d.ts +6 -7
- package/dist/form/submission.d.ts +93 -0
- package/dist/form/type-guards.d.ts +14 -73
- package/dist/form/types/contracts.d.ts +19 -92
- package/dist/form/types/field-config.d.ts +27 -0
- package/dist/form/types/index.d.ts +5 -57
- package/dist/form/types/nodes.d.ts +159 -0
- package/dist/form/types/schema-node.d.ts +139 -63
- package/dist/form/types/validation-schema.d.ts +23 -27
- package/dist/form/validation/context.d.ts +42 -7
- package/dist/form/validation/index.d.ts +9 -8
- package/dist/form/validation/operators.d.ts +22 -30
- package/dist/form/validation/run.d.ts +93 -18
- package/dist/form/validation/strategy.d.ts +41 -13
- package/dist/form/validation/types.d.ts +74 -20
- package/dist/form/validators/date-utils.d.ts +13 -1
- package/dist/form/validators/email.d.ts +8 -7
- package/dist/form/validators/file-type.d.ts +8 -7
- package/dist/form/validators/file-utils.d.ts +2 -1
- package/dist/form/validators/future-date.d.ts +8 -7
- package/dist/form/validators/index.d.ts +1 -1
- package/dist/form/validators/integer.d.ts +8 -7
- package/dist/form/validators/is-date.d.ts +11 -9
- package/dist/form/validators/is-number.d.ts +11 -9
- package/dist/form/validators/max-age.d.ts +13 -9
- package/dist/form/validators/max-date.d.ts +8 -7
- package/dist/form/validators/max-file-size.d.ts +8 -7
- package/dist/form/validators/max-files.d.ts +8 -7
- package/dist/form/validators/max-length.d.ts +14 -7
- package/dist/form/validators/max-total-file-size.d.ts +8 -7
- package/dist/form/validators/max.d.ts +10 -8
- package/dist/form/validators/min-age.d.ts +12 -9
- package/dist/form/validators/min-date.d.ts +8 -7
- package/dist/form/validators/min-file-size.d.ts +8 -7
- package/dist/form/validators/min-files.d.ts +11 -9
- package/dist/form/validators/min-length.d.ts +14 -7
- package/dist/form/validators/min.d.ts +10 -8
- package/dist/form/validators/multiple-of.d.ts +10 -8
- package/dist/form/validators/non-negative.d.ts +8 -7
- package/dist/form/validators/non-zero.d.ts +8 -7
- package/dist/form/validators/past-date.d.ts +8 -7
- package/dist/form/validators/pattern.d.ts +9 -8
- package/dist/form/validators/phone.d.ts +8 -7
- package/dist/form/validators/required.d.ts +11 -9
- package/dist/form/validators/url.d.ts +20 -13
- package/dist/form/validators/validation-issue.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +1 -1
- package/dist/i18n/en.json.d.ts +5 -1
- package/dist/i18n/locale-store.d.ts +7 -0
- package/dist/i18n/message-format.d.ts +24 -4
- package/dist/i18n/ru.json.d.ts +5 -1
- package/dist/i18n/translator.d.ts +11 -0
- package/dist/i18n/validation-message.d.ts +42 -7
- package/dist/i18n.d.ts +7 -12
- package/dist/i18n.js +66 -283
- package/dist/index.d.ts +16 -7
- package/dist/index.js +924 -2855
- package/dist/loader-BWkigpVe.js +52 -0
- package/dist/locale/en.js +1 -1
- package/dist/locale/en.json +4 -1
- package/dist/locale/ru.js +4 -1
- package/dist/locale/ru.json +4 -1
- package/dist/locale-CG54AU2Q.js +219 -0
- package/dist/locale.js +1 -1
- package/dist/model/behaviors-value.d.ts +24 -6
- package/dist/model/create-model.d.ts +2 -2
- package/dist/model/derived-registry.d.ts +1 -1
- package/dist/model/index.d.ts +11 -5
- package/dist/model/model-nodes.d.ts +79 -12
- package/dist/model/model-signals-proxy.d.ts +5 -6
- package/dist/model/model-value-proxy.d.ts +20 -15
- package/dist/model/safe-effect.d.ts +33 -1
- package/dist/model/types.d.ts +63 -13
- package/dist/model.js +20 -18
- package/dist/node-kind-C-r6ZMdo.js +15 -0
- package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
- package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
- package/dist/platforms/react/form-bundle-context.d.ts +2 -2
- package/dist/platforms/react/hooks/types.d.ts +57 -38
- package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
- package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
- package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
- package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
- package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
- package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
- package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
- package/dist/platforms/react/i18n/context.d.ts +6 -1
- package/dist/platforms/react/i18n/hooks.d.ts +16 -10
- package/dist/platforms/react/i18n/provider.d.ts +9 -2
- package/dist/platforms/react/index.d.ts +6 -4
- package/dist/react.d.ts +22 -0
- package/dist/react.js +505 -0
- package/dist/runtime-token.d.ts +1 -2
- package/dist/safe-effect-BbtX3KuN.js +643 -0
- package/dist/schema-controller-CpiO2JoK.js +143 -0
- package/dist/signals.d.ts +15 -9
- package/dist/signals.js +11 -6
- package/dist/strategy-CoFSemuP.js +375 -0
- package/dist/type-guards-D-N_5Ddh.js +19 -0
- package/dist/validation-issue-B9mjI8jz.js +7 -0
- package/dist/validation-message-Ced9MGxs.js +194 -0
- package/dist/validation.js +9 -9
- package/dist/validators/email.js +5 -5
- package/dist/validators/future-date.js +8 -8
- package/dist/validators/integer.js +3 -3
- package/dist/validators/is-date.js +4 -4
- package/dist/validators/is-number.js +4 -4
- package/dist/validators/max-age.js +8 -9
- package/dist/validators/max-date.js +8 -8
- package/dist/validators/max-length.js +7 -7
- package/dist/validators/max.js +4 -4
- package/dist/validators/min-age.js +10 -8
- package/dist/validators/min-date.js +3 -3
- package/dist/validators/min-length.js +5 -5
- package/dist/validators/min.js +4 -4
- package/dist/validators/multiple-of.js +11 -7
- package/dist/validators/non-negative.js +3 -3
- package/dist/validators/non-zero.js +3 -3
- package/dist/validators/past-date.js +8 -8
- package/dist/validators/pattern.js +5 -5
- package/dist/validators/phone.js +11 -9
- package/dist/validators/required.js +2 -2
- package/dist/validators/url.js +48 -12
- package/dist/validators.js +17 -17
- package/docs/llms/01-api-reference.md +60 -34
- package/docs/llms/02-quick-start.md +111 -80
- package/docs/llms/03-api-signatures.md +368 -153
- package/docs/llms/04-common-patterns.md +58 -56
- package/docs/llms/05-common-mistakes.md +69 -44
- package/docs/llms/06-troubleshooting.md +14 -9
- package/docs/llms/07-complete-import.md +74 -43
- package/docs/llms/08-form-types.md +11 -4
- package/docs/llms/09-formschema.md +129 -81
- package/docs/llms/10-arrays.md +106 -75
- package/docs/llms/11-async-watchfield.md +2 -2
- package/docs/llms/13-multi-step.md +134 -63
- package/docs/llms/14-extended-mistakes.md +20 -17
- package/docs/llms/15-project-structure.md +221 -74
- package/docs/llms/16-ui-components.md +55 -42
- package/docs/llms/17-nonexistent-api.md +199 -32
- package/docs/llms/18-conditional-fields.md +66 -32
- package/docs/llms/19-reading-values.md +15 -3
- package/docs/llms/20-compute-vs-watch.md +16 -10
- package/docs/llms/21-array-operations.md +51 -27
- package/docs/llms/22-cycle-detection.md +1 -1
- package/docs/llms/23-copy-from.md +2 -2
- package/docs/llms/24-sync-fields.md +6 -5
- package/docs/llms/25-reset-when.md +14 -7
- package/docs/llms/26-transform-value.md +4 -4
- package/docs/llms/27-revalidate-when.md +16 -15
- package/docs/llms/28-submit-and-reset.md +276 -128
- package/docs/llms/29-async-preload.md +39 -29
- package/docs/llms/30-type-safety-recipes.md +105 -51
- package/docs/llms/31-async-validator-debounce.md +96 -53
- package/docs/llms/33-validation-strategy.md +177 -65
- package/docs/llms/34-i18n.md +69 -43
- package/docs/llms/35-unified-contract.md +100 -39
- package/docs/llms/36-issue-messages.md +284 -0
- package/docs/llms/37-compiled-schema.md +332 -0
- package/docs/llms/38-form-flow.md +397 -0
- package/llms-index.json +1 -1
- package/llms.txt +6084 -3420
- package/package.json +23 -4
- package/dist/behaviors-value-C7dSEhCc.js +0 -134
- package/dist/date-utils-dTT_x_Be.js +0 -31
- package/dist/form/create-core-form.d.ts +0 -72
- package/dist/form/factories/index.d.ts +0 -6
- package/dist/form/factories/node-factory.d.ts +0 -189
- package/dist/form/form-submitter.d.ts +0 -121
- package/dist/form/nodes/array-node.d.ts +0 -276
- package/dist/form/nodes/field-node.d.ts +0 -297
- package/dist/form/nodes/form-node.d.ts +0 -326
- package/dist/form/nodes/group-node.d.ts +0 -284
- package/dist/form/nodes/model-array-node.d.ts +0 -126
- package/dist/form/nodes/subscription-manager.d.ts +0 -180
- package/dist/form/status-machine.d.ts +0 -115
- package/dist/form/types/deep-schema.d.ts +0 -107
- package/dist/form/types/form-proxy.d.ts +0 -141
- package/dist/form/unique-id.d.ts +0 -45
- package/dist/form/validation/config.d.ts +0 -73
- package/dist/form/validation/error-handler.d.ts +0 -166
- package/dist/form/validators/validation-error.d.ts +0 -8
- package/dist/index-BrNkQj1y.js +0 -105
- package/dist/loader-Dv_Hl7JC.js +0 -216
- package/dist/model-value-proxy-caTmAiUd.js +0 -419
- package/dist/node-BeV_lV5i.js +0 -100
- package/dist/strategy-DgzELWno.js +0 -204
- package/dist/validation-error-B-U3RsV0.js +0 -11
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
## 15. NON-EXISTENT API (DO NOT USE)
|
|
2
2
|
|
|
3
|
-
**Следующего API НЕТ в @reformer/core** (наследие старой path-based
|
|
4
|
-
|
|
3
|
+
**Следующего API НЕТ в @reformer/core** (наследие старой path-based архитектуры и прежних
|
|
4
|
+
контрактов модели, валидации, схемы, визарда и нод):
|
|
5
5
|
|
|
6
|
-
> ⚠️ Операторы валидации `validate`/`validateAsync`/`validateWhen`/`
|
|
7
|
-
> **существуют** — но живут в сабпути `@reformer/core/validation` (
|
|
8
|
-
> `
|
|
6
|
+
> ⚠️ Операторы валидации `validate`/`validateAsync`/`validateWhen`/`apply`/`applyEach`
|
|
7
|
+
> **существуют** — но живут в сабпути `@reformer/core/validation` (схема
|
|
8
|
+
> `defineValidation(({ model, cross }) => …)`, раннер `validateModel`), а НЕ в корне и НЕ
|
|
9
9
|
> в `@reformer/core/validators`. Ниже перечислено то, чего действительно нет.
|
|
10
10
|
|
|
11
11
|
| Wrong | Correct | Notes |
|
|
12
12
|
| -------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
|
|
13
|
-
| `useForm` | `
|
|
13
|
+
| `useForm` | `createForm` + `useFormBundle` | Хука useForm нет |
|
|
14
|
+
| `FormValue` | тип значения самого поля либо `unknown` | Тип удалён: модель обобщённая (`createModel<T>`), параметры замечания — `Record<string, unknown>` |
|
|
15
|
+
| `value instanceof Signal` | `isSignal(value)` из `@reformer/core/signals` | Проверка по классу не узнаёт сигнал другой копии рантайма; ручку поля проверяет `isValueSignal` |
|
|
16
|
+
| `import { useFormControl, useFormBundle, … } from '@reformer/core'` | те же имена из `@reformer/core/react` | Корень ядра без React: хуки (`useFormControl`, `useFormControlValue`, `useArrayLength`, `useFormValidation`, `useFormBundle`, `useFormFlow`), `FormBundleContext`, связка поля (`bindFieldProps`, `getFieldAdapter`, `FieldAdapter`, `FieldHandle`) и типы снимков (`FieldControlState`, `ArrayControlState`) отдаёт только сабпат `/react` |
|
|
17
|
+
| `import { I18nProvider, useI18n, … } from '@reformer/core/i18n'` | те же имена из `@reformer/core/react` | `/i18n` — локаль, перевод и тексты замечаний без React; провайдер и хуки (`I18nProvider`, `useI18n`, `useMessages`, `useIssueMessage`) — в `/react` |
|
|
18
|
+
| `ElementType` / `ComponentType` в типе узла схемы | `ComponentRef` из `@reformer/core` | Типы схемы и ноды не импортируют типы React: `component` — непрозрачная ссылка (`string | object`), к типу своего UI-слоя её приводит рендерер |
|
|
14
19
|
| `validateForm`, `validateFormModel` | `validateModel(model, schema)` из `@reformer/core/validation` | Legacy-движок дерева `{ value, validators }` удалён; внешний раннер — `validateModel` |
|
|
15
20
|
| `validateModelSync` | `await validateModel(model, schema)` | Синхронного раннера нет — прогон асинхронный (`Promise<boolean>`) |
|
|
16
21
|
| `applyWhen` | `validateWhen(() => cond, () => { … })` | Условная валидация — оператором `validateWhen`, не узлом `{ when, children }` |
|
|
17
|
-
| `validateItems`, `validateGroup`, `validateTree` | `
|
|
18
|
-
| `ValidationSchemaFn`, `BehaviorSchemaFn` | `
|
|
19
|
-
| `equalTo`, `custom`, `notEmpty` (validators) | inline `Rule<T>` `(value) =>
|
|
20
|
-
| `form.submit()` / `form.validate()` прогоняют схему | `await validateModel(model, schema)` перед submit/шагом | submit/validate НЕ запускают ambient-схему валидации |
|
|
22
|
+
| `validateItems`, `validateGroup`, `validateTree` | `applyEach(model.$.arr, rowRules)` / `apply(model.$.group, groupRules)` | Правила строк и подформы — отдельные схемы, подключённые привязкой |
|
|
23
|
+
| `ValidationSchemaFn`, `BehaviorSchemaFn` | `ValidationDefinition<T>` (`defineValidation`) / `defineFormBehavior` | Типы path-схем удалены |
|
|
24
|
+
| `equalTo`, `custom`, `notEmpty` (validators) | inline `Rule<T>` `(value) => issue \| null` в `validate(sig, [...])`; сравнение полей — `cross(sig, fn)` | Таких фабрик нет; кастомное правило — обычная функция значения, cross-field — `cross` |
|
|
21
25
|
| `FieldPath`, `FieldPathNode` | `model.$.field` (`PathAwareSignal`) | Пути заменены сигналами |
|
|
22
26
|
| `ctx.form.x.value.value` | `model.x` / `model.$.x.value` | В behaviors читаем модель напрямую |
|
|
23
27
|
| `ctx.setFieldValue(name, value)` | `model.x = value` / `compute(...)` | Не существует |
|
|
@@ -26,6 +30,156 @@
|
|
|
26
30
|
| `FormProvider`, `control` prop, `register()` | `<Component form={form} />`, `useFormControl(form.field)` | Форма передаётся через props |
|
|
27
31
|
| `getFieldValue()` | `model.field` / `useFormControlValue(form.field)`| Не существует |
|
|
28
32
|
|
|
33
|
+
### Удалено: модель (этап 1)
|
|
34
|
+
|
|
35
|
+
| Wrong | Correct | Notes |
|
|
36
|
+
| -------------------------------------- | ---------------------------------------------------- | --------------------------------------- |
|
|
37
|
+
| `model.set(partial)` для части полей | `model.patch(partial)` | `set` — полная установка: недостающие ключи в dev дают предупреждение и не меняются; `patch` сливает переданные ключи, вложенные группы — тоже частично, массив — целиком |
|
|
38
|
+
| `FormValue`, `T extends FormValue` | `T` без ограничения / `Record<string, unknown>` | Тип удалён |
|
|
39
|
+
|
|
40
|
+
### Удалено: компонент массива (этап 2)
|
|
41
|
+
|
|
42
|
+
Имя `FormArray` теперь занято **типом ноды** массива под-форм в `@reformer/core`; компонент массива
|
|
43
|
+
в cdk и ui-kit называется `FieldArray`.
|
|
44
|
+
|
|
45
|
+
| Wrong | Correct | Notes |
|
|
46
|
+
| ------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
|
|
47
|
+
| компонент `FormArray` (`@reformer/cdk`, `@reformer/ui-kit`) | `FieldArray` | `{ model: model.$.items, component: FieldArray, item }` |
|
|
48
|
+
| `FormArraySection` | `FieldArraySection` | ui-kit |
|
|
49
|
+
| `useFormArray` | `useFieldArray` | cdk |
|
|
50
|
+
| `@reformer/cdk/form-array`, `@reformer/ui-kit/form-array` | `@reformer/cdk/field-array`, `@reformer/ui-kit/field-array` | сабпаты |
|
|
51
|
+
| ключи локали `kit.formArray.*` | `kit.fieldArray.*` | ui-kit |
|
|
52
|
+
|
|
53
|
+
JSON-документ схемы со старыми именами `FormArray` / `FormArraySection` реестр renderer-json ещё
|
|
54
|
+
принимает как псевдонимы (dev-предупреждение).
|
|
55
|
+
|
|
56
|
+
### Удалено: замечание валидации без текста (этап 4)
|
|
57
|
+
|
|
58
|
+
Замечание несёт код и параметры; текст по коду даёт словарь формы — см. `36-issue-messages.md`.
|
|
59
|
+
|
|
60
|
+
| Wrong | Correct | Notes |
|
|
61
|
+
| ------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
|
|
62
|
+
| `required({ message: '…' })`, `{ messageKey }` | `required({ code: 'phone.required' })` + `defineIssueMessages({ 'phone.required': '…' })` | У `RuleOptions` только `code` и `params` |
|
|
63
|
+
| `ValidationError` (`{ code, message, … }`) | `ValidationIssue` (`{ code, params?, severity? }`) | Поля `message` нет — текст через резолвер |
|
|
64
|
+
| `ValidateOptions` | `RuleOptions` | `{ code?, params? }` |
|
|
65
|
+
| `ValidationSchema`, `defineValidationSchema` | `ValidationDefinition`, `defineValidation` | `@reformer/core/validation` |
|
|
66
|
+
| `resolveValidationError` | `resolveIssueMessage(issue, i18n, messages?)` | `@reformer/core/i18n` |
|
|
67
|
+
| `useValidationMessage` | `useIssueMessage(messages?)` | `@reformer/core/react` |
|
|
68
|
+
| `node.errors`, `setErrors`, `clearErrors` | `node.issues`, `setIssues`, `clearIssues` | И в снимке `useFormControl` — `issues` |
|
|
69
|
+
| `result.errors` у `ValidationResult` | `result.issues` | Замечания по ручке поля |
|
|
70
|
+
| `getErrors(filter)`, `ErrorFilterOptions` | `node.issues.value.filter(…)` | Фильтр — обычный `filter` |
|
|
71
|
+
| `errors[0].message` в разметке | `useIssueMessage(messages)(issues[0])` | Строку даёт резолвер при показе |
|
|
72
|
+
| cdk `createMessageResolver`, `defaultErrorResolver`, `ValidationMessageTable` | `<ValidationMessagesProvider messages={defineIssueMessages({…})}>` | Словарь формы поверх локали |
|
|
73
|
+
| cdk `ValidationErrorResolver`, `useValidationErrorResolver` | `IssueMessageResolver`, `useIssueMessageResolver` | `@reformer/cdk` |
|
|
74
|
+
|
|
75
|
+
### Удалено: схема (этап 5)
|
|
76
|
+
|
|
77
|
+
| Wrong | Correct | Notes |
|
|
78
|
+
| ------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
|
|
79
|
+
| `bundle.render.tree` как авторское дерево | `bundle.render.tree` — `CompiledSchema`; авторское дерево — `bundle.render.tree.root.source` | См. `37-compiled-schema.md` |
|
|
80
|
+
| повтор `selector` в одной области «с предупреждением» | разные селекторы | Повтор — исключение при сборке |
|
|
81
|
+
| свой разбор ключей `model` / `item` / `part` у узла | `compileSchema(tree, model)` — `kind` узла | `field \| array \| part \| container \| text` |
|
|
82
|
+
|
|
83
|
+
### Удалено: уровень формы, поток шагов, визард (этап 6)
|
|
84
|
+
|
|
85
|
+
| Wrong | Correct | Notes |
|
|
86
|
+
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| `bundle.validation.validateAll()` / `.runAll()` | `bundle.validate()` → `ValidationResult` | Поле `bundle.validation` осталось служебным (`{ rules, controller }` для `useFormBundle`) — методов проверки на нём нет |
|
|
88
|
+
| `bundle.validation.validateStep(n)` / `.runStep(n)` | `bundle.validateStep(id)` → `ValidationResult` | Идентификатор шага — `selector`, не номер |
|
|
89
|
+
| `createForm({ validation: { steps: { loan: rules } } })` | `createForm({ flow: { steps: [{ id: 'loan', rules }] } })` | Правила вне шагов — `validation: { extras }` |
|
|
90
|
+
| `buildValidation`, `FormValidationBundle` | `createForm({ validation, flow })` / `createFormLevel` | Уровень формы собирает сборка |
|
|
91
|
+
| `form.submit(handler)`, `SubmitOptions`, `form.submitting` | `bundle.submission.submit(handler)` → `SubmitResult`, `bundle.submission.submitting` | Один прогон правил; обработчик — только при `valid` |
|
|
92
|
+
| `bundle.submit(handler)`, `bundle.submitting` | `bundle.submission.submit(handler)` → `SubmitResult`, `bundle.submission.submitting` | Отправка — отдельный объект сборки; псевдонимов нет. То же у `level` в `renderBehavior` и бандлов `createReactForm` / `createJsonForm`: `level.submission.submit(handler)` |
|
|
93
|
+
| `form.validate()` | `bundle.validate()` | У нод методов проверки нет |
|
|
94
|
+
| `<FormWizard config={…}>`, `FormWizardConfig` | `<FormWizard flow={bundle.flow}>` | Без `flow` и сборки в контексте — позиционный поток без правил |
|
|
95
|
+
| `<FormWizard ref={…}>`, `FormWizardHandle`, `ref.current.goToStep(n)` | `bundle.flow.goTo(id)` / `next()` / `previous()` | Навигация — методами потока |
|
|
96
|
+
| `defineSteps(model, { steps, extras, strategy })` | `createForm({ validation: { extras, strategy }, flow })` | Живая проверка — стратегия формы |
|
|
97
|
+
| `useWizardStepValidation(config)` | `validation: { strategy }` сборки | Стратегию армирует `useFormBundle` |
|
|
98
|
+
| `makeValidationConfig(model)` (`validateStep(n)` / `validateAll()`) | `flow` сборки | Визард берёт поток, а не конфиг |
|
|
99
|
+
|
|
100
|
+
### Удалено: ноды формы — контракты вместо классов (этап 7)
|
|
101
|
+
|
|
102
|
+
| Wrong | Correct | Notes |
|
|
103
|
+
| ------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
|
|
104
|
+
| классы `FormNode`, `FieldNode`, `GroupNode`, `ModelArrayNode` | интерфейсы `FormNode`, `FormControl`, `FormGroup`, `FormArray` | Классы реализации внутренние; ноды строит сборка |
|
|
105
|
+
| `FormProxy<T>` | `FormGroup<T>` | Тип группы |
|
|
106
|
+
| `isFieldNode`, `isGroupNode`, `isArrayNode` | `isFormControl`, `isFormGroup`, `isFormArray` | Гарды по метке вида; `isFormNode` остался |
|
|
107
|
+
| `node instanceof FieldNode` | `isFormControl(node)` | `instanceof` по интерфейсу невозможен |
|
|
108
|
+
| `node.status.value`, `FieldStatus` | `node.valid` / `invalid` / `pending` / `disabled` | Отдельного статуса нет |
|
|
109
|
+
| `node.patchValue(v)` | `node.setValue(v)` / `model.patch(partial)` | Частичное обновление — у модели |
|
|
110
|
+
| `markAsDirty()`, `markAsPristine()` | `dirty` ставит `setValue(v)`, снимает `reset()` | Изменились ли значения модели — `model.isDirty()` |
|
|
111
|
+
| `node.validate()` | `bundle.validate()` / `bundle.validateStep(id)` | |
|
|
112
|
+
| `group.fields` (Map), `group.getProxy()` | `form.<поле>`, `form.$` (все поля), `Object.values(form.$)` | `fields` теперь обычное поле данных |
|
|
113
|
+
| `node.id` | ключ строки — `array.keyAt(index)` | `id` теперь обычное поле данных |
|
|
114
|
+
| `array.items` | `array.at(i)` / `map` / `forEach` | |
|
|
115
|
+
| `array.length.value` | `array.length` (число; в эффекте подписывает) | Не сигнал |
|
|
116
|
+
| `key={row.id}` / `key={index}` у строк массива | `key={array.keyAt(index)}` | Ключ не меняется при `move` / `swap` |
|
|
117
|
+
|
|
118
|
+
Затеняют поле группы только члены `FormNode` (`value`, `issues`, `reset`, …): такое поле берут через
|
|
119
|
+
`form.$.value`. Имена `id`, `status`, `fields`, `submit` — обычные поля данных.
|
|
120
|
+
|
|
121
|
+
### Удалено вместе со старым путём сборки формы
|
|
122
|
+
|
|
123
|
+
Форма собирается только из модели: `createForm({ initial | model, schema })`. Ноды строятся по
|
|
124
|
+
виду узла модели, правила живут в схеме валидации.
|
|
125
|
+
|
|
126
|
+
| Wrong | Correct | Notes |
|
|
127
|
+
| -------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
|
|
128
|
+
| `createLegacyForm(schema)` | `createForm({ initial, schema })` | Формы без модели нет |
|
|
129
|
+
| `new FormGroup({ email: { value, component } })` | `createFormFromModel({ model, schema })` | `FormGroup` — тип, ноды строит сборка |
|
|
130
|
+
| `new FormControl({ value: '' })` | `createForm({ initial: { email: '' } }).form.email` | `FormControl` — тип; значение принадлежит модели |
|
|
131
|
+
| `FieldConfig.validators` / `asyncValidators` | `validate` / `validateAsync` в `defineValidation` | Собственных валидаторов у ноды нет |
|
|
132
|
+
| `updateOn`, `debounce` узла поля; `field.setUpdateOn()` / `getUpdateOn()` | `validation: { strategy, debounce }` в `createForm` | Когда запускать проверку, решает стратегия валидации формы |
|
|
133
|
+
| `field.setValue(value, { emitEvent: false })` | `field.setValue(value)` | Опций у `setValue` нет |
|
|
134
|
+
| `ArrayNode` | `FormArray` | Нода массива одна — над массивом модели |
|
|
135
|
+
| `NodeFactory` | — | Сборка по ключам конфига удалена |
|
|
136
|
+
| `FormErrorHandler`, `ErrorStrategy` | — | Обслуживали валидаторы ноды |
|
|
137
|
+
| `FormSchema<T>`, `GroupNodeConfig<T>` | `FormSchemaNode` | Схема — дерево узлов, а не запись по полям |
|
|
138
|
+
| `ValidatorFn`, `AsyncValidatorFn`, `Validator<TForm, TField>` | `Rule<T>`, `AsyncRule<T>` из `@reformer/core/validation` | Правило — функция одного аргумента |
|
|
139
|
+
|
|
140
|
+
### Удалено: подписки на нодах, доступ по строковому пути, псевдонимы
|
|
141
|
+
|
|
142
|
+
Реакции живут на слое модели (`defineFormBehavior`), поле берётся через форму, у каждой
|
|
143
|
+
операции одно имя.
|
|
144
|
+
|
|
145
|
+
| Wrong | Correct | Notes |
|
|
146
|
+
| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
|
147
|
+
| `field.watch(cb)`, `form.watchField('email', cb)`, `form.watchFieldByPath(path, cb)` | `onChange(model.$.email, cb)` в `defineFormBehavior`; вне поведения — `watchField(model.$.email, cb)` из `@reformer/core/model` | Подписок на ноде нет |
|
|
148
|
+
| `field.computeFrom(sources, fn)`, `form.linkFields('a', 'b', fn)` | `compute(model.$.b, () => fn(model.a))` в `defineFormBehavior` | Вычисляемое значение — оператор поведения |
|
|
149
|
+
| `field.dispose()` | сборку — `bundle.dispose()`; форму из `createFormFromModel` — `disposeForm(form)` | В контракте нод `dispose` нет |
|
|
150
|
+
| `form.getFieldByPath('address.city')`, `form.getField('email')` | `form.address.city`, `form.email`; имя, занятое членом ноды, — `form.$.value` | Строковых путей к нодам нет |
|
|
151
|
+
| `form.getAllFields()` | `Object.values(form.$)` | `form.$` — ноды всех полей группы по имени |
|
|
152
|
+
| `form.touchAll()` | `form.markAsTouched()` | Отметка каскадная: уходит во все вложенные поля |
|
|
153
|
+
| `form.resetToInitial()`, `field.resetToInitial()` | `form.reset()` — к значениям создания формы; `model.reset()` — к точке отсчёта модели | См. `28-submit-and-reset.md` |
|
|
154
|
+
| `form.submitWithResult(handler)` | `bundle.submission.submit(handler)` → `SubmitResult` | Проверка и отправка одним вызовом |
|
|
155
|
+
| `getNodeType(node)` | `isFormControl` / `isFormGroup` / `isFormArray` | Вид ноды — гардами |
|
|
156
|
+
| `each(model.items, (item) => …)` | `applyEach(model.$.items, itemRules)` | Правила строки — отдельная схема над элементом |
|
|
157
|
+
| `import { cross } from '@reformer/core/validation'` | `defineValidation(({ model, cross }) => …)` | `cross` — только из аргумента схемы |
|
|
158
|
+
| `createCoreForm`, `CreateCoreFormConfig` | `createForm`, `CreateFormConfig` | Сборка одна |
|
|
159
|
+
| `FormArrayProxy<T>` | `FormArray<T>` | Псевдоним типа удалён |
|
|
160
|
+
| `FormSubmitter`, `SubmittableForm`, `SubscriptionManager`, `createSchemaOverrideMaps`, `ModelArrayControl`, `FormControlsProxy` | — | Внутренние детали, из пакета не экспортируются |
|
|
161
|
+
|
|
162
|
+
### Прежние ключи узла схемы
|
|
163
|
+
|
|
164
|
+
| Wrong | Correct | Notes |
|
|
165
|
+
| -------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
|
|
166
|
+
| `{ value: model.$.email, component }` | `{ model: model.$.email, component }` | Привязка поля — ключ `model` |
|
|
167
|
+
| `{ array: model.items, item }` | `{ model: model.$.items, item }` | Привязка массива под-форм — ручка, а не фасад |
|
|
168
|
+
| `{ email: { model, component } }` (запись «имя → узел») | `{ children: [{ model, component }] }` | Вложенные узлы читаются только из `children` |
|
|
169
|
+
| узел внутри `componentProps` | узел в `children` | В пропсы обход схемы не заглядывает |
|
|
170
|
+
| `testId` ключом узла | `componentProps: { testId }` | Ключа узла `testId` нет |
|
|
171
|
+
|
|
172
|
+
### Операторы над сигналами — не в корне
|
|
173
|
+
|
|
174
|
+
`computeFrom`, `copyFrom`, `watchField`, `transformValue`, `resetWhen`, `syncFields`,
|
|
175
|
+
`revalidateWhen`, `enableWhen`, `disableWhen` из `@reformer/core` не импортируются.
|
|
176
|
+
|
|
177
|
+
| Wrong | Correct |
|
|
178
|
+
| -------------------------------- | ------------------------------------------------ |
|
|
179
|
+
| `import { enableWhen, copyFrom } from '@reformer/core'` | `import { enableWhen, copyFrom } from '@reformer/core/behaviors'` — внутри `defineFormBehavior` |
|
|
180
|
+
| `import { watchField } from '@reformer/core'` | `onChange` из `@reformer/core/behaviors`; сам примитив — `@reformer/core/model` |
|
|
181
|
+
| `import { computeFrom } from '@reformer/core'` | `compute` / `computeFrom` из `@reformer/core/behaviors` |
|
|
182
|
+
|
|
29
183
|
### UI: удалённые field-версии компонентов (`@reformer/ui-kit`)
|
|
30
184
|
|
|
31
185
|
Отдельных «field-версий» у компонентов кита больше нет: в `component` поля кладётся сам
|
|
@@ -45,46 +199,59 @@
|
|
|
45
199
|
```typescript
|
|
46
200
|
// WRONG - этих символов / путей НЕ существует
|
|
47
201
|
import { useForm, validateForm, validateFormModel } from '@reformer/core'; // NO!
|
|
202
|
+
import { useFormControl, useFormBundle } from '@reformer/core'; // NO! хуки — из /react
|
|
48
203
|
import { validate, applyWhen, equalTo } from '@reformer/core/validators'; // NO! операторов тут нет
|
|
204
|
+
import { defineValidationSchema, type ValidationSchema } from '@reformer/core/validation'; // NO!
|
|
205
|
+
import { resolveValidationError, useValidationMessage } from '@reformer/core/i18n'; // NO!
|
|
206
|
+
import { FieldNode, GroupNode, isFieldNode, type FormProxy } from '@reformer/core'; // NO!
|
|
207
|
+
import { defineSteps, useWizardStepValidation } from '@reformer/cdk/form-wizard'; // NO!
|
|
208
|
+
import { FormArray, useFormArray } from '@reformer/cdk/form-array'; // NO! — field-array
|
|
49
209
|
import { transformers } from '@reformer/core/behaviors'; // NO!
|
|
50
|
-
import type { FieldPath, ValidationSchemaFn } from '@reformer/core';
|
|
210
|
+
import type { FieldPath, ValidationSchemaFn, ValidationError } from '@reformer/core'; // NO!
|
|
51
211
|
|
|
52
212
|
// CORRECT
|
|
53
|
-
import { createModel, createForm,
|
|
54
|
-
import
|
|
55
|
-
|
|
213
|
+
import { createModel, createForm, isFormControl } from '@reformer/core';
|
|
214
|
+
import { useFormBundle, useFormControl, useIssueMessage } from '@reformer/core/react';
|
|
215
|
+
import type { FormModel, FormGroup, FormSchemaNode, ValidationIssue } from '@reformer/core';
|
|
216
|
+
// Операторы валидации + раннер + defineValidation — отдельный сабпуть:
|
|
56
217
|
import {
|
|
57
|
-
validate, validateAsync, validateWhen,
|
|
58
|
-
|
|
59
|
-
type Rule, type AsyncRule, type
|
|
218
|
+
validate, validateAsync, validateWhen, apply, applyEach,
|
|
219
|
+
defineValidation, validateModel, runValidation, applyValidationResult,
|
|
220
|
+
type Rule, type AsyncRule, type ValidationDefinition, type ValidationResult,
|
|
60
221
|
} from '@reformer/core/validation';
|
|
61
|
-
// Фабрики-валидаторы
|
|
222
|
+
// Фабрики-валидаторы — правила значения `Rule<T>`, пустое значение пропускают:
|
|
62
223
|
import { required, email, min, minLength, pattern } from '@reformer/core/validators';
|
|
63
|
-
//
|
|
224
|
+
// Тексты замечаний — словарь формы:
|
|
225
|
+
import { defineIssueMessages } from '@reformer/core/i18n';
|
|
226
|
+
// Поведение (отдельный слой):
|
|
64
227
|
import { defineFormBehavior, compute, onChange, revalidateWhen } from '@reformer/core/behaviors';
|
|
228
|
+
// Компонент массива — FieldArray:
|
|
229
|
+
import { FieldArray, useFieldArray } from '@reformer/cdk/field-array';
|
|
65
230
|
```
|
|
66
231
|
|
|
67
232
|
### Schema Common Mistakes
|
|
68
233
|
|
|
69
|
-
Валидация и
|
|
70
|
-
|
|
234
|
+
Валидация и разметка — **разные** контракты. Узел схемы привязывает поле к ручке модели и
|
|
235
|
+
правил не несёт; правила живут в отдельной `defineValidation`.
|
|
71
236
|
|
|
72
237
|
```typescript
|
|
73
|
-
// WRONG -
|
|
238
|
+
// WRONG - запись «имя → узел», литерал вместо ручки и validators прямо в узле
|
|
74
239
|
const schema = {
|
|
75
|
-
name: '', // нет привязки к
|
|
76
|
-
email: { value: '', validators: [required()] }, //
|
|
240
|
+
name: '', // нет привязки к модели
|
|
241
|
+
email: { value: '', validators: [required()] }, // ни ключа value, ни validators у узла нет
|
|
77
242
|
};
|
|
78
243
|
|
|
79
|
-
// CORRECT -
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
244
|
+
// CORRECT - схема только связывает поле с ручкой модели...
|
|
245
|
+
const schema: FormSchemaNode = {
|
|
246
|
+
children: [
|
|
247
|
+
{ model: model.$.name, component: Input, componentProps: { label: 'Name' } },
|
|
248
|
+
{ model: model.$.email, component: Input, componentProps: { label: 'Email' } },
|
|
249
|
+
],
|
|
83
250
|
};
|
|
84
251
|
|
|
85
|
-
// ...а правила — отдельная
|
|
86
|
-
const validation =
|
|
87
|
-
validate(model.$.email, [required({
|
|
252
|
+
// ...а правила — отдельная схема (уходит в createForm({ validation }) либо в validateModel):
|
|
253
|
+
const validation = defineValidation<Form>(({ model }) => {
|
|
254
|
+
validate(model.$.email, [required({ code: 'email.required' }), email()]);
|
|
88
255
|
});
|
|
89
256
|
```
|
|
90
257
|
|
|
@@ -99,7 +266,7 @@ enableWhen(model.$.city, () => Boolean(model.country), { resetOnDisable: true })
|
|
|
99
266
|
```
|
|
100
267
|
|
|
101
268
|
Поведение **не владеет** валидацией. Чтобы поведение инициировало прогон схемы — мост
|
|
102
|
-
`revalidateWhen` (
|
|
269
|
+
`revalidateWhen` (или живая стратегия сборки `validation: { strategy: 'change' }`):
|
|
103
270
|
|
|
104
271
|
```typescript
|
|
105
272
|
// CORRECT - поведение дёргает внешний раннер валидации при изменении зависимости
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
«Показать поле X только если Y»
|
|
6
|
-
|
|
5
|
+
«Показать поле X только если Y» решается тремя независимыми механизмами — выбирай по тому, что
|
|
6
|
+
именно должно быть условным:
|
|
7
7
|
|
|
8
8
|
1. **Видимость / доступность** (поле остаётся в модели, но выключается/включается и не валидируется)
|
|
9
9
|
— behaviors: `enableWhen`, а для условной записи значения — `compute` / `copyFrom` с опцией `{ when }`.
|
|
10
10
|
2. **Условная валидация** (правила применяются только в истинной ветке) — оператор
|
|
11
|
-
`validateWhen(() => cond, () => {…})` внутри схемы `
|
|
12
|
-
|
|
11
|
+
`validateWhen(() => cond, () => {…})` внутри схемы `defineValidation`; прогон — уровнем формы
|
|
12
|
+
(`bundle.validate()` / `bundle.submission.submit`) либо раннером `validateModel`.
|
|
13
|
+
3. **Скрытие из разметки** (узел вообще не рендерится) — в схеме под рендерером — оператор
|
|
14
|
+
`hideWhen(schema.node(selector), cond)`; при разметке в JSX — `useFormControlValue` + условный
|
|
15
|
+
рендер.
|
|
13
16
|
|
|
14
17
|
> Условная **валидация** — оператор `validateWhen` из `@reformer/core/validation`; старого branch-узла
|
|
15
18
|
> `{ when, children }` и хелпера `applyWhen` больше нет — см.
|
|
@@ -56,36 +59,51 @@ export const creditBehavior = defineFormBehavior<CreditForm>(({ model }) => {
|
|
|
56
59
|
- **Все поля должны быть материализованы в модели** (`createModel`), даже те, что показываются
|
|
57
60
|
только под условием. `enableWhen`/`compute`/`copyFrom` принимают сигналы `model.$.field`, а не
|
|
58
61
|
строковые пути; сигнала для несуществующего поля нет.
|
|
59
|
-
- Поведение подключается к форме через `
|
|
60
|
-
|
|
62
|
+
- Поведение подключается к форме через `createForm({ …, behavior })` (или
|
|
63
|
+
`createFormFromModel({ model, schema, behavior })`) — форма владеет жизненным циклом,
|
|
64
|
+
DSL-операторы регистрируют свой cleanup сами (ручной массив cleanup'ов не нужен).
|
|
61
65
|
- Таргетом может быть и **группа**: `enableWhen(model.$.residenceAddress, () => model.sameAsRegistration === false)`
|
|
62
66
|
проходит по поддереву; без `resetOnDisable` значение группы сохраняется (удобно, когда оно копируется
|
|
63
67
|
из другого источника).
|
|
64
68
|
|
|
65
|
-
>
|
|
66
|
-
>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
> `enableWhen` / `disableWhen` существуют только как операторы поведения
|
|
70
|
+
> (`@reformer/core/behaviors`): в корне `@reformer/core` и в `@reformer/core/model` их нет.
|
|
71
|
+
|
|
72
|
+
Скрыть узел разметки, когда форму рисует рендерер, — оператор узла схемы в том же поведении:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { defineFormBehavior, enableWhen, hideWhen } from '@reformer/core/behaviors';
|
|
76
|
+
|
|
77
|
+
export const mortgageBehavior = defineFormBehavior<CreditForm>(({ model, schema }) => {
|
|
78
|
+
const isMortgage = () => model.loanType === 'mortgage';
|
|
79
|
+
enableWhen([model.$.propertyValue, model.$.initialPayment], isMortgage, { resetOnDisable: true });
|
|
80
|
+
hideWhen(schema.node('mortgage'), () => !isMortgage()); // узел с selector: 'mortgage'
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`hideWhen` только записывает правило — исполняет его рендерер; при разметке в JSX он ничего не
|
|
85
|
+
делает (см. раздел «Скрытие в JSX»).
|
|
69
86
|
|
|
70
87
|
## Условная валидация
|
|
71
88
|
|
|
72
89
|
Правила, действующие только при выполнении условия, оборачиваются оператором
|
|
73
|
-
`validateWhen(() => cond, () => {…})` внутри схемы `
|
|
90
|
+
`validateWhen(() => cond, () => {…})` внутри схемы `defineValidation<T>(({ model }) => …)`.
|
|
74
91
|
Условие `cond` читает модель напрямую (`model.field` — снимок на момент прогона): при истинном условии
|
|
75
92
|
раннер применяет вложенные `validate`/`cross`/…, при ложном — правила ветки **гасятся** (поля,
|
|
76
|
-
затронутые ранее, получают `
|
|
77
|
-
`
|
|
93
|
+
затронутые ранее, получают `setIssues([])`). Прогон — уровнем формы (`createForm({ validation })`,
|
|
94
|
+
затем `bundle.validate()` / `bundle.submission.submit(handler)`) либо раннером
|
|
95
|
+
`validateModel(model, schema): Promise<boolean>`; замечания сами доезжают до нод формы.
|
|
78
96
|
|
|
79
97
|
```typescript
|
|
80
98
|
import {
|
|
81
99
|
validate,
|
|
82
100
|
validateWhen,
|
|
83
|
-
|
|
101
|
+
defineValidation,
|
|
84
102
|
validateModel,
|
|
85
103
|
} from '@reformer/core/validation';
|
|
86
104
|
import { required, min } from '@reformer/core/validators';
|
|
87
105
|
|
|
88
|
-
const schema =
|
|
106
|
+
const schema = defineValidation<CreditForm>(({ model }) => {
|
|
89
107
|
validate(model.$.loanType, [required()]);
|
|
90
108
|
|
|
91
109
|
// ветка применяется только для ипотеки
|
|
@@ -102,19 +120,28 @@ const ok: boolean = await validateModel(model, schema);
|
|
|
102
120
|
```
|
|
103
121
|
|
|
104
122
|
Переключение `loanType` с `mortgage` на другой тип автоматически снимает ошибки `required` с
|
|
105
|
-
`propertyValue`/`initialPayment` — при ложном условии ветка `validateWhen` гасит их (`
|
|
123
|
+
`propertyValue`/`initialPayment` — при ложном условии ветка `validateWhen` гасит их (`setIssues([])`).
|
|
106
124
|
`validateWhen` управляет только **валидацией**; чтобы поля ещё и выпадали из состояния (сброс
|
|
107
125
|
значения), держи их под `enableWhen` из раздела выше — слои независимы.
|
|
108
126
|
|
|
127
|
+
Выключенное поле (`enableWhen`, `disable()`) в проверке **уровня формы** не участвует:
|
|
128
|
+
`bundle.validate`, `validateStep`, `submission.submit`, `flow.next()` и живая проверка гасят его
|
|
129
|
+
замечания, и статус прогона совпадает с `valid` формы. Значение выключенного поля при этом остаётся
|
|
130
|
+
в модели и уходит в обработчик отправки — модель передаётся целиком; убрать его из отправки —
|
|
131
|
+
забота обработчика либо `enableWhen(…, { resetOnDisable: true })`. `validateWhen` нужен, когда
|
|
132
|
+
поле остаётся доступным, но правило по условию не действует, — и в независимом раннере
|
|
133
|
+
`validateModel(model, rules)`, который о нодах не знает.
|
|
134
|
+
|
|
109
135
|
## Скрытие в JSX
|
|
110
136
|
|
|
111
137
|
Чтобы условный блок вообще не рендерился, читай значение-триггер хуком `useFormControlValue(form.field)`
|
|
112
138
|
и делай ранний `return null`. Хук возвращает **только значение** (без деструктуризации `{ value }`).
|
|
113
139
|
|
|
114
140
|
```tsx
|
|
115
|
-
import {
|
|
141
|
+
import type { FormGroup } from '@reformer/core';
|
|
142
|
+
import { useFormControlValue } from '@reformer/core/react';
|
|
116
143
|
|
|
117
|
-
function MortgageFields({ form }: { form: CreditForm }) {
|
|
144
|
+
function MortgageFields({ form }: { form: FormGroup<CreditForm> }) {
|
|
118
145
|
const loanType = useFormControlValue(form.loanType);
|
|
119
146
|
if (loanType !== 'mortgage') return null;
|
|
120
147
|
|
|
@@ -137,7 +164,7 @@ function MortgageFields({ form }: { form: CreditForm }) {
|
|
|
137
164
|
|
|
138
165
|
- **Нет branch-узла `{ when, children }` в дереве схемы.** Старая схема была деревом
|
|
139
166
|
`{ value, validators: [...] }` с ветками `{ when: (scope, root) => boolean, children: [...] }`,
|
|
140
|
-
которое обходил `validateFormModel`. Теперь схема — функция `({ model }) => void`, а условие —
|
|
167
|
+
которое обходил `validateFormModel`. Теперь схема — функция `({ model, cross }) => void`, а условие —
|
|
141
168
|
оператор `validateWhen(() => cond, () => {…})`; отдельного объекта-узла нет.
|
|
142
169
|
|
|
143
170
|
```typescript
|
|
@@ -154,25 +181,32 @@ function MortgageFields({ form }: { form: CreditForm }) {
|
|
|
154
181
|
- **`applyWhen` не существует** — ни как экспорт, ни как локальный сахар: он эмитил branch-узел,
|
|
155
182
|
которого больше нет. Оборачивай условные правила оператором `validateWhen`.
|
|
156
183
|
|
|
157
|
-
- **`ValidationSchemaFn` удалён** (тип старой path-схемы). Тип схемы — `
|
|
158
|
-
(`(
|
|
184
|
+
- **`ValidationSchemaFn` удалён** (тип старой path-схемы). Тип схемы — `ValidationDefinition<T>`
|
|
185
|
+
(`(scope: { model: FormModel<T>; cross }) => void`), фабрика-обёртка — `defineValidation<T>(fn)`.
|
|
159
186
|
|
|
160
|
-
- **Cross-field — обычная функция над
|
|
161
|
-
|
|
162
|
-
оператором `cross(sig,
|
|
187
|
+
- **Cross-field — обычная функция над снимком, а не `ModelValidator (value, scope, root)`.**
|
|
188
|
+
Правило значения — `Rule<T> = (value: T) => error | null`, параметров `scope` / `root` у него нет.
|
|
189
|
+
Соседние поля читаются из снимка модели, а правило вешается оператором `cross(sig, check)` из
|
|
190
|
+
аргумента схемы. Каноничное использование — каталог `validation/` формы `complex-multy-step-form`.
|
|
163
191
|
|
|
164
192
|
```typescript
|
|
165
|
-
import {
|
|
166
|
-
import type {
|
|
193
|
+
import { defineValidation } from '@reformer/core/validation';
|
|
194
|
+
import type { ValidationIssue } from '@reformer/core';
|
|
167
195
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
196
|
+
type Passwords = { password: string; confirmPassword: string };
|
|
197
|
+
|
|
198
|
+
// (snapshot) => error — сравнение с соседним полем
|
|
199
|
+
const passwordsMatch = (passwords: Passwords): ValidationIssue | null =>
|
|
200
|
+
passwords.confirmPassword &&
|
|
201
|
+
passwords.password &&
|
|
202
|
+
passwords.confirmPassword !== passwords.password
|
|
203
|
+
? { code: 'confirmPassword.mismatch' } // текст кода — в словаре формы
|
|
172
204
|
: null;
|
|
173
205
|
|
|
174
|
-
//
|
|
175
|
-
|
|
206
|
+
// cross — из аргумента схемы; check получает снимок модели области:
|
|
207
|
+
const passwordRules = defineValidation<Passwords>(({ model, cross }) => {
|
|
208
|
+
cross(model.$.confirmPassword, passwordsMatch);
|
|
209
|
+
});
|
|
176
210
|
```
|
|
177
211
|
|
|
178
212
|
## See also
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
## 16. READING FIELD VALUES (CRITICALLY IMPORTANT)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Значения живут в модели. Есть три контекста чтения: value-доступ модели, сигналы, и React-хуки
|
|
4
|
+
(`@reformer/core/react`).
|
|
4
5
|
|
|
5
6
|
### В React-компоненте — хуки
|
|
6
7
|
|
|
7
8
|
```typescript
|
|
8
|
-
// Полное состояние поля (объект)
|
|
9
|
-
|
|
9
|
+
// Полное состояние поля (объект); текст замечания — useIssueMessage(messages)(issues[0])
|
|
10
|
+
// shouldShowIssues — показывать ли тексты замечаний; shouldShowError — aria-invalid
|
|
11
|
+
const { value, issues, disabled, touched, shouldShowIssues } = useFormControl(control.email);
|
|
10
12
|
|
|
11
13
|
// Только значение (напрямую, БЕЗ деструктуризации!)
|
|
12
14
|
const email = useFormControlValue(control.email);
|
|
@@ -15,6 +17,16 @@ const email = useFormControlValue(control.email);
|
|
|
15
17
|
const count = useArrayLength(control.items);
|
|
16
18
|
```
|
|
17
19
|
|
|
20
|
+
### Вне React — нода формы
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
form.email.value.value; // сигнал значения ноды (реактивно в effect/computed)
|
|
24
|
+
form.email.getValue(); // значение без подписки
|
|
25
|
+
form.email.issues.value; // замечания: ValidationIssue[] — код и параметры, без текста
|
|
26
|
+
form.items.length; // число строк массива (число; в эффекте подписывает)
|
|
27
|
+
form.getValue(); // значение группы целиком
|
|
28
|
+
```
|
|
29
|
+
|
|
18
30
|
### Вне React — модель
|
|
19
31
|
|
|
20
32
|
```typescript
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
## 17. COMPUTE vs ONCHANGE
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Поведение работает на сигналах модели. Писать его можно двумя способами:
|
|
4
4
|
|
|
5
|
-
- **примитивы из `@reformer/core`** — принимают сигналы, возвращают cleanup, вызываются
|
|
6
|
-
императивно (например, в `useEffect`), cleanup складывается в массив;
|
|
7
5
|
- **декларативный DSL из `@reformer/core/behaviors`** — `defineFormBehavior(...)` + операторы,
|
|
8
|
-
cleanup управляется формой, передаётся в `createForm({ behavior })`.
|
|
6
|
+
cleanup управляется формой, передаётся в `createForm({ behavior })`. Это способ для формы;
|
|
7
|
+
- **примитивы из `@reformer/core/model`** — принимают сигналы, возвращают cleanup, вызываются
|
|
8
|
+
императивно (например, в `useEffect`), cleanup складывается в массив. Нужны вне формы; в корне
|
|
9
|
+
`@reformer/core` их нет.
|
|
9
10
|
|
|
10
11
|
Для производных значений — `compute`/`computeFrom`. Для side-эффектов на изменение (async,
|
|
11
12
|
обновление componentProps) — `onChange` (DSL) или примитив `watchField`.
|
|
@@ -14,7 +15,9 @@
|
|
|
14
15
|
|
|
15
16
|
`compute(target, read)` подписывается на сигналы, прочитанные внутри `read()`, и пишет
|
|
16
17
|
результат в `target`. Цель не входит в источники → цикла нет; запись идемпотентна (peek-guard).
|
|
17
|
-
Кросс-уровневые вычисления работают так же — читай любые поля модели.
|
|
18
|
+
Кросс-уровневые вычисления работают так же — читай любые поля модели. Тип результата задаёт поле:
|
|
19
|
+
`compute(model.$.total, () => model.amount)` при `amount: number | null` и `total: number` не
|
|
20
|
+
компилируется — вычисление писало бы `null` в число (то же у `computeFrom`).
|
|
18
21
|
|
|
19
22
|
```typescript
|
|
20
23
|
import { defineFormBehavior, compute } from '@reformer/core/behaviors';
|
|
@@ -39,7 +42,7 @@ const behavior = defineFormBehavior<MyForm>(({ model }) => {
|
|
|
39
42
|
> отследится и пересчёта не будет. Читай поля по отдельности (`model.field` или `model.$.field.value`).
|
|
40
43
|
> `model.get()` — только вне реактивного контекста (в `onChange`, обработчиках событий).
|
|
41
44
|
|
|
42
|
-
### Computed sum over a
|
|
45
|
+
### Computed sum over a FieldArray
|
|
43
46
|
|
|
44
47
|
Агрегат по массиву (сумма доходов созаёмщиков и т.п.) считается **реактивно** через value-proxy
|
|
45
48
|
массива — `model.<array>.map(...)`:
|
|
@@ -79,7 +82,7 @@ compute(model.$.interestRate, () => computeRate(model.properties.map(() => null)
|
|
|
79
82
|
источников приходят в `fn` позиционно.
|
|
80
83
|
|
|
81
84
|
```typescript
|
|
82
|
-
import { computeFrom } from '@reformer/core/behaviors'; //
|
|
85
|
+
import { computeFrom } from '@reformer/core/behaviors'; // примитив — из '@reformer/core/model'
|
|
83
86
|
|
|
84
87
|
computeFrom(
|
|
85
88
|
[model.$.loanAmount, model.$.loanTerm, model.$.interestRate],
|
|
@@ -88,7 +91,10 @@ computeFrom(
|
|
|
88
91
|
);
|
|
89
92
|
```
|
|
90
93
|
|
|
91
|
-
> Примитив `computeFrom` из `@reformer/core` имеет ту же сигнатуру и возвращает cleanup
|
|
94
|
+
> Примитив `computeFrom` из `@reformer/core/model` имеет ту же сигнатуру и возвращает cleanup-функцию,
|
|
95
|
+
> но цель вычисляемой НЕ помечает: `model.set` / `patch` / `reset` её перезаписывают, `isDirty()` её
|
|
96
|
+
> учитывает — до следующей смены источников. Оператор из `@reformer/core/behaviors` цель помечает
|
|
97
|
+
> (`markDerived`), и массовая запись её не трогает. В поведении формы — оператор.
|
|
92
98
|
|
|
93
99
|
### onChange — реакция на изменение (async, side-effects)
|
|
94
100
|
|
|
@@ -113,11 +119,11 @@ const behavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
|
113
119
|
|
|
114
120
|
### Примитив watchField
|
|
115
121
|
|
|
116
|
-
Низкоуровневая подписка из `@reformer/core` (без debounce/AbortSignal). `onChange` построен
|
|
122
|
+
Низкоуровневая подписка из `@reformer/core/model` (без debounce/AbortSignal). `onChange` построен
|
|
117
123
|
поверх неё. Для простых синхронных реакций:
|
|
118
124
|
|
|
119
125
|
```typescript
|
|
120
|
-
import { watchField } from '@reformer/core';
|
|
126
|
+
import { watchField } from '@reformer/core/model';
|
|
121
127
|
const stop = watchField(model.$.country, () => { model.city = ''; });
|
|
122
128
|
```
|
|
123
129
|
|