@reformer/core 11.0.0-beta.1 → 11.0.0-beta.10
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 +90 -68
- package/dist/behaviors-value-D05z3my6.js +109 -0
- package/dist/behaviors.d.ts +1 -1
- package/dist/behaviors.js +376 -211
- package/dist/create-model-DPSG3I0O.js +30 -0
- package/dist/date-utils-90-qZbse.js +43 -0
- package/dist/en-HP8XEych.js +44 -0
- package/dist/form/aggregate-signals.d.ts +21 -63
- package/dist/form/behaviors/collections.d.ts +84 -0
- package/dist/form/behaviors/context.d.ts +56 -0
- package/dist/form/behaviors/index.d.ts +30 -0
- package/dist/form/behaviors/internals.d.ts +41 -0
- package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +28 -5
- package/dist/form/behaviors/operators.d.ts +59 -0
- package/dist/form/behaviors/schema-operators.d.ts +83 -0
- package/dist/form/behaviors/types.d.ts +37 -0
- package/dist/form/create-form.d.ts +59 -70
- package/dist/form/flow/flow.d.ts +87 -0
- package/dist/form/form-bundle.d.ts +153 -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 +23 -24
- 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 +159 -0
- package/dist/form/schema-subtree.d.ts +28 -0
- package/dist/form/signal-node-registry.d.ts +27 -22
- package/dist/form/submission.d.ts +93 -0
- package/dist/form/type-guards.d.ts +14 -73
- package/dist/form/types/contracts.d.ts +21 -85
- 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 +147 -46
- package/dist/form/types/validation-schema.d.ts +25 -17
- package/dist/form/validation/context.d.ts +67 -0
- package/dist/form/validation/index.d.ts +24 -28
- package/dist/form/validation/operators.d.ts +101 -0
- package/dist/form/validation/run.d.ts +120 -0
- package/dist/form/validation/strategy.d.ts +89 -0
- package/dist/form/validation/types.d.ts +83 -0
- package/dist/form/validators/date-utils.d.ts +42 -0
- package/dist/form/validators/email.d.ts +23 -0
- package/dist/form/{validation/validators → validators}/file-type.d.ts +10 -10
- package/dist/form/{validation/validators → validators}/file-utils.d.ts +3 -2
- package/dist/form/validators/future-date.d.ts +23 -0
- package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
- package/dist/form/validators/integer.d.ts +22 -0
- package/dist/form/validators/is-date.d.ts +24 -0
- package/dist/form/validators/is-number.d.ts +25 -0
- package/dist/form/validators/max-age.d.ts +28 -0
- package/dist/form/validators/max-date.d.ts +25 -0
- package/dist/form/{validation/validators → validators}/max-file-size.d.ts +10 -10
- package/dist/form/validators/max-files.d.ts +26 -0
- package/dist/form/validators/max-length.d.ts +33 -0
- package/dist/form/validators/max-total-file-size.d.ts +26 -0
- package/dist/form/validators/max.d.ts +26 -0
- package/dist/form/validators/min-age.d.ts +27 -0
- package/dist/form/validators/min-date.d.ts +25 -0
- package/dist/form/{validation/validators → validators}/min-file-size.d.ts +10 -10
- package/dist/form/validators/min-files.d.ts +27 -0
- package/dist/form/validators/min-length.d.ts +33 -0
- package/dist/form/validators/min.d.ts +26 -0
- package/dist/form/validators/multiple-of.d.ts +25 -0
- package/dist/form/validators/non-negative.d.ts +22 -0
- package/dist/form/validators/non-zero.d.ts +22 -0
- package/dist/form/validators/past-date.d.ts +23 -0
- package/dist/form/validators/pattern.d.ts +26 -0
- package/dist/form/{validation/validators → validators}/phone.d.ts +11 -11
- package/dist/form/validators/required.d.ts +29 -0
- package/dist/form/validators/url.d.ts +45 -0
- package/dist/form/validators/validation-issue.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +54 -0
- package/dist/i18n/en.json.d.ts +49 -0
- package/dist/i18n/loader.d.ts +71 -0
- package/dist/i18n/locale-store.d.ts +46 -0
- package/dist/i18n/locale.d.ts +90 -0
- package/dist/i18n/message-format.d.ts +136 -0
- package/dist/i18n/resolve-localized.d.ts +17 -0
- package/dist/i18n/ru.json.d.ts +49 -0
- package/dist/i18n/translator.d.ts +61 -0
- package/dist/i18n/validation-message.d.ts +53 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.js +86 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +942 -2601
- package/dist/loader-BWkigpVe.js +52 -0
- package/dist/locale/en.d.ts +2 -0
- package/dist/locale/en.js +5 -0
- package/dist/locale/en.json +45 -0
- package/dist/locale/index.d.ts +18 -0
- package/dist/locale/ru.d.ts +2 -0
- package/dist/locale/ru.js +44 -0
- package/dist/locale/ru.json +45 -0
- package/dist/locale-CG54AU2Q.js +219 -0
- package/dist/locale.d.ts +2 -0
- package/dist/locale.js +9 -0
- package/dist/{state → model}/behaviors-value.d.ts +24 -6
- package/dist/{state/form-model.d.ts → model/create-model.d.ts} +18 -4
- package/dist/{state → model}/derived-registry.d.ts +1 -1
- package/dist/model/index.d.ts +42 -0
- package/dist/model/model-nodes.d.ts +190 -0
- package/dist/model/model-signals-proxy.d.ts +63 -0
- package/dist/model/model-value-proxy.d.ts +84 -0
- package/dist/{state → model}/safe-effect.d.ts +50 -2
- package/dist/model/types.d.ts +239 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +27 -0
- package/dist/node-kind-C-r6ZMdo.js +15 -0
- package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
- package/dist/platforms/react/field-binding/field-handle.d.ts +53 -0
- package/dist/platforms/react/form-bundle-context.d.ts +27 -0
- package/dist/{form → platforms/react}/hooks/types.d.ts +58 -36
- package/dist/platforms/react/hooks/use-form-bundle.d.ts +41 -0
- package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
- package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +13 -7
- package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +6 -6
- package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +7 -9
- package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +18 -18
- package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +9 -1
- package/dist/platforms/react/i18n/context.d.ts +18 -0
- package/dist/platforms/react/i18n/hooks.d.ts +65 -0
- package/dist/platforms/react/i18n/provider.d.ts +59 -0
- package/dist/platforms/react/index.d.ts +30 -0
- 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-BazLOv1g.js +669 -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-DzvnsWLh.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.d.ts +1 -1
- package/dist/validation.js +11 -165
- package/dist/validators/email.d.ts +1 -1
- package/dist/validators/email.js +5 -8
- package/dist/validators/future-date.d.ts +1 -1
- package/dist/validators/future-date.js +10 -13
- package/dist/validators/integer.d.ts +1 -1
- package/dist/validators/integer.js +4 -7
- package/dist/validators/is-date.d.ts +1 -1
- package/dist/validators/is-date.js +5 -8
- package/dist/validators/is-number.d.ts +1 -1
- package/dist/validators/is-number.js +4 -7
- package/dist/validators/max-age.d.ts +1 -1
- package/dist/validators/max-age.js +8 -12
- package/dist/validators/max-date.d.ts +1 -1
- package/dist/validators/max-date.js +10 -13
- package/dist/validators/max-length.d.ts +1 -1
- package/dist/validators/max-length.js +7 -10
- package/dist/validators/max.d.ts +1 -1
- package/dist/validators/max.js +4 -7
- package/dist/validators/min-age.d.ts +1 -1
- package/dist/validators/min-age.js +10 -11
- package/dist/validators/min-date.d.ts +1 -1
- package/dist/validators/min-date.js +8 -11
- package/dist/validators/min-length.d.ts +1 -1
- package/dist/validators/min-length.js +4 -7
- package/dist/validators/min.d.ts +1 -1
- package/dist/validators/min.js +4 -7
- package/dist/validators/multiple-of.d.ts +1 -1
- package/dist/validators/multiple-of.js +11 -10
- package/dist/validators/non-negative.d.ts +1 -1
- package/dist/validators/non-negative.js +2 -5
- package/dist/validators/non-zero.d.ts +1 -1
- package/dist/validators/non-zero.js +4 -7
- package/dist/validators/past-date.d.ts +1 -1
- package/dist/validators/past-date.js +10 -13
- package/dist/validators/pattern.d.ts +1 -1
- package/dist/validators/pattern.js +5 -8
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/phone.js +10 -11
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/required.js +4 -7
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators/url.js +48 -17
- package/dist/validators.d.ts +1 -1
- package/dist/validators.js +86 -108
- package/docs/llms/01-api-reference.md +60 -34
- package/docs/llms/02-quick-start.md +112 -74
- package/docs/llms/03-api-signatures.md +369 -148
- 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 +132 -84
- package/docs/llms/10-arrays.md +141 -61
- 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 +224 -70
- package/docs/llms/16-ui-components.md +86 -46
- package/docs/llms/17-nonexistent-api.md +217 -32
- package/docs/llms/18-conditional-fields.md +71 -32
- package/docs/llms/19-reading-values.md +45 -4
- package/docs/llms/20-compute-vs-watch.md +25 -10
- package/docs/llms/21-array-operations.md +76 -26
- 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 +278 -128
- package/docs/llms/29-async-preload.md +41 -31
- 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 +178 -66
- package/docs/llms/34-i18n.md +298 -0
- package/docs/llms/35-unified-contract.md +309 -0
- 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 +396 -0
- package/llms-index.json +1 -0
- package/llms.txt +10690 -3089
- package/package.json +48 -9
- package/dist/behaviors-node-DGEFMTQu.js +0 -18
- package/dist/behaviors-value-C7dSEhCc.js +0 -134
- package/dist/date-utils-dTT_x_Be.js +0 -31
- package/dist/derived-registry-CluZCQ7U.js +0 -16
- package/dist/form/behaviors.d.ts +0 -133
- package/dist/form/error-handler.d.ts +0 -166
- package/dist/form/factories/index.d.ts +0 -6
- package/dist/form/factories/node-factory.d.ts +0 -188
- 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 -294
- 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 -121
- 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/validators/date-utils.d.ts +0 -30
- package/dist/form/validation/validators/email.d.ts +0 -23
- package/dist/form/validation/validators/future-date.d.ts +0 -23
- package/dist/form/validation/validators/integer.d.ts +0 -22
- package/dist/form/validation/validators/is-date.d.ts +0 -23
- package/dist/form/validation/validators/is-number.d.ts +0 -24
- package/dist/form/validation/validators/max-age.d.ts +0 -25
- package/dist/form/validation/validators/max-date.d.ts +0 -25
- package/dist/form/validation/validators/max-files.d.ts +0 -26
- package/dist/form/validation/validators/max-length.d.ts +0 -27
- package/dist/form/validation/validators/max-total-file-size.d.ts +0 -26
- package/dist/form/validation/validators/max.d.ts +0 -25
- package/dist/form/validation/validators/min-age.d.ts +0 -25
- package/dist/form/validation/validators/min-date.d.ts +0 -25
- package/dist/form/validation/validators/min-files.d.ts +0 -26
- package/dist/form/validation/validators/min-length.d.ts +0 -27
- package/dist/form/validation/validators/min.d.ts +0 -25
- package/dist/form/validation/validators/multiple-of.d.ts +0 -24
- package/dist/form/validation/validators/non-negative.d.ts +0 -22
- package/dist/form/validation/validators/non-zero.d.ts +0 -22
- package/dist/form/validation/validators/past-date.d.ts +0 -23
- package/dist/form/validation/validators/pattern.d.ts +0 -33
- package/dist/form/validation/validators/required.d.ts +0 -32
- package/dist/form/validation/validators/url.d.ts +0 -43
- package/dist/form/validation-schema.d.ts +0 -106
- package/dist/form/validation-strategy.d.ts +0 -55
- package/dist/form/validators.d.ts +0 -11
- package/dist/form-model-DYCzGGKF.js +0 -286
- package/dist/state/index.d.ts +0 -31
- package/dist/state/subscription-manager.d.ts +0 -180
- package/dist/state/types.d.ts +0 -144
- package/dist/state.d.ts +0 -2
- package/dist/state.js +0 -21
- package/dist/subscription-manager-705bls97.js +0 -171
|
@@ -4,29 +4,52 @@
|
|
|
4
4
|
|
|
5
5
|
Одна декларативная точка выбора «когда прогонять схему валидации» вместо ручной разводки
|
|
6
6
|
`markAsTouched` + `validateModel` + `revalidateWhen` + `useState(pending)`. Аддитивный слой над
|
|
7
|
-
функциональной схемой (`@reformer/core/validation`): переиспользует
|
|
8
|
-
ЕДИНСТВЕННЫЙ движок (
|
|
9
|
-
второго движка не заводит.
|
|
10
|
-
|
|
7
|
+
функциональной схемой (`@reformer/core/validation`): переиспользует раннер схемы как
|
|
8
|
+
ЕДИНСТВЕННЫЙ движок (разнос ошибок по нодам, отмена устаревших прогонов, дедуп по `(model, schema)`),
|
|
9
|
+
второго движка не заводит. Других триггеров валидации нет: у ноды поля собственных валидаторов,
|
|
10
|
+
`updateOn` и `debounce` не бывает — когда запускать проверку, решает только стратегия.
|
|
11
11
|
|
|
12
12
|
Стратегия определяет только момент запуска и раскрытие ошибок — сами правила остаются в отдельной
|
|
13
|
-
`
|
|
13
|
+
`defineValidation<T>(({ model }) => …)`.
|
|
14
14
|
|
|
15
15
|
## API
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
Три точки входа к одному контроллеру:
|
|
18
|
+
|
|
19
|
+
- **сборка `createForm`** — `validation: { rules?, extras?, strategy, debounce, liveAfterSubmit }`.
|
|
20
|
+
Контроллер собирает уровень формы, армирует `useFormBundle` (в эффекте, не при SSR);
|
|
21
|
+
`bundle.validate()` и `bundle.submission.submit(handler)` — полный прогон с раскрытием замечаний.
|
|
22
|
+
Это путь по умолчанию для формы со сборкой;
|
|
23
|
+
- **React-хук `useFormValidation`** из `@reformer/core/react` — для формы без сборки
|
|
24
|
+
(`createFormFromModel` + своя схема);
|
|
25
|
+
- **headless-фабрика `createFormValidation`** из `@reformer/core/validation` — для SSR / тестов /
|
|
26
|
+
не-React потребителей.
|
|
20
27
|
|
|
21
28
|
```typescript
|
|
22
|
-
//
|
|
29
|
+
// Сборка (@reformer/core)
|
|
30
|
+
createForm<T>({
|
|
31
|
+
…,
|
|
32
|
+
validation: {
|
|
33
|
+
rules?: ValidationDefinition<T>; // полный набор; без него — правила шагов flow + extras
|
|
34
|
+
extras?: ValidationDefinition<T>;
|
|
35
|
+
strategy?: ValidationStrategyKind; // default 'submit'
|
|
36
|
+
debounce?: number;
|
|
37
|
+
liveAfterSubmit?: 'change' | 'blur';
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// React (@reformer/core/react)
|
|
23
42
|
function useFormValidation<T>(args: {
|
|
24
43
|
model: FormModel<T>;
|
|
25
|
-
schema:
|
|
44
|
+
schema: ValidationDefinition<T>; // СТАБИЛЬНАЯ ссылка (module-level const / useMemo)
|
|
26
45
|
strategy?: ValidationStrategyKind; // default 'submit'
|
|
27
46
|
debounce?: number; // мс, для live-фаз (change / live-часть afterFirstSubmit)
|
|
28
47
|
liveAfterSubmit?: 'change' | 'blur'; // default 'change'
|
|
29
|
-
}): {
|
|
48
|
+
}): {
|
|
49
|
+
submit: () => Promise<boolean>; // попытка отправки: при отказе раскрыта вся форма, afterFirstSubmit → живая фаза
|
|
50
|
+
validate: () => Promise<boolean>; // проверка без отправки: раскрыты проверенные поля, фаза не меняется
|
|
51
|
+
isValidating: boolean;
|
|
52
|
+
};
|
|
30
53
|
|
|
31
54
|
// headless (@reformer/core/validation)
|
|
32
55
|
type ValidationStrategyKind = 'submit' | 'blur' | 'change' | 'afterFirstSubmit';
|
|
@@ -35,51 +58,101 @@ interface ValidationStrategyOptions {
|
|
|
35
58
|
debounce?: number;
|
|
36
59
|
liveAfterSubmit?: 'change' | 'blur';
|
|
37
60
|
}
|
|
61
|
+
interface FormValidationRunOptions {
|
|
62
|
+
touch?: true | 'all'; // true (default) — проверенные поля; 'all' — при отказе ещё и вся форма
|
|
63
|
+
submit?: boolean; // прогон — попытка отправки: afterFirstSubmit → живая фаза
|
|
64
|
+
}
|
|
38
65
|
interface FormValidationController {
|
|
39
|
-
validate(): Promise<boolean>; //
|
|
66
|
+
validate(): Promise<boolean>; // нейтральная проверка: touch:true, фазу afterFirstSubmit не трогает; true — только valid
|
|
67
|
+
run(options?: FormValidationRunOptions): Promise<ValidationResult>; // valid | invalid | error | cancelled
|
|
40
68
|
start(): () => void; // армит реактивные подписки (ТОЛЬКО на клиенте) → dispose
|
|
41
|
-
dispose(): void;
|
|
69
|
+
dispose(): void; // снять подписки и таймеры, отменить идущий прогон (он получит cancelled)
|
|
42
70
|
readonly isValidating: boolean;
|
|
43
71
|
readonly validating: ReadonlySignal<boolean>;
|
|
44
72
|
}
|
|
45
73
|
function createFormValidation<T>(
|
|
46
74
|
model: FormModel<T>,
|
|
47
|
-
schema:
|
|
75
|
+
schema: ValidationDefinition<T>,
|
|
48
76
|
options?: ValidationStrategyOptions,
|
|
49
77
|
): FormValidationController;
|
|
50
78
|
```
|
|
51
79
|
|
|
52
|
-
- `useFormValidation` мемоизирует контроллер по
|
|
53
|
-
|
|
80
|
+
- `useFormValidation` мемоизирует контроллер по
|
|
81
|
+
`[model, schema, strategy, debounce, liveAfterSubmit]` и армит стратегию в `useEffect`
|
|
82
|
+
(SSR-safe). `submit()` = `controller.run({ touch: 'all', submit: true })`, `validate()` =
|
|
83
|
+
`controller.validate()`; обе — стабильные ссылки, отвечают `true` только при `valid`.
|
|
54
84
|
- `createFormValidation` ЧИСТА до `start()` (никаких подписок) → SSR-safe; `start()` возвращает
|
|
55
85
|
`dispose` и арминует триггеры только на клиенте.
|
|
56
86
|
|
|
87
|
+
### Живая фаза `afterFirstSubmit` — одно правило
|
|
88
|
+
|
|
89
|
+
Живую фазу включает **только попытка отправки**:
|
|
90
|
+
|
|
91
|
+
| Вызов | Попытка отправки | Касание при отказе |
|
|
92
|
+
| --------------------------------------------------------------- | ---------------- | ------------------ |
|
|
93
|
+
| `bundle.submission.submit(handler)` | да | вся форма |
|
|
94
|
+
| `submit()` хука `useFormValidation` | да | вся форма |
|
|
95
|
+
| `controller.run({ touch: 'all', submit: true })` | да | вся форма |
|
|
96
|
+
| `bundle.validate()`, `validate()` хука, `controller.validate()` | нет | проверенные поля |
|
|
97
|
+
| `bundle.validateStep(id)`, `flow.next()` | нет | поля шага |
|
|
98
|
+
|
|
99
|
+
Кнопка «Проверить» — `validate()`: показывает замечания, но форма остаётся в тихой фазе. Отменённая
|
|
100
|
+
отправка (`cancelled`) форму тронутой не помечает.
|
|
101
|
+
|
|
57
102
|
### Стратегии
|
|
58
103
|
|
|
59
|
-
| strategy
|
|
60
|
-
|
|
|
61
|
-
| `submit` (default) | только `validate()` / `submit()` | `true`
|
|
62
|
-
| `blur`
|
|
63
|
-
| `change`
|
|
64
|
-
| `afterFirstSubmit` | тихо до
|
|
104
|
+
| strategy | когда прогон | touch | раскрытие замечаний |
|
|
105
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------- |
|
|
106
|
+
| `submit` (default) | только `validate()` / `submit()` (в сборке — `bundle.validate()` / `bundle.submission.submit()`) | `true` | всё на submit |
|
|
107
|
+
| `blur` | каждый уход из поля (потеря фокуса), и повторный тоже | `false` | только сблюренные (по `touched`) |
|
|
108
|
+
| `change` | на каждый ввод (+ `debounce`) | `false` | только редактированные (по `dirty`) |
|
|
109
|
+
| `afterFirstSubmit` | тихо до первой попытки отправки, затем live (`liveAfterSubmit: 'change'` \| `'blur'`, default `'change'`); `validate()` фазу не переключает | при отправке — вся форма (при отказе), дальше live — `false` | тихо → раскрыть всё на отправке → далее live |
|
|
65
110
|
|
|
66
111
|
## Examples
|
|
67
112
|
|
|
68
|
-
###
|
|
113
|
+
### Сборка — стратегия полем `validation`
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { createForm } from '@reformer/core';
|
|
117
|
+
import { useFormBundle } from '@reformer/core/react';
|
|
118
|
+
|
|
119
|
+
const createRegistration = () =>
|
|
120
|
+
createForm<RegistrationData>({
|
|
121
|
+
initial: { username: '', email: '', password: '' },
|
|
122
|
+
schema: registrationSchema,
|
|
123
|
+
validation: { rules: registrationValidation, strategy: 'afterFirstSubmit', debounce: 400 },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
function RegistrationForm() {
|
|
127
|
+
const registration = useFormBundle(createRegistration); // армирует живую стратегию в эффекте
|
|
128
|
+
const onSubmit = async (event: React.FormEvent) => {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
// первая отправка раскрывает все замечания и переводит форму в живую фазу;
|
|
131
|
+
// registration.validate() фазу не переключает
|
|
132
|
+
await registration.submission.submit(api.save);
|
|
133
|
+
};
|
|
134
|
+
/* … */
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Без сборки — useFormValidation, afterFirstSubmit + debounce
|
|
69
139
|
|
|
70
|
-
Тихо до первой отправки, `submit()` раскрывает все
|
|
71
|
-
(дальше проверка на ввод с задержкой 400 мс). `submit()` сам метит `touched` —
|
|
72
|
-
`markAsTouched` не нужен.
|
|
140
|
+
Тихо до первой отправки, `submit()` раскрывает все замечания и переводит форму в живую фазу
|
|
141
|
+
(дальше проверка на ввод с задержкой 400 мс). `submit()` сам метит `touched` (при отказе — всю
|
|
142
|
+
форму) — ручной `markAsTouched` не нужен. `validate()` — кнопка «Проверить»: раскрывает замечания
|
|
143
|
+
проверенных полей, а форма остаётся в тихой фазе.
|
|
73
144
|
|
|
74
145
|
```tsx
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
146
|
+
import { useMemo } from 'react';
|
|
147
|
+
import { createModel, createFormFromModel } from '@reformer/core';
|
|
148
|
+
import { useFormValidation } from '@reformer/core/react';
|
|
149
|
+
import { defineValidation, validate } from '@reformer/core/validation';
|
|
77
150
|
import { required, email, minLength } from '@reformer/core/validators';
|
|
78
151
|
|
|
79
152
|
type RegistrationData = { username: string; email: string; password: string };
|
|
80
153
|
|
|
81
154
|
// Схема — стабильный module-level const (иначе ломается дедуп раннера).
|
|
82
|
-
const registrationValidation =
|
|
155
|
+
const registrationValidation = defineValidation<RegistrationData>(({ model }) => {
|
|
83
156
|
validate(model.$.username, [required(), minLength(3)]);
|
|
84
157
|
validate(model.$.email, [required(), email()]);
|
|
85
158
|
validate(model.$.password, [required(), minLength(8)]);
|
|
@@ -87,17 +160,17 @@ const registrationValidation = defineValidationSchema<RegistrationData>(({ model
|
|
|
87
160
|
|
|
88
161
|
function RegistrationForm() {
|
|
89
162
|
const model = useMemo(() => createModel<RegistrationData>({ username: '', email: '', password: '' }), []);
|
|
90
|
-
const form = useMemo(() =>
|
|
163
|
+
const form = useMemo(() => createFormFromModel({ model }), [model]);
|
|
91
164
|
|
|
92
|
-
const { submit, isValidating } = useFormValidation({
|
|
165
|
+
const { submit, validate, isValidating } = useFormValidation({
|
|
93
166
|
model,
|
|
94
167
|
schema: registrationValidation,
|
|
95
168
|
strategy: 'afterFirstSubmit',
|
|
96
169
|
debounce: 400,
|
|
97
170
|
});
|
|
98
171
|
|
|
99
|
-
const onSubmit = async (
|
|
100
|
-
|
|
172
|
+
const onSubmit = async (event: React.FormEvent) => {
|
|
173
|
+
event.preventDefault();
|
|
101
174
|
if (await submit()) await api.save(model.get()); // true = нет блокирующих ошибок
|
|
102
175
|
};
|
|
103
176
|
|
|
@@ -106,49 +179,69 @@ function RegistrationForm() {
|
|
|
106
179
|
<FormField control={form.username} />
|
|
107
180
|
<FormField control={form.email} />
|
|
108
181
|
<FormField control={form.password} />
|
|
109
|
-
<button type="
|
|
182
|
+
<button type="button" onClick={() => void validate()}>
|
|
183
|
+
Проверить
|
|
184
|
+
</button>
|
|
185
|
+
<button type="submit" disabled={isValidating}>
|
|
186
|
+
Отправить
|
|
187
|
+
</button>
|
|
110
188
|
</form>
|
|
111
189
|
);
|
|
112
190
|
}
|
|
113
191
|
```
|
|
114
192
|
|
|
115
|
-
###
|
|
193
|
+
### Визард — стратегия формы и проверка шага потоком
|
|
116
194
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
195
|
+
Живая стратегия — свойство формы, а не шага: она задаётся полем `validation` сборки и армируется
|
|
196
|
+
`useFormBundle`. Проверку шага делает поток: «Далее» визарда — `flow.next()`, он прогоняет правила
|
|
197
|
+
текущего шага и не пускает дальше при замечаниях. Отправка — `bundle.submission.submit` (все
|
|
198
|
+
шаги + `extras`). Живую фазу `afterFirstSubmit` включает только она: `flow.next()` и
|
|
199
|
+
`validateStep` фазу не переключают.
|
|
121
200
|
|
|
122
201
|
```tsx
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
202
|
+
const createCredit = () =>
|
|
203
|
+
createForm<CreditForm>({
|
|
204
|
+
model: createCreditModel(),
|
|
205
|
+
schema: creditSchema,
|
|
206
|
+
validation: { extras: crossFieldRules, strategy: 'blur' }, // живая проверка при потере фокуса
|
|
207
|
+
flow: {
|
|
208
|
+
steps: [
|
|
209
|
+
{ id: 'loan', rules: loanStep },
|
|
210
|
+
{ id: 'applicant', rules: applicantStep },
|
|
211
|
+
{ id: 'confirm' }, // без правил
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
});
|
|
130
215
|
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
return
|
|
216
|
+
function CreditWizard() {
|
|
217
|
+
const credit = useFormBundle(createCredit);
|
|
218
|
+
return (
|
|
219
|
+
<FormWizard
|
|
220
|
+
form={credit.form}
|
|
221
|
+
flow={credit.flow}
|
|
222
|
+
steps={STEPS}
|
|
223
|
+
onSubmit={() => void credit.submission.submit(api.save)}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
134
226
|
}
|
|
135
|
-
|
|
136
|
-
<FormWizard form={form} config={config}>
|
|
137
|
-
<LoanStepBody />
|
|
138
|
-
</FormWizard>;
|
|
139
227
|
```
|
|
140
228
|
|
|
229
|
+
Живая фаза прогоняет **полный** набор правил (все шаги + `extras`), а показ замечаний гейтится
|
|
230
|
+
`touched` / `dirty` полей: поля следующих шагов пользователь ещё не трогал — их замечания не видны.
|
|
231
|
+
Прежний per-step механизм (`defineSteps(…, { strategy })` + `useWizardStepValidation`) удалён.
|
|
232
|
+
|
|
141
233
|
### Headless / SSR / тесты — createFormValidation
|
|
142
234
|
|
|
143
235
|
```typescript
|
|
144
236
|
import { createFormValidation } from '@reformer/core/validation';
|
|
145
237
|
|
|
146
|
-
const
|
|
238
|
+
const controller = createFormValidation(model, registrationValidation, {
|
|
147
239
|
strategy: 'afterFirstSubmit',
|
|
148
240
|
debounce: 300,
|
|
149
241
|
});
|
|
150
|
-
const dispose =
|
|
151
|
-
const
|
|
242
|
+
const dispose = controller.start(); // ТОЛЬКО на клиенте (в React — из useEffect)
|
|
243
|
+
const checked = await controller.validate(); // проверка: замечания проверенных полей, фаза тихая
|
|
244
|
+
const sent = await controller.run({ touch: 'all', submit: true }); // попытка отправки → живая фаза
|
|
152
245
|
dispose();
|
|
153
246
|
```
|
|
154
247
|
|
|
@@ -157,32 +250,50 @@ dispose();
|
|
|
157
250
|
```typescript
|
|
158
251
|
// ❌ schema пересоздаётся каждый рендер → контроллер и дедуп раннера ломаются
|
|
159
252
|
function MyForm() {
|
|
160
|
-
const schema =
|
|
253
|
+
const schema = defineValidation<Form>(({ model }) => { … }); // новая ссылка на каждый рендер
|
|
161
254
|
useFormValidation({ model, schema, strategy: 'change' });
|
|
162
255
|
}
|
|
163
256
|
|
|
164
257
|
// ✅ Стабильная ссылка: module-level const (или useMemo с пустыми deps)
|
|
165
|
-
const formValidation =
|
|
258
|
+
const formValidation = defineValidation<Form>(({ model }) => { … });
|
|
166
259
|
function MyForm() {
|
|
167
260
|
useFormValidation({ model, schema: formValidation, strategy: 'change' });
|
|
168
261
|
}
|
|
169
262
|
```
|
|
170
263
|
|
|
171
264
|
```typescript
|
|
172
|
-
// ❌
|
|
173
|
-
//
|
|
265
|
+
// ❌ updateOn / debounce / validators в узле схемы — таких ключей у узла нет, сборка их не читает
|
|
266
|
+
// { model: model.$.email, component: Input, updateOn: 'blur', validators: [required()] }
|
|
174
267
|
|
|
175
|
-
// ✅
|
|
268
|
+
// ✅ Момент запуска — стратегия формы; правила — схема валидации
|
|
269
|
+
// createForm({ …, validation: { rules: formValidation, strategy: 'blur', debounce: 300 } })
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// ❌ Своя стратегия поверх сборки: второй контроллер на ту же модель и правила
|
|
274
|
+
const bundle = useFormBundle(() => createForm({ …, validation: rules }));
|
|
275
|
+
useFormValidation({ model: bundle.model, schema: rules, strategy: 'change' });
|
|
276
|
+
|
|
277
|
+
// ✅ Стратегия — полем validation сборки; армирует useFormBundle
|
|
278
|
+
const bundle = useFormBundle(() => createForm({ …, validation: { rules, strategy: 'change' } }));
|
|
176
279
|
```
|
|
177
280
|
|
|
178
281
|
```typescript
|
|
179
282
|
// ❌ start() на сервере — арминует реактивные подписки при SSR
|
|
180
|
-
const
|
|
181
|
-
|
|
283
|
+
const controller = createFormValidation(model, schema, { strategy: 'change' });
|
|
284
|
+
controller.start(); // на сервере подписок быть не должно
|
|
182
285
|
|
|
183
286
|
// ✅ Фабрика чиста до start(); армить только на клиенте (useFormValidation делает это в useEffect)
|
|
184
|
-
const
|
|
185
|
-
if (typeof window !== 'undefined')
|
|
287
|
+
const controller = createFormValidation(model, schema, { strategy: 'change' });
|
|
288
|
+
if (typeof window !== 'undefined') controller.start();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// ❌ afterFirstSubmit: ждать живой фазы после проверки — validate() фазу не переключает
|
|
293
|
+
await bundle.validate(); // форма в тихой фазе, ввод не перепроверяется
|
|
294
|
+
|
|
295
|
+
// ✅ Живую фазу включает попытка отправки
|
|
296
|
+
await bundle.submission.submit(api.save); // без сборки — submit() хука useFormValidation
|
|
186
297
|
```
|
|
187
298
|
|
|
188
299
|
## See also
|
|
@@ -190,4 +301,5 @@ if (typeof window !== 'undefined') ctrl.start();
|
|
|
190
301
|
- [28-submit-and-reset.md](./28-submit-and-reset.md) — submit-флоу и `validateModel` (движок под стратегией)
|
|
191
302
|
- [27-revalidate-when.md](./27-revalidate-when.md) — ручные триггеры перевалидации (низкоуровневый примитив)
|
|
192
303
|
- [31-async-validator-debounce.md](./31-async-validator-debounce.md) — async-правила и отмена устаревших прогонов
|
|
193
|
-
- [13-multi-step.md](./13-multi-step.md) —
|
|
304
|
+
- [13-multi-step.md](./13-multi-step.md) — многошаговая форма: поток `flow`, `bundle.validateStep(id)`
|
|
305
|
+
- [38-form-flow.md](./38-form-flow.md) — уровень формы: `validate`, `submission`, поток шагов
|