@reformer/core 1.1.0-beta.6 → 1.1.0-beta.8
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-BRaiR-UY.js +528 -0
- package/dist/behaviors.d.ts +6 -2
- package/dist/behaviors.js +18 -227
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3380 -10
- package/dist/validators-DjXtDVoE.js +455 -0
- package/dist/validators.d.ts +6 -2
- package/dist/validators.js +29 -281
- package/package.json +1 -1
- package/dist/core/behavior/behavior-applicator.js +0 -92
- package/dist/core/behavior/behavior-context.js +0 -43
- package/dist/core/behavior/behavior-registry.js +0 -198
- package/dist/core/behavior/behaviors/compute-from.js +0 -84
- package/dist/core/behavior/behaviors/copy-from.js +0 -64
- package/dist/core/behavior/behaviors/enable-when.js +0 -81
- package/dist/core/behavior/behaviors/index.js +0 -11
- package/dist/core/behavior/behaviors/reset-when.js +0 -63
- package/dist/core/behavior/behaviors/revalidate-when.js +0 -51
- package/dist/core/behavior/behaviors/sync-fields.js +0 -66
- package/dist/core/behavior/behaviors/transform-value.js +0 -110
- package/dist/core/behavior/behaviors/watch-field.js +0 -56
- package/dist/core/behavior/compose-behavior.js +0 -166
- package/dist/core/behavior/create-field-path.js +0 -69
- package/dist/core/behavior/index.js +0 -17
- package/dist/core/behavior/types.js +0 -7
- package/dist/core/context/form-context-impl.js +0 -37
- package/dist/core/factories/index.js +0 -6
- package/dist/core/factories/node-factory.js +0 -281
- package/dist/core/nodes/array-node.js +0 -534
- package/dist/core/nodes/field-node.js +0 -510
- package/dist/core/nodes/form-node.js +0 -343
- package/dist/core/nodes/group-node/field-registry.js +0 -215
- package/dist/core/nodes/group-node/index.js +0 -11
- package/dist/core/nodes/group-node/proxy-builder.js +0 -161
- package/dist/core/nodes/group-node/state-manager.js +0 -265
- package/dist/core/nodes/group-node.js +0 -770
- package/dist/core/types/deep-schema.js +0 -11
- package/dist/core/types/field-path.js +0 -4
- package/dist/core/types/form-context.js +0 -25
- package/dist/core/types/group-node-proxy.js +0 -31
- package/dist/core/types/index.js +0 -4
- package/dist/core/types/validation-schema.js +0 -10
- package/dist/core/utils/create-form.js +0 -24
- package/dist/core/utils/debounce.js +0 -197
- package/dist/core/utils/error-handler.js +0 -226
- package/dist/core/utils/field-path-navigator.js +0 -374
- package/dist/core/utils/index.js +0 -14
- package/dist/core/utils/registry-helpers.js +0 -79
- package/dist/core/utils/registry-stack.js +0 -86
- package/dist/core/utils/resources.js +0 -69
- package/dist/core/utils/subscription-manager.js +0 -214
- package/dist/core/utils/type-guards.js +0 -169
- package/dist/core/validation/core/apply-when.js +0 -41
- package/dist/core/validation/core/apply.js +0 -38
- package/dist/core/validation/core/index.js +0 -8
- package/dist/core/validation/core/validate-async.js +0 -45
- package/dist/core/validation/core/validate-tree.js +0 -43
- package/dist/core/validation/core/validate.js +0 -38
- package/dist/core/validation/field-path.js +0 -147
- package/dist/core/validation/index.js +0 -33
- package/dist/core/validation/validate-form.js +0 -152
- package/dist/core/validation/validation-applicator.js +0 -217
- package/dist/core/validation/validation-context.js +0 -75
- package/dist/core/validation/validation-registry.js +0 -298
- package/dist/core/validation/validators/array-validators.js +0 -86
- package/dist/core/validation/validators/date.js +0 -117
- package/dist/core/validation/validators/email.js +0 -60
- package/dist/core/validation/validators/index.js +0 -14
- package/dist/core/validation/validators/max-length.js +0 -60
- package/dist/core/validation/validators/max.js +0 -60
- package/dist/core/validation/validators/min-length.js +0 -60
- package/dist/core/validation/validators/min.js +0 -60
- package/dist/core/validation/validators/number.js +0 -90
- package/dist/core/validation/validators/pattern.js +0 -62
- package/dist/core/validation/validators/phone.js +0 -58
- package/dist/core/validation/validators/required.js +0 -69
- package/dist/core/validation/validators/url.js +0 -55
- package/dist/create-field-path-nXfTtl55.js +0 -283
- package/dist/hooks/useFormControl.js +0 -298
- package/dist/validation-context-cWXmh_Ho.js +0 -156
package/dist/validators.js
CHANGED
|
@@ -1,283 +1,31 @@
|
|
|
1
|
-
import { e as
|
|
2
|
-
import {
|
|
3
|
-
import { g as u } from "./registry-helpers-BfCZcMkO.js";
|
|
4
|
-
import { V as j } from "./registry-helpers-BfCZcMkO.js";
|
|
5
|
-
function n(r, e, a) {
|
|
6
|
-
if (!r) return;
|
|
7
|
-
const m = c(r);
|
|
8
|
-
u().registerSync(m, e, a);
|
|
9
|
-
}
|
|
10
|
-
function A(r, e, a) {
|
|
11
|
-
const m = c(r);
|
|
12
|
-
u().registerAsync(m, e, a);
|
|
13
|
-
}
|
|
14
|
-
function _(r, e) {
|
|
15
|
-
u().registerTree(r, e);
|
|
16
|
-
}
|
|
17
|
-
function $(r, e) {
|
|
18
|
-
if (!Array.isArray(r) && !Array.isArray(e) && r && !("__key" in r) && !("__path" in r)) {
|
|
19
|
-
e(r);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const a = (Array.isArray(r) ? r : [r]).filter(
|
|
23
|
-
Boolean
|
|
24
|
-
), m = Array.isArray(e) ? e : [e];
|
|
25
|
-
for (const s of a) {
|
|
26
|
-
const t = l(s);
|
|
27
|
-
for (const g of m)
|
|
28
|
-
g(t);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function o(r, e, a) {
|
|
32
|
-
const m = c(r);
|
|
33
|
-
u().enterCondition(m, e);
|
|
34
|
-
try {
|
|
35
|
-
const s = i();
|
|
36
|
-
a(s);
|
|
37
|
-
} finally {
|
|
38
|
-
u().exitCondition();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function w(r, e) {
|
|
42
|
-
r && n(r, (a) => a == null || a === "" ? {
|
|
43
|
-
code: "required",
|
|
44
|
-
message: e?.message || "Поле обязательно для заполнения",
|
|
45
|
-
params: e?.params
|
|
46
|
-
} : typeof a == "boolean" && a !== !0 ? {
|
|
47
|
-
code: "required",
|
|
48
|
-
message: e?.message || "Поле обязательно для заполнения",
|
|
49
|
-
params: e?.params
|
|
50
|
-
} : null);
|
|
51
|
-
}
|
|
52
|
-
function D(r, e, a) {
|
|
53
|
-
r && n(r, (m) => m == null ? null : m < e ? {
|
|
54
|
-
code: "min",
|
|
55
|
-
message: a?.message || `Минимальное значение: ${e}`,
|
|
56
|
-
params: { min: e, actual: m, ...a?.params }
|
|
57
|
-
} : null);
|
|
58
|
-
}
|
|
59
|
-
function h(r, e, a) {
|
|
60
|
-
r && n(r, (m) => m == null ? null : m > e ? {
|
|
61
|
-
code: "max",
|
|
62
|
-
message: a?.message || `Максимальное значение: ${e}`,
|
|
63
|
-
params: { max: e, actual: m, ...a?.params }
|
|
64
|
-
} : null);
|
|
65
|
-
}
|
|
66
|
-
function d(r, e, a) {
|
|
67
|
-
r && n(r, (m) => m && m.length < e ? {
|
|
68
|
-
code: "minLength",
|
|
69
|
-
message: a?.message || `Минимальная длина: ${e} символов`,
|
|
70
|
-
params: { minLength: e, actualLength: m.length, ...a?.params }
|
|
71
|
-
} : null);
|
|
72
|
-
}
|
|
73
|
-
function b(r, e, a) {
|
|
74
|
-
r && n(r, (m) => m && m.length > e ? {
|
|
75
|
-
code: "maxLength",
|
|
76
|
-
message: a?.message || `Максимальная длина: ${e} символов`,
|
|
77
|
-
params: { maxLength: e, actualLength: m.length, ...a?.params }
|
|
78
|
-
} : null);
|
|
79
|
-
}
|
|
80
|
-
function L(r, e) {
|
|
81
|
-
if (!r) return;
|
|
82
|
-
const a = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
83
|
-
n(r, (m) => m ? a.test(m) ? null : {
|
|
84
|
-
code: "email",
|
|
85
|
-
message: e?.message || "Неверный формат email",
|
|
86
|
-
params: e?.params
|
|
87
|
-
} : null);
|
|
88
|
-
}
|
|
89
|
-
function R(r, e, a) {
|
|
90
|
-
r && n(r, (m) => m ? e.test(m) ? null : {
|
|
91
|
-
code: "pattern",
|
|
92
|
-
message: a?.message || "Значение не соответствует требуемому формату",
|
|
93
|
-
params: { pattern: e.source, ...a?.params }
|
|
94
|
-
} : null);
|
|
95
|
-
}
|
|
96
|
-
function T(r, e) {
|
|
97
|
-
if (!r) return;
|
|
98
|
-
const a = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/i, m = /^https?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/i;
|
|
99
|
-
n(r, (s) => s ? (e?.requireProtocol ? m : a).test(s) ? e?.allowedProtocols && e.allowedProtocols.length > 0 && !e.allowedProtocols.some(
|
|
100
|
-
(f) => s.toLowerCase().startsWith(`${f}://`)
|
|
101
|
-
) ? {
|
|
102
|
-
code: "url_protocol",
|
|
103
|
-
message: e?.message || `URL должен использовать один из протоколов: ${e.allowedProtocols.join(", ")}`,
|
|
104
|
-
params: { allowedProtocols: e.allowedProtocols, ...e?.params }
|
|
105
|
-
} : null : {
|
|
106
|
-
code: "url",
|
|
107
|
-
message: e?.message || "Неверный формат URL",
|
|
108
|
-
params: e?.params
|
|
109
|
-
} : null);
|
|
110
|
-
}
|
|
111
|
-
function C(r, e) {
|
|
112
|
-
if (!r) return;
|
|
113
|
-
const a = e?.format || "any", m = {
|
|
114
|
-
// Международный формат: +1234567890 или +1 234 567 8900
|
|
115
|
-
international: /^\+?[1-9]\d{1,14}$/,
|
|
116
|
-
// Российский формат: +7 (XXX) XXX-XX-XX, 8 (XXX) XXX-XX-XX, и вариации
|
|
117
|
-
ru: /^(\+7|7|8)?[\s\-]?\(?[489][0-9]{2}\)?[\s\-]?[0-9]{3}[\s\-]?[0-9]{2}[\s\-]?[0-9]{2}$/,
|
|
118
|
-
// US формат: (123) 456-7890, 123-456-7890, 1234567890
|
|
119
|
-
us: /^(\+?1)?[\s\-]?\(?[2-9]\d{2}\)?[\s\-]?\d{3}[\s\-]?\d{4}$/,
|
|
120
|
-
// Любой формат: минимум 10 цифр с возможными разделителями
|
|
121
|
-
any: /^[\+]?[(]?[0-9]{1,4}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[0-9]{1,9}$/
|
|
122
|
-
};
|
|
123
|
-
n(r, (s) => {
|
|
124
|
-
if (!s)
|
|
125
|
-
return null;
|
|
126
|
-
if (!m[a].test(s)) {
|
|
127
|
-
const g = {
|
|
128
|
-
international: "Введите телефон в международном формате (например, +1234567890)",
|
|
129
|
-
ru: "Введите российский номер телефона (например, +7 900 123-45-67)",
|
|
130
|
-
us: "Введите американский номер телефона (например, (123) 456-7890)",
|
|
131
|
-
any: "Неверный формат телефона"
|
|
132
|
-
};
|
|
133
|
-
return {
|
|
134
|
-
code: "phone",
|
|
135
|
-
message: e?.message || g[a],
|
|
136
|
-
params: { format: a, ...e?.params }
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
function N(r, e) {
|
|
143
|
-
r && n(r, (a) => a == null ? null : typeof a != "number" || isNaN(a) ? {
|
|
144
|
-
code: "number",
|
|
145
|
-
message: e?.message || "Значение должно быть числом",
|
|
146
|
-
params: e?.params
|
|
147
|
-
} : e?.integer && !Number.isInteger(a) ? {
|
|
148
|
-
code: "number_integer",
|
|
149
|
-
message: e?.message || "Значение должно быть целым числом",
|
|
150
|
-
params: e?.params
|
|
151
|
-
} : e?.min !== void 0 && a < e.min ? {
|
|
152
|
-
code: "number_min",
|
|
153
|
-
message: e?.message || `Значение должно быть не менее ${e.min}`,
|
|
154
|
-
params: { min: e.min, ...e?.params }
|
|
155
|
-
} : e?.max !== void 0 && a > e.max ? {
|
|
156
|
-
code: "number_max",
|
|
157
|
-
message: e?.message || `Значение должно быть не более ${e.max}`,
|
|
158
|
-
params: { max: e.max, ...e?.params }
|
|
159
|
-
} : e?.multipleOf !== void 0 && a % e.multipleOf !== 0 ? {
|
|
160
|
-
code: "number_multiple",
|
|
161
|
-
message: e?.message || `Значение должно быть кратно ${e.multipleOf}`,
|
|
162
|
-
params: { multipleOf: e.multipleOf, ...e?.params }
|
|
163
|
-
} : e?.allowNegative === !1 && a < 0 ? {
|
|
164
|
-
code: "number_negative",
|
|
165
|
-
message: e?.message || "Отрицательные числа не допускаются",
|
|
166
|
-
params: e?.params
|
|
167
|
-
} : e?.allowZero === !1 && a === 0 ? {
|
|
168
|
-
code: "number_zero",
|
|
169
|
-
message: e?.message || "Ноль не допускается",
|
|
170
|
-
params: e?.params
|
|
171
|
-
} : null);
|
|
172
|
-
}
|
|
173
|
-
function q(r, e) {
|
|
174
|
-
r && n(r, (a) => {
|
|
175
|
-
if (!a)
|
|
176
|
-
return null;
|
|
177
|
-
let m;
|
|
178
|
-
if (a instanceof Date)
|
|
179
|
-
m = a;
|
|
180
|
-
else if (typeof a == "string")
|
|
181
|
-
m = new Date(a);
|
|
182
|
-
else
|
|
183
|
-
return {
|
|
184
|
-
code: "date_invalid",
|
|
185
|
-
message: e?.message || "Неверный формат даты",
|
|
186
|
-
params: e?.params
|
|
187
|
-
};
|
|
188
|
-
if (isNaN(m.getTime()))
|
|
189
|
-
return {
|
|
190
|
-
code: "date_invalid",
|
|
191
|
-
message: e?.message || "Неверный формат даты",
|
|
192
|
-
params: e?.params
|
|
193
|
-
};
|
|
194
|
-
const s = /* @__PURE__ */ new Date();
|
|
195
|
-
if (s.setHours(0, 0, 0, 0), e?.minDate) {
|
|
196
|
-
const t = new Date(e.minDate);
|
|
197
|
-
if (t.setHours(0, 0, 0, 0), m < t)
|
|
198
|
-
return {
|
|
199
|
-
code: "date_min",
|
|
200
|
-
message: e?.message || `Дата должна быть не ранее ${t.toLocaleDateString()}`,
|
|
201
|
-
params: { minDate: e.minDate, ...e?.params }
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
if (e?.maxDate) {
|
|
205
|
-
const t = new Date(e.maxDate);
|
|
206
|
-
if (t.setHours(0, 0, 0, 0), m > t)
|
|
207
|
-
return {
|
|
208
|
-
code: "date_max",
|
|
209
|
-
message: e?.message || `Дата должна быть не позднее ${t.toLocaleDateString()}`,
|
|
210
|
-
params: { maxDate: e.maxDate, ...e?.params }
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
if (e?.noFuture && m > s)
|
|
214
|
-
return {
|
|
215
|
-
code: "date_future",
|
|
216
|
-
message: e?.message || "Дата не может быть в будущем",
|
|
217
|
-
params: e?.params
|
|
218
|
-
};
|
|
219
|
-
if (e?.noPast && m < s)
|
|
220
|
-
return {
|
|
221
|
-
code: "date_past",
|
|
222
|
-
message: e?.message || "Дата не может быть в прошлом",
|
|
223
|
-
params: e?.params
|
|
224
|
-
};
|
|
225
|
-
if (e?.minAge !== void 0 || e?.maxAge !== void 0) {
|
|
226
|
-
const t = Math.floor(
|
|
227
|
-
(s.getTime() - m.getTime()) / 315576e5
|
|
228
|
-
);
|
|
229
|
-
if (e?.minAge !== void 0 && t < e.minAge)
|
|
230
|
-
return {
|
|
231
|
-
code: "date_min_age",
|
|
232
|
-
message: e?.message || `Минимальный возраст: ${e.minAge} лет`,
|
|
233
|
-
params: { minAge: e.minAge, currentAge: t, ...e?.params }
|
|
234
|
-
};
|
|
235
|
-
if (e?.maxAge !== void 0 && t > e.maxAge)
|
|
236
|
-
return {
|
|
237
|
-
code: "date_max_age",
|
|
238
|
-
message: e?.message || `Максимальный возраст: ${e.maxAge} лет`,
|
|
239
|
-
params: { maxAge: e.maxAge, currentAge: t, ...e?.params }
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
return null;
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
function z(r, e) {
|
|
246
|
-
r && d(r, 1, {
|
|
247
|
-
message: e?.message || "Массив не должен быть пустым",
|
|
248
|
-
params: { minLength: 1, ...e?.params }
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
function I(r, e) {
|
|
252
|
-
if (!r) return;
|
|
253
|
-
const a = c(r);
|
|
254
|
-
u().registerArrayItemValidation(a, e);
|
|
255
|
-
}
|
|
1
|
+
import { T as e, V as s, k as i, l, c as r, x as n, a as m, q as o, B as d, A as p, n as x, p as h, m as y, o as v, y as c, w as u, s as V, t as f, r as g, C, u as F, f as I, h as P, v as T, z as q, j as A } from "./validators-DjXtDVoE.js";
|
|
2
|
+
import { V as b } from "./registry-helpers-BfCZcMkO.js";
|
|
256
3
|
export {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
4
|
+
e as TreeValidationContextImpl,
|
|
5
|
+
s as ValidationContextImpl,
|
|
6
|
+
b as ValidationRegistry,
|
|
7
|
+
i as apply,
|
|
8
|
+
l as applyWhen,
|
|
9
|
+
r as createFieldPath,
|
|
10
|
+
n as date,
|
|
11
|
+
m as default,
|
|
12
|
+
o as email,
|
|
13
|
+
d as extractKey,
|
|
14
|
+
p as extractPath,
|
|
15
|
+
x as max,
|
|
16
|
+
h as maxLength,
|
|
17
|
+
y as min,
|
|
18
|
+
v as minLength,
|
|
19
|
+
c as notEmpty,
|
|
20
|
+
u as number,
|
|
21
|
+
V as pattern,
|
|
22
|
+
f as phone,
|
|
23
|
+
g as required,
|
|
24
|
+
C as toFieldPath,
|
|
25
|
+
F as url,
|
|
26
|
+
I as validate,
|
|
27
|
+
P as validateAsync,
|
|
28
|
+
T as validateForm,
|
|
29
|
+
q as validateItems,
|
|
30
|
+
A as validateTree
|
|
283
31
|
};
|
package/package.json
CHANGED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Применение behavior схемы к форме
|
|
3
|
-
*
|
|
4
|
-
* Извлечено из GroupNode для соблюдения SRP (Single Responsibility Principle).
|
|
5
|
-
* Управляет процессом регистрации и применения behaviors.
|
|
6
|
-
*
|
|
7
|
-
* @template T Тип формы
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* class GroupNode {
|
|
12
|
-
* private readonly behaviorApplicator = new BehaviorApplicator(this);
|
|
13
|
-
*
|
|
14
|
-
* applyBehaviorSchema(schemaFn: BehaviorSchemaFn<T>): () => void {
|
|
15
|
-
* return this.behaviorApplicator.apply(schemaFn);
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
import { createFieldPath as createBehaviorFieldPath } from './create-field-path';
|
|
21
|
-
import { FormErrorHandler, ErrorStrategy } from '../utils/error-handler';
|
|
22
|
-
/**
|
|
23
|
-
* Класс для применения behavior схемы к форме
|
|
24
|
-
*
|
|
25
|
-
* Выполняет:
|
|
26
|
-
* 1. Начало регистрации behaviors (beginRegistration)
|
|
27
|
-
* 2. Выполнение схемы (регистрация behaviors)
|
|
28
|
-
* 3. Завершение регистрации (endRegistration) - применение behaviors
|
|
29
|
-
* 4. Возврат функции cleanup для отписки
|
|
30
|
-
*
|
|
31
|
-
* @template T Тип формы (объект)
|
|
32
|
-
*/
|
|
33
|
-
export class BehaviorApplicator {
|
|
34
|
-
form;
|
|
35
|
-
behaviorRegistry;
|
|
36
|
-
constructor(form, behaviorRegistry) {
|
|
37
|
-
this.form = form;
|
|
38
|
-
this.behaviorRegistry = behaviorRegistry;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Применить behavior схему к форме
|
|
42
|
-
*
|
|
43
|
-
* Этапы:
|
|
44
|
-
* 1. Начать регистрацию (beginRegistration)
|
|
45
|
-
* 2. Выполнить схему (регистрация behaviors)
|
|
46
|
-
* 3. Завершить регистрацию (endRegistration) - применить behaviors
|
|
47
|
-
* 4. Вернуть функцию cleanup для отписки
|
|
48
|
-
*
|
|
49
|
-
* @param schemaFn Функция-схема behavior
|
|
50
|
-
* @returns Функция отписки от всех behaviors
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```typescript
|
|
54
|
-
* const cleanup = behaviorApplicator.apply((path) => {
|
|
55
|
-
* copyFrom(path.residenceAddress, path.registrationAddress, {
|
|
56
|
-
* when: (form) => form.sameAsRegistration === true
|
|
57
|
-
* });
|
|
58
|
-
*
|
|
59
|
-
* enableWhen(path.propertyValue, (form) => form.loanType === 'mortgage');
|
|
60
|
-
*
|
|
61
|
-
* computeFrom(
|
|
62
|
-
* path.initialPayment,
|
|
63
|
-
* [path.propertyValue],
|
|
64
|
-
* (propertyValue) => propertyValue ? propertyValue * 0.2 : null
|
|
65
|
-
* );
|
|
66
|
-
* });
|
|
67
|
-
*
|
|
68
|
-
* // Cleanup при unmount
|
|
69
|
-
* useEffect(() => cleanup, []);
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
apply(schemaFn) {
|
|
73
|
-
this.behaviorRegistry.beginRegistration();
|
|
74
|
-
try {
|
|
75
|
-
// 1. Создать field path для type-safe доступа к полям
|
|
76
|
-
const path = createBehaviorFieldPath();
|
|
77
|
-
// 2. Выполнить схему (регистрация behaviors)
|
|
78
|
-
schemaFn(path);
|
|
79
|
-
// 3. Завершить регистрацию и применить behaviors
|
|
80
|
-
// Используем публичный метод getProxy() для получения proxy-инстанса
|
|
81
|
-
const formToUse = this.form.getProxy();
|
|
82
|
-
const result = this.behaviorRegistry.endRegistration(formToUse);
|
|
83
|
-
// 4. Вернуть функцию cleanup
|
|
84
|
-
return result.cleanup;
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
FormErrorHandler.handle(error, 'BehaviorApplicator', ErrorStrategy.THROW);
|
|
88
|
-
// TypeScript требует return, но код никогда не дойдет сюда
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BehaviorContext - контекст для behavior callback функций
|
|
3
|
-
*
|
|
4
|
-
* Реализует FormContext для behavior схем
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Реализация BehaviorContext (FormContext)
|
|
8
|
-
*
|
|
9
|
-
* Предоставляет:
|
|
10
|
-
* - `form` - прямой типизированный доступ к форме
|
|
11
|
-
* - `setFieldValue` - безопасная установка значения (emitEvent: false)
|
|
12
|
-
*/
|
|
13
|
-
export class BehaviorContextImpl {
|
|
14
|
-
/**
|
|
15
|
-
* Форма с типизированным Proxy-доступом к полям
|
|
16
|
-
*/
|
|
17
|
-
form;
|
|
18
|
-
_form;
|
|
19
|
-
constructor(form) {
|
|
20
|
-
this._form = form;
|
|
21
|
-
// Используем _proxyInstance если доступен, иначе fallback на form
|
|
22
|
-
const proxy = (form
|
|
23
|
-
._proxyInstance || form);
|
|
24
|
-
this.form = proxy;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Безопасно установить значение поля по строковому пути или FieldPath
|
|
28
|
-
*
|
|
29
|
-
* Автоматически использует emitEvent: false для предотвращения циклов
|
|
30
|
-
*
|
|
31
|
-
* @param path - Строковый путь к полю или FieldPath объект
|
|
32
|
-
* @param value - Новое значение
|
|
33
|
-
*/
|
|
34
|
-
setFieldValue(path, value) {
|
|
35
|
-
// Преобразуем FieldPath в строку если необходимо
|
|
36
|
-
const pathStr = typeof path === 'string' ? path : path.toString();
|
|
37
|
-
const node = this._form.getFieldByPath(pathStr);
|
|
38
|
-
if (node) {
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
-
node.setValue(value, { emitEvent: false });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BehaviorRegistry - регистрация и управление behavior схемами
|
|
3
|
-
*
|
|
4
|
-
* Аналогично ValidationRegistry, но для реактивного поведения форм
|
|
5
|
-
*/
|
|
6
|
-
import { BehaviorContextImpl } from './behavior-context';
|
|
7
|
-
import { RegistryStack } from '../utils/registry-stack';
|
|
8
|
-
/**
|
|
9
|
-
* Реестр behaviors для формы
|
|
10
|
-
*
|
|
11
|
-
* Каждый экземпляр GroupNode создает собственный реестр (композиция).
|
|
12
|
-
* Устраняет race conditions и изолирует формы друг от друга.
|
|
13
|
-
*
|
|
14
|
-
* Context stack используется для tracking текущего активного реестра:
|
|
15
|
-
* - beginRegistration() помещает this в stack
|
|
16
|
-
* - endRegistration() извлекает из stack
|
|
17
|
-
* - getCurrent() возвращает текущий активный реестр
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* class GroupNode {
|
|
22
|
-
* private readonly behaviorRegistry = new BehaviorRegistry();
|
|
23
|
-
*
|
|
24
|
-
* applyBehaviorSchema(schemaFn) {
|
|
25
|
-
* this.behaviorRegistry.beginRegistration(); // Pushes this to stack
|
|
26
|
-
* schemaFn(createBehaviorFieldPath(this)); // Uses getCurrent()
|
|
27
|
-
* return this.behaviorRegistry.endRegistration(this); // Pops from stack
|
|
28
|
-
* }
|
|
29
|
-
* }
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export class BehaviorRegistry {
|
|
33
|
-
/**
|
|
34
|
-
* Stack активных контекстов регистрации
|
|
35
|
-
* Используется для изоляции форм друг от друга
|
|
36
|
-
*/
|
|
37
|
-
static contextStack = new RegistryStack();
|
|
38
|
-
registrations = [];
|
|
39
|
-
isRegistering = false;
|
|
40
|
-
/**
|
|
41
|
-
* Получить текущий активный реестр из context stack
|
|
42
|
-
*
|
|
43
|
-
* @returns Текущий активный реестр или null
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```typescript
|
|
47
|
-
* // В schema-behaviors.ts
|
|
48
|
-
* export function copyFrom(...) {
|
|
49
|
-
* const registry = BehaviorRegistry.getCurrent();
|
|
50
|
-
* if (registry) {
|
|
51
|
-
* registry.register({ ... });
|
|
52
|
-
* }
|
|
53
|
-
* }
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
static getCurrent() {
|
|
57
|
-
return BehaviorRegistry.contextStack.getCurrent();
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Начать регистрацию behaviors
|
|
61
|
-
* Вызывается перед применением схемы
|
|
62
|
-
*
|
|
63
|
-
* Помещает this в context stack для изоляции форм
|
|
64
|
-
*/
|
|
65
|
-
beginRegistration() {
|
|
66
|
-
this.isRegistering = true;
|
|
67
|
-
this.registrations = [];
|
|
68
|
-
// Помещаем this в stack для tracking текущего активного реестра
|
|
69
|
-
BehaviorRegistry.contextStack.push(this);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Зарегистрировать behavior handler
|
|
73
|
-
* Вызывается функциями из schema-behaviors.ts
|
|
74
|
-
*
|
|
75
|
-
* @param handler - BehaviorHandlerFn функция
|
|
76
|
-
* @param options - Опции behavior (debounce)
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```typescript
|
|
80
|
-
* const handler = createCopyBehavior(target, source, { when: ... });
|
|
81
|
-
* registry.register(handler, { debounce: 300 });
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
register(handler, options) {
|
|
85
|
-
if (!this.isRegistering) {
|
|
86
|
-
if (import.meta.env.DEV) {
|
|
87
|
-
throw new Error('BehaviorRegistry: call beginRegistration() before registering behaviors');
|
|
88
|
-
}
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
this.registrations.push({
|
|
92
|
-
// Type assertion безопасен: handler будет вызван с правильным типом формы в createEffect
|
|
93
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
94
|
-
handler: handler,
|
|
95
|
-
debounce: options?.debounce,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Завершить регистрацию и применить behaviors к форме
|
|
100
|
-
* Создает effect подписки для всех зарегистрированных behaviors
|
|
101
|
-
*
|
|
102
|
-
* Извлекает this из context stack
|
|
103
|
-
*
|
|
104
|
-
* @param form - GroupNode формы
|
|
105
|
-
* @returns Количество зарегистрированных behaviors и функция cleanup
|
|
106
|
-
*/
|
|
107
|
-
endRegistration(form) {
|
|
108
|
-
this.isRegistering = false;
|
|
109
|
-
// Извлекаем из stack с проверкой
|
|
110
|
-
BehaviorRegistry.contextStack.verify(this, 'BehaviorRegistry');
|
|
111
|
-
const context = new BehaviorContextImpl(form);
|
|
112
|
-
const disposeCallbacks = [];
|
|
113
|
-
// Создаем effect подписки для каждого behavior
|
|
114
|
-
for (const registered of this.registrations) {
|
|
115
|
-
const dispose = this.createEffect(registered, form, context);
|
|
116
|
-
if (dispose) {
|
|
117
|
-
disposeCallbacks.push(dispose);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// Функция cleanup для отписки от всех effects
|
|
121
|
-
const cleanup = () => {
|
|
122
|
-
disposeCallbacks.forEach((dispose) => dispose());
|
|
123
|
-
};
|
|
124
|
-
return {
|
|
125
|
-
count: this.registrations.length,
|
|
126
|
-
cleanup,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Создать effect подписку для behavior
|
|
131
|
-
* @private
|
|
132
|
-
*/
|
|
133
|
-
createEffect(registered, form, context) {
|
|
134
|
-
const { handler, debounce: debounceMs = 0 } = registered;
|
|
135
|
-
let debounceTimer = null;
|
|
136
|
-
// Обертка для debounce
|
|
137
|
-
const withDebounce = (callback) => {
|
|
138
|
-
if (debounceMs > 0) {
|
|
139
|
-
if (debounceTimer)
|
|
140
|
-
clearTimeout(debounceTimer);
|
|
141
|
-
debounceTimer = setTimeout(callback, debounceMs);
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
callback();
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
// Cleanup функция для debounce таймера
|
|
148
|
-
const cleanupDebounce = () => {
|
|
149
|
-
if (debounceTimer) {
|
|
150
|
-
clearTimeout(debounceTimer);
|
|
151
|
-
debounceTimer = null;
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
// Вызываем handler напрямую
|
|
155
|
-
// Type assertion необходим из-за contravariance: handler хранится как
|
|
156
|
-
// BehaviorHandlerFn<FormFields>, но вызывается с более специфичным типом T.
|
|
157
|
-
// Используем any для обхода ограничений TypeScript при хранении generic handlers в массиве.
|
|
158
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
159
|
-
const effectDispose = handler(form, context, withDebounce);
|
|
160
|
-
if (!effectDispose) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
// Возвращаем комбинированный cleanup
|
|
164
|
-
// который очищает и effect, и debounce таймер
|
|
165
|
-
return () => {
|
|
166
|
-
cleanupDebounce();
|
|
167
|
-
if (effectDispose) {
|
|
168
|
-
effectDispose();
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// ============================================================================
|
|
174
|
-
// Глобальный экземпляр BehaviorRegistry УДАЛЕН
|
|
175
|
-
// ============================================================================
|
|
176
|
-
//
|
|
177
|
-
// Ранее здесь был глобальный Singleton экземпляр BehaviorRegistry,
|
|
178
|
-
// который создавал race conditions и нарушал изоляцию форм.
|
|
179
|
-
//
|
|
180
|
-
// Теперь каждый GroupNode создает собственный экземпляр BehaviorRegistry:
|
|
181
|
-
//
|
|
182
|
-
// @example
|
|
183
|
-
// ```typescript
|
|
184
|
-
// class GroupNode {
|
|
185
|
-
// private readonly behaviorRegistry = new BehaviorRegistry();
|
|
186
|
-
//
|
|
187
|
-
// applyBehaviorSchema(schemaFn) {
|
|
188
|
-
// this.behaviorRegistry.beginRegistration();
|
|
189
|
-
// schemaFn(createBehaviorFieldPath(this));
|
|
190
|
-
// return this.behaviorRegistry.endRegistration(this);
|
|
191
|
-
// }
|
|
192
|
-
// }
|
|
193
|
-
// ```
|
|
194
|
-
//
|
|
195
|
-
// Это обеспечивает:
|
|
196
|
-
// - Полную изоляцию форм друг от друга
|
|
197
|
-
// - Отсутствие race conditions при параллельной регистрации
|
|
198
|
-
// - Возможность применять разные behavior схемы к разным формам одновременно
|