@reformer/core 10.2.0 → 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 -27
- 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 -91
- package/dist/form/types/field-config.d.ts +27 -0
- package/dist/form/types/index.d.ts +5 -91
- package/dist/form/types/nodes.d.ts +159 -0
- package/dist/form/types/schema-node.d.ts +148 -50
- package/dist/form/types/validation-schema.d.ts +24 -41
- 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 +18 -6
- package/dist/index.js +932 -2779
- 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 +60 -38
- 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 +22 -0
- 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 -5
- package/dist/validators.js +98 -154
- package/docs/llms/01-api-reference.md +89 -0
- package/docs/llms/02-quick-start.md +181 -0
- package/docs/llms/03-api-signatures.md +494 -0
- package/docs/llms/04-common-patterns.md +204 -0
- package/docs/llms/05-common-mistakes.md +192 -0
- package/docs/llms/06-troubleshooting.md +16 -0
- package/docs/llms/07-complete-import.md +130 -0
- package/docs/llms/08-form-types.md +54 -0
- package/docs/llms/09-formschema.md +191 -0
- package/docs/llms/10-arrays.md +241 -0
- package/docs/llms/11-async-watchfield.md +53 -0
- package/docs/llms/12-array-cleanup.md +31 -0
- package/docs/llms/13-multi-step.md +162 -0
- package/docs/llms/14-extended-mistakes.md +101 -0
- package/docs/llms/15-project-structure.md +279 -0
- package/docs/llms/16-ui-components.md +229 -0
- package/docs/llms/17-nonexistent-api.md +278 -0
- package/docs/llms/18-conditional-fields.md +222 -0
- package/docs/llms/19-reading-values.md +100 -0
- package/docs/llms/20-compute-vs-watch.md +153 -0
- package/docs/llms/21-array-operations.md +163 -0
- package/docs/llms/22-cycle-detection.md +86 -0
- package/docs/llms/23-copy-from.md +141 -0
- package/docs/llms/24-sync-fields.md +115 -0
- package/docs/llms/25-reset-when.md +122 -0
- package/docs/llms/26-transform-value.md +136 -0
- package/docs/llms/27-revalidate-when.md +133 -0
- package/docs/llms/28-submit-and-reset.md +409 -0
- package/docs/llms/29-async-preload.md +203 -0
- package/docs/llms/30-type-safety-recipes.md +213 -0
- package/docs/llms/31-async-validator-debounce.md +178 -0
- package/docs/llms/32-async-options-loading.md +108 -0
- package/docs/llms/33-validation-strategy.md +305 -0
- 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 +10014 -3826
- package/package.json +77 -15
- 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 -125
- package/dist/form/nodes/array-node.d.ts +0 -360
- package/dist/form/nodes/field-node.d.ts +0 -294
- package/dist/form/nodes/form-node.d.ts +0 -340
- package/dist/form/nodes/group-node.d.ts +0 -288
- package/dist/form/nodes/model-array-node.d.ts +0 -121
- package/dist/form/status-machine.d.ts +0 -153
- package/dist/form/types/deep-schema.d.ts +0 -116
- package/dist/form/types/form-proxy.d.ts +0 -141
- package/dist/form/unique-id.d.ts +0 -53
- 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 -6
- 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
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@reformer/core)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
Reactive form state management
|
|
7
|
+
Reactive form state management on signals: a UI-agnostic core with React bindings in
|
|
8
|
+
`@reformer/core/react`.
|
|
8
9
|
|
|
9
10
|
## Playground
|
|
10
11
|
|
|
@@ -17,10 +18,12 @@ Full documentation is available at [https://alexandrbukhtatyy.github.io/ReFormer
|
|
|
17
18
|
## Features
|
|
18
19
|
|
|
19
20
|
- Signals-based reactive state management
|
|
20
|
-
- Declarative form validation
|
|
21
|
+
- Declarative form validation with typed rules and status results
|
|
22
|
+
- Validation issues without text: codes and params, texts from a form dictionary and the locale
|
|
21
23
|
- Dynamic form behaviors
|
|
24
|
+
- Multi-step flows: step rules, transitions, `submit` with a result object
|
|
22
25
|
- TypeScript support
|
|
23
|
-
- Tree-shakeable exports
|
|
26
|
+
- Tree-shakeable exports; the root entry has no React dependency
|
|
24
27
|
|
|
25
28
|
## Installation
|
|
26
29
|
|
|
@@ -30,27 +33,45 @@ npm install @reformer/core@beta # Active development is underway, so you can try
|
|
|
30
33
|
|
|
31
34
|
## Quick Start
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
A reactive `FormModel` owns the values, a form schema — a tree of nodes — binds field config
|
|
37
|
+
(component / props) to the model's handles (`model.$.<field>`), and `createForm` assembles the
|
|
38
|
+
model, the form and the form level (`validate`, `submission`, step flow) in one call. Validation is a
|
|
39
|
+
**separate** contract — `defineValidation` — so the form schema carries no validators. A rule
|
|
40
|
+
reports a code, not a text: the text comes from a form dictionary (`defineIssueMessages`) on top of
|
|
41
|
+
the locale.
|
|
37
42
|
|
|
38
43
|
```tsx
|
|
39
|
-
import { useMemo } from 'react';
|
|
40
44
|
import {
|
|
41
|
-
createModel,
|
|
42
45
|
createForm,
|
|
43
|
-
|
|
44
|
-
type
|
|
45
|
-
type
|
|
46
|
+
type FormControl,
|
|
47
|
+
type FormModel,
|
|
48
|
+
type FormSchemaNode,
|
|
49
|
+
type ValidationIssue,
|
|
46
50
|
} from '@reformer/core';
|
|
47
|
-
import {
|
|
51
|
+
import { useFormBundle, useFormControl, useIssueMessage } from '@reformer/core/react';
|
|
52
|
+
import { defineIssueMessages } from '@reformer/core/i18n';
|
|
53
|
+
import { validate, defineValidation } from '@reformer/core/validation';
|
|
48
54
|
import { required, email, minLength } from '@reformer/core/validators';
|
|
49
55
|
import { defineFormBehavior, onChange } from '@reformer/core/behaviors';
|
|
50
56
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
// 1. Form data type — `interface` or `type`, both work
|
|
58
|
+
interface RegistrationForm {
|
|
59
|
+
username: string;
|
|
60
|
+
email: string;
|
|
61
|
+
password: string;
|
|
62
|
+
confirmPassword: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. Issue texts by code — the form dictionary (built-in codes fall back to the locale)
|
|
66
|
+
const messages = defineIssueMessages({
|
|
67
|
+
'username.required': 'Enter a username',
|
|
68
|
+
'confirmPassword.mismatch': 'Passwords do not match',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 3. Simple FormField component: the issue text comes from the resolver
|
|
72
|
+
function FormField({ label, control }: { label: string; control: FormControl<string> }) {
|
|
73
|
+
const { value, issues, shouldShowError } = useFormControl(control);
|
|
74
|
+
const message = useIssueMessage(messages);
|
|
54
75
|
|
|
55
76
|
return (
|
|
56
77
|
<div>
|
|
@@ -60,78 +81,69 @@ function FormField({ label, control }: { label: string; control: FieldNode<strin
|
|
|
60
81
|
onChange={(e) => control.setValue(e.target.value)}
|
|
61
82
|
onBlur={() => control.markAsTouched()}
|
|
62
83
|
/>
|
|
63
|
-
{shouldShowError && <span className="error">{
|
|
84
|
+
{shouldShowError && issues[0] && <span className="error">{message(issues[0])}</span>}
|
|
64
85
|
</div>
|
|
65
86
|
);
|
|
66
87
|
}
|
|
67
88
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
password: string;
|
|
73
|
-
confirmPassword: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 2. Reactive model — the source of truth for values
|
|
77
|
-
const model = createModel<RegistrationForm>({
|
|
78
|
-
username: '',
|
|
79
|
-
email: '',
|
|
80
|
-
password: '',
|
|
81
|
-
confirmPassword: '',
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// 3. Layout schema — binds field config (component / props) to model signals
|
|
85
|
-
// (`model.$.<field>`). Layout carries NO validators; rules live in the
|
|
86
|
-
// validation schema below.
|
|
87
|
-
const schema = {
|
|
89
|
+
// 4. Form schema — a tree of nodes. A field node binds field config (component / props)
|
|
90
|
+
// to a model handle (`model: model.$.<field>`); nested nodes live in `children`.
|
|
91
|
+
// The schema carries NO validators; rules live in the validation schema below.
|
|
92
|
+
const schema = (model: FormModel<RegistrationForm>): FormSchemaNode => ({
|
|
88
93
|
children: [
|
|
89
|
-
{
|
|
90
|
-
{
|
|
91
|
-
{
|
|
92
|
-
{
|
|
94
|
+
{ model: model.$.username, component: 'input' },
|
|
95
|
+
{ model: model.$.email, component: 'input' },
|
|
96
|
+
{ model: model.$.password, component: 'input' },
|
|
97
|
+
{ model: model.$.confirmPassword, component: 'input' },
|
|
93
98
|
],
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// is
|
|
100
|
-
const passwordsMatch = (
|
|
101
|
-
|
|
102
|
-
? { code: 'mismatch'
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// 5. Validation schema — a separate contract from `@reformer/core/validation`.
|
|
102
|
+
// Field rules go through `validate(sig, [rules])`; cross-field rules through
|
|
103
|
+
// `cross(sig, snapshot => issue | null)` taken from the schema argument — the snapshot is the
|
|
104
|
+
// model value, its type is inferred. Rule factories come from `@reformer/core/validators`.
|
|
105
|
+
const passwordsMatch = (form: RegistrationForm): ValidationIssue | null =>
|
|
106
|
+
form.confirmPassword && form.password && form.confirmPassword !== form.password
|
|
107
|
+
? { code: 'confirmPassword.mismatch' }
|
|
103
108
|
: null;
|
|
104
109
|
|
|
105
|
-
const
|
|
106
|
-
validate(model.$.username, [required(), minLength(2)]);
|
|
110
|
+
const validation = defineValidation<RegistrationForm>(({ model, cross }) => {
|
|
111
|
+
validate(model.$.username, [required({ code: 'username.required' }), minLength(2)]);
|
|
107
112
|
validate(model.$.email, [required(), email()]);
|
|
108
113
|
validate(model.$.password, [required(), minLength(8)]);
|
|
109
114
|
validate(model.$.confirmPassword, [required()]);
|
|
110
115
|
cross(model.$.confirmPassword, passwordsMatch);
|
|
111
116
|
});
|
|
112
117
|
|
|
113
|
-
//
|
|
114
|
-
// Validation runs on demand, so the stale cross-field
|
|
115
|
-
// is cleared the moment `password` changes
|
|
118
|
+
// 6. Behavior — declarative reactive logic, run by `createForm({ behavior })`.
|
|
119
|
+
// Validation runs on demand, so the stale cross-field issue on `confirmPassword`
|
|
120
|
+
// is cleared the moment `password` changes.
|
|
116
121
|
const behavior = defineFormBehavior<RegistrationForm>(({ model, form }) => {
|
|
117
122
|
onChange(model.$.password, () => {
|
|
118
|
-
form.confirmPassword.
|
|
123
|
+
form.confirmPassword.clearIssues();
|
|
119
124
|
});
|
|
120
125
|
});
|
|
121
126
|
|
|
122
|
-
//
|
|
127
|
+
// 7. One assembly: model, form, behavior, validation and the form level
|
|
128
|
+
const createRegistration = () =>
|
|
129
|
+
createForm<RegistrationForm>({
|
|
130
|
+
initial: { username: '', email: '', password: '', confirmPassword: '' },
|
|
131
|
+
schema,
|
|
132
|
+
behavior,
|
|
133
|
+
validation,
|
|
134
|
+
});
|
|
135
|
+
|
|
123
136
|
function RegistrationFormExample() {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
137
|
+
// useFormBundle calls the factory exactly once and keeps the bundle stable between renders
|
|
138
|
+
const bundle = useFormBundle(createRegistration);
|
|
139
|
+
const { form } = bundle;
|
|
140
|
+
|
|
141
|
+
const handleSubmit = async (event: React.FormEvent) => {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
// One run of all the rules (sync + async). Issues land on the form nodes and, on a refusal,
|
|
144
|
+
// the whole form is marked touched; the handler is called only when the status is 'valid'.
|
|
145
|
+
const outcome = await bundle.submission.submit((values) => console.log('Form data:', values));
|
|
146
|
+
if (outcome.status === 'error') console.warn('Could not check the form, try again');
|
|
135
147
|
};
|
|
136
148
|
|
|
137
149
|
return (
|
|
@@ -146,6 +158,16 @@ function RegistrationFormExample() {
|
|
|
146
158
|
}
|
|
147
159
|
```
|
|
148
160
|
|
|
161
|
+
Submission is a separate object of the bundle: `bundle.submission.submit(handler)` returns a
|
|
162
|
+
`SubmitResult`: `submitted` (with the handler's result), `invalid` / `error` / `cancelled` (with
|
|
163
|
+
the validation result; the handler was not called) or `failed` (the handler threw). The handler
|
|
164
|
+
receives the whole model, disabled fields included; `bundle.submission.submitting` is `true` while
|
|
165
|
+
it runs. Multi-step forms declare their steps in the same call —
|
|
166
|
+
`createForm({ flow: { steps: [{ id, rules }] } })` — and navigate with `bundle.flow.next()`.
|
|
167
|
+
Outside React, the owner of the bundle releases it with `bundle.dispose()` when the form is no
|
|
168
|
+
longer needed: the behavior, the node subscriptions and the live validation stop, the model keeps
|
|
169
|
+
its values.
|
|
170
|
+
|
|
149
171
|
## License
|
|
150
172
|
|
|
151
173
|
MIT
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { effect as s } from "@preact/signals-core";
|
|
2
|
+
import { h as f } from "./safe-effect-BazLOv1g.js";
|
|
3
|
+
function m(t, r, l, e) {
|
|
4
|
+
return s(() => {
|
|
5
|
+
const n = t.map((c) => c.value);
|
|
6
|
+
if (e?.when && !e.when(...n)) return;
|
|
7
|
+
const u = l(...n);
|
|
8
|
+
r.peek() !== u && (r.value = u);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function h(t, r, l) {
|
|
12
|
+
return s(() => {
|
|
13
|
+
const e = t.value;
|
|
14
|
+
if (l?.when && !l.when()) return;
|
|
15
|
+
const n = l?.transform ? l.transform(e) : e;
|
|
16
|
+
r.peek() !== n && (r.value = n);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function w(t, r, l) {
|
|
20
|
+
let e = !0;
|
|
21
|
+
return s(() => {
|
|
22
|
+
const n = t.value;
|
|
23
|
+
e && (e = !1, !l?.immediate) || r(n);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function k(t, r) {
|
|
27
|
+
let l = !1;
|
|
28
|
+
const e = f(), n = s(() => {
|
|
29
|
+
t.value, !l && e.schedule(() => {
|
|
30
|
+
const u = r(t.peek());
|
|
31
|
+
if (t.peek() !== u) {
|
|
32
|
+
l = !0;
|
|
33
|
+
try {
|
|
34
|
+
t.value = u;
|
|
35
|
+
} finally {
|
|
36
|
+
l = !1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
return () => {
|
|
42
|
+
n(), e.cancelAll();
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function y(t, r, ...[l]) {
|
|
46
|
+
const e = l?.resetValue ?? null, n = f(), u = s(() => {
|
|
47
|
+
r(), n.schedule(() => {
|
|
48
|
+
r() && t.peek() !== e && (t.value = e);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
return () => {
|
|
52
|
+
u(), n.cancelAll();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function A(t, r, l) {
|
|
56
|
+
const e = l?.transform;
|
|
57
|
+
let n = !1, u = !0;
|
|
58
|
+
const c = f(), o = s(() => {
|
|
59
|
+
t.value, !n && (c.cancelAll(), c.schedule(() => {
|
|
60
|
+
n = !0;
|
|
61
|
+
try {
|
|
62
|
+
const a = t.peek(), i = e ? e(a) : a;
|
|
63
|
+
r.peek() !== i && (r.value = i);
|
|
64
|
+
} finally {
|
|
65
|
+
n = !1;
|
|
66
|
+
}
|
|
67
|
+
}));
|
|
68
|
+
}), v = s(() => {
|
|
69
|
+
if (r.value, u) {
|
|
70
|
+
u = !1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
n || (c.cancelAll(), c.schedule(() => {
|
|
74
|
+
n = !0;
|
|
75
|
+
try {
|
|
76
|
+
const a = r.peek(), i = t.peek();
|
|
77
|
+
if ((e ? e(i) : i) === a) return;
|
|
78
|
+
i !== a && (t.value = a);
|
|
79
|
+
} finally {
|
|
80
|
+
n = !1;
|
|
81
|
+
}
|
|
82
|
+
}));
|
|
83
|
+
});
|
|
84
|
+
return () => {
|
|
85
|
+
o(), v(), c.cancelAll();
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function x(t, r) {
|
|
89
|
+
let l = !0;
|
|
90
|
+
const e = f(), n = s(() => {
|
|
91
|
+
if (t.forEach((u) => u.value), l) {
|
|
92
|
+
l = !1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
e.schedule(() => r());
|
|
96
|
+
});
|
|
97
|
+
return () => {
|
|
98
|
+
n(), e.cancelAll();
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
h as a,
|
|
103
|
+
x as b,
|
|
104
|
+
m as c,
|
|
105
|
+
y as r,
|
|
106
|
+
A as s,
|
|
107
|
+
k as t,
|
|
108
|
+
w
|
|
109
|
+
};
|
package/dist/behaviors.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './form/behaviors'
|
|
1
|
+
export * from './form/behaviors/index'
|
|
2
2
|
export {}
|