@reformer/core 9.0.0-beta.1 → 9.0.0-beta.2
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-Br4Im38V.js +263 -0
- package/dist/behaviors.js +146 -146
- package/dist/core/model/behaviors.d.ts +15 -0
- package/dist/core/nodes/group-node.d.ts +32 -2
- package/dist/core/nodes/model-array-node.d.ts +8 -1
- package/dist/core/types/deep-schema.d.ts +23 -1
- package/dist/core/types/form-proxy.d.ts +26 -1
- package/dist/core/types/validation-schema.d.ts +18 -8
- package/dist/core/utils/derived-registry.d.ts +29 -0
- package/dist/core/utils/form-submitter.d.ts +6 -1
- package/dist/core/utils/index.d.ts +1 -1
- package/dist/core/utils/safe-effect.d.ts +27 -7
- package/dist/core/validation/validators/required.d.ts +2 -1
- package/dist/hooks/types.d.ts +30 -0
- package/dist/hooks/useSignalSubscription.d.ts +8 -1
- package/dist/index.js +766 -758
- package/dist/validators/pattern.js +7 -6
- package/dist/validators/required.js +5 -9
- package/llms.txt +149 -22
- package/package.json +1 -1
- package/dist/behaviors-O9a1Djj9.js +0 -147
package/dist/behaviors.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { effect as
|
|
2
|
-
import { r as $, m as x, c as
|
|
1
|
+
import { effect as O } from "@preact/signals-core";
|
|
2
|
+
import { r as $, m as k, u as x, c as S, w as F, e as A, t as C, a as D, s as M, b as G } from "./behaviors-Br4Im38V.js";
|
|
3
3
|
let p = null;
|
|
4
|
-
function
|
|
4
|
+
function P(e) {
|
|
5
5
|
if (!p)
|
|
6
6
|
throw new Error(
|
|
7
7
|
`[@reformer/core/behaviors] "${e}" вызван вне defineFormBehavior(...) — операторы поведения можно вызывать только внутри схемы.`
|
|
8
8
|
);
|
|
9
9
|
return p;
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
11
|
+
function s(e) {
|
|
12
|
+
P("onDispose").cleanups.push(e);
|
|
13
13
|
}
|
|
14
14
|
function y() {
|
|
15
|
-
const e =
|
|
15
|
+
const e = P("getScope");
|
|
16
16
|
return { model: e.model, form: e.form };
|
|
17
17
|
}
|
|
18
|
-
function
|
|
18
|
+
function T(e) {
|
|
19
19
|
return {
|
|
20
|
-
__run(
|
|
21
|
-
const o = { cleanups: [], model:
|
|
20
|
+
__run(t, n) {
|
|
21
|
+
const o = { cleanups: [], model: t, form: n }, r = p;
|
|
22
22
|
p = o;
|
|
23
23
|
try {
|
|
24
|
-
e({ model:
|
|
24
|
+
e({ model: t, form: n });
|
|
25
25
|
} finally {
|
|
26
26
|
p = r;
|
|
27
27
|
}
|
|
@@ -32,211 +32,211 @@ function G(e) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
function m(e) {
|
|
35
|
-
|
|
35
|
+
s(O(e));
|
|
36
36
|
}
|
|
37
|
-
function
|
|
37
|
+
function _(e) {
|
|
38
38
|
$(e);
|
|
39
39
|
}
|
|
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
|
|
40
|
+
const h = (e) => typeof e == "object" && e !== null && typeof e.peek == "function", B = (e) => Array.isArray(e) ? e : [e];
|
|
41
|
+
function E(e) {
|
|
42
|
+
const t = {};
|
|
43
|
+
for (const n of Object.keys(e)) {
|
|
44
|
+
const o = e[n];
|
|
45
|
+
t[n] = h(o) ? o.value : E(o);
|
|
46
46
|
}
|
|
47
|
-
return
|
|
47
|
+
return t;
|
|
48
48
|
}
|
|
49
|
-
function
|
|
50
|
-
for (const
|
|
51
|
-
const o = e[
|
|
52
|
-
h(o) ? o.value =
|
|
49
|
+
function W(e, t) {
|
|
50
|
+
for (const n of Object.keys(e)) {
|
|
51
|
+
const o = e[n];
|
|
52
|
+
h(o) ? o.value = t?.[n] : W(o, t?.[n] ?? {});
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
function
|
|
55
|
+
function I(e) {
|
|
56
56
|
if (!e) return;
|
|
57
|
-
const { form:
|
|
58
|
-
return
|
|
57
|
+
const { form: t } = y();
|
|
58
|
+
return t.getFieldByPath(e);
|
|
59
59
|
}
|
|
60
|
-
function
|
|
60
|
+
function V(e) {
|
|
61
61
|
return new Proxy(
|
|
62
62
|
{},
|
|
63
63
|
{
|
|
64
|
-
get: (
|
|
65
|
-
if (
|
|
66
|
-
if (typeof
|
|
67
|
-
const o = e[
|
|
64
|
+
get: (t, n) => {
|
|
65
|
+
if (n === "$") return e;
|
|
66
|
+
if (typeof n != "string") return;
|
|
67
|
+
const o = e[n];
|
|
68
68
|
if (o != null)
|
|
69
|
-
return h(o) ? o.value :
|
|
69
|
+
return h(o) ? o.value : V(o);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
|
-
function
|
|
75
|
-
return (
|
|
74
|
+
function j(e) {
|
|
75
|
+
return (t) => {
|
|
76
76
|
try {
|
|
77
|
-
|
|
78
|
-
} catch (
|
|
79
|
-
if (
|
|
77
|
+
t();
|
|
78
|
+
} catch (n) {
|
|
79
|
+
if (n instanceof Error && /cycle detected/i.test(n.message)) {
|
|
80
80
|
const o = e.__path ?? "?";
|
|
81
81
|
throw new Error(
|
|
82
82
|
`[@reformer/core/behaviors] compute("${o}"): расходящийся цикл пересчёта — взаимные compute/computeFrom без стабилизации. Проверьте зависимости или добавьте стабилизирующее условие (when) / разорвите цикл через peek.`
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
|
-
throw
|
|
85
|
+
throw n;
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
-
function
|
|
90
|
-
x(e);
|
|
91
|
-
const o =
|
|
89
|
+
function L(e, t, n) {
|
|
90
|
+
k(e), s(() => x(e));
|
|
91
|
+
const o = j(e);
|
|
92
92
|
m(() => {
|
|
93
|
-
if (
|
|
94
|
-
const r =
|
|
93
|
+
if (n?.when && !n.when()) return;
|
|
94
|
+
const r = t();
|
|
95
95
|
e.peek() !== r && o(() => {
|
|
96
96
|
e.value = r;
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
-
function
|
|
101
|
-
x(
|
|
102
|
-
const r =
|
|
100
|
+
function Q(e, t, n, o) {
|
|
101
|
+
k(t), s(() => x(t));
|
|
102
|
+
const r = j(t);
|
|
103
103
|
m(() => {
|
|
104
104
|
const c = e.map((f) => f.value);
|
|
105
105
|
if (o?.when && !o.when(...c)) return;
|
|
106
|
-
const i =
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
const i = n(...c);
|
|
107
|
+
t.peek() !== i && r(() => {
|
|
108
|
+
t.value = i;
|
|
109
109
|
});
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
|
-
function
|
|
113
|
-
if (h(e) && h(
|
|
114
|
-
|
|
112
|
+
function U(e, t, n) {
|
|
113
|
+
if (h(e) && h(t)) {
|
|
114
|
+
s(S(e, t, n));
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
|
-
const o = e, r =
|
|
117
|
+
const o = e, r = t;
|
|
118
118
|
m(() => {
|
|
119
|
-
const c =
|
|
120
|
-
|
|
119
|
+
const c = E(o);
|
|
120
|
+
n?.when && !n.when() || _(() => W(r, c));
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
|
-
function
|
|
123
|
+
function R(e, t, n) {
|
|
124
124
|
let o;
|
|
125
125
|
const r = (c) => {
|
|
126
126
|
o?.abort(), o = new AbortController();
|
|
127
127
|
const { signal: i } = o;
|
|
128
|
-
$(() =>
|
|
128
|
+
$(() => t(c, { signal: i }));
|
|
129
129
|
};
|
|
130
|
-
if (!
|
|
131
|
-
|
|
130
|
+
if (!n?.debounce)
|
|
131
|
+
s(F(e, r, { immediate: n?.immediate }));
|
|
132
132
|
else {
|
|
133
133
|
let c;
|
|
134
134
|
const i = F(
|
|
135
135
|
e,
|
|
136
136
|
(f) => {
|
|
137
|
-
c && clearTimeout(c), c = setTimeout(() => r(f),
|
|
137
|
+
c && clearTimeout(c), c = setTimeout(() => r(f), n.debounce);
|
|
138
138
|
},
|
|
139
|
-
{ immediate:
|
|
139
|
+
{ immediate: n?.immediate }
|
|
140
140
|
);
|
|
141
|
-
|
|
141
|
+
s(() => {
|
|
142
142
|
c && clearTimeout(c), i();
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
|
-
|
|
145
|
+
s(() => o?.abort());
|
|
146
146
|
}
|
|
147
|
-
function
|
|
148
|
-
for (const o of
|
|
149
|
-
h(o) ?
|
|
147
|
+
function q(e, t, n) {
|
|
148
|
+
for (const o of B(e))
|
|
149
|
+
h(o) ? s(A(o, t, n)) : z(o, t, n);
|
|
150
150
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
151
|
+
function X(e, t, n) {
|
|
152
|
+
q(e, () => !t(), n);
|
|
153
153
|
}
|
|
154
|
-
function
|
|
155
|
-
const o =
|
|
154
|
+
function z(e, t, n) {
|
|
155
|
+
const o = I(e.__path);
|
|
156
156
|
o && m(() => {
|
|
157
|
-
const r =
|
|
158
|
-
|
|
159
|
-
r ? o.enable() : (o.disable(),
|
|
157
|
+
const r = t();
|
|
158
|
+
_(() => {
|
|
159
|
+
r ? o.enable() : (o.disable(), n?.resetOnDisable && o.reset());
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
-
function
|
|
164
|
-
|
|
163
|
+
function Y(e, t) {
|
|
164
|
+
s(C(e, t));
|
|
165
165
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
166
|
+
function Z(e, t, n) {
|
|
167
|
+
s(D(e, t, n));
|
|
168
168
|
}
|
|
169
|
-
function
|
|
170
|
-
|
|
169
|
+
function ee(e, t, n) {
|
|
170
|
+
s(M(e, t, n));
|
|
171
171
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
172
|
+
function ne(e, t) {
|
|
173
|
+
s(G(e, t));
|
|
174
174
|
}
|
|
175
|
-
function
|
|
176
|
-
return
|
|
175
|
+
function g(e, t) {
|
|
176
|
+
return t.split(".").reduce((n, o) => n?.[o], e);
|
|
177
177
|
}
|
|
178
|
-
function
|
|
178
|
+
function N(e) {
|
|
179
179
|
return new Proxy(
|
|
180
180
|
{},
|
|
181
181
|
{
|
|
182
|
-
get(
|
|
183
|
-
if (!(typeof
|
|
182
|
+
get(t, n) {
|
|
183
|
+
if (!(typeof n == "symbol" || n === "then"))
|
|
184
184
|
throw new Error(
|
|
185
185
|
`[@reformer/core/behaviors] applyEach: форма строки массива "${e}" недоступна (form.${String(
|
|
186
|
-
|
|
186
|
+
n
|
|
187
187
|
)}) — массив не материализован в схеме формы. Per-row node-операции (enableWhen/updateComponentProps/reset) требуют узла { array, item } в схеме; value-операции (row.$.*: compute/copyFrom/transformValue) работают и без материализации.`
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
);
|
|
192
192
|
}
|
|
193
|
-
function
|
|
194
|
-
const { model:
|
|
193
|
+
function H(e, t) {
|
|
194
|
+
const { model: n, form: o } = y(), r = e.__path;
|
|
195
195
|
if (!r) return;
|
|
196
|
-
const c =
|
|
196
|
+
const c = g(n, r);
|
|
197
197
|
if (!c) return;
|
|
198
198
|
const i = o.getFieldByPath(
|
|
199
199
|
r
|
|
200
|
-
), f = i?.length,
|
|
200
|
+
), f = i?.length, u = /* @__PURE__ */ new Map();
|
|
201
201
|
m(() => {
|
|
202
202
|
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
|
-
|
|
203
|
+
for (let a = 0; a < d; a++) {
|
|
204
|
+
const l = c.at(a);
|
|
205
|
+
if (l != null && (w.add(l), !u.has(l))) {
|
|
206
|
+
const b = i?.at?.(a) ?? N(r);
|
|
207
|
+
u.set(l, t.__run(l, b));
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
-
for (const [
|
|
211
|
-
w.has(
|
|
212
|
-
}),
|
|
213
|
-
for (const d of
|
|
214
|
-
|
|
210
|
+
for (const [a, l] of u)
|
|
211
|
+
w.has(a) || (l(), u.delete(a));
|
|
212
|
+
}), s(() => {
|
|
213
|
+
for (const d of u.values()) d();
|
|
214
|
+
u.clear();
|
|
215
215
|
});
|
|
216
216
|
}
|
|
217
217
|
function v(e) {
|
|
218
218
|
if (e == null || typeof e != "object") return;
|
|
219
|
-
const
|
|
220
|
-
if (typeof
|
|
221
|
-
const
|
|
222
|
-
for (let o = 0; o <
|
|
219
|
+
const t = e;
|
|
220
|
+
if (typeof t.at == "function" && typeof t.length == "number") {
|
|
221
|
+
const n = t.length;
|
|
222
|
+
for (let o = 0; o < n; o++) v(t.at(o));
|
|
223
223
|
return;
|
|
224
224
|
}
|
|
225
|
-
for (const
|
|
225
|
+
for (const n of Object.keys(e)) v(e[n]);
|
|
226
226
|
}
|
|
227
|
-
function te(e,
|
|
228
|
-
const { model:
|
|
227
|
+
function te(e, t) {
|
|
228
|
+
const { model: n } = y(), o = e.__path;
|
|
229
229
|
if (!o) return;
|
|
230
|
-
const r =
|
|
231
|
-
r &&
|
|
230
|
+
const r = g(n, o);
|
|
231
|
+
r && H(
|
|
232
232
|
e,
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
T(({ model: c }) => {
|
|
234
|
+
R(t(c), (i) => {
|
|
235
235
|
if (i)
|
|
236
236
|
for (let f = 0; f < r.length; f++) {
|
|
237
|
-
const
|
|
238
|
-
if (
|
|
239
|
-
const d =
|
|
237
|
+
const u = r.at(f);
|
|
238
|
+
if (u && u !== c) {
|
|
239
|
+
const d = t(u);
|
|
240
240
|
d.peek() && (d.value = !1);
|
|
241
241
|
}
|
|
242
242
|
}
|
|
@@ -244,57 +244,57 @@ function te(e, n) {
|
|
|
244
244
|
})
|
|
245
245
|
);
|
|
246
246
|
}
|
|
247
|
-
function
|
|
248
|
-
const { model:
|
|
247
|
+
function oe(e, t) {
|
|
248
|
+
const { model: n } = y(), o = e.__path;
|
|
249
249
|
if (!o) return;
|
|
250
|
-
const r =
|
|
250
|
+
const r = g(n, o);
|
|
251
251
|
if (!r) return;
|
|
252
252
|
let c = !1, i = 0;
|
|
253
253
|
m(() => {
|
|
254
|
-
v(r), !c && (c = !0,
|
|
254
|
+
v(r), !c && (c = !0, _(() => {
|
|
255
255
|
c = !1;
|
|
256
|
-
const f =
|
|
257
|
-
let
|
|
256
|
+
const f = t(r.toArray());
|
|
257
|
+
let u = !1;
|
|
258
258
|
for (const { index: d, patch: w } of f) {
|
|
259
|
-
const
|
|
260
|
-
if (
|
|
259
|
+
const a = r.at(d);
|
|
260
|
+
if (a)
|
|
261
261
|
for (const [l, b] of Object.entries(w))
|
|
262
|
-
|
|
262
|
+
a[l] !== b && (a[l] = b, u = !0);
|
|
263
263
|
}
|
|
264
|
-
if (i =
|
|
264
|
+
if (i = u ? i + 1 : 0, i > 50)
|
|
265
265
|
throw i = 0, new Error(
|
|
266
266
|
`[@reformer/core/behaviors] aggregateInto("${o}"): запись не сходится (>50 проходов) — derive должна быть идемпотентной на фикспоинте.`
|
|
267
267
|
);
|
|
268
268
|
}));
|
|
269
269
|
});
|
|
270
270
|
}
|
|
271
|
-
function
|
|
272
|
-
const { form:
|
|
273
|
-
for (const o of
|
|
271
|
+
function re(e, t) {
|
|
272
|
+
const { form: n } = y();
|
|
273
|
+
for (const o of B(e)) {
|
|
274
274
|
const r = o, c = r.__path;
|
|
275
275
|
if (!c) continue;
|
|
276
|
-
const i =
|
|
277
|
-
|
|
276
|
+
const i = n ? n.getFieldByPath(c) : void 0, f = i ? i.getProxy?.() ?? i : void 0;
|
|
277
|
+
s(t.__run(V(r), f));
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
export {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
281
|
+
oe as aggregateInto,
|
|
282
|
+
re as apply,
|
|
283
|
+
H as applyEach,
|
|
284
|
+
L as compute,
|
|
285
|
+
Q as computeFrom,
|
|
286
|
+
U as copyFrom,
|
|
287
|
+
_ as defer,
|
|
288
|
+
T as defineFormBehavior,
|
|
289
|
+
X as disableWhen,
|
|
290
290
|
m as effect,
|
|
291
|
-
|
|
291
|
+
q as enableWhen,
|
|
292
292
|
te as exclusiveFlag,
|
|
293
293
|
y as getScope,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
294
|
+
R as onChange,
|
|
295
|
+
s as onDispose,
|
|
296
|
+
Z as resetWhen,
|
|
297
|
+
ne as revalidateWhen,
|
|
298
|
+
ee as syncFields,
|
|
299
|
+
Y as transformValue
|
|
300
300
|
};
|
|
@@ -57,10 +57,25 @@ export declare function watchField<T>(source: ReadonlySignal<T>, cb: (value: T)
|
|
|
57
57
|
* ⚠️ Поле должно быть материализовано в форме (`createForm`) — иначе ноды в реестре нет (например,
|
|
58
58
|
* элемент массива, который строится per-item).
|
|
59
59
|
*
|
|
60
|
+
* ⚠️ Если то же поле одновременно является целью `compute`/`computeFrom`, обязательно ограничь
|
|
61
|
+
* compute тем же условием (`when`), что и это `enableWhen`. Механизмы не согласованы: `enableWhen`
|
|
62
|
+
* работает со статус-машиной ноды (enable/disable/reset), а `compute` пишет `target.value` напрямую
|
|
63
|
+
* в сигнал модели и проверяет только собственный `options.when` — состояние `disabled` он НЕ смотрит.
|
|
64
|
+
* Поэтому при выключенном поле любое изменение зависимости compute молча заново заполнит его (а с
|
|
65
|
+
* `resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()`/`submit`
|
|
66
|
+
* включают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,
|
|
67
|
+
* которое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.
|
|
68
|
+
*
|
|
60
69
|
* @group Model
|
|
61
70
|
* @example
|
|
62
71
|
* ```typescript
|
|
63
72
|
* enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
|
|
73
|
+
*
|
|
74
|
+
* // Поле-цель compute, которое ТАКЖЕ в resetOnDisable enableWhen: compute обязан нести тот же when,
|
|
75
|
+
* // иначе initialPayment заново заполнится в выключенном состоянии и утечёт в submit.
|
|
76
|
+
* compute(model.$.initialPayment, () => model.propertyValue * 0.2, {
|
|
77
|
+
* when: () => model.loanType === 'mortgage',
|
|
78
|
+
* });
|
|
64
79
|
* ```
|
|
65
80
|
*/
|
|
66
81
|
export declare function enableWhen(target: ReadonlySignal<unknown>, condition: () => boolean, options?: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReadonlySignal } from '@preact/signals-core';
|
|
1
|
+
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
|
2
2
|
import { FormNode, SetValueOptions } from './form-node';
|
|
3
3
|
import { ValidationError, FieldStatus, FormSchema, GroupNodeConfig, FormValue } from '../types';
|
|
4
4
|
import { FormProxy } from '../types/form-proxy';
|
|
@@ -54,6 +54,18 @@ export declare class GroupNode<T> extends FormNode<T> {
|
|
|
54
54
|
private readonly _disabled;
|
|
55
55
|
/** Form-level validation errors */
|
|
56
56
|
private readonly _formErrors;
|
|
57
|
+
/**
|
|
58
|
+
* M1: валидация уровня модели/схемы (`validateFormModel(model, schema)`). Привязывается
|
|
59
|
+
* `createForm({ model, schema })`, т.к. под M1 валидаторы срезаны с FieldNode и живут на слое
|
|
60
|
+
* модели. Без неё `validate()`/`submit()` пропускали бы все schema-валидаторы. @internal
|
|
61
|
+
*/
|
|
62
|
+
private _modelValidate?;
|
|
63
|
+
/**
|
|
64
|
+
* M1: связь листовой ноды-ребёнка → её сигнал модели (тот, что помечает `markDerived`).
|
|
65
|
+
* Нужна bulk-сеттерам (`setValue`/`patchValue`), чтобы корректно определять derived-поля:
|
|
66
|
+
* `field.value` — computed-обёртка, отличная от записываемого сигнала модели. @internal
|
|
67
|
+
*/
|
|
68
|
+
private readonly _fieldSignals;
|
|
57
69
|
readonly value: ReadonlySignal<T>;
|
|
58
70
|
readonly valid: ReadonlySignal<boolean>;
|
|
59
71
|
readonly invalid: ReadonlySignal<boolean>;
|
|
@@ -156,7 +168,12 @@ export declare class GroupNode<T> extends FormNode<T> {
|
|
|
156
168
|
*
|
|
157
169
|
* @param onSubmit - Callback для отправки данных
|
|
158
170
|
* @param options - Опции submit (skipValidation, skipTouch)
|
|
159
|
-
* @returns Результат от onSubmit или null если валидация не пройдена
|
|
171
|
+
* @returns Результат от onSubmit или `null` если валидация не пройдена
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* `null` перегружен: он означает и «валидация не пройдена», и легитимный `null`-результат
|
|
175
|
+
* `onSubmit` (или void-обработчик). Если вызывающей стороне нужно различать эти случаи —
|
|
176
|
+
* используйте {@link submitWithResult}, который возвращает явный флаг `success`.
|
|
160
177
|
*/
|
|
161
178
|
submit<R>(onSubmit: (values: T) => Promise<R> | R, options?: SubmitOptions): Promise<R | null>;
|
|
162
179
|
/**
|
|
@@ -264,6 +281,19 @@ export declare class GroupNode<T> extends FormNode<T> {
|
|
|
264
281
|
* @internal
|
|
265
282
|
*/
|
|
266
283
|
attachBehaviorCleanup(cleanup: () => void): void;
|
|
284
|
+
/**
|
|
285
|
+
* Прикрепить валидацию уровня модели/схемы (M1). Вызывается `createForm({ model, schema })`,
|
|
286
|
+
* чтобы `validate()`/`submit()` прогоняли schema-валидаторы (`validateFormModel`), а не только
|
|
287
|
+
* пустые FieldNode. Возвращаемый флаг — валидна ли модель по схеме.
|
|
288
|
+
* @internal
|
|
289
|
+
*/
|
|
290
|
+
attachModelValidator(validate: () => Promise<boolean>): void;
|
|
291
|
+
/**
|
|
292
|
+
* Связать листовую ноду-ребёнка с её сигналом модели (M1). Вызывается `createForm` для каждого
|
|
293
|
+
* листового поля на его владеющей группе — используется bulk-сеттерами для derived-guard (F9).
|
|
294
|
+
* @internal
|
|
295
|
+
*/
|
|
296
|
+
registerFieldSignal(node: FormNode<any>, signal: Signal<unknown>): void;
|
|
267
297
|
/**
|
|
268
298
|
* Очистить все ресурсы узла
|
|
269
299
|
*/
|
|
@@ -58,6 +58,7 @@ export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
58
58
|
private readonly buildItem;
|
|
59
59
|
private readonly itemNodes;
|
|
60
60
|
private readonly cache;
|
|
61
|
+
private mountedModels;
|
|
61
62
|
private readonly initial;
|
|
62
63
|
private readonly _arrayErrors;
|
|
63
64
|
private readonly disposeSync;
|
|
@@ -109,6 +110,12 @@ export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
|
|
|
109
110
|
protected onMarkAsPristine(): void;
|
|
110
111
|
protected onDisable(): void;
|
|
111
112
|
protected onEnable(): void;
|
|
112
|
-
/**
|
|
113
|
+
/** Диспозит форму элемента (эффекты/поведения/вложенные подписки); терпима к не-GroupNode. */
|
|
114
|
+
private disposeItemNode;
|
|
115
|
+
/**
|
|
116
|
+
* Очистка подписки синхронизации И детерминированный teardown всех форм элементов.
|
|
117
|
+
* Без пробрасывания dispose в itemNodes эффекты/поведения каждой формы элемента (в т.ч. disposeSync
|
|
118
|
+
* вложенных ModelArrayNode) остались бы жить после teardown формы — утечка подписок.
|
|
119
|
+
*/
|
|
113
120
|
dispose(): void;
|
|
114
121
|
}
|
|
@@ -37,6 +37,27 @@ export interface FieldConfig<T> {
|
|
|
37
37
|
/** Задержка (в мс) перед запуском асинхронной валидации */
|
|
38
38
|
debounce?: number;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* «Хотя бы одно поле обязано присутствовать»: превращает объект со всеми опциональными
|
|
42
|
+
* свойствами в объединение вариантов, где ровно один ключ обязателен, а остальные опциональны.
|
|
43
|
+
* Пустой объект `{}` не assignable ни к одному варианту и потому отклоняется.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
type RequireAtLeastOne<T> = {
|
|
48
|
+
[K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
|
|
49
|
+
}[keyof T];
|
|
50
|
+
/**
|
|
51
|
+
* {@link FieldConfig}, из которого нельзя сделать пустой `{}`.
|
|
52
|
+
*
|
|
53
|
+
* Все свойства {@link FieldConfig} опциональны, поэтому в {@link FormSchema} опечатка вида
|
|
54
|
+
* `{ email: {} }` молча проходит проверку типов и в рантайме становится пустой подгруппой
|
|
55
|
+
* вместо поля. `NonEmptyFieldConfig` требует наличия хотя бы одного свойства (value/valueSignal/
|
|
56
|
+
* component/validators/…), оставляя каждое из них опциональным по отдельности.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
type NonEmptyFieldConfig<T> = RequireAtLeastOne<FieldConfig<T>>;
|
|
40
61
|
/**
|
|
41
62
|
* Конфигурация массива
|
|
42
63
|
* @group Types
|
|
@@ -90,5 +111,6 @@ export interface ArrayConfig<T extends object> {
|
|
|
90
111
|
* ```
|
|
91
112
|
*/
|
|
92
113
|
export type FormSchema<T> = {
|
|
93
|
-
[K in keyof T]: NonNullable<T[K]> extends string | number | boolean ?
|
|
114
|
+
[K in keyof T]: NonNullable<T[K]> extends string | number | boolean ? NonEmptyFieldConfig<T[K]> : NonNullable<T[K]> extends Array<infer U> ? U extends string | number | boolean ? NonEmptyFieldConfig<T[K]> : U extends Date | File | Blob | AnyFunction ? NonEmptyFieldConfig<T[K]> : [FormSchema<U>] : NonNullable<T[K]> extends Date | File | Blob | AnyFunction ? NonEmptyFieldConfig<T[K]> : FormSchema<NonNullable<T[K]>>;
|
|
94
115
|
};
|
|
116
|
+
export {};
|
|
@@ -64,8 +64,33 @@ export type FormControlsProxy<T> = {
|
|
|
64
64
|
* form.email.setValue('test@mail.com');
|
|
65
65
|
* form.profile.name.setValue('John');
|
|
66
66
|
* ```
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* **Зарезервированные имена.** Прямой доступ `form.<name>` отдаёт приоритет собственным
|
|
70
|
+
* членам {@link GroupNode} (`value`, `status`, `id`, `errors`, `disabled`, `reset`, `submit`, …).
|
|
71
|
+
* Поэтому поле модели, названное любым из этих имён, недостижимо как `form.<name>` — там будет
|
|
72
|
+
* агрегированный сигнал/метод узла, а не {@link FieldNode}. Такие имена исключены из полевого
|
|
73
|
+
* пространства имён на уровне типов (через `Omit<…, keyof GroupNode>`), чтобы затенение не
|
|
74
|
+
* компилировалось молча. Для доступа к любому полю по имени — включая затенённые — используйте
|
|
75
|
+
* escape-hatch пространство {@link FormProxy.$}: `form.$.status`, `form.$.id`, `form.$.value`.
|
|
67
76
|
*/
|
|
68
|
-
export type FormProxy<T> = GroupNode<T> & FormControlsProxy<T
|
|
77
|
+
export type FormProxy<T> = GroupNode<T> & Omit<FormControlsProxy<T>, keyof GroupNode<T>> & {
|
|
78
|
+
/**
|
|
79
|
+
* Escape-hatch пространство имён «controls»: типобезопасный доступ ко ВСЕМ полям формы
|
|
80
|
+
* по имени, включая поля, чьи имена совпадают с членами {@link GroupNode}
|
|
81
|
+
* (`value`/`status`/`id`/`errors`/…), недостижимые через `form.<name>`.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // модель: { status: string; email: string }
|
|
86
|
+
* form.status; // ReadonlySignal<FieldStatus> — агрегат GroupNode (не поле!)
|
|
87
|
+
* form.$.status; // FieldNode<string> — поле пользователя
|
|
88
|
+
* form.$.status.setValue('active');
|
|
89
|
+
* form.$.email; // FieldNode<string> — так же доступны и незатенённые поля
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
readonly $: FormControlsProxy<T>;
|
|
93
|
+
};
|
|
69
94
|
/**
|
|
70
95
|
* Комбинированный тип для ArrayNode с Proxy доступом к элементам
|
|
71
96
|
*
|