@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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { M as f, j as c, k as r, l } from "./safe-effect-BazLOv1g.js";
|
|
2
|
+
function n(o, e, a) {
|
|
3
|
+
if (o.kind === "leaf") {
|
|
4
|
+
e(o.signal);
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (o.kind === "group") {
|
|
8
|
+
for (const t of o.children.values()) n(t, e, a);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
a && e(l(o));
|
|
12
|
+
for (const t of o.items.value) n(t, e, a);
|
|
13
|
+
}
|
|
14
|
+
function u(o, e) {
|
|
15
|
+
const a = r.get(o);
|
|
16
|
+
a && n(a, e, !1);
|
|
17
|
+
}
|
|
18
|
+
function i(o, e) {
|
|
19
|
+
const a = r.get(o);
|
|
20
|
+
a && n(a, e, !0);
|
|
21
|
+
}
|
|
22
|
+
function g(o) {
|
|
23
|
+
const e = new f(o, "");
|
|
24
|
+
return c(e);
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
i as a,
|
|
28
|
+
g as c,
|
|
29
|
+
u as e
|
|
30
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const a = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
2
|
+
function g(t) {
|
|
3
|
+
if (t == null || t === "")
|
|
4
|
+
return null;
|
|
5
|
+
if (t instanceof Date)
|
|
6
|
+
return isNaN(t.getTime()) ? null : t;
|
|
7
|
+
if (typeof t == "string") {
|
|
8
|
+
const e = a.exec(t);
|
|
9
|
+
return e ? l(Number(e[1]), Number(e[2]) - 1, Number(e[3])) : null;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function l(t, e, r) {
|
|
14
|
+
const n = /* @__PURE__ */ new Date(0);
|
|
15
|
+
return n.setFullYear(t, e, r), n.setHours(0, 0, 0, 0), n.getFullYear() === t && n.getMonth() === e && n.getDate() === r ? n : null;
|
|
16
|
+
}
|
|
17
|
+
function i() {
|
|
18
|
+
const t = /* @__PURE__ */ new Date();
|
|
19
|
+
return t.setHours(0, 0, 0, 0), t;
|
|
20
|
+
}
|
|
21
|
+
function u(t) {
|
|
22
|
+
const e = new Date(t);
|
|
23
|
+
return e.setHours(0, 0, 0, 0), e;
|
|
24
|
+
}
|
|
25
|
+
const c = (t) => t % 4 === 0 && t % 100 !== 0 || t % 400 === 0;
|
|
26
|
+
function f(t) {
|
|
27
|
+
return u(t) > i();
|
|
28
|
+
}
|
|
29
|
+
function D(t) {
|
|
30
|
+
const e = i(), r = u(t);
|
|
31
|
+
let n = r.getDate();
|
|
32
|
+
r.getMonth() === 1 && n === 29 && !c(e.getFullYear()) && (n = 28);
|
|
33
|
+
let o = e.getFullYear() - r.getFullYear();
|
|
34
|
+
const s = e.getMonth() - r.getMonth();
|
|
35
|
+
return (s < 0 || s === 0 && e.getDate() < n) && (o -= 1), o;
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
D as c,
|
|
39
|
+
i as g,
|
|
40
|
+
f as i,
|
|
41
|
+
u as n,
|
|
42
|
+
g as p
|
|
43
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const e = {
|
|
2
|
+
"format.fileSize.b": "{value} B",
|
|
3
|
+
"format.fileSize.kb": "{value} KB",
|
|
4
|
+
"format.fileSize.mb": "{value} MB",
|
|
5
|
+
"format.fileSize.gb": "{value} GB",
|
|
6
|
+
"format.fileSize.tb": "{value} TB",
|
|
7
|
+
"validation.required": "This field is required",
|
|
8
|
+
"validation.pattern": "Invalid format",
|
|
9
|
+
"validation.email": "Enter a valid email address",
|
|
10
|
+
"validation.url": "Enter a valid URL",
|
|
11
|
+
"validation.url_protocol": "The URL must use one of these protocols: {allowedProtocols}",
|
|
12
|
+
"validation.phone": "Enter a valid phone number",
|
|
13
|
+
"validation.min": "Must be at least {min}",
|
|
14
|
+
"validation.max": "Must be at most {max}",
|
|
15
|
+
"validation.minLength": "Enter at least {minLength, plural, one{# character} other{# characters}}",
|
|
16
|
+
"validation.maxLength": "Enter no more than {maxLength, plural, one{# character} other{# characters}}",
|
|
17
|
+
"validation.minItems": "At least {minLength, plural, one{# item} other{# items}}",
|
|
18
|
+
"validation.maxItems": "No more than {maxLength, plural, one{# item} other{# items}}",
|
|
19
|
+
"validation.isNumber": "Enter a number",
|
|
20
|
+
"validation.integer": "Enter a whole number",
|
|
21
|
+
"validation.multipleOf": "Must be a multiple of {multipleOf}",
|
|
22
|
+
"validation.nonNegative": "Must not be negative",
|
|
23
|
+
"validation.nonZero": "Must not be zero",
|
|
24
|
+
"validation.date_invalid": "Enter a valid date",
|
|
25
|
+
"validation.date_min": "The date must not be earlier than {minDate}",
|
|
26
|
+
"validation.date_max": "The date must not be later than {maxDate}",
|
|
27
|
+
"validation.date_past": "The date must not be in the past",
|
|
28
|
+
"validation.date_future": "The date must not be in the future",
|
|
29
|
+
"validation.date_min_age": "Age must be at least {minAge}",
|
|
30
|
+
"validation.date_max_age": "Age must be at most {maxAge}",
|
|
31
|
+
"validation.fileType": "File {fileName} has a type that is not allowed. Allowed: {accept}",
|
|
32
|
+
"validation.maxFileSize": "File {fileName} is larger than {maxFileSize}",
|
|
33
|
+
"validation.minFileSize": "File {fileName} is smaller than {minFileSize}",
|
|
34
|
+
"validation.maxFiles": "No more than {maxFiles, plural, one{# file} other{# files}}",
|
|
35
|
+
"validation.minFiles": "At least {minFiles, plural, one{# file} other{# files}}",
|
|
36
|
+
"validation.maxTotalFileSize": "The total size of the files exceeds {maxTotalFileSize}",
|
|
37
|
+
"validation.fileExists": "File {fileName} has already been added",
|
|
38
|
+
"validation.uploadAborted": "Upload cancelled",
|
|
39
|
+
"validation.uploadFailed": "Failed to upload {fileName}",
|
|
40
|
+
"validation.ruleFailed": "Could not check this field. Try again"
|
|
41
|
+
};
|
|
42
|
+
export {
|
|
43
|
+
e as m
|
|
44
|
+
};
|
|
@@ -1,71 +1,29 @@
|
|
|
1
|
-
import { ReadonlySignal
|
|
2
|
-
import {
|
|
3
|
-
import { FormNode } from './nodes
|
|
4
|
-
/**
|
|
5
|
-
* Функция получения дочерних узлов
|
|
6
|
-
* Возвращает массив FormNode для агрегации состояния
|
|
7
|
-
*/
|
|
8
|
-
type GetChildrenFn<T> = () => FormNode<T>[];
|
|
9
|
-
/**
|
|
10
|
-
* Результат создания агрегированных сигналов
|
|
11
|
-
*/
|
|
1
|
+
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { ValidationIssue } from './types/contracts';
|
|
3
|
+
import { FormNode } from './types/nodes';
|
|
12
4
|
export interface AggregateSignals {
|
|
13
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Контейнер включён, блокирующих своих замечаний нет, никто не ждёт проверки, каждый включённый
|
|
7
|
+
* ребёнок валиден.
|
|
8
|
+
*/
|
|
14
9
|
valid: ReadonlySignal<boolean>;
|
|
15
|
-
/**
|
|
10
|
+
/** Никто не ждёт проверки, и есть блокирующее своё замечание или включённый невалидный ребёнок. */
|
|
16
11
|
invalid: ReadonlySignal<boolean>;
|
|
17
|
-
/**
|
|
12
|
+
/** Контейнер или хотя бы один ребёнок ждёт проверки. */
|
|
18
13
|
pending: ReadonlySignal<boolean>;
|
|
19
|
-
/** Хотя бы один дочерний узел touched */
|
|
20
14
|
touched: ReadonlySignal<boolean>;
|
|
21
|
-
/** Хотя бы один дочерний узел dirty */
|
|
22
15
|
dirty: ReadonlySignal<boolean>;
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
/** Статус: disabled > pending > invalid > valid */
|
|
26
|
-
status: ReadonlySignal<FieldStatus>;
|
|
16
|
+
/** Свои замечания и замечания всех детей. */
|
|
17
|
+
issues: ReadonlySignal<readonly ValidationIssue[]>;
|
|
27
18
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
disabled?: Signal<boolean>;
|
|
19
|
+
export interface AggregateSignalsOptions {
|
|
20
|
+
/** Дети контейнера; чтение внутри `computed` подписывает на их состав. */
|
|
21
|
+
getChildren: () => readonly FormNode<unknown>[];
|
|
22
|
+
/** Замечания самого контейнера. */
|
|
23
|
+
ownIssues: ReadonlySignal<readonly ValidationIssue[]>;
|
|
24
|
+
/** Ожидание async-правила на ручке самого контейнера (`validateAsync(model.$.items, …)`). */
|
|
25
|
+
ownPending?: ReadonlySignal<boolean>;
|
|
26
|
+
/** Выключен ли сам контейнер: выключенный — ни `valid`, ни `invalid`, ни `pending`, как поле. */
|
|
27
|
+
disabled?: ReadonlySignal<boolean>;
|
|
38
28
|
}
|
|
39
|
-
|
|
40
|
-
* Создать агрегированные computed signals для контейнерного узла
|
|
41
|
-
*
|
|
42
|
-
* Используется в GroupNode и ArrayNode для унификации логики
|
|
43
|
-
* вычисления состояния на основе дочерних узлов.
|
|
44
|
-
*
|
|
45
|
-
* @param options - Опции конфигурации
|
|
46
|
-
* @returns Объект с computed signals
|
|
47
|
-
*
|
|
48
|
-
* @example GroupNode
|
|
49
|
-
* ```typescript
|
|
50
|
-
* const signals = createAggregateSignals({
|
|
51
|
-
* getChildren: () => Array.from(this._fields.values()),
|
|
52
|
-
* ownErrors: this._formErrors,
|
|
53
|
-
* disabled: this._disabled,
|
|
54
|
-
* });
|
|
55
|
-
* this.valid = signals.valid;
|
|
56
|
-
* this.invalid = signals.invalid;
|
|
57
|
-
* // ...
|
|
58
|
-
* ```
|
|
59
|
-
*
|
|
60
|
-
* @example ArrayNode
|
|
61
|
-
* ```typescript
|
|
62
|
-
* const signals = createAggregateSignals({
|
|
63
|
-
* getChildren: () => this.items.value,
|
|
64
|
-
* ownErrors: this._arrayErrors,
|
|
65
|
-
* });
|
|
66
|
-
* this.valid = signals.valid;
|
|
67
|
-
* // ...
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
export declare function createAggregateSignals<T>(options: AggregateSignalsOptions<T>): AggregateSignals;
|
|
71
|
-
export {};
|
|
29
|
+
export declare function createAggregateSignals(options: AggregateSignalsOptions): AggregateSignals;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Signal } from '@preact/signals-core';
|
|
2
|
+
import { FormModel, ModelArray } from '../../model/types';
|
|
3
|
+
import { FormBehavior } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Ручка массива под-форм `model.$.<массив>`. Тип строки выводится из её `peek()` — так же, как у
|
|
6
|
+
* `applyEach` валидации. Раньше цель была `object`: тип строки не выводился (`unknown`), а чужая
|
|
7
|
+
* схема строки и ручка не того вида проходили компиляцию.
|
|
8
|
+
*/
|
|
9
|
+
interface ArrayHandle<TItem> {
|
|
10
|
+
peek(): readonly TItem[] | null | undefined;
|
|
11
|
+
}
|
|
12
|
+
/** Цель операторов массива: ручка `model.$.<массив>` либо его value-фасад `model.<массив>`. */
|
|
13
|
+
type ArrayTarget<TItem> = ArrayHandle<TItem> | ModelArray<TItem>;
|
|
14
|
+
/** Ручка группы `model.$.<группа>`: тип значения — из её `peek()`. */
|
|
15
|
+
interface GroupHandle<V> {
|
|
16
|
+
peek(): V;
|
|
17
|
+
}
|
|
18
|
+
/** Цель `apply`: ручка `model.$.<группа>` либо её value-фасад `model.<группа>`. */
|
|
19
|
+
type GroupTarget<V> = GroupHandle<V> | FormModel<NonNullable<V>>;
|
|
20
|
+
/**
|
|
21
|
+
* Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).
|
|
22
|
+
* Реагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.
|
|
23
|
+
*
|
|
24
|
+
* Под-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).
|
|
25
|
+
* - Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.
|
|
26
|
+
* - Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был
|
|
27
|
+
* МАТЕРИАЛИЗОВАН в форме (узел `{ model: model.$.<массив>, item }` в схеме) — тогда `form` строки = та же нода, что
|
|
28
|
+
* рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
|
|
29
|
+
* к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
|
|
30
|
+
*
|
|
31
|
+
* Работает на любой глубине: массив в корне, в группе и в строке другого массива — в схеме строки
|
|
32
|
+
* можно снова вызвать `applyEach`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
|
|
36
|
+
* compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
|
|
37
|
+
* enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
|
|
38
|
+
* applyEach(row.$.phones, phoneBehavior); // массив внутри строки
|
|
39
|
+
* }));
|
|
40
|
+
*/
|
|
41
|
+
export declare function applyEach<TItem extends object>(array: ArrayTarget<TItem>, itemSchema: FormBehavior<NoInfer<TItem>>): void;
|
|
42
|
+
/**
|
|
43
|
+
* Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).
|
|
44
|
+
* Когда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:
|
|
45
|
+
* новые строки с флагом false исключения не запускают.
|
|
46
|
+
*
|
|
47
|
+
* Начальные данные и добавленные строки флаг не МЕНЯЮТ — он приходит сразу `true`. Такая строка
|
|
48
|
+
* прежний выбор не снимает: флаг сбрасывается у неё. Из нескольких пришедших с `true` сразу
|
|
49
|
+
* (начальные данные) остаётся первая по порядку строк.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* exclusiveFlag(model.$.contacts, (row) => row.$.primary);
|
|
53
|
+
*/
|
|
54
|
+
export declare function exclusiveFlag<TItem extends object>(array: ArrayTarget<TItem>, getFlag: (row: FormModel<NoInfer<TItem>>) => Signal<boolean>): void;
|
|
55
|
+
/**
|
|
56
|
+
* Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список
|
|
57
|
+
* `{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на
|
|
58
|
+
* финальном состоянии — поэтому массовые синхронные мутации (push в цикле) не вызывают каскад на
|
|
59
|
+
* промежуточных состояниях. `derive` должна сходиться (на фикспоинте возвращать те же значения).
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // последняя строка = 100 − Σ(остальные)
|
|
63
|
+
* aggregateInto(model.$.rows, (rows) => {
|
|
64
|
+
* const n = rows.length; if (n === 0) return [];
|
|
65
|
+
* const others = rows.slice(0, n - 1).reduce((s, r) => s + r.percent, 0);
|
|
66
|
+
* return [{ index: n - 1, patch: { percent: 100 - others } }];
|
|
67
|
+
* });
|
|
68
|
+
*/
|
|
69
|
+
export declare function aggregateInto<TItem extends object>(array: ArrayTarget<TItem>, derive: (rows: NoInfer<TItem>[]) => Array<{
|
|
70
|
+
index: number;
|
|
71
|
+
patch: Partial<NoInfer<TItem>>;
|
|
72
|
+
}>): void;
|
|
73
|
+
/**
|
|
74
|
+
* Применить под-схему к одному или нескольким полям-группам (переиспользование).
|
|
75
|
+
*
|
|
76
|
+
* Под-схема получает scope группы: `model` — настоящая под-модель (`model.$.field`, чтение и
|
|
77
|
+
* запись значений, `get`/`set`/`patch`), `form` — нода группы. Цель — ручка группы
|
|
78
|
+
* (`model.$.address`); работает и в корневой схеме, и в схеме строки массива.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* apply([model.$.registrationAddress, model.$.residenceAddress], addressBehavior);
|
|
82
|
+
*/
|
|
83
|
+
export declare function apply<V extends object | null | undefined>(targets: GroupTarget<V> | GroupTarget<V>[], subSchema: FormBehavior<NoInfer<NonNullable<V>>>): void;
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BehaviorCleanup } from '../../model/behaviors-value';
|
|
2
|
+
import { SchemaController } from '../schema-controller';
|
|
3
|
+
import { BehaviorScope, FormBehavior } from './types';
|
|
4
|
+
import { FormFlow } from '../flow/flow';
|
|
5
|
+
/** Зарегистрировать произвольную отписку в активной схеме. */
|
|
6
|
+
export declare function onDispose(cleanup: BehaviorCleanup): void;
|
|
7
|
+
/**
|
|
8
|
+
* Оператор вызван внутри схемы поведения? Иначе — понятная ошибка с именем оператора ДО любых
|
|
9
|
+
* побочных эффектов: effect, созданный до проверки, остался бы жить, а признак derived — висеть.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertInsideSchema(op: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Зарегистрировать отписку, если оператор вызван внутри схемы поведения; вне схемы — ничего.
|
|
16
|
+
* Для операторов, которые работают и сами по себе (правила узлов схемы).
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function onDisposeIfActive(cleanup: BehaviorCleanup): void;
|
|
21
|
+
/** Текущая область ({ model, form, schema }) активной схемы (escape hatch для кросс-операторов). */
|
|
22
|
+
export declare function getScope<T>(): BehaviorScope<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Схема-контроллер сборки, в которой идёт активная схема: под-схемы (`apply`, `applyEach`)
|
|
25
|
+
* получают через него область своей под-модели.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export declare function getController(op?: string): SchemaController;
|
|
30
|
+
/**
|
|
31
|
+
* Поток шагов текущего прогона схемы — его получают поведения подформ и строк массива.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function getFlow(op?: string): FormFlow | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* export const myBehavior = defineFormBehavior<MyForm>(({ model, form, schema }) => {
|
|
42
|
+
* compute(model.$.total, () => model.price * model.qty);
|
|
43
|
+
* enableWhen([model.$.city], () => Boolean(model.country));
|
|
44
|
+
* onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
|
|
45
|
+
* hideWhen(schema.node('delivery'), () => model.country === '');
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>;
|
|
50
|
+
/** Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup. */
|
|
51
|
+
export declare function effect(fn: () => void | (() => void)): void;
|
|
52
|
+
/**
|
|
53
|
+
* Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected». Внутри схемы
|
|
54
|
+
* поведения (в `setup`, в её effect'ах и отложенных заданиях) запись отменяется снятием схемы.
|
|
55
|
+
*/
|
|
56
|
+
export declare function defer(fn: () => void): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Декларативный контракт схемы поведения формы — `@reformer/core/behaviors`.
|
|
3
|
+
*
|
|
4
|
+
* Свободные операторы (`compute`/`copyFrom`/`enableWhen`/`onChange`/…) вызываются внутри
|
|
5
|
+
* `defineFormBehavior(({ model, form, schema }) => { … })` и САМИ регистрируют свои отписки в ambient-стоке —
|
|
6
|
+
* автор схемы не видит ни массива `cleanups`, ни `.push`, ни вызовов через точку. Жизненным циклом
|
|
7
|
+
* владеет форма (`createForm({ behavior })`). Операторы — тонкие обёртки над примитивами слоя данных
|
|
8
|
+
* ({@link module:model/behaviors-value}); пользовательские операторы пишутся так же и неотличимы от встроенных.
|
|
9
|
+
*
|
|
10
|
+
* Граница ответственности: контракт НЕ управляет валидацией — это отдельный слой
|
|
11
|
+
* (`defineValidation` + раннер `validateModel` из `@reformer/core/validation`), он остаётся
|
|
12
|
+
* владельцем `issues`. Это сделано намеренно. Для редких крайних случаев
|
|
13
|
+
* behavior-driven валидации (cross-field, async-uniqueness) можно написать собственный оператор поверх
|
|
14
|
+
* `effect`/`onChange` + `node.setIssues(...)` — он будет неотличим от встроенного; примеры см. в
|
|
15
|
+
* tests/behaviors/web-scenarios (W1/W3). В общем случае правила валидности держите в слое валидации.
|
|
16
|
+
*
|
|
17
|
+
* Модуль разложен по зонам ответственности: `./types` — контракт схемы, `./context` — ambient-сток
|
|
18
|
+
* и низкоуровневый набор авторинга, `./internals` — чистые утилиты, `./operators` — операторы над
|
|
19
|
+
* полем и группой, `./collections` — операторы над массивами и под-моделями.
|
|
20
|
+
*
|
|
21
|
+
* @group Behaviors
|
|
22
|
+
* @module form/behaviors
|
|
23
|
+
*/
|
|
24
|
+
export type { BehaviorScope, FormBehavior, ChangeContext } from './types';
|
|
25
|
+
export type { Signal, ReadonlySignal, BehaviorCleanup } from './types';
|
|
26
|
+
export { onDispose, getScope, defineFormBehavior, effect, defer } from './context';
|
|
27
|
+
export { compute, computeFrom, copyFrom, onChange, enableWhen, disableWhen, transformValue, resetWhen, syncFields, revalidateWhen, } from './operators';
|
|
28
|
+
export { applyEach, exclusiveFlag, aggregateInto, apply } from './collections';
|
|
29
|
+
export { hideWhen, onComponentEvent, renderEffect, onInit, onMount, onUnmount, } from './schema-operators';
|
|
30
|
+
export type { SchemaScope, SchemaNodeControl } from '../schema-controller';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Signal } from '@preact/signals-core';
|
|
2
|
+
/** Групповой узел дерева `$`: набор дочерних сигналов плюс служебный путь. */
|
|
3
|
+
export type GroupSignals = Record<string, unknown> & {
|
|
4
|
+
__path?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Лист модели, а не контейнерный узел дерева `$`.
|
|
8
|
+
*
|
|
9
|
+
* Узлы-группы/массивы тоже структурно совместимы с `ReadonlySignal` (у них есть `peek`/`value`/
|
|
10
|
+
* `subscribe` над агрегатом поддерева), поэтому одного duck-typing по `peek` мало: без отсечки по
|
|
11
|
+
* `isModelContainerSignal` группа уходила бы в скалярные ветки операторов — `enableWhen` молча
|
|
12
|
+
* терял бы `enableGroup`, `apply` получал бы значение вместо под-модели, а `copyFrom` — запись в
|
|
13
|
+
* read-only агрегат вместо рекурсивного `writeGroup`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const isLeafSignal: (v: unknown) => v is Signal<unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Ручка значения целиком: лист либо узел-массив дерева `$`.
|
|
18
|
+
*
|
|
19
|
+
* Массив копируется и читается ЗНАЧЕНИЕМ — заменой целиком, а не обходом по ключам: у его узла
|
|
20
|
+
* собственный ключ один (`length`), и обход, годный для группы, отдавал бы вместо массива мусор.
|
|
21
|
+
* Для `enableWhen` массив при этом остаётся НЕ листом ({@link isLeafSignal}): его нода ищется по
|
|
22
|
+
* пути, а она бывает и полем, и массивом под-форм.
|
|
23
|
+
*/
|
|
24
|
+
export declare const isValueHandle: (v: unknown) => v is Signal<unknown>;
|
|
25
|
+
export declare const asArray: <X>(v: X | X[]) => X[];
|
|
26
|
+
export declare function readGroup(g: GroupSignals): Record<string, unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* Запись значения в группу — как полная установка `model.<группа>.set(...)`: поля цели, которых в
|
|
29
|
+
* значении нет, остаются прежними, а не стираются. Их пути (относительно группы) складываются в
|
|
30
|
+
* `missing` — по ним вызывающий предупреждает в dev. Значение не объект — группа не тронута.
|
|
31
|
+
*
|
|
32
|
+
* @param at - Путь текущей группы относительно корня записи (для `missing`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function writeGroup(g: GroupSignals, val: Record<string, unknown>, missing?: string[], at?: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Guard от расходящихся циклов пересчёта. Расходящийся взаимный compute/computeFrom (без
|
|
37
|
+
* стабилизации) preact обрывает невнятным «Cycle detected» — перехватываем и заменяем понятной
|
|
38
|
+
* ошибкой с именем поля и подсказкой. Сходящиеся compute (упираются в peek-guard) и массовые
|
|
39
|
+
* синхронные мутации цикла не порождают → не затрагиваются.
|
|
40
|
+
*/
|
|
41
|
+
export declare function makeCycleGuard(target: Signal<unknown>): (write: () => void) => void;
|
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
-
import { BehaviorCleanup } from '
|
|
2
|
+
import { BehaviorCleanup } from '../../model/behaviors-value';
|
|
3
|
+
/** Нода формы, чьей доступностью управляет оператор: поле, группа или массив под-форм. */
|
|
4
|
+
export interface AvailabilityTarget {
|
|
5
|
+
enable(): void;
|
|
6
|
+
disable(): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Отложенное применение доступности ноды по условию.
|
|
10
|
+
*
|
|
11
|
+
* Решение принимается в момент записи, а новое задание отменяет ожидающее (побеждает последнее):
|
|
12
|
+
* иначе задание «выключить и стереть» первого прогона выполнилось бы после загрузки данных в том же
|
|
13
|
+
* тике и стёрло их, хотя поле к этому моменту уже снова доступно. Снятие отменяет ожидающее
|
|
14
|
+
* задание — запись после dispose не случится.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare function availabilityWrites(condition: () => boolean, options?: {
|
|
19
|
+
resetOnDisable?: boolean;
|
|
20
|
+
}): {
|
|
21
|
+
schedule(node: AvailabilityTarget): void;
|
|
22
|
+
cancel(): void;
|
|
23
|
+
};
|
|
3
24
|
/**
|
|
4
25
|
* Условное включение поля (state-операция). Резолвит ноду по сигналу-цели через реестр
|
|
5
26
|
* сигнал→нода (заполняется `createForm`) и вызывает `enable()`/`disable()` (+`reset()` при
|
|
6
27
|
* `resetOnDisable`). `condition` реактивен (читает свои сигналы модели).
|
|
7
28
|
*
|
|
8
|
-
* Запись состояния отложена
|
|
29
|
+
* Запись состояния отложена на микротаск для защиты от «Cycle detected». Условие читается в момент
|
|
30
|
+
* записи, и побеждает последнее решение: загрузка данных в том же тике, что и сборка, не стирается
|
|
31
|
+
* заданием первого прогона. Снятие отменяет ещё не выполненную запись.
|
|
9
32
|
* ⚠️ Поле должно быть материализовано в форме (`createForm`) — иначе ноды в реестре нет (например,
|
|
10
33
|
* элемент массива, который строится per-item).
|
|
11
34
|
*
|
|
@@ -14,11 +37,11 @@ import { BehaviorCleanup } from '../state/behaviors-value';
|
|
|
14
37
|
* работает со статус-машиной ноды (enable/disable/reset), а `compute` пишет `target.value` напрямую
|
|
15
38
|
* в сигнал модели и проверяет только собственный `options.when` — состояние `disabled` он НЕ смотрит.
|
|
16
39
|
* Поэтому при выключенном поле любое изменение зависимости compute молча заново заполнит его (а с
|
|
17
|
-
* `resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()
|
|
40
|
+
* `resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()` и отправка
|
|
18
41
|
* включают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,
|
|
19
42
|
* которое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.
|
|
20
43
|
*
|
|
21
|
-
* @group
|
|
44
|
+
* @group Form
|
|
22
45
|
* @example
|
|
23
46
|
* ```typescript
|
|
24
47
|
* enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
|
|
@@ -38,7 +61,7 @@ export declare function enableWhen(target: ReadonlySignal<unknown>, condition: (
|
|
|
38
61
|
* сигнал→нода и вызывает `disable()`, когда `condition` истинно (+`reset()` при `resetOnDisable`).
|
|
39
62
|
* `condition` реактивен (читает свои сигналы модели).
|
|
40
63
|
*
|
|
41
|
-
* @group
|
|
64
|
+
* @group Form
|
|
42
65
|
* @param target Сигнал-цель поля (`model.$.<path>`).
|
|
43
66
|
* @param condition Реактивное условие; при `true` поле выключается.
|
|
44
67
|
* @param options.resetOnDisable Сбросить значение при выключении (по умолчанию `false`).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { ResetWhenOptionsArg } from '../../model/behaviors-value';
|
|
3
|
+
import { ChangeContext } from './types';
|
|
4
|
+
/** Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов. */
|
|
5
|
+
export declare function compute<R>(target: Signal<R>, read: () => NoInfer<R>, options?: {
|
|
6
|
+
when?: () => boolean;
|
|
7
|
+
}): void;
|
|
8
|
+
/** Вычисляемое поле с явным списком зависимостей (escape hatch). */
|
|
9
|
+
export declare function computeFrom<R>(sources: ReadonlySignal<unknown>[], target: Signal<R>, fn: (...values: any[]) => NoInfer<R>, options?: {
|
|
10
|
+
when?: (...values: any[]) => boolean;
|
|
11
|
+
}): void;
|
|
12
|
+
/**
|
|
13
|
+
* Копирование `source → target` — скаляр или группа (объект целиком).
|
|
14
|
+
*
|
|
15
|
+
* Группы разной формы копируются как `set` группы модели: поля цели, которых нет в значении,
|
|
16
|
+
* остаются прежними; в dev об этом одно предупреждение на оператор. Значение цели целиком
|
|
17
|
+
* собирает `transform`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function copyFrom<T>(source: ReadonlySignal<T> | object, target: Signal<T> | object, options?: {
|
|
20
|
+
when?: () => boolean;
|
|
21
|
+
transform?: (value: T) => T;
|
|
22
|
+
}): void;
|
|
23
|
+
/**
|
|
24
|
+
* Реакция на изменение поля; `{ debounce, immediate }`.
|
|
25
|
+
*
|
|
26
|
+
* Колбэк выполняется ВНЕ effect-контекста (микротаск/таймер) — можно безопасно писать сигналы и ноды
|
|
27
|
+
* (`updateComponentProps`/`reset`/`clear`) без ручного `defer` и без «Cycle detected».
|
|
28
|
+
*
|
|
29
|
+
* Для async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения
|
|
30
|
+
* предыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй
|
|
31
|
+
* `signal.aborted` перед применением результата — это убирает гонки устаревших ответов.
|
|
32
|
+
*/
|
|
33
|
+
export declare function onChange<T>(source: ReadonlySignal<T>, cb: (value: T, ctx: ChangeContext) => void, options?: {
|
|
34
|
+
immediate?: boolean;
|
|
35
|
+
debounce?: number;
|
|
36
|
+
}): void;
|
|
37
|
+
type EnableTarget = ReadonlySignal<unknown> | object;
|
|
38
|
+
/** Условное включение поля(ей) — скаляр, массив целей или группа. `resetOnDisable=false` по умолчанию. */
|
|
39
|
+
export declare function enableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
|
|
40
|
+
resetOnDisable?: boolean;
|
|
41
|
+
}): void;
|
|
42
|
+
/** Условное выключение поля(ей) (инверсия {@link enableWhen}). */
|
|
43
|
+
export declare function disableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
|
|
44
|
+
resetOnDisable?: boolean;
|
|
45
|
+
}): void;
|
|
46
|
+
/** Трансформация значения поля (идемпотентная). */
|
|
47
|
+
export declare function transformValue<T>(target: Signal<T>, transformer: (value: T) => T): void;
|
|
48
|
+
/**
|
|
49
|
+
* Сброс значения по условию. Без `resetValue` поле сбрасывается в `null` — поэтому полю, в тип
|
|
50
|
+
* которого `null` не входит, `resetValue` обязателен (см. {@link ResetWhenOptionsArg}).
|
|
51
|
+
*/
|
|
52
|
+
export declare function resetWhen<T>(target: Signal<T>, condition: () => boolean, ...options: ResetWhenOptionsArg<T>): void;
|
|
53
|
+
/** Двусторонняя синхронизация двух полей. */
|
|
54
|
+
export declare function syncFields<T>(a: Signal<T>, b: Signal<NoInfer<T>>, options?: {
|
|
55
|
+
transform?: (value: T) => T;
|
|
56
|
+
}): void;
|
|
57
|
+
/** Ревалидация при изменении зависимостей. */
|
|
58
|
+
export declare function revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): void;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { SchemaNodeControl, SchemaScope } from '../schema-controller';
|
|
2
|
+
/**
|
|
3
|
+
* Скрывать узел, пока условие истинно.
|
|
4
|
+
*
|
|
5
|
+
* Условие реактивно: пересчитывается при изменении любого сигнала, прочитанного внутри него.
|
|
6
|
+
* Принудительное `schema.node(selector).setHidden(...)` главнее условия.
|
|
7
|
+
*
|
|
8
|
+
* Только видимость: поля скрытого узла не выключаются и проверяются правилами — переход шага
|
|
9
|
+
* откажет без замечаний на экране. Поля узла выключай тем же условием через `enableWhen`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const mortgage = () => model.loanType === 'mortgage';
|
|
14
|
+
* enableWhen([model.$.propertyValue, model.$.initialPayment], mortgage, { resetOnDisable: true });
|
|
15
|
+
* hideWhen(schema.node('mortgage'), () => !mortgage());
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function hideWhen(node: SchemaNodeControl, condition: () => boolean): void;
|
|
19
|
+
/**
|
|
20
|
+
* Повесить обработчик на проп-событие компонента узла (`onSubmit`, `onChange`, …).
|
|
21
|
+
*
|
|
22
|
+
* Обработчик получает те же аргументы, что и исходный проп компонента. У контейнера он перекрывает
|
|
23
|
+
* одноимённый проп из `componentProps`. У поля и массива событие уже занято привязкой (запись
|
|
24
|
+
* значения в форму, кнопки массива) — обработчик вызывается после неё, а не вместо: ввод не ломается.
|
|
25
|
+
* У подформы своего компонента нет — правило не действует, в dev рендерер предупреждает.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* onComponentEvent(schema.node('wizard'), 'onSubmit', async () => {
|
|
30
|
+
* await submitApplication(model.get());
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function onComponentEvent(node: SchemaNodeControl, event: string, handler: (...args: any[]) => any): void;
|
|
35
|
+
/**
|
|
36
|
+
* Реактивный эффект, живущий, пока схема области смонтирована рендерером.
|
|
37
|
+
*
|
|
38
|
+
* В отличие от `effect` поведения, запускается после монтирования — внутри доступны ref узлов
|
|
39
|
+
* (`schema.node(selector).getRef().current`). Может вернуть функцию очистки.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const panel = schema.node('summary').getRef<HTMLElement>();
|
|
44
|
+
* renderEffect(schema, () => {
|
|
45
|
+
* if (model.loanType === 'mortgage') panel.current?.scrollIntoView();
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function renderEffect(schema: SchemaScope, effectFn: () => void | (() => void)): void;
|
|
50
|
+
/**
|
|
51
|
+
* Синхронный хук времени сборки: `fn` вызывается сразу, до первого рендера узла. Единственный
|
|
52
|
+
* хук, способный повлиять на первый рендер, — внутри можно править пропсы через
|
|
53
|
+
* `schema.node(selector).patchProps({ … })`.
|
|
54
|
+
*
|
|
55
|
+
* К монтированию узла не привязан; аргумент `node` принимается ради симметрии с
|
|
56
|
+
* {@link onMount} / {@link onUnmount}.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* onInit(schema.node('summary'), () => {
|
|
61
|
+
* schema.node('summary').patchProps({ currency: detectCurrency() });
|
|
62
|
+
* });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function onInit(_node: SchemaNodeControl, fn: () => void): void;
|
|
66
|
+
/**
|
|
67
|
+
* Хук после первого монтирования узла. Может вернуть очистку — она выполнится при размонтировании.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* onMount(schema.node('data-boundary'), () => void loadApplication());
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function onMount(node: SchemaNodeControl, fn: () => void | (() => void)): void;
|
|
75
|
+
/**
|
|
76
|
+
* Хук перед размонтированием узла.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* onUnmount(schema.node('wizard'), () => saveDraft(model.get()));
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function onUnmount(node: SchemaNodeControl, fn: () => void): void;
|