@reformer/core 9.0.0 → 9.1.0
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 +52 -57
- package/dist/behaviors-node-BMOI2JJF.js +26 -0
- package/dist/behaviors-value-lCpqicfa.js +148 -0
- package/dist/behaviors.d.ts +2 -133
- package/dist/behaviors.js +148 -147
- package/dist/{core/utils → form}/aggregate-signals.d.ts +2 -2
- package/dist/form/behaviors-node.d.ts +55 -0
- package/dist/form/behaviors.d.ts +133 -0
- package/dist/{core/utils → form}/create-form.d.ts +5 -4
- package/dist/{core/utils → form}/error-handler.d.ts +1 -1
- package/dist/{core → form}/factories/node-factory.d.ts +1 -1
- package/dist/{core/utils → form}/form-proxy-builder.d.ts +2 -2
- package/dist/{core/utils → form}/form-submitter.d.ts +6 -1
- package/dist/{hooks → form/hooks}/types.d.ts +31 -1
- package/dist/{hooks → form/hooks}/useArrayLength.d.ts +1 -1
- package/dist/{hooks → form/hooks}/useFormControl.d.ts +3 -3
- package/dist/{hooks → form/hooks}/useFormControlValue.d.ts +2 -2
- package/dist/{hooks → form/hooks}/useSignalSubscription.d.ts +8 -1
- package/dist/form/index.d.ts +36 -0
- package/dist/{core → form}/nodes/array-node.d.ts +1 -1
- package/dist/{core → form}/nodes/field-node.d.ts +1 -1
- package/dist/{core → form}/nodes/form-node.d.ts +1 -1
- package/dist/{core → form}/nodes/group-node.d.ts +34 -4
- package/dist/{core → form}/nodes/model-array-node.d.ts +10 -3
- package/dist/{core/utils → form}/signal-node-registry.d.ts +1 -1
- package/dist/{core/utils → form}/status-machine.d.ts +1 -1
- package/dist/{core/utils → form}/type-guards.d.ts +5 -5
- package/dist/{core/types/index.d.ts → form/types/contracts.d.ts} +11 -90
- package/dist/{core → form}/types/deep-schema.d.ts +23 -1
- package/dist/{core → form}/types/form-proxy.d.ts +26 -1
- package/dist/form/types/index.d.ts +91 -0
- package/dist/{core → form}/types/schema-node.d.ts +1 -1
- package/dist/{core → form}/types/validation-schema.d.ts +19 -9
- package/dist/{core/model/validate-model.d.ts → form/validate-model-core.d.ts} +25 -25
- package/dist/form/validate-model.d.ts +27 -0
- package/dist/{core → form}/validation/validators/required.d.ts +2 -1
- package/dist/form/validators.d.ts +6 -0
- package/dist/form-model-C8Fi83k4.js +439 -0
- package/dist/index.d.ts +11 -16
- package/dist/index.js +666 -1011
- package/dist/{core/model/behaviors.d.ts → state/behaviors-value.d.ts} +0 -38
- package/dist/{core/utils → state}/derived-registry.d.ts +29 -0
- package/dist/state/form-model.d.ts +26 -0
- package/dist/state/index.d.ts +31 -0
- package/dist/state/safe-effect.d.ts +93 -0
- package/dist/{core/model → state}/types.d.ts +14 -11
- package/dist/state.d.ts +2 -0
- package/dist/state.js +19 -0
- 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/pattern.js +7 -6
- package/dist/validators/phone.d.ts +1 -1
- package/dist/validators/required.d.ts +1 -1
- package/dist/validators/required.js +5 -9
- package/dist/validators/url.d.ts +1 -1
- package/dist/validators.d.ts +6 -6
- package/llms.txt +307 -166
- package/package.json +5 -1
- package/dist/behaviors-O9a1Djj9.js +0 -147
- package/dist/core/model/form-model.d.ts +0 -18
- package/dist/core/model/index.d.ts +0 -12
- package/dist/core/utils/index.d.ts +0 -18
- package/dist/core/utils/safe-effect.d.ts +0 -73
- /package/dist/{core → form}/factories/index.d.ts +0 -0
- /package/dist/{core/utils → form}/unique-id.d.ts +0 -0
- /package/dist/{core → form}/validation/index.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/date-utils.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/email.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/future-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/index.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/integer.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/is-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/is-number.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/max-age.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/max-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/max-length.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/max.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/min-age.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/min-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/min-length.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/min.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/multiple-of.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/non-negative.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/non-zero.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/past-date.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/pattern.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/phone.d.ts +0 -0
- /package/dist/{core → form}/validation/validators/url.d.ts +0 -0
- /package/dist/{core/utils → state}/subscription-manager.d.ts +0 -0
package/dist/behaviors.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import { effect as
|
|
2
|
-
import {
|
|
1
|
+
import { effect as O } from "@preact/signals-core";
|
|
2
|
+
import { d as $, m as k, u as x, a as S, w as F, t as A, r as C, s as D, b as M } from "./behaviors-value-lCpqicfa.js";
|
|
3
|
+
import { e as G } from "./behaviors-node-BMOI2JJF.js";
|
|
3
4
|
let p = null;
|
|
4
|
-
function
|
|
5
|
+
function P(e) {
|
|
5
6
|
if (!p)
|
|
6
7
|
throw new Error(
|
|
7
8
|
`[@reformer/core/behaviors] "${e}" вызван вне defineFormBehavior(...) — операторы поведения можно вызывать только внутри схемы.`
|
|
8
9
|
);
|
|
9
10
|
return p;
|
|
10
11
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
12
|
+
function s(e) {
|
|
13
|
+
P("onDispose").cleanups.push(e);
|
|
13
14
|
}
|
|
14
15
|
function y() {
|
|
15
|
-
const e =
|
|
16
|
+
const e = P("getScope");
|
|
16
17
|
return { model: e.model, form: e.form };
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
+
function T(e) {
|
|
19
20
|
return {
|
|
20
|
-
__run(
|
|
21
|
-
const o = { cleanups: [], model:
|
|
21
|
+
__run(t, n) {
|
|
22
|
+
const o = { cleanups: [], model: t, form: n }, r = p;
|
|
22
23
|
p = o;
|
|
23
24
|
try {
|
|
24
|
-
e({ model:
|
|
25
|
+
e({ model: t, form: n });
|
|
25
26
|
} finally {
|
|
26
27
|
p = r;
|
|
27
28
|
}
|
|
@@ -32,211 +33,211 @@ function G(e) {
|
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
function m(e) {
|
|
35
|
-
|
|
36
|
+
s(O(e));
|
|
36
37
|
}
|
|
37
|
-
function
|
|
38
|
+
function _(e) {
|
|
38
39
|
$(e);
|
|
39
40
|
}
|
|
40
|
-
const h = (e) => typeof e == "object" && e !== null && typeof e.peek == "function",
|
|
41
|
-
function
|
|
42
|
-
const
|
|
43
|
-
for (const
|
|
44
|
-
const o = e[
|
|
45
|
-
n
|
|
41
|
+
const h = (e) => typeof e == "object" && e !== null && typeof e.peek == "function", B = (e) => Array.isArray(e) ? e : [e];
|
|
42
|
+
function E(e) {
|
|
43
|
+
const t = {};
|
|
44
|
+
for (const n of Object.keys(e)) {
|
|
45
|
+
const o = e[n];
|
|
46
|
+
t[n] = h(o) ? o.value : E(o);
|
|
46
47
|
}
|
|
47
|
-
return
|
|
48
|
+
return t;
|
|
48
49
|
}
|
|
49
|
-
function
|
|
50
|
-
for (const
|
|
51
|
-
const o = e[
|
|
52
|
-
h(o) ? o.value =
|
|
50
|
+
function W(e, t) {
|
|
51
|
+
for (const n of Object.keys(e)) {
|
|
52
|
+
const o = e[n];
|
|
53
|
+
h(o) ? o.value = t?.[n] : W(o, t?.[n] ?? {});
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
|
-
function
|
|
56
|
+
function I(e) {
|
|
56
57
|
if (!e) return;
|
|
57
|
-
const { form:
|
|
58
|
-
return
|
|
58
|
+
const { form: t } = y();
|
|
59
|
+
return t.getFieldByPath(e);
|
|
59
60
|
}
|
|
60
|
-
function
|
|
61
|
+
function V(e) {
|
|
61
62
|
return new Proxy(
|
|
62
63
|
{},
|
|
63
64
|
{
|
|
64
|
-
get: (
|
|
65
|
-
if (
|
|
66
|
-
if (typeof
|
|
67
|
-
const o = e[
|
|
65
|
+
get: (t, n) => {
|
|
66
|
+
if (n === "$") return e;
|
|
67
|
+
if (typeof n != "string") return;
|
|
68
|
+
const o = e[n];
|
|
68
69
|
if (o != null)
|
|
69
|
-
return h(o) ? o.value :
|
|
70
|
+
return h(o) ? o.value : V(o);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
);
|
|
73
74
|
}
|
|
74
|
-
function
|
|
75
|
-
return (
|
|
75
|
+
function j(e) {
|
|
76
|
+
return (t) => {
|
|
76
77
|
try {
|
|
77
|
-
|
|
78
|
-
} catch (
|
|
79
|
-
if (
|
|
78
|
+
t();
|
|
79
|
+
} catch (n) {
|
|
80
|
+
if (n instanceof Error && /cycle detected/i.test(n.message)) {
|
|
80
81
|
const o = e.__path ?? "?";
|
|
81
82
|
throw new Error(
|
|
82
83
|
`[@reformer/core/behaviors] compute("${o}"): расходящийся цикл пересчёта — взаимные compute/computeFrom без стабилизации. Проверьте зависимости или добавьте стабилизирующее условие (when) / разорвите цикл через peek.`
|
|
83
84
|
);
|
|
84
85
|
}
|
|
85
|
-
throw
|
|
86
|
+
throw n;
|
|
86
87
|
}
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
|
-
function
|
|
90
|
-
x(e);
|
|
91
|
-
const o =
|
|
90
|
+
function Q(e, t, n) {
|
|
91
|
+
k(e), s(() => x(e));
|
|
92
|
+
const o = j(e);
|
|
92
93
|
m(() => {
|
|
93
|
-
if (
|
|
94
|
-
const r =
|
|
94
|
+
if (n?.when && !n.when()) return;
|
|
95
|
+
const r = t();
|
|
95
96
|
e.peek() !== r && o(() => {
|
|
96
97
|
e.value = r;
|
|
97
98
|
});
|
|
98
99
|
});
|
|
99
100
|
}
|
|
100
|
-
function
|
|
101
|
-
x(
|
|
102
|
-
const r =
|
|
101
|
+
function U(e, t, n, o) {
|
|
102
|
+
k(t), s(() => x(t));
|
|
103
|
+
const r = j(t);
|
|
103
104
|
m(() => {
|
|
104
105
|
const c = e.map((f) => f.value);
|
|
105
106
|
if (o?.when && !o.when(...c)) return;
|
|
106
|
-
const i =
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const i = n(...c);
|
|
108
|
+
t.peek() !== i && r(() => {
|
|
109
|
+
t.value = i;
|
|
109
110
|
});
|
|
110
111
|
});
|
|
111
112
|
}
|
|
112
|
-
function
|
|
113
|
-
if (h(e) && h(
|
|
114
|
-
|
|
113
|
+
function X(e, t, n) {
|
|
114
|
+
if (h(e) && h(t)) {
|
|
115
|
+
s(S(e, t, n));
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
|
-
const o = e, r =
|
|
118
|
+
const o = e, r = t;
|
|
118
119
|
m(() => {
|
|
119
|
-
const c =
|
|
120
|
-
|
|
120
|
+
const c = E(o);
|
|
121
|
+
n?.when && !n.when() || _(() => W(r, c));
|
|
121
122
|
});
|
|
122
123
|
}
|
|
123
|
-
function
|
|
124
|
+
function R(e, t, n) {
|
|
124
125
|
let o;
|
|
125
126
|
const r = (c) => {
|
|
126
127
|
o?.abort(), o = new AbortController();
|
|
127
128
|
const { signal: i } = o;
|
|
128
|
-
$(() =>
|
|
129
|
+
$(() => t(c, { signal: i }));
|
|
129
130
|
};
|
|
130
|
-
if (!
|
|
131
|
-
|
|
131
|
+
if (!n?.debounce)
|
|
132
|
+
s(F(e, r, { immediate: n?.immediate }));
|
|
132
133
|
else {
|
|
133
134
|
let c;
|
|
134
135
|
const i = F(
|
|
135
136
|
e,
|
|
136
137
|
(f) => {
|
|
137
|
-
c && clearTimeout(c), c = setTimeout(() => r(f),
|
|
138
|
+
c && clearTimeout(c), c = setTimeout(() => r(f), n.debounce);
|
|
138
139
|
},
|
|
139
|
-
{ immediate:
|
|
140
|
+
{ immediate: n?.immediate }
|
|
140
141
|
);
|
|
141
|
-
|
|
142
|
+
s(() => {
|
|
142
143
|
c && clearTimeout(c), i();
|
|
143
144
|
});
|
|
144
145
|
}
|
|
145
|
-
|
|
146
|
+
s(() => o?.abort());
|
|
146
147
|
}
|
|
147
|
-
function
|
|
148
|
-
for (const o of
|
|
149
|
-
h(o) ?
|
|
148
|
+
function q(e, t, n) {
|
|
149
|
+
for (const o of B(e))
|
|
150
|
+
h(o) ? s(G(o, t, n)) : z(o, t, n);
|
|
150
151
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
152
|
+
function Y(e, t, n) {
|
|
153
|
+
q(e, () => !t(), n);
|
|
153
154
|
}
|
|
154
|
-
function
|
|
155
|
-
const o =
|
|
155
|
+
function z(e, t, n) {
|
|
156
|
+
const o = I(e.__path);
|
|
156
157
|
o && m(() => {
|
|
157
|
-
const r =
|
|
158
|
-
|
|
159
|
-
r ? o.enable() : (o.disable(),
|
|
158
|
+
const r = t();
|
|
159
|
+
_(() => {
|
|
160
|
+
r ? o.enable() : (o.disable(), n?.resetOnDisable && o.reset());
|
|
160
161
|
});
|
|
161
162
|
});
|
|
162
163
|
}
|
|
163
|
-
function
|
|
164
|
-
|
|
164
|
+
function Z(e, t) {
|
|
165
|
+
s(A(e, t));
|
|
165
166
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
167
|
+
function ee(e, t, n) {
|
|
168
|
+
s(C(e, t, n));
|
|
168
169
|
}
|
|
169
|
-
function
|
|
170
|
-
|
|
170
|
+
function ne(e, t, n) {
|
|
171
|
+
s(D(e, t, n));
|
|
171
172
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
173
|
+
function te(e, t) {
|
|
174
|
+
s(M(e, t));
|
|
174
175
|
}
|
|
175
|
-
function
|
|
176
|
-
return
|
|
176
|
+
function g(e, t) {
|
|
177
|
+
return t.split(".").reduce((n, o) => n?.[o], e);
|
|
177
178
|
}
|
|
178
|
-
function
|
|
179
|
+
function N(e) {
|
|
179
180
|
return new Proxy(
|
|
180
181
|
{},
|
|
181
182
|
{
|
|
182
|
-
get(
|
|
183
|
-
if (!(typeof
|
|
183
|
+
get(t, n) {
|
|
184
|
+
if (!(typeof n == "symbol" || n === "then"))
|
|
184
185
|
throw new Error(
|
|
185
186
|
`[@reformer/core/behaviors] applyEach: форма строки массива "${e}" недоступна (form.${String(
|
|
186
|
-
|
|
187
|
+
n
|
|
187
188
|
)}) — массив не материализован в схеме формы. Per-row node-операции (enableWhen/updateComponentProps/reset) требуют узла { array, item } в схеме; value-операции (row.$.*: compute/copyFrom/transformValue) работают и без материализации.`
|
|
188
189
|
);
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
);
|
|
192
193
|
}
|
|
193
|
-
function
|
|
194
|
-
const { model:
|
|
194
|
+
function H(e, t) {
|
|
195
|
+
const { model: n, form: o } = y(), r = e.__path;
|
|
195
196
|
if (!r) return;
|
|
196
|
-
const c =
|
|
197
|
+
const c = g(n, r);
|
|
197
198
|
if (!c) return;
|
|
198
199
|
const i = o.getFieldByPath(
|
|
199
200
|
r
|
|
200
|
-
), f = i?.length,
|
|
201
|
+
), f = i?.length, u = /* @__PURE__ */ new Map();
|
|
201
202
|
m(() => {
|
|
202
203
|
const d = f ? f.value : c.length, w = /* @__PURE__ */ new Set();
|
|
203
|
-
for (let
|
|
204
|
-
const l = c.at(
|
|
205
|
-
if (l != null && (w.add(l), !
|
|
206
|
-
const b = i?.at?.(
|
|
207
|
-
|
|
204
|
+
for (let a = 0; a < d; a++) {
|
|
205
|
+
const l = c.at(a);
|
|
206
|
+
if (l != null && (w.add(l), !u.has(l))) {
|
|
207
|
+
const b = i?.at?.(a) ?? N(r);
|
|
208
|
+
u.set(l, t.__run(l, b));
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
|
-
for (const [
|
|
211
|
-
w.has(
|
|
212
|
-
}),
|
|
213
|
-
for (const d of
|
|
214
|
-
|
|
211
|
+
for (const [a, l] of u)
|
|
212
|
+
w.has(a) || (l(), u.delete(a));
|
|
213
|
+
}), s(() => {
|
|
214
|
+
for (const d of u.values()) d();
|
|
215
|
+
u.clear();
|
|
215
216
|
});
|
|
216
217
|
}
|
|
217
218
|
function v(e) {
|
|
218
219
|
if (e == null || typeof e != "object") return;
|
|
219
|
-
const
|
|
220
|
-
if (typeof
|
|
221
|
-
const
|
|
222
|
-
for (let o = 0; o <
|
|
220
|
+
const t = e;
|
|
221
|
+
if (typeof t.at == "function" && typeof t.length == "number") {
|
|
222
|
+
const n = t.length;
|
|
223
|
+
for (let o = 0; o < n; o++) v(t.at(o));
|
|
223
224
|
return;
|
|
224
225
|
}
|
|
225
|
-
for (const
|
|
226
|
+
for (const n of Object.keys(e)) v(e[n]);
|
|
226
227
|
}
|
|
227
|
-
function
|
|
228
|
-
const { model:
|
|
228
|
+
function oe(e, t) {
|
|
229
|
+
const { model: n } = y(), o = e.__path;
|
|
229
230
|
if (!o) return;
|
|
230
|
-
const r =
|
|
231
|
-
r &&
|
|
231
|
+
const r = g(n, o);
|
|
232
|
+
r && H(
|
|
232
233
|
e,
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
T(({ model: c }) => {
|
|
235
|
+
R(t(c), (i) => {
|
|
235
236
|
if (i)
|
|
236
237
|
for (let f = 0; f < r.length; f++) {
|
|
237
|
-
const
|
|
238
|
-
if (
|
|
239
|
-
const d =
|
|
238
|
+
const u = r.at(f);
|
|
239
|
+
if (u && u !== c) {
|
|
240
|
+
const d = t(u);
|
|
240
241
|
d.peek() && (d.value = !1);
|
|
241
242
|
}
|
|
242
243
|
}
|
|
@@ -244,57 +245,57 @@ function te(e, n) {
|
|
|
244
245
|
})
|
|
245
246
|
);
|
|
246
247
|
}
|
|
247
|
-
function
|
|
248
|
-
const { model:
|
|
248
|
+
function re(e, t) {
|
|
249
|
+
const { model: n } = y(), o = e.__path;
|
|
249
250
|
if (!o) return;
|
|
250
|
-
const r =
|
|
251
|
+
const r = g(n, o);
|
|
251
252
|
if (!r) return;
|
|
252
253
|
let c = !1, i = 0;
|
|
253
254
|
m(() => {
|
|
254
|
-
v(r), !c && (c = !0,
|
|
255
|
+
v(r), !c && (c = !0, _(() => {
|
|
255
256
|
c = !1;
|
|
256
|
-
const f =
|
|
257
|
-
let
|
|
257
|
+
const f = t(r.toArray());
|
|
258
|
+
let u = !1;
|
|
258
259
|
for (const { index: d, patch: w } of f) {
|
|
259
|
-
const
|
|
260
|
-
if (
|
|
260
|
+
const a = r.at(d);
|
|
261
|
+
if (a)
|
|
261
262
|
for (const [l, b] of Object.entries(w))
|
|
262
|
-
|
|
263
|
+
a[l] !== b && (a[l] = b, u = !0);
|
|
263
264
|
}
|
|
264
|
-
if (i =
|
|
265
|
+
if (i = u ? i + 1 : 0, i > 50)
|
|
265
266
|
throw i = 0, new Error(
|
|
266
267
|
`[@reformer/core/behaviors] aggregateInto("${o}"): запись не сходится (>50 проходов) — derive должна быть идемпотентной на фикспоинте.`
|
|
267
268
|
);
|
|
268
269
|
}));
|
|
269
270
|
});
|
|
270
271
|
}
|
|
271
|
-
function
|
|
272
|
-
const { form:
|
|
273
|
-
for (const o of
|
|
272
|
+
function ce(e, t) {
|
|
273
|
+
const { form: n } = y();
|
|
274
|
+
for (const o of B(e)) {
|
|
274
275
|
const r = o, c = r.__path;
|
|
275
276
|
if (!c) continue;
|
|
276
|
-
const i =
|
|
277
|
-
|
|
277
|
+
const i = n ? n.getFieldByPath(c) : void 0, f = i ? i.getProxy?.() ?? i : void 0;
|
|
278
|
+
s(t.__run(V(r), f));
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
281
|
export {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
282
|
+
re as aggregateInto,
|
|
283
|
+
ce as apply,
|
|
284
|
+
H as applyEach,
|
|
285
|
+
Q as compute,
|
|
286
|
+
U as computeFrom,
|
|
287
|
+
X as copyFrom,
|
|
288
|
+
_ as defer,
|
|
289
|
+
T as defineFormBehavior,
|
|
290
|
+
Y as disableWhen,
|
|
290
291
|
m as effect,
|
|
291
|
-
|
|
292
|
-
|
|
292
|
+
q as enableWhen,
|
|
293
|
+
oe as exclusiveFlag,
|
|
293
294
|
y as getScope,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
295
|
+
R as onChange,
|
|
296
|
+
s as onDispose,
|
|
297
|
+
ee as resetWhen,
|
|
298
|
+
te as revalidateWhen,
|
|
299
|
+
ne as syncFields,
|
|
300
|
+
Z as transformValue
|
|
300
301
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReadonlySignal, Signal } from '@preact/signals-core';
|
|
2
|
-
import { FieldStatus, ValidationError } from '
|
|
3
|
-
import { FormNode } from '
|
|
2
|
+
import { FieldStatus, ValidationError } from './types/index';
|
|
3
|
+
import { FormNode } from './nodes/form-node';
|
|
4
4
|
/**
|
|
5
5
|
* Функция получения дочерних узлов
|
|
6
6
|
* Возвращает массив FormNode для агрегации состояния
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ReadonlySignal } from '@preact/signals-core';
|
|
2
|
+
import { BehaviorCleanup } from '../state/behaviors-value';
|
|
3
|
+
/**
|
|
4
|
+
* Условное включение поля (state-операция). Резолвит ноду по сигналу-цели через реестр
|
|
5
|
+
* сигнал→нода (заполняется `createForm`) и вызывает `enable()`/`disable()` (+`reset()` при
|
|
6
|
+
* `resetOnDisable`). `condition` реактивен (читает свои сигналы модели).
|
|
7
|
+
*
|
|
8
|
+
* Запись состояния отложена через `runOutsideEffect` (микротаск) для защиты от «Cycle detected».
|
|
9
|
+
* ⚠️ Поле должно быть материализовано в форме (`createForm`) — иначе ноды в реестре нет (например,
|
|
10
|
+
* элемент массива, который строится per-item).
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ Если то же поле одновременно является целью `compute`/`computeFrom`, обязательно ограничь
|
|
13
|
+
* compute тем же условием (`when`), что и это `enableWhen`. Механизмы не согласованы: `enableWhen`
|
|
14
|
+
* работает со статус-машиной ноды (enable/disable/reset), а `compute` пишет `target.value` напрямую
|
|
15
|
+
* в сигнал модели и проверяет только собственный `options.when` — состояние `disabled` он НЕ смотрит.
|
|
16
|
+
* Поэтому при выключенном поле любое изменение зависимости compute молча заново заполнит его (а с
|
|
17
|
+
* `resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()`/`submit`
|
|
18
|
+
* включают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,
|
|
19
|
+
* которое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.
|
|
20
|
+
*
|
|
21
|
+
* @group Model
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
|
|
25
|
+
*
|
|
26
|
+
* // Поле-цель compute, которое ТАКЖЕ в resetOnDisable enableWhen: compute обязан нести тот же when,
|
|
27
|
+
* // иначе initialPayment заново заполнится в выключенном состоянии и утечёт в submit.
|
|
28
|
+
* compute(model.$.initialPayment, () => model.propertyValue * 0.2, {
|
|
29
|
+
* when: () => model.loanType === 'mortgage',
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function enableWhen(target: ReadonlySignal<unknown>, condition: () => boolean, options?: {
|
|
34
|
+
resetOnDisable?: boolean;
|
|
35
|
+
}): BehaviorCleanup;
|
|
36
|
+
/**
|
|
37
|
+
* Условное выключение поля (инверсия {@link enableWhen}). Резолвит ноду по сигналу-цели через реестр
|
|
38
|
+
* сигнал→нода и вызывает `disable()`, когда `condition` истинно (+`reset()` при `resetOnDisable`).
|
|
39
|
+
* `condition` реактивен (читает свои сигналы модели).
|
|
40
|
+
*
|
|
41
|
+
* @group Model
|
|
42
|
+
* @param target Сигнал-цель поля (`model.$.<path>`).
|
|
43
|
+
* @param condition Реактивное условие; при `true` поле выключается.
|
|
44
|
+
* @param options.resetOnDisable Сбросить значение при выключении (по умолчанию `false`).
|
|
45
|
+
* @returns Cleanup для отписки.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // Поле скидки недоступно, пока не выбран промо-тариф
|
|
50
|
+
* disableWhen(model.$.discount, () => model.plan !== 'promo', { resetOnDisable: true });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function disableWhen(target: ReadonlySignal<unknown>, condition: () => boolean, options?: {
|
|
54
|
+
resetOnDisable?: boolean;
|
|
55
|
+
}): BehaviorCleanup;
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
/** Зарегистрировать произвольную отписку в активной схеме. */
|
|
15
|
+
export declare function onDispose(cleanup: BehaviorCleanup): void;
|
|
16
|
+
/** Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов). */
|
|
17
|
+
export declare function getScope<T>(): BehaviorScope<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
|
|
24
|
+
* compute(model.$.total, () => model.price * model.qty);
|
|
25
|
+
* enableWhen([model.$.city], () => Boolean(model.country));
|
|
26
|
+
* onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>;
|
|
31
|
+
/** Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup. */
|
|
32
|
+
export declare function effect(fn: () => void | (() => void)): void;
|
|
33
|
+
/** Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected». */
|
|
34
|
+
export declare function defer(fn: () => void): void;
|
|
35
|
+
/** Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов. */
|
|
36
|
+
export declare function compute<R>(target: Signal<R>, read: () => R, options?: {
|
|
37
|
+
when?: () => boolean;
|
|
38
|
+
}): void;
|
|
39
|
+
/** Вычисляемое поле с явным списком зависимостей (escape hatch). */
|
|
40
|
+
export declare function computeFrom<R>(sources: ReadonlySignal<unknown>[], target: Signal<R>, fn: (...values: any[]) => R, options?: {
|
|
41
|
+
when?: (...values: any[]) => boolean;
|
|
42
|
+
}): void;
|
|
43
|
+
/** Копирование `source → target` — скаляр или группа (объект целиком). */
|
|
44
|
+
export declare function copyFrom<T>(source: ReadonlySignal<T> | object, target: Signal<T> | object, options?: {
|
|
45
|
+
when?: () => boolean;
|
|
46
|
+
transform?: (value: T) => T;
|
|
47
|
+
}): void;
|
|
48
|
+
/** Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка. */
|
|
49
|
+
export interface ChangeContext {
|
|
50
|
+
signal: AbortSignal;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Реакция на изменение поля; `{ debounce, immediate }`.
|
|
54
|
+
*
|
|
55
|
+
* Колбэк выполняется ВНЕ effect-контекста (микротаск/таймер) — можно безопасно писать сигналы и ноды
|
|
56
|
+
* (`updateComponentProps`/`reset`/`clear`) без ручного `defer` и без «Cycle detected».
|
|
57
|
+
*
|
|
58
|
+
* Для async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения
|
|
59
|
+
* предыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй
|
|
60
|
+
* `signal.aborted` перед применением результата — это убирает гонки устаревших ответов (F2).
|
|
61
|
+
*/
|
|
62
|
+
export declare function onChange<T>(source: ReadonlySignal<T>, cb: (value: T, ctx: ChangeContext) => void, options?: {
|
|
63
|
+
immediate?: boolean;
|
|
64
|
+
debounce?: number;
|
|
65
|
+
}): void;
|
|
66
|
+
type EnableTarget = ReadonlySignal<unknown> | object;
|
|
67
|
+
/** Условное включение поля(ей) — скаляр, массив целей или группа. `resetOnDisable=false` по умолчанию. */
|
|
68
|
+
export declare function enableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
|
|
69
|
+
resetOnDisable?: boolean;
|
|
70
|
+
}): void;
|
|
71
|
+
/** Условное выключение поля(ей) (инверсия {@link enableWhen}). */
|
|
72
|
+
export declare function disableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
|
|
73
|
+
resetOnDisable?: boolean;
|
|
74
|
+
}): void;
|
|
75
|
+
/** Трансформация значения поля (идемпотентная). */
|
|
76
|
+
export declare function transformValue<T>(target: Signal<T>, transformer: (value: T) => T): void;
|
|
77
|
+
/** Сброс значения по условию. */
|
|
78
|
+
export declare function resetWhen<T>(target: Signal<T>, condition: () => boolean, options?: {
|
|
79
|
+
resetValue?: T;
|
|
80
|
+
}): void;
|
|
81
|
+
/** Двусторонняя синхронизация двух полей. */
|
|
82
|
+
export declare function syncFields<T>(a: Signal<T>, b: Signal<T>, options?: {
|
|
83
|
+
transform?: (value: T) => T;
|
|
84
|
+
}): void;
|
|
85
|
+
/** Ревалидация при изменении зависимостей. */
|
|
86
|
+
export declare function revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): void;
|
|
87
|
+
/**
|
|
88
|
+
* Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).
|
|
89
|
+
* Реагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.
|
|
90
|
+
*
|
|
91
|
+
* Под-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).
|
|
92
|
+
* - Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.
|
|
93
|
+
* - Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был
|
|
94
|
+
* МАТЕРИАЛИЗОВАН в форме (узел `{ array, item }` в схеме) — тогда `form` строки = та же нода, что
|
|
95
|
+
* рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
|
|
96
|
+
* к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
|
|
100
|
+
* compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
|
|
101
|
+
* enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
|
|
102
|
+
* }));
|
|
103
|
+
*/
|
|
104
|
+
export declare function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void;
|
|
105
|
+
/**
|
|
106
|
+
* Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).
|
|
107
|
+
* Когда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:
|
|
108
|
+
* новые строки с флагом false исключения не запускают.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* exclusiveFlag(model.$.contacts, (row) => row.$.primary);
|
|
112
|
+
*/
|
|
113
|
+
export declare function exclusiveFlag<TItem>(array: object, getFlag: (row: FormModel<TItem>) => Signal<boolean>): void;
|
|
114
|
+
/**
|
|
115
|
+
* Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список
|
|
116
|
+
* `{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на
|
|
117
|
+
* финальном состоянии — поэтому массовые синхронные мутации (push в цикле) не вызывают каскад на
|
|
118
|
+
* промежуточных состояниях. `derive` должна сходиться (на фикспоинте возвращать те же значения).
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* // последняя строка = 100 − Σ(остальные)
|
|
122
|
+
* aggregateInto(model.$.rows, (rows) => {
|
|
123
|
+
* const n = rows.length; if (n === 0) return [];
|
|
124
|
+
* const others = rows.slice(0, n - 1).reduce((s, r) => s + r.percent, 0);
|
|
125
|
+
* return [{ index: n - 1, patch: { percent: 100 - others } }];
|
|
126
|
+
* });
|
|
127
|
+
*/
|
|
128
|
+
export declare function aggregateInto<TItem>(array: object, derive: (rows: TItem[]) => Array<{
|
|
129
|
+
index: number;
|
|
130
|
+
patch: Partial<TItem>;
|
|
131
|
+
}>): void;
|
|
132
|
+
/** Применить под-схему к одному или нескольким полям-группам (переиспользование). */
|
|
133
|
+
export declare function apply<TField>(targets: object | object[], subSchema: FormBehavior<TField>): void;
|