@reformer/core 11.0.0-beta.3 → 11.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/behaviors.d.ts +1 -1
- package/dist/behaviors.js +37 -37
- package/dist/create-model-Dxh9cfLp.js +97 -0
- package/dist/form/behaviors/collections.d.ts +50 -0
- package/dist/form/behaviors/context.d.ts +23 -0
- package/dist/form/behaviors/index.d.ts +28 -0
- package/dist/form/behaviors/internals.d.ts +26 -0
- package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +3 -3
- package/dist/form/behaviors/operators.d.ts +51 -0
- package/dist/form/behaviors/types.d.ts +17 -0
- package/dist/form/create-core-form.d.ts +3 -3
- package/dist/form/create-form.d.ts +1 -1
- package/dist/form/index.d.ts +10 -16
- package/dist/form/nodes/model-array-node.d.ts +1 -1
- package/dist/form/types/contracts.d.ts +9 -6
- package/dist/form/types/validation-schema.d.ts +1 -1
- package/dist/form/{validation-config.d.ts → validation/config.d.ts} +3 -3
- package/dist/form/validation/context.d.ts +27 -0
- package/dist/form/{error-handler.d.ts → validation/error-handler.d.ts} +1 -1
- package/dist/form/validation/index.d.ts +23 -28
- package/dist/form/validation/operators.d.ts +42 -0
- package/dist/form/validation/run.d.ts +45 -0
- package/dist/form/{validation-strategy.d.ts → validation/strategy.d.ts} +2 -2
- package/dist/form/validation/types.d.ts +28 -0
- package/dist/form/{validation/validators → validators}/date-utils.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/email.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/file-type.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/file-utils.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/future-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/index.d.ts +6 -4
- package/dist/form/{validation/validators → validators}/integer.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/is-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/is-number.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-age.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-file-size.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-files.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-length.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max-total-file-size.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/max.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-age.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-file-size.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-files.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min-length.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/min.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/multiple-of.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/non-negative.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/non-zero.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/past-date.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/pattern.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/phone.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/required.d.ts +1 -1
- package/dist/form/{validation/validators → validators}/url.d.ts +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +246 -71
- package/dist/{state/form-model.d.ts → model/create-model.d.ts} +0 -14
- package/dist/{state → model}/index.d.ts +10 -11
- package/dist/model/model-nodes.d.ts +60 -0
- package/dist/model/model-signals-proxy.d.ts +32 -0
- package/dist/model/model-value-proxy.d.ts +3 -0
- package/dist/{state → model}/safe-effect.d.ts +1 -1
- package/dist/model-signals-proxy-mV3WGdlO.js +233 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +21 -0
- package/dist/{form → platforms/react}/hooks/types.d.ts +1 -1
- package/dist/{form → platforms/react}/hooks/use-form-bundle.d.ts +1 -1
- package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +3 -3
- package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +1 -1
- package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +3 -3
- package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +2 -2
- package/dist/platforms/react/index.d.ts +23 -0
- package/dist/signals.d.ts +1 -1
- package/dist/strategy-DA3JwxsR.js +170 -0
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +10 -165
- package/dist/validators/email.d.ts +1 -1
- package/dist/validators/future-date.d.ts +1 -1
- package/dist/validators/integer.d.ts +1 -1
- package/dist/validators/is-date.d.ts +1 -1
- package/dist/validators/is-number.d.ts +1 -1
- package/dist/validators/max-age.d.ts +1 -1
- package/dist/validators/max-date.d.ts +1 -1
- package/dist/validators/max-length.d.ts +1 -1
- package/dist/validators/max.d.ts +1 -1
- package/dist/validators/min-age.d.ts +1 -1
- package/dist/validators/min-date.d.ts +1 -1
- package/dist/validators/min-length.d.ts +1 -1
- package/dist/validators/min.d.ts +1 -1
- package/dist/validators/multiple-of.d.ts +1 -1
- package/dist/validators/non-negative.d.ts +1 -1
- package/dist/validators/non-zero.d.ts +1 -1
- package/dist/validators/past-date.d.ts +1 -1
- package/dist/validators/pattern.d.ts +1 -1
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators.d.ts +1 -1
- package/llms.txt +64 -43
- package/package.json +4 -4
- package/dist/form/behaviors.d.ts +0 -133
- package/dist/form/validation-schema.d.ts +0 -106
- package/dist/form/validators.d.ts +0 -11
- package/dist/form-model-DhW2HZOB.js +0 -323
- package/dist/state.d.ts +0 -2
- package/dist/state.js +0 -21
- package/dist/subscription-manager-705bls97.js +0 -171
- /package/dist/{state → form/nodes}/subscription-manager.d.ts +0 -0
- /package/dist/{state → model}/behaviors-value.d.ts +0 -0
- /package/dist/{state → model}/derived-registry.d.ts +0 -0
- /package/dist/{state → model}/types.d.ts +0 -0
- /package/dist/{behaviors-node-DGEFMTQu.js → node-DGEFMTQu.js} +0 -0
- /package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +0 -0
package/dist/behaviors.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './form/behaviors'
|
|
1
|
+
export * from './form/behaviors/index'
|
|
2
2
|
export {}
|
package/dist/behaviors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { effect as j } from "@preact/signals-core";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { e as T } from "./
|
|
2
|
+
import { d as $, a as C, w as F, t as M, r as O, s as A, b as D } from "./behaviors-value-C7dSEhCc.js";
|
|
3
|
+
import { i as G, m as k, u as x } from "./model-signals-proxy-mV3WGdlO.js";
|
|
4
|
+
import { e as T } from "./node-DGEFMTQu.js";
|
|
5
5
|
let p = null;
|
|
6
6
|
function P(e) {
|
|
7
7
|
if (!p)
|
|
@@ -37,9 +37,9 @@ function h(e) {
|
|
|
37
37
|
s(j(e));
|
|
38
38
|
}
|
|
39
39
|
function g(e) {
|
|
40
|
-
|
|
40
|
+
$(e);
|
|
41
41
|
}
|
|
42
|
-
const m = (e) => typeof e == "object" && e !== null && typeof e.peek == "function" && !
|
|
42
|
+
const m = (e) => typeof e == "object" && e !== null && typeof e.peek == "function" && !G(e), B = (e) => Array.isArray(e) ? e : [e];
|
|
43
43
|
function E(e) {
|
|
44
44
|
const t = {};
|
|
45
45
|
for (const n of Object.keys(e)) {
|
|
@@ -54,26 +54,7 @@ function W(e, t) {
|
|
|
54
54
|
m(o) ? o.value = t?.[n] : W(o, t?.[n] ?? {});
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
function R(e) {
|
|
58
|
-
if (!e) return;
|
|
59
|
-
const { form: t } = y();
|
|
60
|
-
return t.getFieldByPath(e);
|
|
61
|
-
}
|
|
62
57
|
function S(e) {
|
|
63
|
-
return new Proxy(
|
|
64
|
-
{},
|
|
65
|
-
{
|
|
66
|
-
get: (t, n) => {
|
|
67
|
-
if (n === "$") return e;
|
|
68
|
-
if (typeof n != "string") return;
|
|
69
|
-
const o = e[n];
|
|
70
|
-
if (o != null)
|
|
71
|
-
return m(o) ? o.value : S(o);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
function V(e) {
|
|
77
58
|
return (t) => {
|
|
78
59
|
try {
|
|
79
60
|
t();
|
|
@@ -88,9 +69,17 @@ function V(e) {
|
|
|
88
69
|
}
|
|
89
70
|
};
|
|
90
71
|
}
|
|
72
|
+
function _(e, t) {
|
|
73
|
+
return t.split(".").reduce((n, o) => n?.[o], e);
|
|
74
|
+
}
|
|
75
|
+
function R(e) {
|
|
76
|
+
if (!e) return;
|
|
77
|
+
const { form: t } = y();
|
|
78
|
+
return t.getFieldByPath(e);
|
|
79
|
+
}
|
|
91
80
|
function X(e, t, n) {
|
|
92
|
-
|
|
93
|
-
const o =
|
|
81
|
+
k(e), s(() => x(e));
|
|
82
|
+
const o = S(e);
|
|
94
83
|
h(() => {
|
|
95
84
|
if (n?.when && !n.when()) return;
|
|
96
85
|
const r = t();
|
|
@@ -100,8 +89,8 @@ function X(e, t, n) {
|
|
|
100
89
|
});
|
|
101
90
|
}
|
|
102
91
|
function Y(e, t, n, o) {
|
|
103
|
-
|
|
104
|
-
const r =
|
|
92
|
+
k(t), s(() => x(t));
|
|
93
|
+
const r = S(t);
|
|
105
94
|
h(() => {
|
|
106
95
|
const c = e.map((f) => f.value);
|
|
107
96
|
if (o?.when && !o.when(...c)) return;
|
|
@@ -113,7 +102,7 @@ function Y(e, t, n, o) {
|
|
|
113
102
|
}
|
|
114
103
|
function Z(e, t, n) {
|
|
115
104
|
if (m(e) && m(t)) {
|
|
116
|
-
s(
|
|
105
|
+
s(C(e, t, n));
|
|
117
106
|
return;
|
|
118
107
|
}
|
|
119
108
|
const o = e, r = t;
|
|
@@ -127,7 +116,7 @@ function q(e, t, n) {
|
|
|
127
116
|
const r = (c) => {
|
|
128
117
|
o?.abort(), o = new AbortController();
|
|
129
118
|
const { signal: i } = o;
|
|
130
|
-
|
|
119
|
+
$(() => t(c, { signal: i }));
|
|
131
120
|
};
|
|
132
121
|
if (!n?.debounce)
|
|
133
122
|
s(F(e, r, { immediate: n?.immediate }));
|
|
@@ -163,19 +152,30 @@ function L(e, t, n) {
|
|
|
163
152
|
});
|
|
164
153
|
}
|
|
165
154
|
function ne(e, t) {
|
|
166
|
-
s(
|
|
155
|
+
s(M(e, t));
|
|
167
156
|
}
|
|
168
157
|
function te(e, t, n) {
|
|
169
|
-
s(
|
|
158
|
+
s(O(e, t, n));
|
|
170
159
|
}
|
|
171
160
|
function oe(e, t, n) {
|
|
172
|
-
s(
|
|
161
|
+
s(A(e, t, n));
|
|
173
162
|
}
|
|
174
163
|
function re(e, t) {
|
|
175
|
-
s(
|
|
164
|
+
s(D(e, t));
|
|
176
165
|
}
|
|
177
|
-
function
|
|
178
|
-
return
|
|
166
|
+
function V(e) {
|
|
167
|
+
return new Proxy(
|
|
168
|
+
{},
|
|
169
|
+
{
|
|
170
|
+
get: (t, n) => {
|
|
171
|
+
if (n === "$") return e;
|
|
172
|
+
if (typeof n != "string") return;
|
|
173
|
+
const o = e[n];
|
|
174
|
+
if (o != null)
|
|
175
|
+
return m(o) ? o.value : V(o);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
179
|
}
|
|
180
180
|
function N(e) {
|
|
181
181
|
return new Proxy(
|
|
@@ -276,7 +276,7 @@ function fe(e, t) {
|
|
|
276
276
|
const r = o, c = r.__path;
|
|
277
277
|
if (!c) continue;
|
|
278
278
|
const i = n ? n.getFieldByPath(c) : void 0, f = i ? i.getProxy?.() ?? i : void 0;
|
|
279
|
-
s(t.__run(
|
|
279
|
+
s(t.__run(V(r), f));
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
export {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { s as p, r as v, b as f, G as g } from "./model-signals-proxy-mV3WGdlO.js";
|
|
2
|
+
function a(t) {
|
|
3
|
+
if (t)
|
|
4
|
+
return t.kind === "group" ? d(t) : t.kind === "array" ? w(t) : t.read();
|
|
5
|
+
}
|
|
6
|
+
function w(t) {
|
|
7
|
+
const s = {
|
|
8
|
+
get length() {
|
|
9
|
+
return t.items.value.length;
|
|
10
|
+
},
|
|
11
|
+
push: (e) => t.push(e),
|
|
12
|
+
insertAt: (e, n) => t.insertAt(e, n),
|
|
13
|
+
removeAt: (e) => t.removeAt(e),
|
|
14
|
+
move: (e, n) => t.move(e, n),
|
|
15
|
+
swap: (e, n) => t.swap(e, n),
|
|
16
|
+
clear: () => t.clear(),
|
|
17
|
+
at: (e) => a(t.items.value[e]),
|
|
18
|
+
map: (e) => t.items.value.map((n, i) => e(a(n), i)),
|
|
19
|
+
forEach: (e) => t.items.value.forEach((n, i) => e(a(n), i)),
|
|
20
|
+
toArray: () => t.peek(),
|
|
21
|
+
[Symbol.iterator]: function* () {
|
|
22
|
+
const e = t.items.value;
|
|
23
|
+
for (let n = 0; n < e.length; n++) yield a(e[n]);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
return new Proxy(s, {
|
|
27
|
+
get: (e, n, i) => n === "__path" ? t.path : typeof n == "string" && f(n) ? a(t.items.value[Number(n)]) : Reflect.get(e, n, i),
|
|
28
|
+
has: (e, n) => typeof n == "string" && f(n) ? Number(n) < t.items.value.length : Reflect.has(e, n)
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const u = /* @__PURE__ */ new Set([
|
|
32
|
+
"$",
|
|
33
|
+
"get",
|
|
34
|
+
"set",
|
|
35
|
+
"patch",
|
|
36
|
+
"isDirty",
|
|
37
|
+
"reset",
|
|
38
|
+
"captureInitial",
|
|
39
|
+
"signalAt"
|
|
40
|
+
]), h = /* @__PURE__ */ new WeakMap(), m = /* @__PURE__ */ new WeakMap();
|
|
41
|
+
function d(t) {
|
|
42
|
+
const s = h.get(t);
|
|
43
|
+
if (s) return s;
|
|
44
|
+
const e = {
|
|
45
|
+
$: p(t),
|
|
46
|
+
get: () => t.peek(),
|
|
47
|
+
set: (i) => t.set(i),
|
|
48
|
+
patch: (i) => t.set(i),
|
|
49
|
+
isDirty: () => t.dirty(),
|
|
50
|
+
reset: () => t.resetToInitial(),
|
|
51
|
+
captureInitial: () => t.captureInitial(),
|
|
52
|
+
signalAt: (i) => v(t, i)
|
|
53
|
+
}, n = new Proxy(
|
|
54
|
+
{},
|
|
55
|
+
{
|
|
56
|
+
get: (i, r) => {
|
|
57
|
+
if (r === "__path") return t.path;
|
|
58
|
+
if (typeof r != "string") return;
|
|
59
|
+
const c = t.children.get(r);
|
|
60
|
+
if (c) return a(c);
|
|
61
|
+
if (u.has(r)) return e[r];
|
|
62
|
+
},
|
|
63
|
+
set: (i, r, c) => {
|
|
64
|
+
if (typeof r != "string") return !1;
|
|
65
|
+
const o = t.children.get(r);
|
|
66
|
+
return o ? (o.set(c), !0) : !1;
|
|
67
|
+
},
|
|
68
|
+
has: (i, r) => typeof r == "string" && (t.children.has(r) || u.has(r)),
|
|
69
|
+
ownKeys: () => [...t.children.keys()],
|
|
70
|
+
getOwnPropertyDescriptor: (i, r) => typeof r == "string" && t.children.has(r) ? { enumerable: !0, configurable: !0 } : void 0
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
return h.set(t, n), m.set(n, t), n;
|
|
74
|
+
}
|
|
75
|
+
function l(t, s) {
|
|
76
|
+
if (t.kind === "leaf") {
|
|
77
|
+
s(t.signal);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (t.kind === "group") {
|
|
81
|
+
for (const e of t.children.values()) l(e, s);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (const e of t.items.value) l(e, s);
|
|
85
|
+
}
|
|
86
|
+
function A(t, s) {
|
|
87
|
+
const e = m.get(t);
|
|
88
|
+
e && l(e, s);
|
|
89
|
+
}
|
|
90
|
+
function _(t) {
|
|
91
|
+
const s = new g(t, "");
|
|
92
|
+
return d(s);
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
_ as c,
|
|
96
|
+
A as e
|
|
97
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Signal } from '@preact/signals-core';
|
|
2
|
+
import { FormModel } from '../../index';
|
|
3
|
+
import { FormBehavior } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).
|
|
6
|
+
* Реагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.
|
|
7
|
+
*
|
|
8
|
+
* Под-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).
|
|
9
|
+
* - Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.
|
|
10
|
+
* - Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был
|
|
11
|
+
* МАТЕРИАЛИЗОВАН в форме (узел `{ array, item }` в схеме) — тогда `form` строки = та же нода, что
|
|
12
|
+
* рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
|
|
13
|
+
* к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
|
|
17
|
+
* compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
|
|
18
|
+
* enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
|
|
19
|
+
* }));
|
|
20
|
+
*/
|
|
21
|
+
export declare function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void;
|
|
22
|
+
/**
|
|
23
|
+
* Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).
|
|
24
|
+
* Когда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:
|
|
25
|
+
* новые строки с флагом false исключения не запускают.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* exclusiveFlag(model.$.contacts, (row) => row.$.primary);
|
|
29
|
+
*/
|
|
30
|
+
export declare function exclusiveFlag<TItem>(array: object, getFlag: (row: FormModel<TItem>) => Signal<boolean>): void;
|
|
31
|
+
/**
|
|
32
|
+
* Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список
|
|
33
|
+
* `{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на
|
|
34
|
+
* финальном состоянии — поэтому массовые синхронные мутации (push в цикле) не вызывают каскад на
|
|
35
|
+
* промежуточных состояниях. `derive` должна сходиться (на фикспоинте возвращать те же значения).
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // последняя строка = 100 − Σ(остальные)
|
|
39
|
+
* aggregateInto(model.$.rows, (rows) => {
|
|
40
|
+
* const n = rows.length; if (n === 0) return [];
|
|
41
|
+
* const others = rows.slice(0, n - 1).reduce((s, r) => s + r.percent, 0);
|
|
42
|
+
* return [{ index: n - 1, patch: { percent: 100 - others } }];
|
|
43
|
+
* });
|
|
44
|
+
*/
|
|
45
|
+
export declare function aggregateInto<TItem>(array: object, derive: (rows: TItem[]) => Array<{
|
|
46
|
+
index: number;
|
|
47
|
+
patch: Partial<TItem>;
|
|
48
|
+
}>): void;
|
|
49
|
+
/** Применить под-схему к одному или нескольким полям-группам (переиспользование). */
|
|
50
|
+
export declare function apply<TField>(targets: object | object[], subSchema: FormBehavior<TField>): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BehaviorCleanup } from '../../index';
|
|
2
|
+
import { BehaviorScope, FormBehavior } from './types';
|
|
3
|
+
/** Зарегистрировать произвольную отписку в активной схеме. */
|
|
4
|
+
export declare function onDispose(cleanup: BehaviorCleanup): void;
|
|
5
|
+
/** Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов). */
|
|
6
|
+
export declare function getScope<T>(): BehaviorScope<T>;
|
|
7
|
+
/**
|
|
8
|
+
* Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
13
|
+
* compute(model.$.total, () => model.price * model.qty);
|
|
14
|
+
* enableWhen([model.$.city], () => Boolean(model.country));
|
|
15
|
+
* onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>;
|
|
20
|
+
/** Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup. */
|
|
21
|
+
export declare function effect(fn: () => void | (() => void)): void;
|
|
22
|
+
/** Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected». */
|
|
23
|
+
export declare function defer(fn: () => void): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Декларативный контракт схемы поведения формы — `@reformer/core/behaviors`.
|
|
3
|
+
*
|
|
4
|
+
* Свободные операторы (`compute`/`copyFrom`/`enableWhen`/`onChange`/…) вызываются внутри
|
|
5
|
+
* `defineFormBehavior(({ model, form }) => { … })` и САМИ регистрируют свои отписки в ambient-стоке —
|
|
6
|
+
* автор схемы не видит ни массива `cleanups`, ни `.push`, ни вызовов через точку. Жизненным циклом
|
|
7
|
+
* владеет форма (`createForm({ behavior })`). Операторы — тонкие обёртки над примитивами слоя данных
|
|
8
|
+
* ({@link module:model/behaviors-value}); пользовательские операторы пишутся так же и неотличимы от встроенных.
|
|
9
|
+
*
|
|
10
|
+
* Граница ответственности: контракт НЕ управляет валидацией — это отдельный слой
|
|
11
|
+
* (`defineValidationSchema` + раннер `validateModel` из `@reformer/core/validation`), он остаётся
|
|
12
|
+
* владельцем `errors`. Это сделано намеренно. Для редких крайних случаев
|
|
13
|
+
* behavior-driven валидации (cross-field, async-uniqueness) можно написать собственный оператор поверх
|
|
14
|
+
* `effect`/`onChange` + `node.setErrors(...)` — он будет неотличим от встроенного; примеры см. в
|
|
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';
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
export declare const asArray: <X>(v: X | X[]) => X[];
|
|
17
|
+
export declare function readGroup(g: GroupSignals): Record<string, unknown>;
|
|
18
|
+
export declare function writeGroup(g: GroupSignals, val: Record<string, unknown>): void;
|
|
19
|
+
/**
|
|
20
|
+
* Guard от расходящихся циклов пересчёта (F7). Расходящийся взаимный compute/computeFrom (без
|
|
21
|
+
* стабилизации) preact обрывает невнятным «Cycle detected» — перехватываем и заменяем понятной
|
|
22
|
+
* ошибкой с именем поля и подсказкой. Сходящиеся compute (упираются в peek-guard) и массовые
|
|
23
|
+
* синхронные мутации цикла не порождают → не затрагиваются.
|
|
24
|
+
*/
|
|
25
|
+
export declare function makeCycleGuard(target: Signal<unknown>): (write: () => void) => void;
|
|
26
|
+
export declare function getByPath(root: unknown, path: string): any;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
-
import { BehaviorCleanup } from '
|
|
2
|
+
import { BehaviorCleanup } from '../../model/behaviors-value';
|
|
3
3
|
/**
|
|
4
4
|
* Условное включение поля (state-операция). Резолвит ноду по сигналу-цели через реестр
|
|
5
5
|
* сигнал→нода (заполняется `createForm`) и вызывает `enable()`/`disable()` (+`reset()` при
|
|
@@ -18,7 +18,7 @@ import { BehaviorCleanup } from '../state/behaviors-value';
|
|
|
18
18
|
* включают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,
|
|
19
19
|
* которое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.
|
|
20
20
|
*
|
|
21
|
-
* @group
|
|
21
|
+
* @group Form
|
|
22
22
|
* @example
|
|
23
23
|
* ```typescript
|
|
24
24
|
* enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
|
|
@@ -38,7 +38,7 @@ export declare function enableWhen(target: ReadonlySignal<unknown>, condition: (
|
|
|
38
38
|
* сигнал→нода и вызывает `disable()`, когда `condition` истинно (+`reset()` при `resetOnDisable`).
|
|
39
39
|
* `condition` реактивен (читает свои сигналы модели).
|
|
40
40
|
*
|
|
41
|
-
* @group
|
|
41
|
+
* @group Form
|
|
42
42
|
* @param target Сигнал-цель поля (`model.$.<path>`).
|
|
43
43
|
* @param condition Реактивное условие; при `true` поле выключается.
|
|
44
44
|
* @param options.resetOnDisable Сбросить значение при выключении (по умолчанию `false`).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { ChangeContext } from './types';
|
|
3
|
+
/** Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов. */
|
|
4
|
+
export declare function compute<R>(target: Signal<R>, read: () => R, options?: {
|
|
5
|
+
when?: () => boolean;
|
|
6
|
+
}): void;
|
|
7
|
+
/** Вычисляемое поле с явным списком зависимостей (escape hatch). */
|
|
8
|
+
export declare function computeFrom<R>(sources: ReadonlySignal<unknown>[], target: Signal<R>, fn: (...values: any[]) => R, options?: {
|
|
9
|
+
when?: (...values: any[]) => boolean;
|
|
10
|
+
}): void;
|
|
11
|
+
/** Копирование `source → target` — скаляр или группа (объект целиком). */
|
|
12
|
+
export declare function copyFrom<T>(source: ReadonlySignal<T> | object, target: Signal<T> | object, options?: {
|
|
13
|
+
when?: () => boolean;
|
|
14
|
+
transform?: (value: T) => T;
|
|
15
|
+
}): void;
|
|
16
|
+
/**
|
|
17
|
+
* Реакция на изменение поля; `{ debounce, immediate }`.
|
|
18
|
+
*
|
|
19
|
+
* Колбэк выполняется ВНЕ effect-контекста (микротаск/таймер) — можно безопасно писать сигналы и ноды
|
|
20
|
+
* (`updateComponentProps`/`reset`/`clear`) без ручного `defer` и без «Cycle detected».
|
|
21
|
+
*
|
|
22
|
+
* Для async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения
|
|
23
|
+
* предыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй
|
|
24
|
+
* `signal.aborted` перед применением результата — это убирает гонки устаревших ответов (F2).
|
|
25
|
+
*/
|
|
26
|
+
export declare function onChange<T>(source: ReadonlySignal<T>, cb: (value: T, ctx: ChangeContext) => void, options?: {
|
|
27
|
+
immediate?: boolean;
|
|
28
|
+
debounce?: number;
|
|
29
|
+
}): void;
|
|
30
|
+
type EnableTarget = ReadonlySignal<unknown> | object;
|
|
31
|
+
/** Условное включение поля(ей) — скаляр, массив целей или группа. `resetOnDisable=false` по умолчанию. */
|
|
32
|
+
export declare function enableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
|
|
33
|
+
resetOnDisable?: boolean;
|
|
34
|
+
}): void;
|
|
35
|
+
/** Условное выключение поля(ей) (инверсия {@link enableWhen}). */
|
|
36
|
+
export declare function disableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
|
|
37
|
+
resetOnDisable?: boolean;
|
|
38
|
+
}): void;
|
|
39
|
+
/** Трансформация значения поля (идемпотентная). */
|
|
40
|
+
export declare function transformValue<T>(target: Signal<T>, transformer: (value: T) => T): void;
|
|
41
|
+
/** Сброс значения по условию. */
|
|
42
|
+
export declare function resetWhen<T>(target: Signal<T>, condition: () => boolean, options?: {
|
|
43
|
+
resetValue?: T;
|
|
44
|
+
}): void;
|
|
45
|
+
/** Двусторонняя синхронизация двух полей. */
|
|
46
|
+
export declare function syncFields<T>(a: Signal<T>, b: Signal<T>, options?: {
|
|
47
|
+
transform?: (value: T) => T;
|
|
48
|
+
}): void;
|
|
49
|
+
/** Ревалидация при изменении зависимостей. */
|
|
50
|
+
export declare function revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): void;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { BehaviorCleanup, FormModel, FormProxy } from '../../index';
|
|
3
|
+
/** Контекст схемы поведения: модель (значения/сигналы) + форма (ноды). */
|
|
4
|
+
export type BehaviorScope<T> = {
|
|
5
|
+
model: FormModel<T>;
|
|
6
|
+
form: FormProxy<T>;
|
|
7
|
+
};
|
|
8
|
+
/** Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`. */
|
|
9
|
+
export interface FormBehavior<T> {
|
|
10
|
+
/** @internal Запускается `createForm` после построения нод и заполнения реестра сигнал→нода. */
|
|
11
|
+
__run(model: FormModel<T>, form: FormProxy<T>): BehaviorCleanup;
|
|
12
|
+
}
|
|
13
|
+
export type { Signal, ReadonlySignal, BehaviorCleanup };
|
|
14
|
+
/** Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка. */
|
|
15
|
+
export interface ChangeContext {
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { FormModel } from '../
|
|
1
|
+
import { FormModel } from '../model/types';
|
|
2
2
|
import { FormSchemaNode } from './types/schema-node';
|
|
3
3
|
import { FormBehavior } from './behaviors';
|
|
4
4
|
import { FormProxy } from './types/index';
|
|
5
|
-
import { FormValidation, FormValidationBundle } from './validation
|
|
6
|
-
import { ValidationSchema } from './validation
|
|
5
|
+
import { FormValidation, FormValidationBundle } from './validation/config';
|
|
6
|
+
import { ValidationSchema } from './validation';
|
|
7
7
|
/**
|
|
8
8
|
* Общая часть конфига всех фабрик формы.
|
|
9
9
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FormProxy, GroupNodeConfig, FormSchema } from './types/index';
|
|
2
2
|
import { FormSchemaNode } from './types/schema-node';
|
|
3
|
-
import { FormModel } from '../
|
|
3
|
+
import { FormModel } from '../model/types';
|
|
4
4
|
import { FormBehavior } from './behaviors';
|
|
5
5
|
/**
|
|
6
6
|
* Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
|
package/dist/form/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Модуль `form` — доменный слой формы поверх
|
|
2
|
+
* Модуль `form` — доменный слой формы поверх реактивной модели (`model`).
|
|
3
3
|
*
|
|
4
4
|
* Ноды (value/touched/dirty/status/errors/componentProps), сборка формы из модели по идентичности
|
|
5
|
-
* сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), submit
|
|
6
|
-
* React
|
|
7
|
-
*
|
|
5
|
+
* сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), submit/статус.
|
|
6
|
+
* React-биндинги вынесены в `platforms/react` — этот слой от React не зависит.
|
|
7
|
+
* Schema-валидация — внешний контракт `@reformer/core/validation` (`validateModel`),
|
|
8
|
+
* роутит ошибки в ноды через реестр сигнал→нода. Зависит от `model` (form→model разрешено);
|
|
8
9
|
* обратной зависимости нет.
|
|
9
10
|
*
|
|
10
11
|
* @group Form
|
|
@@ -21,19 +22,12 @@ export { createForm, createFormFromModel } from './create-form';
|
|
|
21
22
|
export type { CreateFormFromModelArgs } from './create-form';
|
|
22
23
|
export { createCoreForm } from './create-core-form';
|
|
23
24
|
export type { CoreForm, CreateCoreFormConfig, CreateFormConfigBase } from './create-core-form';
|
|
24
|
-
export { buildValidation } from './validation
|
|
25
|
-
export type { FormValidation, FormValidationBundle } from './validation
|
|
26
|
-
export {
|
|
27
|
-
export
|
|
28
|
-
export { enableWhen, disableWhen } from './behaviors-node';
|
|
29
|
-
export { FormErrorHandler, ErrorStrategy } from './error-handler';
|
|
25
|
+
export { buildValidation } from './validation/config';
|
|
26
|
+
export type { FormValidation, FormValidationBundle } from './validation/config';
|
|
27
|
+
export { enableWhen, disableWhen } from './behaviors/node';
|
|
28
|
+
export { FormErrorHandler, ErrorStrategy } from './validation/error-handler';
|
|
30
29
|
export { registerSignalNode, getNodeForSignal } from './signal-node-registry';
|
|
31
30
|
export { FormSubmitter } from './form-submitter';
|
|
31
|
+
export { SubscriptionManager } from './nodes/subscription-manager';
|
|
32
32
|
export type { SubmittableForm, SubmitOptions, SubmitResult } from './form-submitter';
|
|
33
33
|
export { isFormNode, isFieldNode, isGroupNode, isArrayNode, getNodeType } from './type-guards';
|
|
34
|
-
export { useFormControl } from './hooks/useFormControl';
|
|
35
|
-
export { useFormControlValue } from './hooks/useFormControlValue';
|
|
36
|
-
export { useArrayLength } from './hooks/useArrayLength';
|
|
37
|
-
export { useFormValidation } from './hooks/use-form-validation';
|
|
38
|
-
export type { UseFormValidationArgs, UseFormValidationResult } from './hooks/use-form-validation';
|
|
39
|
-
export type { FieldControlState, ArrayControlState } from './hooks/types';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
|
2
2
|
import { FormNode } from './form-node';
|
|
3
3
|
import { FieldStatus, ValidationError } from '../types/index';
|
|
4
|
-
import { FormModel } from '../../
|
|
4
|
+
import { FormModel } from '../../model/types';
|
|
5
5
|
import { FormProxy } from '../types/form-proxy';
|
|
6
6
|
/** Минимальный контракт реактивного массива модели, используемый узлом. */
|
|
7
7
|
export interface ModelArrayControl<TItem extends object> {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Общие контракт-типы значения и
|
|
2
|
+
* Общие контракт-типы значения и валидации.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Базовый словарь «значение + ошибка + статус», на который опираются узлы формы
|
|
5
|
+
* (`FormNode.getErrors`, `FieldNode`, `FormStatusMachine`, `aggregate-signals`), слой валидации
|
|
6
|
+
* и платформенные биндинги. Файл не импортирует ничего — это лист графа зависимостей.
|
|
7
|
+
*
|
|
8
|
+
* Живёт в `form/types/`, а не в слое модели: `FieldStatus`/`ErrorFilterOptions` — понятия формы,
|
|
9
|
+
* а модель этих типов не использует (и импортировать их оттуда запрещено границей model⇏form).
|
|
10
|
+
* Наружу отдаётся через `form/types/index`.
|
|
8
11
|
*
|
|
9
12
|
* @group Types
|
|
10
|
-
* @module
|
|
13
|
+
* @module form/types/contracts
|
|
11
14
|
*/
|
|
12
15
|
/**
|
|
13
16
|
* Represents any valid form value type
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
-
import { FormModel } from '
|
|
3
|
-
import { ValidationSchema } from './
|
|
4
|
-
import { FormValidationController, ValidationStrategyKind } from './
|
|
2
|
+
import { FormModel } from '../../model/types';
|
|
3
|
+
import { ValidationSchema } from './types';
|
|
4
|
+
import { FormValidationController, ValidationStrategyKind } from './strategy';
|
|
5
5
|
/**
|
|
6
6
|
* Правила валидации формы — ДАННЫЕ (без привязки к рендеру).
|
|
7
7
|
*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FormModel, PathAwareSignal, ValidationError } from '../../index';
|
|
2
|
+
/** Сток одного прогона: модель scope, накопленные ошибки, незавершённые async-правила, гейты. */
|
|
3
|
+
export interface VContext {
|
|
4
|
+
/** Модель текущего scope — источник снапшота для `cross` и модель для `apply`. */
|
|
5
|
+
model: FormModel<unknown>;
|
|
6
|
+
/** Накопленные ошибки по сигналу (ключ — идентичность `PathAwareSignal`). */
|
|
7
|
+
errors: Map<PathAwareSignal<unknown>, ValidationError[]>;
|
|
8
|
+
/** Незавершённые async-правила (раннер их дожидается). */
|
|
9
|
+
pending: Promise<void>[];
|
|
10
|
+
/** Стек активных условий `validateWhen` (все должны быть истинны, чтобы правило сработало). */
|
|
11
|
+
whenStack: Array<() => boolean>;
|
|
12
|
+
/** Отмена устаревшего прогона: прокидывается в async-правила, гасит роутинг после await. */
|
|
13
|
+
signal: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Открыть ambient-окно на время `fn` и закрыть его в `finally`.
|
|
17
|
+
*
|
|
18
|
+
* Единственный способ установить контекст: прямое присваивание `current` из другого модуля
|
|
19
|
+
* невозможно. Сохранение/восстановление `prev` делает вложенный прогон безопасным.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runWithContext<R>(ctx: VContext, fn: () => R): R;
|
|
22
|
+
/** Контекст активного прогона либо понятная ошибка, если оператор вызван вне схемы. */
|
|
23
|
+
export declare function requireCtx(op: string): VContext;
|
|
24
|
+
/** «Тронуть» сигнал: гарантирует его наличие в `errors` (даже пустым) → он попадёт в очистку. */
|
|
25
|
+
export declare function touch(ctx: VContext, sig: PathAwareSignal<unknown>): ValidationError[];
|
|
26
|
+
/** Активна ли текущая ветка (все `validateWhen`-условия истинны). */
|
|
27
|
+
export declare const gated: (ctx: VContext) => boolean;
|