@reformer/core 11.0.0-beta.8 → 11.0.0-beta.9
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 +91 -73
- package/dist/behaviors-value-HA_Q03hc.js +108 -0
- package/dist/behaviors.js +370 -242
- package/dist/{create-model-BBRHhHwK.js → create-model-BvcbNPgL.js} +6 -6
- package/dist/date-utils-90-qZbse.js +43 -0
- package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
- package/dist/form/aggregate-signals.d.ts +21 -63
- package/dist/form/behaviors/collections.d.ts +28 -7
- package/dist/form/behaviors/context.d.ts +19 -2
- package/dist/form/behaviors/index.d.ts +3 -3
- package/dist/form/behaviors/internals.d.ts +9 -2
- package/dist/form/behaviors/node.d.ts +25 -2
- package/dist/form/behaviors/operators.d.ts +17 -9
- package/dist/form/behaviors/schema-operators.d.ts +6 -4
- package/dist/form/behaviors/types.d.ts +12 -4
- package/dist/form/create-form.d.ts +47 -35
- package/dist/form/flow/flow.d.ts +87 -0
- package/dist/form/form-bundle.d.ts +45 -28
- package/dist/form/form-config-base.d.ts +46 -0
- package/dist/form/form-level.d.ts +121 -0
- package/dist/form/form-proxy-builder.d.ts +6 -22
- package/dist/form/index.d.ts +16 -21
- package/dist/form/nodes/form-array-node.d.ts +100 -0
- package/dist/form/nodes/form-control-node.d.ts +73 -0
- package/dist/form/nodes/form-group-node.d.ts +66 -0
- package/dist/form/nodes/node-kind.d.ts +29 -0
- package/dist/form/nodes/state/availability.d.ts +6 -0
- package/dist/form/nodes/state/component.d.ts +6 -0
- package/dist/form/nodes/state/index.d.ts +12 -0
- package/dist/form/nodes/state/interaction.d.ts +13 -0
- package/dist/form/nodes/state/issues.d.ts +9 -0
- package/dist/form/nodes/state/pending.d.ts +6 -0
- package/dist/form/schema/compile.d.ts +59 -0
- package/dist/form/schema/compiled.d.ts +143 -0
- package/dist/form/schema/row-key.d.ts +11 -0
- package/dist/form/schema-controller.d.ts +30 -14
- package/dist/form/signal-node-registry.d.ts +6 -7
- package/dist/form/submission.d.ts +93 -0
- package/dist/form/type-guards.d.ts +14 -73
- package/dist/form/types/contracts.d.ts +19 -92
- package/dist/form/types/field-config.d.ts +27 -0
- package/dist/form/types/index.d.ts +5 -57
- package/dist/form/types/nodes.d.ts +159 -0
- package/dist/form/types/schema-node.d.ts +139 -63
- package/dist/form/types/validation-schema.d.ts +23 -27
- package/dist/form/validation/context.d.ts +42 -7
- package/dist/form/validation/index.d.ts +9 -8
- package/dist/form/validation/operators.d.ts +22 -30
- package/dist/form/validation/run.d.ts +93 -18
- package/dist/form/validation/strategy.d.ts +41 -13
- package/dist/form/validation/types.d.ts +74 -20
- package/dist/form/validators/date-utils.d.ts +13 -1
- package/dist/form/validators/email.d.ts +8 -7
- package/dist/form/validators/file-type.d.ts +8 -7
- package/dist/form/validators/file-utils.d.ts +2 -1
- package/dist/form/validators/future-date.d.ts +8 -7
- package/dist/form/validators/index.d.ts +1 -1
- package/dist/form/validators/integer.d.ts +8 -7
- package/dist/form/validators/is-date.d.ts +11 -9
- package/dist/form/validators/is-number.d.ts +11 -9
- package/dist/form/validators/max-age.d.ts +13 -9
- package/dist/form/validators/max-date.d.ts +8 -7
- package/dist/form/validators/max-file-size.d.ts +8 -7
- package/dist/form/validators/max-files.d.ts +8 -7
- package/dist/form/validators/max-length.d.ts +14 -7
- package/dist/form/validators/max-total-file-size.d.ts +8 -7
- package/dist/form/validators/max.d.ts +10 -8
- package/dist/form/validators/min-age.d.ts +12 -9
- package/dist/form/validators/min-date.d.ts +8 -7
- package/dist/form/validators/min-file-size.d.ts +8 -7
- package/dist/form/validators/min-files.d.ts +11 -9
- package/dist/form/validators/min-length.d.ts +14 -7
- package/dist/form/validators/min.d.ts +10 -8
- package/dist/form/validators/multiple-of.d.ts +10 -8
- package/dist/form/validators/non-negative.d.ts +8 -7
- package/dist/form/validators/non-zero.d.ts +8 -7
- package/dist/form/validators/past-date.d.ts +8 -7
- package/dist/form/validators/pattern.d.ts +9 -8
- package/dist/form/validators/phone.d.ts +8 -7
- package/dist/form/validators/required.d.ts +11 -9
- package/dist/form/validators/url.d.ts +20 -13
- package/dist/form/validators/validation-issue.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +1 -1
- package/dist/i18n/en.json.d.ts +5 -1
- package/dist/i18n/locale-store.d.ts +7 -0
- package/dist/i18n/message-format.d.ts +24 -4
- package/dist/i18n/ru.json.d.ts +5 -1
- package/dist/i18n/translator.d.ts +11 -0
- package/dist/i18n/validation-message.d.ts +42 -7
- package/dist/i18n.d.ts +7 -12
- package/dist/i18n.js +66 -283
- package/dist/index.d.ts +16 -7
- package/dist/index.js +924 -2855
- package/dist/loader-BWkigpVe.js +52 -0
- package/dist/locale/en.js +1 -1
- package/dist/locale/en.json +4 -1
- package/dist/locale/ru.js +4 -1
- package/dist/locale/ru.json +4 -1
- package/dist/locale-CG54AU2Q.js +219 -0
- package/dist/locale.js +1 -1
- package/dist/model/behaviors-value.d.ts +24 -6
- package/dist/model/create-model.d.ts +2 -2
- package/dist/model/derived-registry.d.ts +1 -1
- package/dist/model/index.d.ts +11 -5
- package/dist/model/model-nodes.d.ts +79 -12
- package/dist/model/model-signals-proxy.d.ts +5 -6
- package/dist/model/model-value-proxy.d.ts +20 -15
- package/dist/model/safe-effect.d.ts +33 -1
- package/dist/model/types.d.ts +63 -13
- package/dist/model.js +20 -18
- package/dist/node-kind-C-r6ZMdo.js +15 -0
- package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
- package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
- package/dist/platforms/react/form-bundle-context.d.ts +2 -2
- package/dist/platforms/react/hooks/types.d.ts +57 -38
- package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
- package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
- package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
- package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
- package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
- package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
- package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
- package/dist/platforms/react/i18n/context.d.ts +6 -1
- package/dist/platforms/react/i18n/hooks.d.ts +16 -10
- package/dist/platforms/react/i18n/provider.d.ts +9 -2
- package/dist/platforms/react/index.d.ts +6 -4
- package/dist/react.d.ts +22 -0
- package/dist/react.js +505 -0
- package/dist/runtime-token.d.ts +1 -2
- package/dist/safe-effect-BbtX3KuN.js +643 -0
- package/dist/schema-controller-CpiO2JoK.js +143 -0
- package/dist/signals.d.ts +15 -9
- package/dist/signals.js +11 -6
- package/dist/strategy-CoFSemuP.js +375 -0
- package/dist/type-guards-D-N_5Ddh.js +19 -0
- package/dist/validation-issue-B9mjI8jz.js +7 -0
- package/dist/validation-message-Ced9MGxs.js +194 -0
- package/dist/validation.js +9 -9
- package/dist/validators/email.js +5 -5
- package/dist/validators/future-date.js +8 -8
- package/dist/validators/integer.js +3 -3
- package/dist/validators/is-date.js +4 -4
- package/dist/validators/is-number.js +4 -4
- package/dist/validators/max-age.js +8 -9
- package/dist/validators/max-date.js +8 -8
- package/dist/validators/max-length.js +7 -7
- package/dist/validators/max.js +4 -4
- package/dist/validators/min-age.js +10 -8
- package/dist/validators/min-date.js +3 -3
- package/dist/validators/min-length.js +5 -5
- package/dist/validators/min.js +4 -4
- package/dist/validators/multiple-of.js +11 -7
- package/dist/validators/non-negative.js +3 -3
- package/dist/validators/non-zero.js +3 -3
- package/dist/validators/past-date.js +8 -8
- package/dist/validators/pattern.js +5 -5
- package/dist/validators/phone.js +11 -9
- package/dist/validators/required.js +2 -2
- package/dist/validators/url.js +48 -12
- package/dist/validators.js +17 -17
- package/docs/llms/01-api-reference.md +60 -34
- package/docs/llms/02-quick-start.md +111 -80
- package/docs/llms/03-api-signatures.md +368 -153
- package/docs/llms/04-common-patterns.md +58 -56
- package/docs/llms/05-common-mistakes.md +69 -44
- package/docs/llms/06-troubleshooting.md +14 -9
- package/docs/llms/07-complete-import.md +74 -43
- package/docs/llms/08-form-types.md +11 -4
- package/docs/llms/09-formschema.md +129 -81
- package/docs/llms/10-arrays.md +106 -75
- package/docs/llms/11-async-watchfield.md +2 -2
- package/docs/llms/13-multi-step.md +134 -63
- package/docs/llms/14-extended-mistakes.md +20 -17
- package/docs/llms/15-project-structure.md +221 -74
- package/docs/llms/16-ui-components.md +55 -42
- package/docs/llms/17-nonexistent-api.md +199 -32
- package/docs/llms/18-conditional-fields.md +66 -32
- package/docs/llms/19-reading-values.md +15 -3
- package/docs/llms/20-compute-vs-watch.md +16 -10
- package/docs/llms/21-array-operations.md +51 -27
- package/docs/llms/22-cycle-detection.md +1 -1
- package/docs/llms/23-copy-from.md +2 -2
- package/docs/llms/24-sync-fields.md +6 -5
- package/docs/llms/25-reset-when.md +14 -7
- package/docs/llms/26-transform-value.md +4 -4
- package/docs/llms/27-revalidate-when.md +16 -15
- package/docs/llms/28-submit-and-reset.md +276 -128
- package/docs/llms/29-async-preload.md +39 -29
- package/docs/llms/30-type-safety-recipes.md +105 -51
- package/docs/llms/31-async-validator-debounce.md +96 -53
- package/docs/llms/33-validation-strategy.md +177 -65
- package/docs/llms/34-i18n.md +69 -43
- package/docs/llms/35-unified-contract.md +100 -39
- package/docs/llms/36-issue-messages.md +284 -0
- package/docs/llms/37-compiled-schema.md +332 -0
- package/docs/llms/38-form-flow.md +397 -0
- package/llms-index.json +1 -1
- package/llms.txt +6084 -3420
- package/package.json +23 -4
- package/dist/behaviors-value-C7dSEhCc.js +0 -134
- package/dist/date-utils-dTT_x_Be.js +0 -31
- package/dist/form/create-core-form.d.ts +0 -72
- package/dist/form/factories/index.d.ts +0 -6
- package/dist/form/factories/node-factory.d.ts +0 -189
- package/dist/form/form-submitter.d.ts +0 -121
- package/dist/form/nodes/array-node.d.ts +0 -276
- package/dist/form/nodes/field-node.d.ts +0 -297
- package/dist/form/nodes/form-node.d.ts +0 -326
- package/dist/form/nodes/group-node.d.ts +0 -284
- package/dist/form/nodes/model-array-node.d.ts +0 -126
- package/dist/form/nodes/subscription-manager.d.ts +0 -180
- package/dist/form/status-machine.d.ts +0 -115
- package/dist/form/types/deep-schema.d.ts +0 -107
- package/dist/form/types/form-proxy.d.ts +0 -141
- package/dist/form/unique-id.d.ts +0 -45
- package/dist/form/validation/config.d.ts +0 -73
- package/dist/form/validation/error-handler.d.ts +0 -166
- package/dist/form/validators/validation-error.d.ts +0 -8
- package/dist/index-BrNkQj1y.js +0 -105
- package/dist/loader-Dv_Hl7JC.js +0 -216
- package/dist/model-value-proxy-caTmAiUd.js +0 -419
- package/dist/node-BeV_lV5i.js +0 -100
- package/dist/strategy-DgzELWno.js +0 -204
- package/dist/validation-error-B-U3RsV0.js +0 -11
package/dist/index.js
CHANGED
|
@@ -1,3013 +1,1082 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import { e as
|
|
3
|
-
import { d as
|
|
4
|
-
import { a as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { r as
|
|
8
|
-
import { g as
|
|
9
|
-
import { c as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
_dirty = m(!1);
|
|
28
|
-
/**
|
|
29
|
-
* Текущий статус узла
|
|
30
|
-
* Protected: наследники могут читать/изменять через методы
|
|
31
|
-
*/
|
|
32
|
-
_status = m("valid");
|
|
33
|
-
// ============================================================================
|
|
34
|
-
// Публичные computed signals (readonly для внешнего мира)
|
|
35
|
-
// ============================================================================
|
|
36
|
-
/**
|
|
37
|
-
* Пользователь взаимодействовал с узлом (touched)
|
|
38
|
-
* Computed из _touched для предоставления readonly интерфейса
|
|
39
|
-
*/
|
|
40
|
-
touched = h(() => this._touched.value);
|
|
41
|
-
/**
|
|
42
|
-
* Значение узла было изменено (dirty)
|
|
43
|
-
* Computed из _dirty для предоставления readonly интерфейса
|
|
44
|
-
*/
|
|
45
|
-
dirty = h(() => this._dirty.value);
|
|
46
|
-
/**
|
|
47
|
-
* Текущий статус узла
|
|
48
|
-
* Computed из _status для предоставления readonly интерфейса
|
|
49
|
-
*/
|
|
50
|
-
status = h(() => this._status.value);
|
|
51
|
-
/**
|
|
52
|
-
* Узел отключен (disabled)
|
|
53
|
-
*/
|
|
54
|
-
disabled = h(() => this._status.value === "disabled");
|
|
55
|
-
/**
|
|
56
|
-
* Получить ошибки валидации с фильтрацией
|
|
57
|
-
*
|
|
58
|
-
* Позволяет фильтровать ошибки по различным критериям:
|
|
59
|
-
* - По коду ошибки
|
|
60
|
-
* - По сообщению (частичное совпадение)
|
|
61
|
-
* - По параметрам
|
|
62
|
-
* - Через кастомный предикат
|
|
63
|
-
*
|
|
64
|
-
* Без параметров возвращает все ошибки (эквивалент errors.value)
|
|
65
|
-
*
|
|
66
|
-
* @param options - Опции фильтрации ошибок
|
|
67
|
-
* @returns Отфильтрованный массив ошибок валидации
|
|
68
|
-
*
|
|
69
|
-
* @example
|
|
70
|
-
* ```typescript
|
|
71
|
-
* // Все ошибки
|
|
72
|
-
* const allErrors = form.getErrors();
|
|
73
|
-
*
|
|
74
|
-
* // Ошибки с конкретным кодом
|
|
75
|
-
* const requiredErrors = form.getErrors({ code: 'required' });
|
|
76
|
-
*
|
|
77
|
-
* // Ошибки с несколькими кодами
|
|
78
|
-
* const errors = form.getErrors({ code: ['required', 'email'] });
|
|
79
|
-
*
|
|
80
|
-
* // Ошибки по сообщению
|
|
81
|
-
* const passwordErrors = form.getErrors({ message: 'Password' });
|
|
82
|
-
*
|
|
83
|
-
* // Ошибки по параметрам
|
|
84
|
-
* const minLengthErrors = form.getErrors({
|
|
85
|
-
* params: { minLength: 8 }
|
|
86
|
-
* });
|
|
87
|
-
*
|
|
88
|
-
* // Кастомная фильтрация
|
|
89
|
-
* const customErrors = form.getErrors({
|
|
90
|
-
* predicate: (err) => err.code.startsWith('custom_')
|
|
91
|
-
* });
|
|
92
|
-
* ```
|
|
93
|
-
*/
|
|
94
|
-
getErrors(e) {
|
|
95
|
-
const t = this.errors.value;
|
|
96
|
-
return e ? t.filter((i) => {
|
|
97
|
-
if (e.code !== void 0 && !(Array.isArray(e.code) ? e.code : [e.code]).includes(i.code) || e.message !== void 0 && !i.message.toLowerCase().includes(e.message.toLowerCase()))
|
|
98
|
-
return !1;
|
|
99
|
-
if (e.params !== void 0) {
|
|
100
|
-
if (!i.params)
|
|
101
|
-
return !1;
|
|
102
|
-
for (const [r, n] of Object.entries(e.params))
|
|
103
|
-
if (i.params[r] !== n)
|
|
104
|
-
return !1;
|
|
105
|
-
}
|
|
106
|
-
return !(e.predicate !== void 0 && !e.predicate(i));
|
|
107
|
-
}) : t;
|
|
108
|
-
}
|
|
109
|
-
// ============================================================================
|
|
110
|
-
// Методы управления состоянием (Template Method)
|
|
111
|
-
// ============================================================================
|
|
112
|
-
/**
|
|
113
|
-
* Отметить узел как touched (пользователь взаимодействовал)
|
|
114
|
-
*
|
|
115
|
-
* Template Method: обновляет signal в базовом классе,
|
|
116
|
-
* вызывает hook для кастомной логики в наследниках
|
|
117
|
-
*/
|
|
118
|
-
markAsTouched() {
|
|
119
|
-
this._touched.value = !0, this.onMarkAsTouched();
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Отметить узел как untouched
|
|
123
|
-
*
|
|
124
|
-
* Template Method: обновляет signal в базовом классе,
|
|
125
|
-
* вызывает hook для кастомной логики в наследниках
|
|
126
|
-
*/
|
|
127
|
-
markAsUntouched() {
|
|
128
|
-
this._touched.value = !1, this.onMarkAsUntouched();
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Отметить узел как dirty (значение изменено)
|
|
132
|
-
*
|
|
133
|
-
* Template Method: обновляет signal в базовом классе,
|
|
134
|
-
* вызывает hook для кастомной логики в наследниках
|
|
135
|
-
*/
|
|
136
|
-
markAsDirty() {
|
|
137
|
-
this._dirty.value = !0, this.onMarkAsDirty();
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Отметить узел как pristine (значение не изменено)
|
|
141
|
-
*
|
|
142
|
-
* Template Method: обновляет signal в базовом классе,
|
|
143
|
-
* вызывает hook для кастомной логики в наследниках
|
|
144
|
-
*/
|
|
145
|
-
markAsPristine() {
|
|
146
|
-
this._dirty.value = !1, this.onMarkAsPristine();
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Пометить все поля (включая вложенные) как touched
|
|
150
|
-
* Алиас для markAsTouched(), но более явно показывает намерение
|
|
151
|
-
* пометить ВСЕ поля рекурсивно
|
|
152
|
-
*
|
|
153
|
-
* Полезно для:
|
|
154
|
-
* - Показа всех ошибок валидации перед submit
|
|
155
|
-
* - Принудительного отображения ошибок при нажатии "Validate All"
|
|
156
|
-
* - Отображения невалидных полей в wizard/step form
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* ```typescript
|
|
160
|
-
* // Показать все ошибки перед submit
|
|
161
|
-
* form.touchAll();
|
|
162
|
-
* const isValid = await form.validate();
|
|
163
|
-
* if (!isValid) {
|
|
164
|
-
* // Все ошибки теперь видны пользователю
|
|
165
|
-
* }
|
|
166
|
-
*
|
|
167
|
-
* // Или использовать submit() который уже вызывает touchAll
|
|
168
|
-
* await form.submit(async (values) => {
|
|
169
|
-
* await api.save(values);
|
|
170
|
-
* });
|
|
171
|
-
* ```
|
|
172
|
-
*/
|
|
173
|
-
touchAll() {
|
|
174
|
-
this.markAsTouched();
|
|
175
|
-
}
|
|
176
|
-
// ============================================================================
|
|
177
|
-
// Методы управления доступностью (Template Method)
|
|
178
|
-
// ============================================================================
|
|
179
|
-
/**
|
|
180
|
-
* Отключить узел
|
|
181
|
-
*
|
|
182
|
-
* Template Method: обновляет статус в базовом классе,
|
|
183
|
-
* вызывает hook для кастомной логики в наследниках
|
|
184
|
-
*
|
|
185
|
-
* Отключенные узлы не проходят валидацию и не включаются в getValue()
|
|
186
|
-
*/
|
|
187
|
-
disable() {
|
|
188
|
-
this._status.value = "disabled", this.onDisable();
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Включить узел
|
|
192
|
-
*
|
|
193
|
-
* Template Method: обновляет статус в базовом классе,
|
|
194
|
-
* вызывает hook для кастомной логики в наследниках
|
|
195
|
-
*/
|
|
196
|
-
enable() {
|
|
197
|
-
this._status.value = "valid", this.onEnable();
|
|
198
|
-
}
|
|
199
|
-
// ============================================================================
|
|
200
|
-
// Protected hooks (для переопределения в наследниках)
|
|
201
|
-
// ============================================================================
|
|
202
|
-
/**
|
|
203
|
-
* Hook: вызывается после markAsTouched()
|
|
204
|
-
*
|
|
205
|
-
* Переопределите в наследниках для дополнительной логики:
|
|
206
|
-
* - GroupNode: пометить все дочерние узлы как touched
|
|
207
|
-
* - ArrayNode: пометить все элементы массива как touched
|
|
208
|
-
* - FieldNode: пустая реализация (нет дочерних узлов)
|
|
209
|
-
*
|
|
210
|
-
* @example
|
|
211
|
-
* ```typescript
|
|
212
|
-
* // GroupNode
|
|
213
|
-
* protected onMarkAsTouched(): void {
|
|
214
|
-
* this.fields.forEach(field => field.markAsTouched());
|
|
215
|
-
* }
|
|
216
|
-
* ```
|
|
217
|
-
*/
|
|
218
|
-
onMarkAsTouched() {
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Hook: вызывается после markAsUntouched()
|
|
222
|
-
*
|
|
223
|
-
* Переопределите в наследниках для дополнительной логики:
|
|
224
|
-
* - GroupNode: пометить все дочерние узлы как untouched
|
|
225
|
-
* - ArrayNode: пометить все элементы массива как untouched
|
|
226
|
-
* - FieldNode: пустая реализация (нет дочерних узлов)
|
|
227
|
-
*/
|
|
228
|
-
onMarkAsUntouched() {
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Hook: вызывается после markAsDirty()
|
|
232
|
-
*
|
|
233
|
-
* Переопределите в наследниках для дополнительной логики:
|
|
234
|
-
* - GroupNode: может обновить родительскую форму
|
|
235
|
-
* - ArrayNode: может обновить родительскую форму
|
|
236
|
-
* - FieldNode: пустая реализация
|
|
237
|
-
*/
|
|
238
|
-
onMarkAsDirty() {
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Hook: вызывается после markAsPristine()
|
|
242
|
-
*
|
|
243
|
-
* Переопределите в наследниках для дополнительной логики:
|
|
244
|
-
* - GroupNode: пометить все дочерние узлы как pristine
|
|
245
|
-
* - ArrayNode: пометить все элементы массива как pristine
|
|
246
|
-
* - FieldNode: пустая реализация
|
|
247
|
-
*/
|
|
248
|
-
onMarkAsPristine() {
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Hook: вызывается после disable()
|
|
252
|
-
*
|
|
253
|
-
* Переопределите в наследниках для дополнительной логики:
|
|
254
|
-
* - GroupNode: отключить все дочерние узлы
|
|
255
|
-
* - ArrayNode: отключить все элементы массива
|
|
256
|
-
* - FieldNode: очистить ошибки валидации
|
|
257
|
-
*
|
|
258
|
-
* @example
|
|
259
|
-
* ```typescript
|
|
260
|
-
* // GroupNode
|
|
261
|
-
* protected onDisable(): void {
|
|
262
|
-
* this.fields.forEach(field => field.disable());
|
|
263
|
-
* }
|
|
264
|
-
* ```
|
|
265
|
-
*/
|
|
266
|
-
onDisable() {
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Hook: вызывается после enable()
|
|
270
|
-
*
|
|
271
|
-
* Переопределите в наследниках для дополнительной логики:
|
|
272
|
-
* - GroupNode: включить все дочерние узлы
|
|
273
|
-
* - ArrayNode: включить все элементы массива
|
|
274
|
-
* - FieldNode: пустая реализация
|
|
275
|
-
*/
|
|
276
|
-
onEnable() {
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
class $ {
|
|
280
|
-
/**
|
|
281
|
-
* Хранилище подписок
|
|
282
|
-
* Ключ: уникальный идентификатор подписки
|
|
283
|
-
* Значение: функция отписки (dispose)
|
|
284
|
-
*/
|
|
285
|
-
subscriptions = /* @__PURE__ */ new Map();
|
|
286
|
-
/**
|
|
287
|
-
* Добавляет подписку
|
|
288
|
-
*
|
|
289
|
-
* Если подписка с таким ключом уже существует, отписывается от неё
|
|
290
|
-
* и заменяет новой. Это предотвращает утечки памяти при повторной
|
|
291
|
-
* регистрации подписки с тем же ключом.
|
|
292
|
-
*
|
|
293
|
-
* @param key Уникальный ключ подписки
|
|
294
|
-
* @param dispose Функция отписки (обычно возвращаемая из effect())
|
|
295
|
-
* @returns Функция для отписки от этой конкретной подписки
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* ```typescript
|
|
299
|
-
* const manager = new SubscriptionManager();
|
|
300
|
-
*
|
|
301
|
-
* // Добавление подписки
|
|
302
|
-
* const unsubscribe = manager.add('mySubscription', () => {
|
|
303
|
-
* console.log('Disposing subscription');
|
|
304
|
-
* });
|
|
305
|
-
*
|
|
306
|
-
* // Отписка через возвращаемую функцию
|
|
307
|
-
* unsubscribe();
|
|
308
|
-
*
|
|
309
|
-
* // Или через manager.remove()
|
|
310
|
-
* manager.add('anotherSub', disposeFn);
|
|
311
|
-
* manager.remove('anotherSub');
|
|
312
|
-
* ```
|
|
313
|
-
*/
|
|
314
|
-
add(e, t) {
|
|
315
|
-
return this.subscriptions.has(e) && (process.env.NODE_ENV !== "production" && console.warn(`[SubscriptionManager] Subscription "${e}" already exists, replacing`), this.subscriptions.get(e)?.()), this.subscriptions.set(e, t), () => this.remove(e);
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Удаляет подписку по ключу
|
|
319
|
-
*
|
|
320
|
-
* Вызывает функцию отписки и удаляет подписку из хранилища.
|
|
321
|
-
* Если подписка с таким ключом не найдена, ничего не делает.
|
|
322
|
-
*
|
|
323
|
-
* @param key Ключ подписки
|
|
324
|
-
* @returns true, если подписка была удалена, false если не найдена
|
|
325
|
-
*
|
|
326
|
-
* @example
|
|
327
|
-
* ```typescript
|
|
328
|
-
* const manager = new SubscriptionManager();
|
|
329
|
-
* manager.add('mySub', disposeFn);
|
|
330
|
-
*
|
|
331
|
-
* // Удаление подписки
|
|
332
|
-
* const removed = manager.remove('mySub'); // true
|
|
333
|
-
*
|
|
334
|
-
* // Попытка удалить несуществующую подписку
|
|
335
|
-
* const removed2 = manager.remove('nonExistent'); // false
|
|
336
|
-
* ```
|
|
337
|
-
*/
|
|
338
|
-
remove(e) {
|
|
339
|
-
const t = this.subscriptions.get(e);
|
|
340
|
-
return t ? (t(), this.subscriptions.delete(e), !0) : !1;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Очищает все подписки
|
|
344
|
-
*
|
|
345
|
-
* Вызывает функции отписки для всех активных подписок
|
|
346
|
-
* и очищает хранилище. Обычно используется при dispose узла формы.
|
|
347
|
-
*
|
|
348
|
-
* @example
|
|
349
|
-
* ```typescript
|
|
350
|
-
* class FieldNode {
|
|
351
|
-
* private subscriptions = new SubscriptionManager();
|
|
352
|
-
*
|
|
353
|
-
* dispose() {
|
|
354
|
-
* // Отписываемся от всех effect
|
|
355
|
-
* this.subscriptions.clear();
|
|
356
|
-
* }
|
|
357
|
-
* }
|
|
358
|
-
* ```
|
|
359
|
-
*/
|
|
360
|
-
clear() {
|
|
361
|
-
this.subscriptions.forEach((e) => {
|
|
362
|
-
e();
|
|
363
|
-
}), this.subscriptions.clear();
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Возвращает количество активных подписок
|
|
367
|
-
*
|
|
368
|
-
* Полезно для отладки утечек памяти. Если количество подписок
|
|
369
|
-
* растет без ограничений, это может указывать на то, что
|
|
370
|
-
* компоненты не отписываются должным образом.
|
|
371
|
-
*
|
|
372
|
-
* @returns Количество активных подписок
|
|
373
|
-
*
|
|
374
|
-
* @example
|
|
375
|
-
* ```typescript
|
|
376
|
-
* const manager = new SubscriptionManager();
|
|
377
|
-
* console.log(manager.size()); // 0
|
|
378
|
-
*
|
|
379
|
-
* manager.add('sub1', disposeFn1);
|
|
380
|
-
* manager.add('sub2', disposeFn2);
|
|
381
|
-
* console.log(manager.size()); // 2
|
|
382
|
-
*
|
|
383
|
-
* manager.clear();
|
|
384
|
-
* console.log(manager.size()); // 0
|
|
385
|
-
* ```
|
|
386
|
-
*/
|
|
387
|
-
size() {
|
|
388
|
-
return this.subscriptions.size;
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Проверяет, есть ли подписка с данным ключом
|
|
392
|
-
*
|
|
393
|
-
* @param key Ключ подписки
|
|
394
|
-
* @returns true, если подписка существует
|
|
395
|
-
*
|
|
396
|
-
* @example
|
|
397
|
-
* ```typescript
|
|
398
|
-
* const manager = new SubscriptionManager();
|
|
399
|
-
* manager.add('mySub', disposeFn);
|
|
400
|
-
*
|
|
401
|
-
* console.log(manager.has('mySub')); // true
|
|
402
|
-
* console.log(manager.has('nonExistent')); // false
|
|
403
|
-
* ```
|
|
404
|
-
*/
|
|
405
|
-
has(e) {
|
|
406
|
-
return this.subscriptions.has(e);
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Возвращает список всех ключей активных подписок
|
|
410
|
-
*
|
|
411
|
-
* Полезно для отладки: можно увидеть, какие подписки активны.
|
|
412
|
-
*
|
|
413
|
-
* @returns Массив ключей всех активных подписок
|
|
414
|
-
*
|
|
415
|
-
* @example
|
|
416
|
-
* ```typescript
|
|
417
|
-
* const manager = new SubscriptionManager();
|
|
418
|
-
* manager.add('watch-value', disposeFn1);
|
|
419
|
-
* manager.add('watch-errors', disposeFn2);
|
|
420
|
-
*
|
|
421
|
-
* console.log(manager.getKeys()); // ['watch-value', 'watch-errors']
|
|
422
|
-
* ```
|
|
423
|
-
*/
|
|
424
|
-
getKeys() {
|
|
425
|
-
return Array.from(this.subscriptions.keys());
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Отписывается от всех подписок (алиас для clear())
|
|
429
|
-
*
|
|
430
|
-
* Используется при dispose() узла формы для совместимости с ожидаемым API.
|
|
431
|
-
*
|
|
432
|
-
* @example
|
|
433
|
-
* ```typescript
|
|
434
|
-
* class FieldNode {
|
|
435
|
-
* private subscriptions = new SubscriptionManager();
|
|
436
|
-
*
|
|
437
|
-
* dispose() {
|
|
438
|
-
* this.subscriptions.dispose();
|
|
439
|
-
* }
|
|
440
|
-
* }
|
|
441
|
-
* ```
|
|
442
|
-
*/
|
|
443
|
-
dispose() {
|
|
444
|
-
this.clear();
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
const k = {
|
|
448
|
-
/** FieldNode.watch() */
|
|
449
|
-
Watch: "watch",
|
|
450
|
-
/** FieldNode.computeFrom() */
|
|
451
|
-
ComputeFrom: "computeFrom",
|
|
452
|
-
/** GroupNode.linkFields() */
|
|
453
|
-
LinkFields: "linkFields",
|
|
454
|
-
/** GroupNode.watchField() */
|
|
455
|
-
WatchField: "watchField"
|
|
456
|
-
};
|
|
457
|
-
let ye = 0;
|
|
458
|
-
function S(s) {
|
|
459
|
-
return `${s}-${++ye}`;
|
|
460
|
-
}
|
|
461
|
-
var b = /* @__PURE__ */ ((s) => (s.THROW = "throw", s.LOG = "log", s.CONVERT = "convert", s))(b || {});
|
|
462
|
-
class w {
|
|
463
|
-
static handle(e, t, i = "throw") {
|
|
464
|
-
const r = this.extractMessage(e);
|
|
465
|
-
switch (process.env.NODE_ENV !== "production" && console.error(`[${t}]`, e), i) {
|
|
466
|
-
case "throw":
|
|
467
|
-
throw e;
|
|
468
|
-
case "log":
|
|
469
|
-
return;
|
|
470
|
-
case "convert":
|
|
471
|
-
return {
|
|
472
|
-
code: "validator_error",
|
|
473
|
-
message: r,
|
|
474
|
-
params: { field: t }
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Извлечь сообщение из ошибки
|
|
480
|
-
*
|
|
481
|
-
* Обрабатывает различные типы ошибок:
|
|
482
|
-
* - Error объекты → error.message
|
|
483
|
-
* - Строки → возвращает как есть
|
|
484
|
-
* - Объекты с message → извлекает message
|
|
485
|
-
* - Другое → String(error)
|
|
486
|
-
*
|
|
487
|
-
* @param error Ошибка для извлечения сообщения
|
|
488
|
-
* @returns Сообщение ошибки
|
|
489
|
-
* @private
|
|
490
|
-
*
|
|
491
|
-
* @example
|
|
492
|
-
* ```typescript
|
|
493
|
-
* FormErrorHandler.extractMessage(new Error('Test'));
|
|
494
|
-
* // 'Test'
|
|
495
|
-
*
|
|
496
|
-
* FormErrorHandler.extractMessage('String error');
|
|
497
|
-
* // 'String error'
|
|
498
|
-
*
|
|
499
|
-
* FormErrorHandler.extractMessage({ message: 'Object error' });
|
|
500
|
-
* // 'Object error'
|
|
501
|
-
*
|
|
502
|
-
* FormErrorHandler.extractMessage(null);
|
|
503
|
-
* // 'null'
|
|
504
|
-
* ```
|
|
505
|
-
*/
|
|
506
|
-
static extractMessage(e) {
|
|
507
|
-
return e instanceof Error ? e.message : typeof e == "string" ? e : typeof e == "object" && e !== null && "message" in e ? String(e.message) : String(e);
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Создать ValidationError с заданными параметрами
|
|
511
|
-
*
|
|
512
|
-
* Утилитная функция для создания ValidationError объектов
|
|
513
|
-
*
|
|
514
|
-
* @param code Код ошибки
|
|
515
|
-
* @param message Сообщение ошибки
|
|
516
|
-
* @param field Поле (опционально)
|
|
517
|
-
* @returns ValidationError объект
|
|
518
|
-
*
|
|
519
|
-
* @example
|
|
520
|
-
* ```typescript
|
|
521
|
-
* const error = FormErrorHandler.createValidationError(
|
|
522
|
-
* 'required',
|
|
523
|
-
* 'This field is required',
|
|
524
|
-
* 'email'
|
|
525
|
-
* );
|
|
526
|
-
* // { code: 'required', message: 'This field is required', field: 'email' }
|
|
527
|
-
* ```
|
|
528
|
-
*/
|
|
529
|
-
static createValidationError(e, t, i) {
|
|
530
|
-
return {
|
|
531
|
-
code: e,
|
|
532
|
-
message: t,
|
|
533
|
-
params: i ? { field: i } : void 0
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Проверить, является ли объект ValidationError
|
|
538
|
-
*
|
|
539
|
-
* Type guard для ValidationError
|
|
540
|
-
*
|
|
541
|
-
* @param value Значение для проверки
|
|
542
|
-
* @returns true если value является ValidationError
|
|
543
|
-
*
|
|
544
|
-
* @example
|
|
545
|
-
* ```typescript
|
|
546
|
-
* if (FormErrorHandler.isValidationError(result)) {
|
|
547
|
-
* console.log(result.code); // OK, типобезопасно
|
|
548
|
-
* }
|
|
549
|
-
* ```
|
|
550
|
-
*/
|
|
551
|
-
static isValidationError(e) {
|
|
552
|
-
return typeof e == "object" && e !== null && "code" in e && "message" in e && typeof e.code == "string" && typeof e.message == "string";
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
class ge {
|
|
556
|
-
/** Внутренний сигнал статуса */
|
|
557
|
-
_status;
|
|
558
|
-
/** Публичный read-only сигнал статуса */
|
|
559
|
-
status;
|
|
560
|
-
/** Поле валидно */
|
|
561
|
-
valid;
|
|
562
|
-
/** Поле невалидно */
|
|
563
|
-
invalid;
|
|
564
|
-
/** Идет валидация */
|
|
565
|
-
pending;
|
|
566
|
-
/** Поле отключено */
|
|
567
|
-
disabled;
|
|
568
|
-
/**
|
|
569
|
-
* @param initial - Начальный статус (по умолчанию 'valid')
|
|
570
|
-
*/
|
|
571
|
-
constructor(e = "valid") {
|
|
572
|
-
this._status = m(e), this.status = h(() => this._status.value), this.valid = h(() => this._status.value === "valid"), this.invalid = h(() => this._status.value === "invalid"), this.pending = h(() => this._status.value === "pending"), this.disabled = h(() => this._status.value === "disabled");
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Начать валидацию
|
|
576
|
-
*
|
|
577
|
-
* Переводит статус в 'pending' если поле не отключено
|
|
578
|
-
*
|
|
579
|
-
* @example
|
|
580
|
-
* ```typescript
|
|
581
|
-
* statusMachine.startValidation();
|
|
582
|
-
* // status: 'pending'
|
|
583
|
-
* ```
|
|
584
|
-
*/
|
|
585
|
-
startValidation() {
|
|
586
|
-
this._status.value !== "disabled" && (this._status.value = "pending");
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Завершить валидацию
|
|
590
|
-
*
|
|
591
|
-
* @param hasErrors - Есть ли ошибки валидации
|
|
592
|
-
*
|
|
593
|
-
* @example
|
|
594
|
-
* ```typescript
|
|
595
|
-
* // Валидация успешна
|
|
596
|
-
* statusMachine.completeValidation(false);
|
|
597
|
-
* // status: 'valid'
|
|
598
|
-
*
|
|
599
|
-
* // Есть ошибки
|
|
600
|
-
* statusMachine.completeValidation(true);
|
|
601
|
-
* // status: 'invalid'
|
|
602
|
-
* ```
|
|
603
|
-
*/
|
|
604
|
-
completeValidation(e) {
|
|
605
|
-
this._status.value !== "disabled" && (this._status.value = e ? "invalid" : "valid");
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* Установить ошибки напрямую (без перехода через pending)
|
|
609
|
-
*
|
|
610
|
-
* Используется для синхронной валидации или установки ошибок извне
|
|
611
|
-
*
|
|
612
|
-
* @param hasErrors - Есть ли ошибки
|
|
613
|
-
*
|
|
614
|
-
* @example
|
|
615
|
-
* ```typescript
|
|
616
|
-
* statusMachine.setErrors(true);
|
|
617
|
-
* // status: 'invalid'
|
|
618
|
-
* ```
|
|
619
|
-
*/
|
|
620
|
-
setErrors(e) {
|
|
621
|
-
this._status.value !== "disabled" && (this._status.value = e ? "invalid" : "valid");
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Отключить поле
|
|
625
|
-
*
|
|
626
|
-
* Переводит статус в 'disabled'
|
|
627
|
-
*
|
|
628
|
-
* @example
|
|
629
|
-
* ```typescript
|
|
630
|
-
* statusMachine.disable();
|
|
631
|
-
* // status: 'disabled'
|
|
632
|
-
* ```
|
|
633
|
-
*/
|
|
634
|
-
disable() {
|
|
635
|
-
this._status.value = "disabled";
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Включить поле
|
|
639
|
-
*
|
|
640
|
-
* @param hasErrors - Есть ли ошибки (определяет valid/invalid)
|
|
641
|
-
*
|
|
642
|
-
* @example
|
|
643
|
-
* ```typescript
|
|
644
|
-
* statusMachine.enable(false);
|
|
645
|
-
* // status: 'valid'
|
|
646
|
-
*
|
|
647
|
-
* statusMachine.enable(true);
|
|
648
|
-
* // status: 'invalid'
|
|
649
|
-
* ```
|
|
650
|
-
*/
|
|
651
|
-
enable(e = !1) {
|
|
652
|
-
this._status.value = e ? "invalid" : "valid";
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
class be extends x {
|
|
656
|
-
// ============================================================================
|
|
657
|
-
// Приватные сигналы
|
|
658
|
-
// ============================================================================
|
|
659
|
-
_value;
|
|
660
|
-
_errors;
|
|
661
|
-
// _touched, _dirty наследуются от FormNode (protected)
|
|
662
|
-
// _status управляется через statusMachine
|
|
663
|
-
_componentProps;
|
|
664
|
-
/**
|
|
665
|
-
* State machine для управления статусом поля
|
|
666
|
-
* Централизует логику переходов между valid/invalid/pending/disabled
|
|
667
|
-
*/
|
|
668
|
-
statusMachine;
|
|
669
|
-
// ============================================================================
|
|
670
|
-
// Публичные computed signals
|
|
671
|
-
// ============================================================================
|
|
672
|
-
value;
|
|
673
|
-
// valid, invalid, pending, status, disabled берутся из statusMachine
|
|
674
|
-
valid;
|
|
675
|
-
invalid;
|
|
676
|
-
pending;
|
|
677
|
-
// Override status и disabled из базового класса
|
|
678
|
-
status;
|
|
679
|
-
disabled;
|
|
680
|
-
// touched, dirty наследуются от FormNode
|
|
681
|
-
errors;
|
|
682
|
-
componentProps;
|
|
683
|
-
/**
|
|
684
|
-
* Вычисляемое свойство: нужно ли показывать ошибку
|
|
685
|
-
* Ошибка показывается если поле невалидно И (touched ИЛИ dirty)
|
|
686
|
-
*/
|
|
687
|
-
shouldShowError;
|
|
688
|
-
// ============================================================================
|
|
689
|
-
// Конфигурация
|
|
690
|
-
// ============================================================================
|
|
691
|
-
validators;
|
|
692
|
-
asyncValidators;
|
|
693
|
-
updateOn;
|
|
694
|
-
initialValue;
|
|
695
|
-
currentAbortController;
|
|
696
|
-
debounceMs;
|
|
697
|
-
validateDebounceTimer;
|
|
698
|
-
/**
|
|
699
|
-
* Pending debounced validation state
|
|
700
|
-
* Contains resolve function and AbortController for cancellation
|
|
701
|
-
*/
|
|
702
|
-
pendingValidation;
|
|
703
|
-
/**
|
|
704
|
-
* Менеджер подписок для централизованного cleanup
|
|
705
|
-
* Использует SubscriptionManager вместо массива для управления подписками
|
|
706
|
-
*/
|
|
707
|
-
disposers = new $();
|
|
708
|
-
component;
|
|
709
|
-
// ============================================================================
|
|
710
|
-
// Конструктор
|
|
711
|
-
// ============================================================================
|
|
712
|
-
constructor(e) {
|
|
713
|
-
super(), this.validators = e.validators || [], this.asyncValidators = e.asyncValidators || [], this.updateOn = e.updateOn || "blur", this.debounceMs = e.debounce || 0, this.component = e.component, this._value = e.valueSignal ?? m(e.value), this.initialValue = this._value.peek(), this._errors = m([]), this._componentProps = m(e.componentProps || {}), this.statusMachine = new ge(e.disabled ? "disabled" : "valid"), this.value = h(() => this._value.value), this.valid = h(() => this.statusMachine.valid.value), this.invalid = h(() => this.statusMachine.invalid.value), this.pending = h(() => this.statusMachine.pending.value), this.status = h(() => this.statusMachine.status.value), this.disabled = h(() => this.statusMachine.disabled.value), this.errors = h(() => this._errors.value), this.componentProps = h(() => this._componentProps.value), this.shouldShowError = h(
|
|
714
|
-
() => this.statusMachine.invalid.value && (this._touched.value || this._dirty.value)
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
// ============================================================================
|
|
718
|
-
// Реализация абстрактных методов FormNode
|
|
719
|
-
// ============================================================================
|
|
720
|
-
getValue() {
|
|
721
|
-
return this._value.peek();
|
|
722
|
-
}
|
|
723
|
-
setValue(e, t) {
|
|
724
|
-
if (this._value.value = e, this._dirty.value = !0, t?.emitEvent === !1)
|
|
725
|
-
return;
|
|
726
|
-
const i = this.validators.length > 0 || this.asyncValidators.length > 0, r = this._errors.value.length > 0;
|
|
727
|
-
if (this.updateOn === "change") {
|
|
728
|
-
this.validate();
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
r && i && this.validate();
|
|
732
|
-
}
|
|
733
|
-
patchValue(e) {
|
|
734
|
-
this.setValue(e);
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* Сбросить поле к указанному значению (или к initialValue)
|
|
738
|
-
*
|
|
739
|
-
* @param value - опциональное значение для сброса. Если не указано, используется initialValue
|
|
740
|
-
*
|
|
741
|
-
* @remarks
|
|
742
|
-
* Этот метод:
|
|
743
|
-
* - Устанавливает значение в value или initialValue
|
|
744
|
-
* - Очищает ошибки валидации
|
|
745
|
-
* - Сбрасывает touched/dirty флаги
|
|
746
|
-
* - Устанавливает статус в 'valid'
|
|
747
|
-
*
|
|
748
|
-
* Если вам нужно сбросить к исходному значению, используйте resetToInitial()
|
|
749
|
-
*
|
|
750
|
-
* @example
|
|
751
|
-
* ```typescript
|
|
752
|
-
* // Сброс к initialValue
|
|
753
|
-
* field.reset();
|
|
754
|
-
*
|
|
755
|
-
* // Сброс к новому значению
|
|
756
|
-
* field.reset('new value');
|
|
757
|
-
* ```
|
|
758
|
-
*/
|
|
759
|
-
reset(e) {
|
|
760
|
-
this._value.value = e !== void 0 ? e : this.initialValue, this._errors.value = [], this._touched.value = !1, this._dirty.value = !1, this.statusMachine.setErrors(!1);
|
|
761
|
-
}
|
|
762
|
-
/**
|
|
763
|
-
* Сбросить поле к исходному значению (initialValue)
|
|
764
|
-
*
|
|
765
|
-
* @remarks
|
|
766
|
-
* Алиас для reset() без параметров, но более явный:
|
|
767
|
-
* - resetToInitial() - явно показывает намерение вернуться к начальному значению
|
|
768
|
-
* - reset() - может принимать новое значение
|
|
769
|
-
*
|
|
770
|
-
* Полезно когда:
|
|
771
|
-
* - Пользователь нажал "Cancel" - вернуть форму в исходное состояние
|
|
772
|
-
* - Форма была изменена через reset(newValue), но нужно вернуться к самому началу
|
|
773
|
-
* - Явное намерение показать "отмену всех изменений"
|
|
774
|
-
*
|
|
775
|
-
* @example
|
|
776
|
-
* ```typescript
|
|
777
|
-
* const field = new FieldNode({ value: 'initial', component: Input });
|
|
778
|
-
*
|
|
779
|
-
* field.setValue('changed');
|
|
780
|
-
* field.reset('temp value');
|
|
781
|
-
* console.log(field.value.value); // 'temp value'
|
|
782
|
-
*
|
|
783
|
-
* field.resetToInitial();
|
|
784
|
-
* console.log(field.value.value); // 'initial'
|
|
785
|
-
* ```
|
|
786
|
-
*/
|
|
787
|
-
resetToInitial() {
|
|
788
|
-
this.reset(this.initialValue);
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Cancel any pending validation (debounced or running)
|
|
792
|
-
* @private
|
|
793
|
-
* @remarks
|
|
794
|
-
* Centralizes all cancellation logic:
|
|
795
|
-
* - Aborts pending debounced validation and resolves its promise
|
|
796
|
-
* - Clears debounce timer
|
|
797
|
-
* - Aborts currently running async validation
|
|
798
|
-
*/
|
|
799
|
-
cancelPendingValidation() {
|
|
800
|
-
this.pendingValidation && (this.pendingValidation.abortController.abort(), this.pendingValidation.resolve(!1), this.pendingValidation = void 0), this.validateDebounceTimer && (clearTimeout(this.validateDebounceTimer), this.validateDebounceTimer = void 0), this.currentAbortController && (this.currentAbortController.abort(), this.currentAbortController = void 0);
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* Запустить валидацию поля
|
|
804
|
-
* @param options - опции валидации
|
|
805
|
-
* @returns `Promise<boolean>` - true если поле валидно
|
|
806
|
-
*
|
|
807
|
-
* @remarks
|
|
808
|
-
* Метод защищен от race conditions через AbortController.
|
|
809
|
-
* При быстром вводе только последняя валидация применяет результаты.
|
|
810
|
-
*
|
|
811
|
-
* @example
|
|
812
|
-
* ```typescript
|
|
813
|
-
* // Обычная валидация
|
|
814
|
-
* await field.validate();
|
|
815
|
-
*
|
|
816
|
-
* // С debounce
|
|
817
|
-
* await field.validate({ debounce: 300 });
|
|
818
|
-
* ```
|
|
819
|
-
*/
|
|
820
|
-
async validate(e) {
|
|
821
|
-
const t = e?.debounce ?? this.debounceMs;
|
|
822
|
-
if (this.cancelPendingValidation(), t <= 0 || this.asyncValidators.length === 0)
|
|
823
|
-
return this.validateImmediate();
|
|
824
|
-
const i = new AbortController();
|
|
825
|
-
return new Promise((r) => {
|
|
826
|
-
this.pendingValidation = { resolve: r, abortController: i }, this.validateDebounceTimer = setTimeout(async () => {
|
|
827
|
-
if (this.validateDebounceTimer = void 0, i.signal.aborted) {
|
|
828
|
-
r(!1);
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
this.pendingValidation = void 0;
|
|
832
|
-
const n = await this.validateImmediate(i);
|
|
833
|
-
r(n);
|
|
834
|
-
}, t), i.signal.addEventListener(
|
|
835
|
-
"abort",
|
|
836
|
-
() => {
|
|
837
|
-
this.validateDebounceTimer && (clearTimeout(this.validateDebounceTimer), this.validateDebounceTimer = void 0), r(!1);
|
|
838
|
-
},
|
|
839
|
-
{ once: !0 }
|
|
840
|
-
);
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Немедленная валидация без debounce
|
|
845
|
-
* @private
|
|
846
|
-
* @param providedController - AbortController from debounced validate()
|
|
847
|
-
* @remarks
|
|
848
|
-
* Защищена от race conditions через AbortController:
|
|
849
|
-
* - Отменяет предыдущую валидацию при запуске новой (if no controller provided)
|
|
850
|
-
* - Передаёт AbortSignal в async валидаторы для отмены операций (например, fetch)
|
|
851
|
-
* - Проверяет signal.aborted в ключевых точках
|
|
852
|
-
*/
|
|
853
|
-
async validateImmediate(e) {
|
|
854
|
-
const t = e ?? new AbortController();
|
|
855
|
-
e || this.currentAbortController?.abort(), this.currentAbortController = t;
|
|
856
|
-
const { signal: i } = t, r = [];
|
|
857
|
-
for (const a of this.validators) {
|
|
858
|
-
const u = a(this._value.value);
|
|
859
|
-
u && r.push(u);
|
|
860
|
-
}
|
|
861
|
-
if (i.aborted)
|
|
862
|
-
return !1;
|
|
863
|
-
if (r.length > 0) {
|
|
864
|
-
this._errors.value = r;
|
|
865
|
-
const a = r.some((u) => u.severity !== "warning");
|
|
866
|
-
if (this.statusMachine.setErrors(a), a)
|
|
867
|
-
return !1;
|
|
868
|
-
}
|
|
869
|
-
if (this.asyncValidators.length > 0) {
|
|
870
|
-
if (i.aborted)
|
|
871
|
-
return !1;
|
|
872
|
-
this.statusMachine.startValidation();
|
|
873
|
-
try {
|
|
874
|
-
const a = await Promise.all(
|
|
875
|
-
this.asyncValidators.map(async (d) => {
|
|
876
|
-
if (i.aborted)
|
|
877
|
-
throw new DOMException("Validation aborted", "AbortError");
|
|
878
|
-
try {
|
|
879
|
-
const l = await d(this._value.value, { signal: i });
|
|
880
|
-
if (i.aborted)
|
|
881
|
-
throw new DOMException("Validation aborted", "AbortError");
|
|
882
|
-
return l;
|
|
883
|
-
} catch (l) {
|
|
884
|
-
if (l instanceof DOMException && l.name === "AbortError")
|
|
885
|
-
throw l;
|
|
886
|
-
return w.handle(
|
|
887
|
-
l,
|
|
888
|
-
"FieldNode AsyncValidator",
|
|
889
|
-
b.CONVERT
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
})
|
|
893
|
-
);
|
|
894
|
-
if (i.aborted)
|
|
895
|
-
return !1;
|
|
896
|
-
const u = a.filter((d) => d !== null);
|
|
897
|
-
if (u.length > 0) {
|
|
898
|
-
this._errors.value = u;
|
|
899
|
-
const d = u.some((l) => l.severity !== "warning");
|
|
900
|
-
if (this.statusMachine.completeValidation(d), d)
|
|
901
|
-
return !1;
|
|
902
|
-
}
|
|
903
|
-
} catch (a) {
|
|
904
|
-
if (a instanceof DOMException && a.name === "AbortError")
|
|
905
|
-
return !1;
|
|
906
|
-
throw a;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
return i.aborted ? !1 : ((this.validators.length > 0 || this.asyncValidators.length > 0) && (this._errors.value = [], this.statusMachine.completeValidation(!1)), !this._errors.value.some((a) => a.severity !== "warning"));
|
|
910
|
-
}
|
|
911
|
-
setErrors(e) {
|
|
912
|
-
this._errors.value = e;
|
|
913
|
-
const t = e.some((i) => i.severity !== "warning");
|
|
914
|
-
this.statusMachine.setErrors(t);
|
|
915
|
-
}
|
|
916
|
-
clearErrors() {
|
|
917
|
-
this._errors.value = [], this.statusMachine.setErrors(!1);
|
|
918
|
-
}
|
|
919
|
-
// ============================================================================
|
|
920
|
-
// Protected hooks (Template Method pattern)
|
|
921
|
-
// ============================================================================
|
|
922
|
-
/**
|
|
923
|
-
* Hook: вызывается после markAsTouched()
|
|
924
|
-
*
|
|
925
|
-
* Для FieldNode: если updateOn === 'blur', запускаем валидацию
|
|
926
|
-
*/
|
|
927
|
-
onMarkAsTouched() {
|
|
928
|
-
this.updateOn === "blur" && this.validate();
|
|
929
|
-
}
|
|
930
|
-
/**
|
|
931
|
-
* Hook: вызывается после disable()
|
|
932
|
-
*
|
|
933
|
-
* Для FieldNode: синхронизируем statusMachine и очищаем ошибки
|
|
934
|
-
*/
|
|
935
|
-
onDisable() {
|
|
936
|
-
this.statusMachine.disable(), this._errors.value = [];
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Hook: вызывается после enable()
|
|
940
|
-
*
|
|
941
|
-
* Для FieldNode: синхронизируем statusMachine и запускаем валидацию
|
|
942
|
-
*/
|
|
943
|
-
onEnable() {
|
|
944
|
-
this.statusMachine.enable(this._errors.value.length > 0), this.validate();
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* Обновляет свойства компонента (например, опции для Select)
|
|
948
|
-
*
|
|
949
|
-
* @example
|
|
950
|
-
* ```typescript
|
|
951
|
-
* // Обновление опций для Select после загрузки справочников
|
|
952
|
-
* form.registrationAddress.city.updateComponentProps({
|
|
953
|
-
* options: cities
|
|
954
|
-
* });
|
|
955
|
-
* ```
|
|
956
|
-
*/
|
|
957
|
-
updateComponentProps(e) {
|
|
958
|
-
this._componentProps.value = {
|
|
959
|
-
...this._componentProps.value,
|
|
960
|
-
...e
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
/**
|
|
964
|
-
* Динамически изменяет триггер валидации (updateOn)
|
|
965
|
-
* Полезно для адаптивной валидации - например, переключиться на instant feedback после первого submit
|
|
966
|
-
*
|
|
967
|
-
* @param updateOn - новый триггер валидации: 'change' | 'blur' | 'submit'
|
|
968
|
-
*
|
|
969
|
-
* @example
|
|
970
|
-
* Пример node-level: плоская схема с инлайн-значениями (back-compat путь до M1). В layout-схеме
|
|
971
|
-
* `createFormFromModel({ model, schema })` поля `validators` нет — правила живут в `defineValidationSchema`.
|
|
972
|
-
* ```typescript
|
|
973
|
-
* // Сценарий 1: Instant feedback после submit
|
|
974
|
-
* const form = createLegacyForm({
|
|
975
|
-
* email: {
|
|
976
|
-
* value: '',
|
|
977
|
-
* component: Input,
|
|
978
|
-
* updateOn: 'submit', // Изначально валидация только при submit
|
|
979
|
-
* validators: [required, email],
|
|
980
|
-
* },
|
|
981
|
-
* });
|
|
982
|
-
*
|
|
983
|
-
* await form.submit(async (values) => {
|
|
984
|
-
* // После submit переключаем на instant feedback
|
|
985
|
-
* form.email.setUpdateOn('change');
|
|
986
|
-
* await api.save(values);
|
|
987
|
-
* });
|
|
988
|
-
*
|
|
989
|
-
* // Теперь валидация происходит при каждом изменении
|
|
990
|
-
*
|
|
991
|
-
* // Сценарий 2: Прогрессивное улучшение
|
|
992
|
-
* form.email.setUpdateOn('blur'); // Сначала только при blur
|
|
993
|
-
* // ... пользователь начинает вводить ...
|
|
994
|
-
* form.email.setUpdateOn('change'); // Переключаем на change для real-time feedback
|
|
995
|
-
* ```
|
|
996
|
-
*/
|
|
997
|
-
setUpdateOn(e) {
|
|
998
|
-
this.updateOn = e;
|
|
999
|
-
}
|
|
1000
|
-
getUpdateOn() {
|
|
1001
|
-
return this.updateOn;
|
|
1002
|
-
}
|
|
1003
|
-
// ============================================================================
|
|
1004
|
-
// Методы-помощники для реактивности (Фаза 1)
|
|
1005
|
-
// ============================================================================
|
|
1006
|
-
/**
|
|
1007
|
-
* Подписка на изменения значения поля
|
|
1008
|
-
* Автоматически отслеживает изменения через @preact/signals effect
|
|
1009
|
-
*
|
|
1010
|
-
* @param callback - Функция, вызываемая при изменении значения.
|
|
1011
|
-
* Для async операций передается AbortSignal во втором параметре.
|
|
1012
|
-
* @returns Функция отписки для cleanup
|
|
1013
|
-
*
|
|
1014
|
-
* @example
|
|
1015
|
-
* ```typescript
|
|
1016
|
-
* // Синхронный callback
|
|
1017
|
-
* const unsubscribe = form.email.watch((value) => {
|
|
1018
|
-
* console.log('Email changed:', value);
|
|
1019
|
-
* });
|
|
1020
|
-
*
|
|
1021
|
-
* // Асинхронный callback с поддержкой отмены
|
|
1022
|
-
* const unsubscribe = form.email.watch(async (value, signal) => {
|
|
1023
|
-
* const result = await fetch('/api/validate', { signal });
|
|
1024
|
-
* // ...
|
|
1025
|
-
* });
|
|
1026
|
-
*
|
|
1027
|
-
* // Cleanup
|
|
1028
|
-
* useEffect(() => unsubscribe, []);
|
|
1029
|
-
* ```
|
|
1030
|
-
*/
|
|
1031
|
-
watch(e) {
|
|
1032
|
-
const t = new AbortController(), i = g(() => {
|
|
1033
|
-
const n = this.value.value;
|
|
1034
|
-
e(n, t.signal);
|
|
1035
|
-
}), r = S(k.Watch);
|
|
1036
|
-
return this.disposers.add(r, () => {
|
|
1037
|
-
t.abort(), i();
|
|
1038
|
-
});
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Вычисляемое значение из других полей
|
|
1042
|
-
* Автоматически обновляет текущее поле при изменении источников
|
|
1043
|
-
*
|
|
1044
|
-
* @param sources - Массив ReadonlySignal для отслеживания
|
|
1045
|
-
* @param computeFn - Функция вычисления нового значения
|
|
1046
|
-
* @returns Функция отписки для cleanup
|
|
1047
|
-
*
|
|
1048
|
-
* @example
|
|
1049
|
-
* ```typescript
|
|
1050
|
-
* // Автоматический расчет первоначального взноса (20% от стоимости)
|
|
1051
|
-
* const dispose = form.initialPayment.computeFrom(
|
|
1052
|
-
* [form.propertyValue.value],
|
|
1053
|
-
* (propertyValue) => {
|
|
1054
|
-
* return propertyValue ? propertyValue * 0.2 : null;
|
|
1055
|
-
* }
|
|
1056
|
-
* );
|
|
1057
|
-
*
|
|
1058
|
-
* // Cleanup
|
|
1059
|
-
* useEffect(() => dispose, []);
|
|
1060
|
-
* ```
|
|
1061
|
-
*/
|
|
1062
|
-
computeFrom(e, t) {
|
|
1063
|
-
const i = g(() => {
|
|
1064
|
-
const n = e.map((u) => u.value), a = t(...n);
|
|
1065
|
-
this.setValue(a, { emitEvent: !1 });
|
|
1066
|
-
}), r = S(k.ComputeFrom);
|
|
1067
|
-
return this.disposers.add(r, i);
|
|
1068
|
-
}
|
|
1069
|
-
/**
|
|
1070
|
-
* Очистить все ресурсы и таймеры
|
|
1071
|
-
* Должен вызываться при unmount компонента
|
|
1072
|
-
*
|
|
1073
|
-
* @remarks
|
|
1074
|
-
* Освобождает все ресурсы:
|
|
1075
|
-
* - Отписывает все subscriptions через SubscriptionManager
|
|
1076
|
-
* - Отменяет pending/running валидации через cancelPendingValidation()
|
|
1077
|
-
*
|
|
1078
|
-
* Использует try-finally для гарантированного cleanup даже при ошибках.
|
|
1079
|
-
*
|
|
1080
|
-
* @example
|
|
1081
|
-
* ```typescript
|
|
1082
|
-
* useEffect(() => {
|
|
1083
|
-
* return () => {
|
|
1084
|
-
* field.dispose();
|
|
1085
|
-
* };
|
|
1086
|
-
* }, []);
|
|
1087
|
-
* ```
|
|
1088
|
-
*/
|
|
1089
|
-
dispose() {
|
|
1090
|
-
try {
|
|
1091
|
-
this.disposers.dispose();
|
|
1092
|
-
} finally {
|
|
1093
|
-
this.cancelPendingValidation();
|
|
1
|
+
import { c as ue } from "./create-model-BvcbNPgL.js";
|
|
2
|
+
import { e as ft, a as vt } from "./create-model-BvcbNPgL.js";
|
|
3
|
+
import { d as T, q as H, t as de, o as he, n as fe, b as J, m as _, i as X, v as ve, w as pe, p as me, f as we, g as ye } from "./safe-effect-BbtX3KuN.js";
|
|
4
|
+
import { a as mt, c as wt, r as yt, s as bt, e as gt, u as kt } from "./safe-effect-BbtX3KuN.js";
|
|
5
|
+
import { signal as w, computed as d, batch as D, effect as be, untracked as A } from "@preact/signals-core";
|
|
6
|
+
import { N as j, R as ge, k as ke, r as k } from "./node-kind-C-r6ZMdo.js";
|
|
7
|
+
import { r as O } from "./signal-node-registry-tiwel-TV.js";
|
|
8
|
+
import { g as $t } from "./signal-node-registry-tiwel-TV.js";
|
|
9
|
+
import { c as Se, u as $e } from "./schema-controller-CpiO2JoK.js";
|
|
10
|
+
import { a as Pt } from "./schema-controller-CpiO2JoK.js";
|
|
11
|
+
import { e as Ee, h as Pe, i as Ae, c as Oe, j as Fe } from "./strategy-CoFSemuP.js";
|
|
12
|
+
import { c as Ot, a as Ft, b as It, i as Nt } from "./type-guards-D-N_5Ddh.js";
|
|
13
|
+
function Ie() {
|
|
14
|
+
const t = w(!1), e = w(!1), s = w(0);
|
|
15
|
+
return {
|
|
16
|
+
touched: d(() => t.value),
|
|
17
|
+
dirty: d(() => e.value),
|
|
18
|
+
touches: d(() => s.value),
|
|
19
|
+
setTouched: (i) => {
|
|
20
|
+
D(() => {
|
|
21
|
+
t.value = i, i && (s.value = s.peek() + 1);
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
setDirty: (i) => {
|
|
25
|
+
e.value = i;
|
|
1094
26
|
}
|
|
1095
|
-
}
|
|
27
|
+
};
|
|
1096
28
|
}
|
|
1097
|
-
function R(
|
|
1098
|
-
const
|
|
1099
|
-
const o = [...t.value];
|
|
1100
|
-
for (const f of e())
|
|
1101
|
-
o.push(...f.errors.value);
|
|
1102
|
-
return o;
|
|
1103
|
-
}), c = h(() => i?.value ? "disabled" : a.value ? "pending" : n.value ? "invalid" : "valid");
|
|
29
|
+
function R() {
|
|
30
|
+
const t = w([]);
|
|
1104
31
|
return {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
32
|
+
list: d(() => t.value),
|
|
33
|
+
blocking: d(() => t.value.some((e) => e.severity !== "warning")),
|
|
34
|
+
set: (e) => {
|
|
35
|
+
t.value = e;
|
|
36
|
+
},
|
|
37
|
+
clear: () => {
|
|
38
|
+
t.value = [];
|
|
39
|
+
}
|
|
1112
40
|
};
|
|
1113
41
|
}
|
|
1114
|
-
function
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const i = () => {
|
|
1121
|
-
if (!t) {
|
|
1122
|
-
const r = {};
|
|
1123
|
-
e.forEach((n, a) => {
|
|
1124
|
-
r[a] = n && U(n) ? n.getProxy() : n;
|
|
1125
|
-
}), t = r;
|
|
42
|
+
function K(t = !1) {
|
|
43
|
+
const e = w(t);
|
|
44
|
+
return {
|
|
45
|
+
disabled: d(() => e.value),
|
|
46
|
+
setDisabled: (s) => {
|
|
47
|
+
e.value = s;
|
|
1126
48
|
}
|
|
1127
|
-
return t;
|
|
1128
49
|
};
|
|
1129
|
-
return new Proxy(s, {
|
|
1130
|
-
get: (r, n) => {
|
|
1131
|
-
if (n === H)
|
|
1132
|
-
return i();
|
|
1133
|
-
if (n in r)
|
|
1134
|
-
return r[n];
|
|
1135
|
-
if (typeof n == "string" && e.has(n)) {
|
|
1136
|
-
const a = e.get(n);
|
|
1137
|
-
return a && U(a) ? a.getProxy() : a;
|
|
1138
|
-
}
|
|
1139
|
-
},
|
|
1140
|
-
set: (r, n, a) => typeof n == "string" && e.has(n) ? !1 : (r[n] = a, !0),
|
|
1141
|
-
has: (r, n) => n === H || typeof n == "string" && e.has(n) ? !0 : n in r,
|
|
1142
|
-
ownKeys: (r) => {
|
|
1143
|
-
const n = Reflect.ownKeys(r), a = Array.from(e.keys());
|
|
1144
|
-
return [.../* @__PURE__ */ new Set([...n, ...a])];
|
|
1145
|
-
},
|
|
1146
|
-
getOwnPropertyDescriptor: (r, n) => typeof n == "string" && e.has(n) ? { enumerable: !0, configurable: !0 } : Reflect.getOwnPropertyDescriptor(r, n)
|
|
1147
|
-
});
|
|
1148
50
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}
|
|
1156
|
-
/** Внутренний сигнал состояния отправки */
|
|
1157
|
-
_submitting = m(!1);
|
|
1158
|
-
/** Публичный read-only сигнал состояния отправки */
|
|
1159
|
-
submitting = h(() => this._submitting.value);
|
|
1160
|
-
/**
|
|
1161
|
-
* Отправить форму
|
|
1162
|
-
*
|
|
1163
|
-
* Процесс:
|
|
1164
|
-
* 1. Помечает все поля как touched (для отображения ошибок)
|
|
1165
|
-
* 2. Валидирует форму
|
|
1166
|
-
* 3. Если валидация успешна - вызывает onSubmit
|
|
1167
|
-
* 4. Управляет состоянием submitting
|
|
1168
|
-
*
|
|
1169
|
-
* @param onSubmit - Callback для отправки данных
|
|
1170
|
-
* @param options - Опции submit
|
|
1171
|
-
* @returns Результат от onSubmit или `null` если валидация не пройдена
|
|
1172
|
-
*
|
|
1173
|
-
* @remarks
|
|
1174
|
-
* Возвращаемый `null` неоднозначен: он сигналит и провал валидации, и легитимный `null`,
|
|
1175
|
-
* который вернул `onSubmit` (или void-обработчик). Чтобы различать эти случаи, используйте
|
|
1176
|
-
* {@link submitWithResult} — он отдаёт явный флаг `success`.
|
|
1177
|
-
*
|
|
1178
|
-
* @example
|
|
1179
|
-
* ```typescript
|
|
1180
|
-
* const result = await submitter.submit(async (values) => {
|
|
1181
|
-
* const response = await fetch('/api/form', {
|
|
1182
|
-
* method: 'POST',
|
|
1183
|
-
* body: JSON.stringify(values)
|
|
1184
|
-
* });
|
|
1185
|
-
* return response.json();
|
|
1186
|
-
* });
|
|
1187
|
-
*
|
|
1188
|
-
* if (result === null) {
|
|
1189
|
-
* console.log('Форма не прошла валидацию');
|
|
1190
|
-
* }
|
|
1191
|
-
* ```
|
|
1192
|
-
*/
|
|
1193
|
-
async submit(e, t) {
|
|
1194
|
-
const { skipValidation: i = !1, skipTouch: r = !1 } = t || {};
|
|
1195
|
-
if (r || this.form.markAsTouched(), !i && !await this.form.validate())
|
|
1196
|
-
return null;
|
|
1197
|
-
this._submitting.value = !0;
|
|
1198
|
-
try {
|
|
1199
|
-
return await e(this.form.getValue());
|
|
1200
|
-
} finally {
|
|
1201
|
-
this._submitting.value = !1;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
/**
|
|
1205
|
-
* Отправить форму с расширенным результатом
|
|
1206
|
-
*
|
|
1207
|
-
* В отличие от submit(), возвращает объект с информацией об успехе/ошибке
|
|
1208
|
-
*
|
|
1209
|
-
* @param onSubmit - Callback для отправки данных
|
|
1210
|
-
* @param options - Опции submit
|
|
1211
|
-
* @returns Объект SubmitResult с данными и статусом
|
|
1212
|
-
*
|
|
1213
|
-
* @example
|
|
1214
|
-
* ```typescript
|
|
1215
|
-
* const result = await submitter.submitWithResult(async (values) => {
|
|
1216
|
-
* return await api.saveForm(values);
|
|
1217
|
-
* });
|
|
1218
|
-
*
|
|
1219
|
-
* if (result.success) {
|
|
1220
|
-
* console.log('Сохранено:', result.data);
|
|
1221
|
-
* } else if (result.error) {
|
|
1222
|
-
* console.error('Ошибка:', result.error.message);
|
|
1223
|
-
* } else {
|
|
1224
|
-
* console.log('Валидация не пройдена');
|
|
1225
|
-
* }
|
|
1226
|
-
* ```
|
|
1227
|
-
*/
|
|
1228
|
-
async submitWithResult(e, t) {
|
|
1229
|
-
const { skipValidation: i = !1, skipTouch: r = !1 } = t || {};
|
|
1230
|
-
if (r || this.form.markAsTouched(), !i && !await this.form.validate())
|
|
1231
|
-
return { success: !1, data: null };
|
|
1232
|
-
this._submitting.value = !0;
|
|
1233
|
-
try {
|
|
1234
|
-
return { success: !0, data: await e(this.form.getValue()) };
|
|
1235
|
-
} catch (n) {
|
|
1236
|
-
return {
|
|
1237
|
-
success: !1,
|
|
1238
|
-
data: null,
|
|
1239
|
-
error: n instanceof Error ? n : new Error(String(n))
|
|
1240
|
-
};
|
|
1241
|
-
} finally {
|
|
1242
|
-
this._submitting.value = !1;
|
|
51
|
+
function W() {
|
|
52
|
+
const t = w(0);
|
|
53
|
+
return {
|
|
54
|
+
active: d(() => t.value > 0),
|
|
55
|
+
mark: (e) => {
|
|
56
|
+
t.value = Math.max(0, t.peek() + (e ? 1 : -1));
|
|
1243
57
|
}
|
|
1244
|
-
}
|
|
58
|
+
};
|
|
1245
59
|
}
|
|
1246
|
-
function
|
|
1247
|
-
const e =
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
60
|
+
function Ne(t = {}) {
|
|
61
|
+
const e = w(t);
|
|
62
|
+
return {
|
|
63
|
+
props: d(() => e.value),
|
|
64
|
+
update: (s) => {
|
|
65
|
+
e.value = { ...e.peek(), ...s };
|
|
66
|
+
}
|
|
67
|
+
};
|
|
1254
68
|
}
|
|
1255
|
-
class
|
|
1256
|
-
|
|
1257
|
-
// Приватные поля
|
|
1258
|
-
// ============================================================================
|
|
1259
|
-
id = crypto.randomUUID();
|
|
1260
|
-
/**
|
|
1261
|
-
* Коллекция полей формы (упрощённый Map вместо FieldRegistry)
|
|
1262
|
-
*/
|
|
1263
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1264
|
-
_fields = /* @__PURE__ */ new Map();
|
|
1265
|
-
/**
|
|
1266
|
-
* Менеджер подписок для централизованного cleanup
|
|
1267
|
-
*/
|
|
1268
|
-
disposers = new $();
|
|
1269
|
-
/**
|
|
1270
|
-
* Ссылка на Proxy-инстанс
|
|
1271
|
-
*/
|
|
1272
|
-
_proxyInstance;
|
|
1273
|
-
/**
|
|
1274
|
-
* Cleanup декларативной схемы поведения (createForm({ behavior })). Вызывается в dispose().
|
|
1275
|
-
*/
|
|
1276
|
-
_behaviorCleanup;
|
|
1277
|
-
/**
|
|
1278
|
-
* Фабрика для создания узлов формы
|
|
1279
|
-
*/
|
|
1280
|
-
nodeFactory = new Ae();
|
|
1281
|
-
// ============================================================================
|
|
1282
|
-
// Приватные сигналы состояния (inline из StateManager)
|
|
1283
|
-
// ============================================================================
|
|
1284
|
-
/** Управление отправкой формы */
|
|
1285
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1286
|
-
formSubmitter;
|
|
1287
|
-
/** Флаг disabled состояния */
|
|
1288
|
-
_disabled = m(!1);
|
|
1289
|
-
/** Form-level validation errors */
|
|
1290
|
-
_formErrors = m([]);
|
|
1291
|
-
/**
|
|
1292
|
-
* M1: связь листовой ноды-ребёнка → её сигнал модели (тот, что помечает `markDerived`).
|
|
1293
|
-
* Нужна bulk-сеттерам (`setValue`/`patchValue`), чтобы корректно определять derived-поля:
|
|
1294
|
-
* `field.value` — computed-обёртка, отличная от записываемого сигнала модели. @internal
|
|
1295
|
-
*/
|
|
1296
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1297
|
-
_fieldSignals = /* @__PURE__ */ new WeakMap();
|
|
1298
|
-
// ============================================================================
|
|
1299
|
-
// Публичные computed signals
|
|
1300
|
-
// ============================================================================
|
|
69
|
+
class Ve {
|
|
70
|
+
[j] = "control";
|
|
1301
71
|
value;
|
|
1302
72
|
valid;
|
|
1303
73
|
invalid;
|
|
74
|
+
pending;
|
|
75
|
+
disabled;
|
|
1304
76
|
touched;
|
|
77
|
+
/** Уходы из поля, включая повторные, — триггер стратегии `blur`. @internal */
|
|
78
|
+
touches;
|
|
1305
79
|
dirty;
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
for (const [u, d] of Object.entries(n)) {
|
|
1314
|
-
const l = this.createNode(d);
|
|
1315
|
-
this._fields.set(u, l);
|
|
1316
|
-
}
|
|
1317
|
-
this.value = h(() => {
|
|
1318
|
-
const u = {};
|
|
1319
|
-
return this._fields.forEach((d, l) => {
|
|
1320
|
-
u[l] = d.value.value;
|
|
1321
|
-
}), u;
|
|
1322
|
-
});
|
|
1323
|
-
const a = R({
|
|
1324
|
-
getChildren: () => Array.from(this._fields.values()),
|
|
1325
|
-
ownErrors: this._formErrors,
|
|
1326
|
-
disabled: this._disabled
|
|
1327
|
-
});
|
|
1328
|
-
this.valid = a.valid, this.invalid = a.invalid, this.pending = a.pending, this.touched = a.touched, this.dirty = a.dirty, this.errors = a.errors, this.status = a.status, this.submitting = this.formSubmitter.submitting;
|
|
1329
|
-
}
|
|
1330
|
-
// ============================================================================
|
|
1331
|
-
// Приватный метод для создания Proxy
|
|
1332
|
-
// ============================================================================
|
|
1333
|
-
/**
|
|
1334
|
-
* Создать Proxy для типобезопасного доступа к полям
|
|
1335
|
-
* @see buildFormProxy
|
|
1336
|
-
*/
|
|
1337
|
-
buildProxy() {
|
|
1338
|
-
return Ee(this, this._fields);
|
|
1339
|
-
}
|
|
1340
|
-
// ============================================================================
|
|
1341
|
-
// Реализация абстрактных методов FormNode
|
|
1342
|
-
// ============================================================================
|
|
1343
|
-
getValue() {
|
|
1344
|
-
const e = {};
|
|
1345
|
-
return this._fields.forEach((t, i) => {
|
|
1346
|
-
e[i] = t.getValue();
|
|
1347
|
-
}), e;
|
|
1348
|
-
}
|
|
1349
|
-
setValue(e, t) {
|
|
1350
|
-
for (const [i, r] of Object.entries(e)) {
|
|
1351
|
-
const n = this._fields.get(i);
|
|
1352
|
-
if (n) {
|
|
1353
|
-
const a = this._fieldSignals.get(n) ?? n.value;
|
|
1354
|
-
if (G(a)) continue;
|
|
1355
|
-
n.setValue(r, t);
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
patchValue(e) {
|
|
1360
|
-
ne(() => {
|
|
1361
|
-
for (const [t, i] of Object.entries(e)) {
|
|
1362
|
-
const r = this._fields.get(t);
|
|
1363
|
-
if (r && i !== void 0) {
|
|
1364
|
-
const n = this._fieldSignals.get(r) ?? r.value;
|
|
1365
|
-
if (G(n)) continue;
|
|
1366
|
-
r.setValue(i, { emitEvent: !1 });
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
80
|
+
issues;
|
|
81
|
+
ownIssues;
|
|
82
|
+
component;
|
|
83
|
+
componentProps;
|
|
84
|
+
shouldShowError;
|
|
85
|
+
shouldShowIssues;
|
|
86
|
+
source;
|
|
1371
87
|
/**
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
* @param value - опциональный объект со значениями для сброса
|
|
1375
|
-
*
|
|
1376
|
-
* @remarks
|
|
1377
|
-
* Рекурсивно вызывает reset() для всех полей формы
|
|
1378
|
-
*
|
|
1379
|
-
* @example
|
|
1380
|
-
* ```typescript
|
|
1381
|
-
* // Сброс к initialValues
|
|
1382
|
-
* form.reset();
|
|
1383
|
-
*
|
|
1384
|
-
* // Сброс к новым значениям
|
|
1385
|
-
* form.reset({ email: 'new@mail.com', password: '' });
|
|
1386
|
-
* ```
|
|
88
|
+
* Значение на момент построения. К нему возвращает `resetToCreation` (`resetOnDisable`), а
|
|
89
|
+
* {@link reset} — только поле над собственным сигналом, без модели за ним.
|
|
1387
90
|
*/
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
91
|
+
initialValue;
|
|
92
|
+
interaction = Ie();
|
|
93
|
+
issueState = R();
|
|
94
|
+
availability;
|
|
1394
95
|
/**
|
|
1395
|
-
*
|
|
96
|
+
* Блокировка из схемы (`disabled: true` узла — вычисляемые `total`, `fullName`). Постоянная:
|
|
97
|
+
* `enable()` контейнера её не снимает, иначе «выключить форму на время отправки → включить»
|
|
98
|
+
* разблокировало бы такие поля. Снимает её только явный `enable()` этого поля.
|
|
1396
99
|
*/
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
100
|
+
locked;
|
|
101
|
+
pendingState = W();
|
|
102
|
+
componentState;
|
|
103
|
+
constructor(e) {
|
|
104
|
+
this.source = e.valueSignal, this.initialValue = this.source.peek(), this.component = e.component, this.availability = K(e.disabled === !0), this.locked = e.disabled === !0, this.componentState = Ne(e.componentProps ?? {});
|
|
105
|
+
const { disabled: s } = this.availability, i = this.issueState.blocking;
|
|
106
|
+
this.value = d(() => this.source.value), this.disabled = s, this.pending = d(() => !s.value && this.pendingState.active.value), this.invalid = d(() => !s.value && !this.pending.value && i.value), this.valid = d(() => !s.value && !this.pending.value && !i.value), this.touched = this.interaction.touched, this.touches = this.interaction.touches, this.dirty = this.interaction.dirty, this.issues = this.issueState.list, this.ownIssues = this.issueState.list, this.componentProps = this.componentState.props;
|
|
107
|
+
const n = d(() => this.touched.value || this.dirty.value);
|
|
108
|
+
this.shouldShowError = d(() => this.invalid.value && n.value), this.shouldShowIssues = d(
|
|
109
|
+
() => !s.value && !this.pending.value && this.issues.value.length > 0 && n.value
|
|
1405
110
|
);
|
|
1406
111
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
*/
|
|
1410
|
-
setErrors(e) {
|
|
1411
|
-
this._formErrors.value = e;
|
|
1412
|
-
}
|
|
1413
|
-
/**
|
|
1414
|
-
* Очистить все errors (form-level + field-level)
|
|
1415
|
-
*/
|
|
1416
|
-
clearErrors() {
|
|
1417
|
-
this._formErrors.value = [], this._fields.forEach((e) => e.clearErrors());
|
|
1418
|
-
}
|
|
1419
|
-
/**
|
|
1420
|
-
* Получить поле по ключу
|
|
1421
|
-
*/
|
|
1422
|
-
getField(e) {
|
|
1423
|
-
return this._fields.get(e);
|
|
112
|
+
getValue() {
|
|
113
|
+
return this.source.peek();
|
|
1424
114
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
*/
|
|
1428
|
-
get fields() {
|
|
1429
|
-
return this._fields;
|
|
115
|
+
setValue(e) {
|
|
116
|
+
this.source.value = e, this.interaction.setDirty(!0);
|
|
1430
117
|
}
|
|
1431
118
|
/**
|
|
1432
|
-
*
|
|
1433
|
-
*
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
* - form.address.city вместо form.fields.get('address').fields.get('city')
|
|
1437
|
-
*
|
|
1438
|
-
* @returns Proxy-инстанс с типобезопасным доступом к полям или сама форма, если proxy не доступен
|
|
1439
|
-
*
|
|
1440
|
-
* @example
|
|
1441
|
-
* ```typescript
|
|
1442
|
-
* const form = new GroupNode({
|
|
1443
|
-
* email: { value: '', component: Input },
|
|
1444
|
-
* name: { value: '', component: Input },
|
|
1445
|
-
* });
|
|
119
|
+
* Сбросить поле: значение, замечания, `touched` и `dirty`. Без аргумента — к точке отсчёта модели,
|
|
120
|
+
* той же, к которой возвращает `model.reset()`: значению создания модели либо последнему
|
|
121
|
+
* `captureInitial()` (загруженные данные). Поле над собственным сигналом, без модели, — к значению,
|
|
122
|
+
* с которым нода создана.
|
|
1446
123
|
*
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
124
|
+
* У производного поля (цель `compute`) без аргумента сбрасывается только состояние: значением
|
|
125
|
+
* владеет поведение, а значение создания ноды снято до первого расчёта — запись вернула бы
|
|
126
|
+
* «сырое» значение, которое `compute` пересчитает лишь при смене источников.
|
|
1450
127
|
*/
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
}
|
|
1454
|
-
/**
|
|
1455
|
-
* Получить все поля формы как итератор
|
|
1456
|
-
*/
|
|
1457
|
-
getAllFields() {
|
|
1458
|
-
return this._fields.values();
|
|
1459
|
-
}
|
|
1460
|
-
// ============================================================================
|
|
1461
|
-
// Protected hooks (Template Method pattern)
|
|
1462
|
-
// ============================================================================
|
|
1463
|
-
onMarkAsTouched() {
|
|
1464
|
-
this._fields.forEach((e) => e.markAsTouched());
|
|
1465
|
-
}
|
|
1466
|
-
onMarkAsUntouched() {
|
|
1467
|
-
this._fields.forEach((e) => e.markAsUntouched());
|
|
1468
|
-
}
|
|
1469
|
-
onMarkAsDirty() {
|
|
1470
|
-
this._fields.forEach((e) => e.markAsDirty());
|
|
128
|
+
reset(e) {
|
|
129
|
+
e !== void 0 ? this.source.value = e : T(this.source) || (this.source.value = this.referenceValue()), this.resetState();
|
|
1471
130
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
131
|
+
/** Точка отсчёта модели за полем; у поля без модели — значение создания. */
|
|
132
|
+
referenceValue() {
|
|
133
|
+
const e = H(this.source);
|
|
134
|
+
return e === void 0 ? this.initialValue : e.value;
|
|
1474
135
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
/**
|
|
1479
|
-
* Отправить форму
|
|
1480
|
-
*
|
|
1481
|
-
* @param onSubmit - Callback для отправки данных
|
|
1482
|
-
* @param options - Опции submit (skipValidation, skipTouch)
|
|
1483
|
-
* @returns Результат от onSubmit или `null` если валидация не пройдена
|
|
1484
|
-
*
|
|
1485
|
-
* @remarks
|
|
1486
|
-
* `null` перегружен: он означает и «валидация не пройдена», и легитимный `null`-результат
|
|
1487
|
-
* `onSubmit` (или void-обработчик). Если вызывающей стороне нужно различать эти случаи —
|
|
1488
|
-
* используйте {@link submitWithResult}, который возвращает явный флаг `success`.
|
|
1489
|
-
*/
|
|
1490
|
-
async submit(e, t) {
|
|
1491
|
-
return this.formSubmitter.submit(e, t);
|
|
136
|
+
/** Значение создания и чистое состояние, у цели `compute` тоже — для `resetOnDisable`. @internal */
|
|
137
|
+
resetToCreation() {
|
|
138
|
+
this.source.value = this.initialValue, this.resetState();
|
|
1492
139
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
*
|
|
1496
|
-
* @param onSubmit - Callback для отправки данных
|
|
1497
|
-
* @param options - Опции submit
|
|
1498
|
-
* @returns Объект SubmitResult с данными, статусом и возможной ошибкой
|
|
1499
|
-
*/
|
|
1500
|
-
async submitWithResult(e, t) {
|
|
1501
|
-
return this.formSubmitter.submitWithResult(e, t);
|
|
140
|
+
resetState() {
|
|
141
|
+
this.issueState.clear(), this.interaction.setTouched(!1), this.interaction.setDirty(!1);
|
|
1502
142
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
*
|
|
1506
|
-
* Поддерживаемые форматы путей:
|
|
1507
|
-
* - Simple: "email" - получить поле верхнего уровня
|
|
1508
|
-
* - Nested: "address.city" - получить вложенное поле
|
|
1509
|
-
* - Array index: "items[0]" - получить элемент массива по индексу
|
|
1510
|
-
* - Combined: "items[0].name" - получить поле элемента массива
|
|
1511
|
-
*
|
|
1512
|
-
* @param path - Путь к полю
|
|
1513
|
-
* @returns FormNode если найдено, undefined если путь не существует
|
|
1514
|
-
*
|
|
1515
|
-
* @example
|
|
1516
|
-
* ```typescript
|
|
1517
|
-
* const form = new GroupNode({
|
|
1518
|
-
* email: { value: '', component: Input },
|
|
1519
|
-
* address: {
|
|
1520
|
-
* city: { value: '', component: Input }
|
|
1521
|
-
* },
|
|
1522
|
-
* items: [{ name: { value: '', component: Input } }]
|
|
1523
|
-
* });
|
|
1524
|
-
*
|
|
1525
|
-
* form.getFieldByPath('email'); // FieldNode
|
|
1526
|
-
* form.getFieldByPath('address.city'); // FieldNode
|
|
1527
|
-
* form.getFieldByPath('items[0]'); // GroupNode
|
|
1528
|
-
* form.getFieldByPath('items[0].name'); // FieldNode
|
|
1529
|
-
* form.getFieldByPath('invalid.path'); // undefined
|
|
1530
|
-
* ```
|
|
1531
|
-
*/
|
|
1532
|
-
getFieldByPath(e) {
|
|
1533
|
-
if (e.startsWith(".") || e.endsWith("."))
|
|
1534
|
-
return;
|
|
1535
|
-
const t = we(e);
|
|
1536
|
-
if (t.length === 0)
|
|
1537
|
-
return;
|
|
1538
|
-
let i = this;
|
|
1539
|
-
for (const r of t) {
|
|
1540
|
-
if (!(i instanceof _) || (i = i.getField(r.key), !i)) return;
|
|
1541
|
-
if (r.index !== void 0)
|
|
1542
|
-
if ("at" in i && "length" in i && typeof i.at == "function") {
|
|
1543
|
-
const n = i.at(r.index);
|
|
1544
|
-
if (!n) return;
|
|
1545
|
-
i = n;
|
|
1546
|
-
} else
|
|
1547
|
-
return;
|
|
1548
|
-
}
|
|
1549
|
-
return i;
|
|
143
|
+
markAsTouched() {
|
|
144
|
+
this.interaction.setTouched(!0);
|
|
1550
145
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
// ============================================================================
|
|
1554
|
-
/**
|
|
1555
|
-
* Создать узел на основе конфигурации
|
|
1556
|
-
*
|
|
1557
|
-
* ✅ РЕФАКТОРИНГ: Полное делегирование NodeFactory
|
|
1558
|
-
*
|
|
1559
|
-
* NodeFactory теперь обрабатывает:
|
|
1560
|
-
* - Массивы [schema, ...items]
|
|
1561
|
-
* - FieldConfig
|
|
1562
|
-
* - GroupConfig
|
|
1563
|
-
* - ArrayConfig
|
|
1564
|
-
*
|
|
1565
|
-
* @param config Конфигурация узла
|
|
1566
|
-
* @returns Созданный узел формы
|
|
1567
|
-
* @private
|
|
1568
|
-
*/
|
|
1569
|
-
createNode(e) {
|
|
1570
|
-
return this.nodeFactory.createNode(e);
|
|
146
|
+
markAsUntouched() {
|
|
147
|
+
this.interaction.setTouched(!1);
|
|
1571
148
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
// ============================================================================
|
|
1575
|
-
/**
|
|
1576
|
-
* Связывает два поля: при изменении source автоматически обновляется target
|
|
1577
|
-
*/
|
|
1578
|
-
linkFields(e, t, i) {
|
|
1579
|
-
const r = this._fields.get(e), n = this._fields.get(t);
|
|
1580
|
-
if (!r || !n) {
|
|
1581
|
-
const d = r ? t : e;
|
|
1582
|
-
throw new Error(`GroupNode.linkFields: field "${String(d)}" not found`);
|
|
1583
|
-
}
|
|
1584
|
-
const a = g(() => {
|
|
1585
|
-
const d = r.value.value, l = i ? i(d) : d;
|
|
1586
|
-
n.setValue(l, { emitEvent: !1 });
|
|
1587
|
-
}), u = S(k.LinkFields);
|
|
1588
|
-
return this.disposers.add(u, a);
|
|
149
|
+
disable() {
|
|
150
|
+
this.availability.setDisabled(!0), this.issueState.clear();
|
|
1589
151
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
* Поддерживает вложенные пути типа "address.city"
|
|
1593
|
-
*
|
|
1594
|
-
* @param fieldPath - Строковый путь к полю (например, "address.city")
|
|
1595
|
-
* @param callback - Функция, вызываемая при изменении поля
|
|
1596
|
-
* @returns Функция отписки для cleanup
|
|
1597
|
-
*
|
|
1598
|
-
* @example
|
|
1599
|
-
* ```typescript
|
|
1600
|
-
* // Подписка на изменение страны для загрузки городов
|
|
1601
|
-
* const dispose = form.watchField(
|
|
1602
|
-
* 'registrationAddress.country',
|
|
1603
|
-
* async (countryCode) => {
|
|
1604
|
-
* if (countryCode) {
|
|
1605
|
-
* const cities = await fetchCitiesByCountry(countryCode);
|
|
1606
|
-
* form.registrationAddress.city.updateComponentProps({
|
|
1607
|
-
* options: cities
|
|
1608
|
-
* });
|
|
1609
|
-
* }
|
|
1610
|
-
* }
|
|
1611
|
-
* );
|
|
1612
|
-
*
|
|
1613
|
-
* // Cleanup
|
|
1614
|
-
* useEffect(() => dispose, []);
|
|
1615
|
-
* ```
|
|
1616
|
-
*/
|
|
1617
|
-
/** Подписка на top-level поле — value типизирован как `T[K]`. */
|
|
1618
|
-
watchField(e, t) {
|
|
1619
|
-
const i = this.getFieldByPath(e);
|
|
1620
|
-
if (!i)
|
|
1621
|
-
throw new Error(`GroupNode.watchField: field "${e}" not found`);
|
|
1622
|
-
const r = g(() => {
|
|
1623
|
-
t(i.value.value);
|
|
1624
|
-
}), n = S(k.WatchField);
|
|
1625
|
-
return this.disposers.add(n, r);
|
|
152
|
+
enable() {
|
|
153
|
+
this.locked = !1, this.availability.setDisabled(!1);
|
|
1626
154
|
}
|
|
1627
|
-
/**
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
*/
|
|
1631
|
-
watchFieldByPath(e, t) {
|
|
1632
|
-
const i = this.getFieldByPath(e);
|
|
1633
|
-
if (!i)
|
|
1634
|
-
throw new Error(`GroupNode.watchFieldByPath: field "${e}" not found`);
|
|
1635
|
-
const r = g(() => {
|
|
1636
|
-
t(i.value.value);
|
|
1637
|
-
}), n = S(k.WatchField);
|
|
1638
|
-
return this.disposers.add(n, r);
|
|
155
|
+
/** Каскад от контейнера: блокировку из схемы не снимает. @internal */
|
|
156
|
+
enableFromParent() {
|
|
157
|
+
this.locked || this.availability.setDisabled(!1);
|
|
1639
158
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
*/
|
|
1643
|
-
onDisable() {
|
|
1644
|
-
this._disabled.value = !0, this._fields.forEach((e) => e.disable());
|
|
159
|
+
setIssues(e) {
|
|
160
|
+
this.issueState.set(e);
|
|
1645
161
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
*/
|
|
1649
|
-
onEnable() {
|
|
1650
|
-
this._disabled.value = !1, this._fields.forEach((e) => e.enable());
|
|
162
|
+
clearIssues() {
|
|
163
|
+
this.issueState.clear();
|
|
1651
164
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
* @internal
|
|
1655
|
-
*/
|
|
1656
|
-
attachBehaviorCleanup(e) {
|
|
1657
|
-
this._behaviorCleanup = e;
|
|
165
|
+
updateComponentProps(e) {
|
|
166
|
+
this.componentState.update(e);
|
|
1658
167
|
}
|
|
1659
|
-
/**
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
* @internal
|
|
1663
|
-
*/
|
|
1664
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1665
|
-
registerFieldSignal(e, t) {
|
|
1666
|
-
this._fieldSignals.set(e, t);
|
|
168
|
+
/** Раннер валидации: начало (`true`) и конец (`false`) async-правил поля. @internal */
|
|
169
|
+
setPending(e) {
|
|
170
|
+
this.pendingState.mark(e);
|
|
1667
171
|
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Очистить все ресурсы узла
|
|
1670
|
-
*/
|
|
172
|
+
/** У поля подписок нет. @internal */
|
|
1671
173
|
dispose() {
|
|
1672
|
-
this._behaviorCleanup?.(), this._behaviorCleanup = void 0, this.disposers.dispose(), this._fields.forEach((e) => {
|
|
1673
|
-
"dispose" in e && typeof e.dispose == "function" && e.dispose();
|
|
1674
|
-
});
|
|
1675
174
|
}
|
|
1676
175
|
}
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
176
|
+
function Q(t) {
|
|
177
|
+
const { getChildren: e, ownIssues: s, ownPending: i } = t, n = () => t.disabled?.value ?? !1, a = d(() => s.value.some((l) => l.severity !== "warning")), r = d(
|
|
178
|
+
() => !n() && ((i?.value ?? !1) || e().some((l) => l.pending.value))
|
|
179
|
+
), o = d(
|
|
180
|
+
() => !n() && !r.value && !a.value && e().every((l) => l.disabled.value || l.valid.value)
|
|
181
|
+
), c = d(
|
|
182
|
+
() => !n() && !r.value && (a.value || e().some((l) => !l.disabled.value && l.invalid.value))
|
|
183
|
+
);
|
|
184
|
+
return {
|
|
185
|
+
valid: o,
|
|
186
|
+
invalid: c,
|
|
187
|
+
pending: r,
|
|
188
|
+
touched: d(() => e().some((l) => l.touched.value)),
|
|
189
|
+
dirty: d(() => e().some((l) => l.dirty.value)),
|
|
190
|
+
issues: d(() => [
|
|
191
|
+
...s.value,
|
|
192
|
+
...e().flatMap((l) => l.issues.value)
|
|
193
|
+
])
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const Ce = [
|
|
197
|
+
"value",
|
|
198
|
+
"valid",
|
|
199
|
+
"invalid",
|
|
200
|
+
"pending",
|
|
201
|
+
"disabled",
|
|
202
|
+
"touched",
|
|
203
|
+
"dirty",
|
|
204
|
+
"issues",
|
|
205
|
+
"ownIssues",
|
|
206
|
+
"getValue",
|
|
207
|
+
"setValue",
|
|
208
|
+
"reset",
|
|
209
|
+
"markAsTouched",
|
|
210
|
+
"markAsUntouched",
|
|
211
|
+
"enable",
|
|
212
|
+
"disable",
|
|
213
|
+
"setIssues",
|
|
214
|
+
"clearIssues"
|
|
215
|
+
], L = new Set(Ce), N = "$";
|
|
216
|
+
function Me(t) {
|
|
217
|
+
return ke(t) === "group" ? t.proxy : t;
|
|
218
|
+
}
|
|
219
|
+
function Te(t, e) {
|
|
220
|
+
let s;
|
|
221
|
+
const i = () => {
|
|
222
|
+
if (!s) {
|
|
223
|
+
const r = {};
|
|
224
|
+
e.forEach((o, c) => {
|
|
225
|
+
r[c] = Me(o);
|
|
226
|
+
}), s = Object.freeze(r);
|
|
227
|
+
}
|
|
228
|
+
return s;
|
|
229
|
+
}, n = /* @__PURE__ */ new Map(), a = (r) => {
|
|
230
|
+
const o = Reflect.get(t, r, t);
|
|
231
|
+
if (typeof o != "function") return o;
|
|
232
|
+
let c = n.get(r);
|
|
233
|
+
return c || (c = o.bind(t), n.set(r, c)), c;
|
|
234
|
+
};
|
|
235
|
+
return new Proxy(t, {
|
|
236
|
+
get: (r, o) => o === ge ? t : o === N ? i() : L.has(o) ? a(o) : typeof o == "string" && e.has(o) ? i()[o] : a(o),
|
|
237
|
+
set: (r, o, c) => o === N || L.has(o) || typeof o == "string" && e.has(o) ? !1 : Reflect.set(t, o, c, t),
|
|
238
|
+
has: (r, o) => o === N || typeof o == "string" && e.has(o) || o in t,
|
|
239
|
+
ownKeys: () => [.../* @__PURE__ */ new Set([...Reflect.ownKeys(t), ...e.keys()])],
|
|
240
|
+
getOwnPropertyDescriptor: (r, o) => typeof o == "string" && e.has(o) ? { enumerable: !0, configurable: !0, value: i()[o] } : Reflect.getOwnPropertyDescriptor(t, o)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
class De {
|
|
244
|
+
[j] = "group";
|
|
1694
245
|
value;
|
|
1695
246
|
valid;
|
|
1696
247
|
invalid;
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
/**
|
|
1721
|
-
* Добавить элемент в конец массива
|
|
1722
|
-
* @param initialValue - Начальные значения для нового элемента
|
|
1723
|
-
*/
|
|
1724
|
-
push(e) {
|
|
1725
|
-
const t = this.createItem(e);
|
|
1726
|
-
this.items.value = [...this.items.value, t];
|
|
1727
|
-
}
|
|
1728
|
-
/**
|
|
1729
|
-
* Удалить элемент по индексу
|
|
1730
|
-
* @param index - Индекс элемента для удаления
|
|
1731
|
-
*
|
|
1732
|
-
* @remarks
|
|
1733
|
-
* Вызывает dispose() на удаляемом элементе для очистки подписок
|
|
1734
|
-
*/
|
|
1735
|
-
removeAt(e) {
|
|
1736
|
-
if (e < 0 || e >= this.items.value.length) {
|
|
1737
|
-
w.handle(
|
|
1738
|
-
new Error(
|
|
1739
|
-
`ArrayNode.removeAt: index ${e} out of bounds (length: ${this.items.value.length})`
|
|
1740
|
-
),
|
|
1741
|
-
"ArrayNode.removeAt",
|
|
1742
|
-
b.LOG
|
|
1743
|
-
);
|
|
1744
|
-
return;
|
|
1745
|
-
}
|
|
1746
|
-
const t = this.items.value[e];
|
|
1747
|
-
this.items.value = this.items.value.filter((i, r) => r !== e), t && "dispose" in t && typeof t.dispose == "function" && t.dispose();
|
|
1748
|
-
}
|
|
1749
|
-
/**
|
|
1750
|
-
* Вставить элемент в массив
|
|
1751
|
-
* @param index - Индекс для вставки
|
|
1752
|
-
* @param initialValue - Начальные значения для нового элемента
|
|
1753
|
-
*/
|
|
1754
|
-
insert(e, t) {
|
|
1755
|
-
if (e < 0 || e > this.items.value.length) {
|
|
1756
|
-
w.handle(
|
|
1757
|
-
new Error(
|
|
1758
|
-
`ArrayNode.insert: index ${e} out of bounds (length: ${this.items.value.length})`
|
|
1759
|
-
),
|
|
1760
|
-
"ArrayNode.insert",
|
|
1761
|
-
b.LOG
|
|
1762
|
-
);
|
|
1763
|
-
return;
|
|
1764
|
-
}
|
|
1765
|
-
const i = this.createItem(t), r = [...this.items.value];
|
|
1766
|
-
r.splice(e, 0, i), this.items.value = r;
|
|
1767
|
-
}
|
|
1768
|
-
/**
|
|
1769
|
-
* Переместить элемент массива с позиции `from` на позицию `to`
|
|
1770
|
-
*
|
|
1771
|
-
* @param from - Индекс перемещаемого элемента
|
|
1772
|
-
* @param to - Целевой индекс
|
|
1773
|
-
*
|
|
1774
|
-
* @remarks
|
|
1775
|
-
* Реордер не уничтожает элемент (dispose НЕ вызывается) — тот же инстанс под-формы
|
|
1776
|
-
* переезжает на новую позицию, состояние/валидация сохраняются. Длина не меняется.
|
|
1777
|
-
*
|
|
1778
|
-
* @example
|
|
1779
|
-
* ```typescript
|
|
1780
|
-
* // Поднять элемент #2 на одну позицию вверх
|
|
1781
|
-
* form.items.move(2, 1);
|
|
1782
|
-
* ```
|
|
1783
|
-
*/
|
|
1784
|
-
move(e, t) {
|
|
1785
|
-
const i = this.items.value.length;
|
|
1786
|
-
if (e < 0 || e >= i || t < 0 || t >= i) {
|
|
1787
|
-
w.handle(
|
|
1788
|
-
new Error(`ArrayNode.move: index out of bounds (from=${e}, to=${t}, length=${i})`),
|
|
1789
|
-
"ArrayNode.move",
|
|
1790
|
-
b.LOG
|
|
1791
|
-
);
|
|
1792
|
-
return;
|
|
1793
|
-
}
|
|
1794
|
-
if (e === t) return;
|
|
1795
|
-
const r = [...this.items.value], [n] = r.splice(e, 1);
|
|
1796
|
-
r.splice(t, 0, n), this.items.value = r;
|
|
1797
|
-
}
|
|
1798
|
-
/**
|
|
1799
|
-
* Поменять местами два элемента массива
|
|
1800
|
-
*
|
|
1801
|
-
* @param a - Индекс первого элемента
|
|
1802
|
-
* @param b - Индекс второго элемента
|
|
1803
|
-
*
|
|
1804
|
-
* @remarks
|
|
1805
|
-
* Как и {@link move}, не уничтожает элементы — меняет только их порядок.
|
|
1806
|
-
*
|
|
1807
|
-
* @example
|
|
1808
|
-
* ```typescript
|
|
1809
|
-
* form.items.swap(0, 2);
|
|
1810
|
-
* ```
|
|
1811
|
-
*/
|
|
1812
|
-
swap(e, t) {
|
|
1813
|
-
const i = this.items.value.length;
|
|
1814
|
-
if (e < 0 || e >= i || t < 0 || t >= i) {
|
|
1815
|
-
w.handle(
|
|
1816
|
-
new Error(`ArrayNode.swap: index out of bounds (a=${e}, b=${t}, length=${i})`),
|
|
1817
|
-
"ArrayNode.swap",
|
|
1818
|
-
b.LOG
|
|
1819
|
-
);
|
|
1820
|
-
return;
|
|
1821
|
-
}
|
|
1822
|
-
if (e === t) return;
|
|
1823
|
-
const r = [...this.items.value];
|
|
1824
|
-
[r[e], r[t]] = [r[t], r[e]], this.items.value = r;
|
|
1825
|
-
}
|
|
1826
|
-
/**
|
|
1827
|
-
* Удалить все элементы массива
|
|
1828
|
-
*
|
|
1829
|
-
* @remarks
|
|
1830
|
-
* Вызывает dispose() на всех элементах для очистки подписок
|
|
1831
|
-
*/
|
|
1832
|
-
clear() {
|
|
1833
|
-
const e = [...this.items.value];
|
|
1834
|
-
this.items.value = [], e.forEach((t) => {
|
|
1835
|
-
"dispose" in t && typeof t.dispose == "function" && t.dispose();
|
|
1836
|
-
});
|
|
1837
|
-
}
|
|
1838
|
-
/**
|
|
1839
|
-
* Получить элемент по индексу
|
|
1840
|
-
* @param index - Индекс элемента
|
|
1841
|
-
* @returns Типизированный GroupNode proxy или undefined если индекс вне границ
|
|
1842
|
-
*/
|
|
1843
|
-
at(e) {
|
|
1844
|
-
const t = this.items.value[e];
|
|
1845
|
-
if (t)
|
|
1846
|
-
return t.getProxy();
|
|
1847
|
-
}
|
|
1848
|
-
// ============================================================================
|
|
1849
|
-
// Реализация абстрактных методов
|
|
1850
|
-
// ============================================================================
|
|
1851
|
-
getValue() {
|
|
1852
|
-
return this.items.value.map((e) => e.getValue());
|
|
1853
|
-
}
|
|
1854
|
-
setValue(e, t) {
|
|
1855
|
-
this.clear(), e.forEach((i) => this.push(i)), t?.emitEvent !== !1 && this.validate().catch((i) => {
|
|
1856
|
-
w.handle(i, "ArrayNode.setValue", b.LOG);
|
|
1857
|
-
});
|
|
1858
|
-
}
|
|
1859
|
-
patchValue(e) {
|
|
1860
|
-
e.forEach((t, i) => {
|
|
1861
|
-
this.items.value[i] && t !== void 0 && this.items.value[i].patchValue(t);
|
|
1862
|
-
});
|
|
1863
|
-
}
|
|
1864
|
-
/**
|
|
1865
|
-
* Сбросить массив к указанным значениям (или очистить)
|
|
1866
|
-
*
|
|
1867
|
-
* @param values - опциональный массив значений для сброса
|
|
1868
|
-
*
|
|
1869
|
-
* @remarks
|
|
1870
|
-
* Очищает текущий массив и заполняет новыми элементами
|
|
1871
|
-
*
|
|
1872
|
-
* @example
|
|
1873
|
-
* ```typescript
|
|
1874
|
-
* // Очистить массив
|
|
1875
|
-
* arrayNode.reset();
|
|
1876
|
-
*
|
|
1877
|
-
* // Сбросить к новым значениям
|
|
1878
|
-
* arrayNode.reset([{ name: 'Item 1' }, { name: 'Item 2' }]);
|
|
1879
|
-
* ```
|
|
1880
|
-
*/
|
|
1881
|
-
reset(e) {
|
|
1882
|
-
this._arrayErrors.value = [], this.clear(), e && e.forEach((t) => this.push(t));
|
|
1883
|
-
}
|
|
1884
|
-
/**
|
|
1885
|
-
* Сбросить массив к исходным значениям (initialItems)
|
|
1886
|
-
*
|
|
1887
|
-
* @remarks
|
|
1888
|
-
* Восстанавливает массив в состояние, которое было при создании ArrayNode.
|
|
1889
|
-
* Более явный способ сброса к начальным значениям по сравнению с reset()
|
|
1890
|
-
*
|
|
1891
|
-
* Полезно когда:
|
|
1892
|
-
* - Пользователь нажал "Cancel" - вернуть массив к исходным элементам
|
|
1893
|
-
* - Массив был изменен через reset(newValues), но нужно вернуться к началу
|
|
1894
|
-
* - Явное намерение показать "отмена всех изменений"
|
|
1895
|
-
*
|
|
1896
|
-
* @example
|
|
1897
|
-
* ```typescript
|
|
1898
|
-
* const arrayNode = new ArrayNode(
|
|
1899
|
-
* { name: { value: '', component: Input } },
|
|
1900
|
-
* [{ name: 'Initial 1' }, { name: 'Initial 2' }]
|
|
1901
|
-
* );
|
|
1902
|
-
*
|
|
1903
|
-
* arrayNode.push({ name: 'New Item' });
|
|
1904
|
-
* arrayNode.reset([{ name: 'Temp' }]);
|
|
1905
|
-
* console.log(arrayNode.length.value); // 1
|
|
1906
|
-
*
|
|
1907
|
-
* arrayNode.resetToInitial();
|
|
1908
|
-
* console.log(arrayNode.length.value); // 2
|
|
1909
|
-
* console.log(arrayNode.at(0)?.name.value.value); // 'Initial 1'
|
|
1910
|
-
* ```
|
|
1911
|
-
*/
|
|
1912
|
-
resetToInitial() {
|
|
1913
|
-
this._arrayErrors.value = [], this.clear(), this.initialItems.forEach((e) => this.push(e));
|
|
1914
|
-
}
|
|
1915
|
-
async validate() {
|
|
1916
|
-
return (await Promise.all(this.items.value.map((t) => t.validate()))).every(Boolean) && this._arrayErrors.value.length === 0;
|
|
1917
|
-
}
|
|
1918
|
-
/**
|
|
1919
|
-
* Установить array-level validation errors
|
|
1920
|
-
*
|
|
1921
|
-
* @param errors - Массив ошибок валидации уровня массива
|
|
1922
|
-
*
|
|
1923
|
-
* @example
|
|
1924
|
-
* ```typescript
|
|
1925
|
-
* arrayNode.setErrors([{
|
|
1926
|
-
* code: 'minItems',
|
|
1927
|
-
* message: 'Минимум 1 элемент обязателен',
|
|
1928
|
-
* }]);
|
|
1929
|
-
* ```
|
|
1930
|
-
*/
|
|
1931
|
-
setErrors(e) {
|
|
1932
|
-
this._arrayErrors.value = e;
|
|
1933
|
-
}
|
|
1934
|
-
/**
|
|
1935
|
-
* Очистить все errors (array-level + item-level)
|
|
1936
|
-
*
|
|
1937
|
-
* @example
|
|
1938
|
-
* ```typescript
|
|
1939
|
-
* arrayNode.clearErrors();
|
|
1940
|
-
* console.log(arrayNode.errors.value); // []
|
|
1941
|
-
* ```
|
|
1942
|
-
*/
|
|
1943
|
-
clearErrors() {
|
|
1944
|
-
this._arrayErrors.value = [], this.items.value.forEach((e) => e.clearErrors());
|
|
1945
|
-
}
|
|
1946
|
-
// ============================================================================
|
|
1947
|
-
// Protected hooks (Template Method pattern)
|
|
1948
|
-
// ============================================================================
|
|
1949
|
-
/**
|
|
1950
|
-
* Hook: вызывается после markAsTouched()
|
|
1951
|
-
*
|
|
1952
|
-
* Для ArrayNode: рекурсивно помечаем все элементы массива как touched
|
|
248
|
+
pending;
|
|
249
|
+
disabled;
|
|
250
|
+
touched;
|
|
251
|
+
dirty;
|
|
252
|
+
issues;
|
|
253
|
+
ownIssues;
|
|
254
|
+
/** Сырые ноды детей по именам полей данных. */
|
|
255
|
+
children;
|
|
256
|
+
/** Замечания самой группы (ответ сервера на форму целиком). */
|
|
257
|
+
own = R();
|
|
258
|
+
availability = K();
|
|
259
|
+
/** Ожидание async-правила на ручке самой группы. */
|
|
260
|
+
ownPending = W();
|
|
261
|
+
/**
|
|
262
|
+
* Лист → его записываемый сигнал модели: по нему `setValue` узнаёт цели `compute` (их
|
|
263
|
+
* помечает `markDerived`), а не по computed-обёртке `value` ноды.
|
|
264
|
+
*/
|
|
265
|
+
leafSignals = /* @__PURE__ */ new WeakMap();
|
|
266
|
+
behaviorCleanup;
|
|
267
|
+
face;
|
|
268
|
+
/**
|
|
269
|
+
* @param children - Готовые ноды детей по именам полей данных. Вид ребёнка задан нодой: по
|
|
270
|
+
* именам и значениям группа ничего не угадывает.
|
|
1953
271
|
*/
|
|
1954
|
-
|
|
1955
|
-
this.
|
|
272
|
+
constructor(e) {
|
|
273
|
+
this.children = new Map(e);
|
|
274
|
+
const s = [...this.children.values()];
|
|
275
|
+
this.value = d(() => {
|
|
276
|
+
const a = {};
|
|
277
|
+
return this.children.forEach((r, o) => {
|
|
278
|
+
a[o] = r.value.value;
|
|
279
|
+
}), a;
|
|
280
|
+
});
|
|
281
|
+
const { disabled: i } = this.availability, n = Q({
|
|
282
|
+
getChildren: () => s,
|
|
283
|
+
ownIssues: this.own.list,
|
|
284
|
+
ownPending: d(() => !i.value && this.ownPending.active.value),
|
|
285
|
+
disabled: i
|
|
286
|
+
});
|
|
287
|
+
this.valid = n.valid, this.invalid = n.invalid, this.pending = n.pending, this.touched = n.touched, this.dirty = n.dirty, this.issues = n.issues, this.ownIssues = this.own.list, this.disabled = this.availability.disabled;
|
|
1956
288
|
}
|
|
1957
|
-
/**
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
* Для ArrayNode: рекурсивно помечаем все элементы массива как untouched
|
|
1961
|
-
*/
|
|
1962
|
-
onMarkAsUntouched() {
|
|
1963
|
-
this.items.value.forEach((e) => e.markAsUntouched());
|
|
289
|
+
/** Лицо группы: прокси с нодами полей. Строится один раз. */
|
|
290
|
+
get proxy() {
|
|
291
|
+
return this.face ??= Te(this, this.children), this.face;
|
|
1964
292
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
onMarkAsDirty() {
|
|
1971
|
-
this.items.value.forEach((e) => e.markAsDirty());
|
|
293
|
+
getValue() {
|
|
294
|
+
const e = {};
|
|
295
|
+
return this.children.forEach((s, i) => {
|
|
296
|
+
e[i] = s.getValue();
|
|
297
|
+
}), e;
|
|
1972
298
|
}
|
|
1973
|
-
/**
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
299
|
+
/** Записать значения полей; цели `compute` пропускаются — ими владеет поведение. */
|
|
300
|
+
setValue(e) {
|
|
301
|
+
if (!(e === null || typeof e != "object"))
|
|
302
|
+
for (const [s, i] of Object.entries(e)) {
|
|
303
|
+
const n = this.children.get(s);
|
|
304
|
+
if (!n) continue;
|
|
305
|
+
const a = this.leafSignals.get(n) ?? n.value;
|
|
306
|
+
T(a) || n.setValue(i);
|
|
307
|
+
}
|
|
1980
308
|
}
|
|
1981
|
-
// ============================================================================
|
|
1982
|
-
// Итерация
|
|
1983
|
-
// ============================================================================
|
|
1984
309
|
/**
|
|
1985
|
-
*
|
|
1986
|
-
*
|
|
310
|
+
* Сбросить группу: замечания группы и всех полей, `touched`, `dirty` и значения. Без аргумента —
|
|
311
|
+
* к точке отсчёта модели, той же, к которой возвращает `model.reset()` (значения создания модели
|
|
312
|
+
* либо последнего `captureInitial()`), — и форма после сброса не изменена.
|
|
313
|
+
* Цели `compute` значения не получают, как и в `setValue`: ими владеет поведение.
|
|
1987
314
|
*/
|
|
1988
|
-
|
|
1989
|
-
this.
|
|
1990
|
-
const
|
|
1991
|
-
|
|
315
|
+
reset(e) {
|
|
316
|
+
this.own.clear(), this.children.forEach((s, i) => {
|
|
317
|
+
const n = this.leafSignals.get(s) ?? s.value, a = T(n) ? void 0 : e?.[i];
|
|
318
|
+
s.reset(a);
|
|
1992
319
|
});
|
|
1993
320
|
}
|
|
1994
|
-
/**
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
* @returns Новый массив результатов
|
|
1998
|
-
*/
|
|
1999
|
-
map(e) {
|
|
2000
|
-
return this.items.value.map((t, i) => {
|
|
2001
|
-
const r = t.getProxy();
|
|
2002
|
-
return e(r, i);
|
|
2003
|
-
});
|
|
321
|
+
/** Сброс всех полей группы к значениям создания, у целей `compute` тоже. @internal */
|
|
322
|
+
resetToCreation() {
|
|
323
|
+
this.own.clear(), this.children.forEach((e) => k(e).resetToCreation());
|
|
2004
324
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
// ============================================================================
|
|
2008
|
-
/**
|
|
2009
|
-
* Создать новый элемент массива на основе схемы
|
|
2010
|
-
* @param initialValue - Начальные значения
|
|
2011
|
-
*/
|
|
2012
|
-
createItem(e) {
|
|
2013
|
-
if (this.isGroupSchema(this.itemSchema)) {
|
|
2014
|
-
const t = new _(this.itemSchema);
|
|
2015
|
-
return e && t.patchValue(e), t;
|
|
2016
|
-
}
|
|
2017
|
-
throw new Error(
|
|
2018
|
-
"ArrayNode поддерживает только GroupNode элементы. Для массива примитивов используйте обычное поле с типом массива."
|
|
2019
|
-
);
|
|
325
|
+
markAsTouched() {
|
|
326
|
+
this.children.forEach((e) => e.markAsTouched());
|
|
2020
327
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
* @param schema - Схема для проверки
|
|
2024
|
-
*/
|
|
2025
|
-
isGroupSchema(e) {
|
|
2026
|
-
return typeof e == "object" && e !== null && !("component" in e) && !Array.isArray(e);
|
|
328
|
+
markAsUntouched() {
|
|
329
|
+
this.children.forEach((e) => e.markAsUntouched());
|
|
2027
330
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
// ============================================================================
|
|
2031
|
-
/**
|
|
2032
|
-
* Очистить все ресурсы узла
|
|
2033
|
-
* Рекурсивно очищает все subscriptions и элементы массива
|
|
2034
|
-
*
|
|
2035
|
-
* @example
|
|
2036
|
-
* ```typescript
|
|
2037
|
-
* useEffect(() => {
|
|
2038
|
-
* return () => {
|
|
2039
|
-
* arrayNode.dispose();
|
|
2040
|
-
* };
|
|
2041
|
-
* }, []);
|
|
2042
|
-
* ```
|
|
2043
|
-
*/
|
|
2044
|
-
dispose() {
|
|
2045
|
-
this.disposers.dispose(), this.items.value.forEach((e) => {
|
|
2046
|
-
"dispose" in e && typeof e.dispose == "function" && e.dispose();
|
|
2047
|
-
});
|
|
331
|
+
disable() {
|
|
332
|
+
this.availability.setDisabled(!0), this.own.clear(), this.children.forEach((e) => e.disable());
|
|
2048
333
|
}
|
|
2049
|
-
/**
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
* Для ArrayNode: рекурсивно отключаем все элементы массива
|
|
2053
|
-
*
|
|
2054
|
-
* @example
|
|
2055
|
-
* ```typescript
|
|
2056
|
-
* // Отключить весь массив полей
|
|
2057
|
-
* form.items.disable();
|
|
2058
|
-
*
|
|
2059
|
-
* // Все элементы становятся disabled
|
|
2060
|
-
* form.items.forEach(item => {
|
|
2061
|
-
* console.log(item.status.value); // 'disabled'
|
|
2062
|
-
* });
|
|
2063
|
-
* ```
|
|
2064
|
-
*/
|
|
2065
|
-
onDisable() {
|
|
2066
|
-
this.items.value.forEach((e) => {
|
|
2067
|
-
e.disable();
|
|
2068
|
-
});
|
|
334
|
+
/** Включить группу и детей; поля с блокировкой из схемы (`disabled: true`) остаются выключенными. */
|
|
335
|
+
enable() {
|
|
336
|
+
this.availability.setDisabled(!1), this.children.forEach((e) => k(e).enableFromParent());
|
|
2069
337
|
}
|
|
2070
|
-
/**
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
* Для ArrayNode: рекурсивно включаем все элементы массива
|
|
2074
|
-
*
|
|
2075
|
-
* @example
|
|
2076
|
-
* ```typescript
|
|
2077
|
-
* // Включить весь массив полей
|
|
2078
|
-
* form.items.enable();
|
|
2079
|
-
*
|
|
2080
|
-
* // Все элементы становятся enabled
|
|
2081
|
-
* form.items.forEach(item => {
|
|
2082
|
-
* console.log(item.status.value); // 'valid' или 'invalid'
|
|
2083
|
-
* });
|
|
2084
|
-
* ```
|
|
2085
|
-
*/
|
|
2086
|
-
onEnable() {
|
|
2087
|
-
this.items.value.forEach((e) => {
|
|
2088
|
-
e.enable();
|
|
2089
|
-
});
|
|
338
|
+
/** Каскад от контейнера — то же, что `enable()`: своей блокировки из схемы у группы нет. @internal */
|
|
339
|
+
enableFromParent() {
|
|
340
|
+
this.enable();
|
|
2090
341
|
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
/**
|
|
2094
|
-
* Создает узел формы на основе конфигурации
|
|
2095
|
-
*
|
|
2096
|
-
* ✅ ОБНОВЛЕНО: Теперь поддерживает массивы напрямую
|
|
2097
|
-
*
|
|
2098
|
-
* Автоматически определяет тип узла:
|
|
2099
|
-
* - FieldNode: имеет value и component
|
|
2100
|
-
* - ArrayNode: массив [schema, ...items] или { schema, initialItems }
|
|
2101
|
-
* - GroupNode: объект без value, component, schema
|
|
2102
|
-
*
|
|
2103
|
-
* @param config Конфигурация узла
|
|
2104
|
-
* @returns Экземпляр FieldNode, GroupNode или ArrayNode
|
|
2105
|
-
* @throws Error если конфиг не соответствует ни одному типу
|
|
2106
|
-
*
|
|
2107
|
-
* @example
|
|
2108
|
-
* ```typescript
|
|
2109
|
-
* const factory = new NodeFactory();
|
|
2110
|
-
*
|
|
2111
|
-
* Конфиг node-level (не layout-схема M1, где `validators` уже нет).
|
|
2112
|
-
* // FieldNode
|
|
2113
|
-
* const field = factory.createNode({
|
|
2114
|
-
* value: 'test@mail.com',
|
|
2115
|
-
* component: Input,
|
|
2116
|
-
* validators: [required, email]
|
|
2117
|
-
* });
|
|
2118
|
-
*
|
|
2119
|
-
* // GroupNode
|
|
2120
|
-
* const group = factory.createNode({
|
|
2121
|
-
* email: { value: '', component: Input },
|
|
2122
|
-
* password: { value: '', component: Input }
|
|
2123
|
-
* });
|
|
2124
|
-
*
|
|
2125
|
-
* // ArrayNode (объект)
|
|
2126
|
-
* const array = factory.createNode({
|
|
2127
|
-
* schema: { title: { value: '', component: Input } },
|
|
2128
|
-
* initialItems: [{ title: 'Item 1' }]
|
|
2129
|
-
* });
|
|
2130
|
-
*
|
|
2131
|
-
* // ArrayNode (массив) - новый формат
|
|
2132
|
-
* const array2 = factory.createNode([
|
|
2133
|
-
* { title: { value: '', component: Input } }, // schema
|
|
2134
|
-
* { title: 'Item 1' }, // initial item 1
|
|
2135
|
-
* { title: 'Item 2' } // initial item 2
|
|
2136
|
-
* ]);
|
|
2137
|
-
* ```
|
|
2138
|
-
*/
|
|
2139
|
-
createNode(e) {
|
|
2140
|
-
if (Array.isArray(e) && e.length >= 1)
|
|
2141
|
-
return this.createArrayNodeFromArray(e);
|
|
2142
|
-
if (this.isFieldConfig(e))
|
|
2143
|
-
return new be(e);
|
|
2144
|
-
if (this.isArrayConfig(e))
|
|
2145
|
-
return new q(
|
|
2146
|
-
e.schema,
|
|
2147
|
-
e.initialItems
|
|
2148
|
-
);
|
|
2149
|
-
if (this.isGroupConfig(e))
|
|
2150
|
-
return new _(e);
|
|
2151
|
-
throw new Error(
|
|
2152
|
-
`NodeFactory: Unknown node config. Expected FieldConfig, GroupConfig, or ArrayConfig, but got: ${JSON.stringify(
|
|
2153
|
-
e
|
|
2154
|
-
)}`
|
|
2155
|
-
);
|
|
342
|
+
setIssues(e) {
|
|
343
|
+
this.own.set(e);
|
|
2156
344
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
*
|
|
2160
|
-
* ✅ НОВОЕ: Извлечено из GroupNode для централизации логики
|
|
2161
|
-
*
|
|
2162
|
-
* Формат: [itemSchema, ...initialItems]
|
|
2163
|
-
* - Первый элемент - схема элемента массива
|
|
2164
|
-
* - Остальные элементы - начальные значения
|
|
2165
|
-
*
|
|
2166
|
-
* @param config Массив с схемой и начальными элементами
|
|
2167
|
-
* @returns ArrayNode
|
|
2168
|
-
*
|
|
2169
|
-
* @example
|
|
2170
|
-
* ```typescript
|
|
2171
|
-
* const factory = new NodeFactory();
|
|
2172
|
-
*
|
|
2173
|
-
* // Массив с начальными элементами
|
|
2174
|
-
* const array = factory.createArrayNodeFromArray([
|
|
2175
|
-
* { title: { value: '', component: Input } }, // schema
|
|
2176
|
-
* { title: 'Item 1' }, // initial value
|
|
2177
|
-
* { title: 'Item 2' } // initial value
|
|
2178
|
-
* ]);
|
|
2179
|
-
* ```
|
|
2180
|
-
* @private
|
|
2181
|
-
*/
|
|
2182
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2183
|
-
createArrayNodeFromArray(e) {
|
|
2184
|
-
const [t, ...i] = e, r = [];
|
|
2185
|
-
this.isGroupConfig(t) && r.push(this.extractValues(t));
|
|
2186
|
-
for (const n of i)
|
|
2187
|
-
this.isGroupConfig(n) ? r.push(this.extractValues(n)) : r.push(n);
|
|
2188
|
-
return new q(t, r);
|
|
345
|
+
clearIssues() {
|
|
346
|
+
this.own.clear(), this.children.forEach((e) => e.clearIssues());
|
|
2189
347
|
}
|
|
2190
|
-
/**
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
* ✅ НОВОЕ: Извлечено из GroupNode для централизации логики
|
|
2194
|
-
*
|
|
2195
|
-
* Преобразует схему формы в объект со значениями:
|
|
2196
|
-
* - `{ name: { value: 'John', component: Input } } → { name: 'John' }`
|
|
2197
|
-
* - Поддерживает вложенные группы
|
|
2198
|
-
* - Поддерживает массивы
|
|
2199
|
-
*
|
|
2200
|
-
* @param schema Схема формы
|
|
2201
|
-
* @returns Объект со значениями полей
|
|
2202
|
-
*
|
|
2203
|
-
* @example
|
|
2204
|
-
* ```typescript
|
|
2205
|
-
* const factory = new NodeFactory();
|
|
2206
|
-
*
|
|
2207
|
-
* const schema = {
|
|
2208
|
-
* name: { value: 'John', component: Input },
|
|
2209
|
-
* age: { value: 30, component: Input },
|
|
2210
|
-
* address: {
|
|
2211
|
-
* city: { value: 'Moscow', component: Input }
|
|
2212
|
-
* }
|
|
2213
|
-
* };
|
|
2214
|
-
*
|
|
2215
|
-
* factory.extractValues(schema);
|
|
2216
|
-
* // { name: 'John', age: 30, address: { city: 'Moscow' } }
|
|
2217
|
-
* ```
|
|
2218
|
-
*/
|
|
2219
|
-
extractValues(e) {
|
|
2220
|
-
if (this.isFieldConfig(e))
|
|
2221
|
-
return e.value;
|
|
2222
|
-
if (Array.isArray(e))
|
|
2223
|
-
return e.map((t) => this.extractValues(t));
|
|
2224
|
-
if (this.isGroupConfig(e)) {
|
|
2225
|
-
const t = {};
|
|
2226
|
-
for (const [i, r] of Object.entries(e))
|
|
2227
|
-
t[i] = this.extractValues(r);
|
|
2228
|
-
return t;
|
|
2229
|
-
}
|
|
2230
|
-
return e;
|
|
348
|
+
/** Своё ожидание async-правила на ручке группы; ожидание детей агрегат добавит сам. @internal */
|
|
349
|
+
setPending(e) {
|
|
350
|
+
this.ownPending.mark(e);
|
|
2231
351
|
}
|
|
2232
|
-
/**
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
* FieldConfig имеет обязательные свойства:
|
|
2236
|
-
* - value: начальное значение поля
|
|
2237
|
-
* - component: React-компонент для отображения
|
|
2238
|
-
*
|
|
2239
|
-
* @param config Проверяемая конфигурация
|
|
2240
|
-
* @returns true если config является FieldConfig
|
|
2241
|
-
*
|
|
2242
|
-
* @example
|
|
2243
|
-
* ```typescript
|
|
2244
|
-
* const factory = new NodeFactory();
|
|
2245
|
-
*
|
|
2246
|
-
* factory.isFieldConfig({ value: '', component: Input }); // true
|
|
2247
|
-
* factory.isFieldConfig({ email: { value: '' } }); // false
|
|
2248
|
-
* factory.isFieldConfig(null); // false
|
|
2249
|
-
* ```
|
|
2250
|
-
*/
|
|
2251
|
-
isFieldConfig(e) {
|
|
2252
|
-
return e == null || typeof e != "object" ? !1 : "valueSignal" in e ? !0 : "value" in e ? "component" in e || "componentProps" in e || "validators" in e || "asyncValidators" in e || "updateOn" in e || "debounce" in e : !1;
|
|
352
|
+
/** Прикрепить cleanup поведения формы (`createForm({ behavior })`). @internal */
|
|
353
|
+
attachBehaviorCleanup(e) {
|
|
354
|
+
this.behaviorCleanup = e;
|
|
2253
355
|
}
|
|
2254
|
-
/**
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
* ArrayConfig имеет обязательное свойство:
|
|
2258
|
-
* - schema: схема для элементов массива
|
|
2259
|
-
*
|
|
2260
|
-
* И НЕ имеет:
|
|
2261
|
-
* - value (отличие от FieldConfig)
|
|
2262
|
-
*
|
|
2263
|
-
* @param config Проверяемая конфигурация
|
|
2264
|
-
* @returns true если config является ArrayConfig
|
|
2265
|
-
*
|
|
2266
|
-
* @example
|
|
2267
|
-
* ```typescript
|
|
2268
|
-
* const factory = new NodeFactory();
|
|
2269
|
-
*
|
|
2270
|
-
* factory.isArrayConfig({ schema: {}, initialItems: [] }); // true
|
|
2271
|
-
* factory.isArrayConfig({ value: '', component: Input }); // false
|
|
2272
|
-
* factory.isArrayConfig({ email: { value: '' } }); // false
|
|
2273
|
-
* ```
|
|
2274
|
-
*/
|
|
2275
|
-
isArrayConfig(e) {
|
|
2276
|
-
return e != null && typeof e == "object" && "schema" in e && !("value" in e);
|
|
356
|
+
/** Связать лист с его сигналом модели для `setValue`. @internal */
|
|
357
|
+
registerLeafSignal(e, s) {
|
|
358
|
+
this.leafSignals.set(e, s);
|
|
2277
359
|
}
|
|
2278
|
-
/**
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
* GroupConfig - это объект, который:
|
|
2282
|
-
* - НЕ является FieldConfig (нет value/component)
|
|
2283
|
-
* - НЕ является ArrayConfig (нет schema)
|
|
2284
|
-
* - Содержит вложенные конфиги полей/групп/массивов
|
|
2285
|
-
*
|
|
2286
|
-
* @param config Проверяемая конфигурация
|
|
2287
|
-
* @returns true если config является GroupConfig
|
|
2288
|
-
*
|
|
2289
|
-
* @example
|
|
2290
|
-
* ```typescript
|
|
2291
|
-
* const factory = new NodeFactory();
|
|
2292
|
-
*
|
|
2293
|
-
* factory.isGroupConfig({
|
|
2294
|
-
* email: { value: '', component: Input },
|
|
2295
|
-
* password: { value: '', component: Input }
|
|
2296
|
-
* }); // true
|
|
2297
|
-
*
|
|
2298
|
-
* factory.isGroupConfig({ value: '', component: Input }); // false
|
|
2299
|
-
* factory.isGroupConfig({ schema: {} }); // false
|
|
2300
|
-
* factory.isGroupConfig(null); // false
|
|
2301
|
-
* ```
|
|
2302
|
-
*/
|
|
2303
|
-
isGroupConfig(e) {
|
|
2304
|
-
return e != null && typeof e == "object" && !this.isFieldConfig(e) && !this.isArrayConfig(e);
|
|
360
|
+
/** Снять поведение и подписки всех детей. @internal */
|
|
361
|
+
dispose() {
|
|
362
|
+
this.behaviorCleanup?.(), this.behaviorCleanup = void 0, this.children.forEach((e) => e.dispose());
|
|
2305
363
|
}
|
|
2306
364
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
365
|
+
const z = /* @__PURE__ */ new WeakMap();
|
|
366
|
+
let _e = 0;
|
|
367
|
+
function Z(t) {
|
|
368
|
+
let e = z.get(t);
|
|
369
|
+
return e === void 0 && (e = _e++, z.set(t, e)), e;
|
|
370
|
+
}
|
|
371
|
+
class je {
|
|
372
|
+
/**
|
|
373
|
+
* @param control - Массив модели (`model.<путь>`).
|
|
374
|
+
* @param buildRow - Форма строки по её под-модели; инъекция снимает цикл импорта со сборкой.
|
|
375
|
+
*/
|
|
376
|
+
constructor(e, s) {
|
|
377
|
+
this.control = e, this.initial = e.toArray(), this.stopSync = be(() => {
|
|
378
|
+
const a = e.length, r = [], o = [];
|
|
379
|
+
for (let h = 0; h < a; h++) {
|
|
380
|
+
const u = e.at(h);
|
|
381
|
+
if (!u) continue;
|
|
382
|
+
let f = this.cache.get(u);
|
|
383
|
+
f || (f = A(() => {
|
|
384
|
+
const p = s(u);
|
|
385
|
+
return this.availability.disabled.peek() && p.disable(), p;
|
|
386
|
+
}), this.cache.set(u, f)), r.push(u), o.push(f);
|
|
2321
387
|
}
|
|
2322
|
-
const
|
|
2323
|
-
for (const
|
|
2324
|
-
if (
|
|
2325
|
-
const
|
|
2326
|
-
this.cache.delete(
|
|
388
|
+
const c = new Set(r), l = [];
|
|
389
|
+
for (const h of this.mounted) {
|
|
390
|
+
if (c.has(h)) continue;
|
|
391
|
+
const u = this.cache.get(h);
|
|
392
|
+
this.cache.delete(h), u && l.push(u);
|
|
2327
393
|
}
|
|
2328
|
-
this.
|
|
2329
|
-
}), this.
|
|
2330
|
-
const i =
|
|
2331
|
-
getChildren: () => this.
|
|
2332
|
-
|
|
394
|
+
this.mounted = r, this.rows.value = o, A(() => l.forEach((h) => k(h).dispose()));
|
|
395
|
+
}), this.value = d(() => this.rows.value.map((a) => a.value.value));
|
|
396
|
+
const { disabled: i } = this.availability, n = Q({
|
|
397
|
+
getChildren: () => this.rows.value,
|
|
398
|
+
ownIssues: this.own.list,
|
|
399
|
+
ownPending: d(() => !i.value && this.ownPending.active.value),
|
|
400
|
+
disabled: i
|
|
2333
401
|
});
|
|
2334
|
-
this.valid =
|
|
2335
|
-
}
|
|
2336
|
-
itemNodes = m([]);
|
|
2337
|
-
// Кэш per-item формы по идентичности фасада под-модели (фасады кэшируются в core).
|
|
2338
|
-
cache = /* @__PURE__ */ new WeakMap();
|
|
2339
|
-
// Фасады под-моделей, для которых сейчас смонтированы формы — «предыдущий» набор для
|
|
2340
|
-
// реконсиляции в disposeSync. Выпавшие из массива элементы диспозятся и вытесняются из кэша.
|
|
2341
|
-
mountedModels = [];
|
|
2342
|
-
initial;
|
|
2343
|
-
_arrayErrors = m([]);
|
|
2344
|
-
disposeSync;
|
|
2345
|
-
/**
|
|
2346
|
-
* Сигнал per-item форм. Публичен в т.ч. чтобы `isArrayNode` распознавал узел как массив
|
|
2347
|
-
* (duck-typing проверяет наличие `items`).
|
|
2348
|
-
*/
|
|
2349
|
-
get items() {
|
|
2350
|
-
return this.itemNodes;
|
|
402
|
+
this.valid = n.valid, this.invalid = n.invalid, this.pending = n.pending, this.touched = n.touched, this.dirty = d(() => this.reshaped.value || n.dirty.value), this.issues = n.issues, this.ownIssues = this.own.list, this.disabled = this.availability.disabled;
|
|
2351
403
|
}
|
|
404
|
+
[j] = "array";
|
|
2352
405
|
value;
|
|
2353
406
|
valid;
|
|
2354
407
|
invalid;
|
|
2355
408
|
pending;
|
|
409
|
+
disabled;
|
|
2356
410
|
touched;
|
|
2357
411
|
dirty;
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
412
|
+
issues;
|
|
413
|
+
ownIssues;
|
|
414
|
+
rows = w([]);
|
|
415
|
+
/**
|
|
416
|
+
* Состав массива меняли методами ноды после создания или сброса. Как `dirty` поля: ставит запись
|
|
417
|
+
* через форму (`push`, `removeAt`, `move`, `setValue`…), а не запись в модель — загрузка данных
|
|
418
|
+
* массив изменённым не делает. Операция, которая состав не поменяла (`clear` пустого массива,
|
|
419
|
+
* `move(i, i)`), — тоже: поведение «очистить список при снятом флаге» после сброса формы её не
|
|
420
|
+
* испачкает. Снимает `reset`.
|
|
421
|
+
*/
|
|
422
|
+
reshaped = w(!1);
|
|
423
|
+
/** Форма строки по фасаду под-модели элемента. */
|
|
424
|
+
cache = /* @__PURE__ */ new WeakMap();
|
|
425
|
+
/** Под-модели строк, для которых сейчас есть формы, — «прежний» набор для сверки. */
|
|
426
|
+
mounted = [];
|
|
427
|
+
initial;
|
|
428
|
+
own = R();
|
|
429
|
+
availability = K();
|
|
430
|
+
/** Ожидание async-правила на ручке самого массива. */
|
|
431
|
+
ownPending = W();
|
|
432
|
+
stopSync;
|
|
433
|
+
get length() {
|
|
434
|
+
return this.rows.value.length;
|
|
435
|
+
}
|
|
2362
436
|
at(e) {
|
|
2363
|
-
return this.
|
|
437
|
+
return this.rows.value[e];
|
|
2364
438
|
}
|
|
2365
439
|
map(e) {
|
|
2366
|
-
return this.
|
|
440
|
+
return this.rows.value.map(e);
|
|
2367
441
|
}
|
|
2368
442
|
forEach(e) {
|
|
2369
|
-
this.
|
|
443
|
+
this.rows.value.forEach(e);
|
|
444
|
+
}
|
|
445
|
+
keyAt(e) {
|
|
446
|
+
const s = e < this.rows.value.length ? this.mounted[e] : void 0;
|
|
447
|
+
if (s === void 0)
|
|
448
|
+
throw new RangeError(`[@reformer/core] FormArray.keyAt: no row at index ${e}`);
|
|
449
|
+
return String(Z(s));
|
|
2370
450
|
}
|
|
2371
|
-
// ── Мутации (делегируют массиву модели; effect пересоберёт itemNodes) ──────
|
|
2372
|
-
/**
|
|
2373
|
-
* Добавить элемент. Без значения — элемент по шаблону массива модели (`arrayOf(blank)` либо
|
|
2374
|
-
* `initialValue` узла-массива схемы); шаблона нет — ошибка модели.
|
|
2375
|
-
*/
|
|
2376
451
|
push(e) {
|
|
2377
|
-
this.control.push(e);
|
|
452
|
+
this.reshape(() => this.control.push(e));
|
|
2378
453
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
this.control.insertAt(e, t);
|
|
454
|
+
insert(e, s) {
|
|
455
|
+
this.reshape(() => this.control.insertAt(this.modelIndex(e), s));
|
|
2382
456
|
}
|
|
2383
457
|
removeAt(e) {
|
|
2384
|
-
this.control.removeAt(e);
|
|
458
|
+
this.reshape(() => this.control.removeAt(this.modelIndex(e)));
|
|
2385
459
|
}
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
* пересоберёт `itemNodes` в новом порядке (per-item формы сохраняются по кешу идентичности).
|
|
2389
|
-
*/
|
|
2390
|
-
move(e, t) {
|
|
2391
|
-
this.control.move(e, t);
|
|
460
|
+
move(e, s) {
|
|
461
|
+
this.reshape(() => this.control.move(this.modelIndex(e), this.modelIndex(s)));
|
|
2392
462
|
}
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
this.control.swap(e, t);
|
|
463
|
+
swap(e, s) {
|
|
464
|
+
this.reshape(() => this.control.swap(this.modelIndex(e), this.modelIndex(s)));
|
|
2396
465
|
}
|
|
2397
466
|
clear() {
|
|
2398
|
-
this.control.clear();
|
|
467
|
+
this.reshape(() => this.control.clear());
|
|
2399
468
|
}
|
|
2400
|
-
// ── Абстрактные методы FormNode ────────────────────────────────────────────
|
|
2401
469
|
getValue() {
|
|
2402
470
|
return this.control.toArray();
|
|
2403
471
|
}
|
|
2404
472
|
setValue(e) {
|
|
2405
|
-
this.
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
this.setValue(e);
|
|
473
|
+
this.reshape(() => {
|
|
474
|
+
this.control.clear(), (e ?? []).forEach((s) => this.control.push(s));
|
|
475
|
+
});
|
|
2409
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Сбросить массив: строки, замечания и `dirty`. Без аргумента — к точке отсчёта модели, той же, к
|
|
479
|
+
* которой возвращает `model.reset()`: элементам создания модели либо последнего
|
|
480
|
+
* `captureInitial()`.
|
|
481
|
+
*/
|
|
2410
482
|
reset(e) {
|
|
2411
|
-
this.
|
|
483
|
+
this.own.clear(), this.setValue(e ?? this.referenceItems()), this.reshaped.value = !1;
|
|
2412
484
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
485
|
+
/** Строки пересобираются из значений создания — их поведение считает заново. @internal */
|
|
486
|
+
resetToCreation() {
|
|
487
|
+
this.own.clear(), this.setValue(this.initial), this.reshaped.value = !1;
|
|
2415
488
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
)
|
|
2421
|
-
)).every(Boolean) && this._arrayErrors.value.length === 0;
|
|
489
|
+
/** Точка отсчёта модели за массивом; без модели — элементы создания. */
|
|
490
|
+
referenceItems() {
|
|
491
|
+
const e = H(this.control);
|
|
492
|
+
return e === void 0 ? this.initial : e.value;
|
|
2422
493
|
}
|
|
2423
|
-
|
|
2424
|
-
this.
|
|
494
|
+
markAsTouched() {
|
|
495
|
+
this.rows.value.forEach((e) => e.markAsTouched());
|
|
2425
496
|
}
|
|
2426
|
-
|
|
2427
|
-
this.
|
|
2428
|
-
(e) => e.clearErrors?.()
|
|
2429
|
-
);
|
|
497
|
+
markAsUntouched() {
|
|
498
|
+
this.rows.value.forEach((e) => e.markAsUntouched());
|
|
2430
499
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
// базовые сигналы бесполезны для чтения — состояние меняется ТОЛЬКО через per-item формы. Форвардим
|
|
2434
|
-
// ВСЕ hook'и (как ArrayNode), иначе markAsUntouched/Pristine/Dirty/disable/enable молча no-op.
|
|
2435
|
-
onMarkAsTouched() {
|
|
2436
|
-
this.itemNodes.value.forEach(
|
|
2437
|
-
(e) => e.markAsTouched?.()
|
|
2438
|
-
);
|
|
500
|
+
disable() {
|
|
501
|
+
this.availability.setDisabled(!0), this.own.clear(), this.rows.value.forEach((e) => e.disable());
|
|
2439
502
|
}
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
);
|
|
503
|
+
/** Включить массив и строки; поля с блокировкой из схемы (`disabled: true`) остаются выключенными. */
|
|
504
|
+
enable() {
|
|
505
|
+
this.availability.setDisabled(!1), this.rows.value.forEach((e) => k(e).enableFromParent());
|
|
2444
506
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
);
|
|
507
|
+
/** Каскад от контейнера — то же, что `enable()`: своей блокировки из схемы у массива нет. @internal */
|
|
508
|
+
enableFromParent() {
|
|
509
|
+
this.enable();
|
|
2449
510
|
}
|
|
2450
|
-
|
|
2451
|
-
this.
|
|
2452
|
-
(e) => e.markAsPristine?.()
|
|
2453
|
-
);
|
|
511
|
+
setIssues(e) {
|
|
512
|
+
this.own.set(e);
|
|
2454
513
|
}
|
|
2455
|
-
|
|
2456
|
-
this.
|
|
514
|
+
clearIssues() {
|
|
515
|
+
this.own.clear(), this.rows.value.forEach((e) => e.clearIssues());
|
|
2457
516
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
517
|
+
/** Своё ожидание async-правила на ручке массива; ожидание строк агрегат добавит сам. @internal */
|
|
518
|
+
setPending(e) {
|
|
519
|
+
this.ownPending.mark(e);
|
|
2460
520
|
}
|
|
2461
|
-
/**
|
|
2462
|
-
|
|
2463
|
-
|
|
521
|
+
/** Выполнить мутацию массива модели и отметить `dirty`, если состав строк изменился. */
|
|
522
|
+
reshape(e) {
|
|
523
|
+
const s = this.composition();
|
|
524
|
+
e();
|
|
525
|
+
const i = this.composition();
|
|
526
|
+
(i.length !== s.length || i.some((n, a) => n !== s[a])) && (this.reshaped.value = !0);
|
|
2464
527
|
}
|
|
2465
528
|
/**
|
|
2466
|
-
*
|
|
2467
|
-
*
|
|
2468
|
-
*
|
|
529
|
+
* Индекс модели для индекса строки формы. Без `null`-элементов они совпадают; с ними строка `i` —
|
|
530
|
+
* `i`-й непустой элемент, а индекс за последней строкой — конец массива модели. Читает модель, а не
|
|
531
|
+
* `mounted`: внутри `batch` синхронизация ещё не прошла.
|
|
2469
532
|
*/
|
|
2470
|
-
|
|
2471
|
-
|
|
533
|
+
modelIndex(e) {
|
|
534
|
+
return A(() => {
|
|
535
|
+
const s = [];
|
|
536
|
+
for (let i = 0; i < this.control.length; i++)
|
|
537
|
+
this.control.at(i) && s.push(i);
|
|
538
|
+
return s.length === this.control.length ? e : e >= 0 && e < s.length ? s[e] : e === s.length ? this.control.length : e;
|
|
539
|
+
});
|
|
2472
540
|
}
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
return t || (t = /* @__PURE__ */ new Map(), K.set(e, t)), t.has(s) || t.set(s, s(e)), t.get(s);
|
|
2478
|
-
}
|
|
2479
|
-
function M(s, e, t, i) {
|
|
2480
|
-
if (s == null || typeof s != "object" || D(s) || j(s) || W(s) || s.$$typeof !== void 0 || i.has(s)) return;
|
|
2481
|
-
if (i.add(s), typeof s.getProxy == "function") {
|
|
2482
|
-
process.env.NODE_ENV !== "production" && console.warn(
|
|
2483
|
-
"[reformer] createForm({ schema }): в схему попал узел формы (FormProxy) — он пропущен. Стройте дерево для createForm БЕЗ формы, а форму донесите до узла вторым проходом (билдер `(model, form?) => …`) либо через render-behavior `patchProps({ form })`."
|
|
541
|
+
/** Под-модели строк по порядку — без подписки: мутацию могут вызвать из effect'а поведения. */
|
|
542
|
+
composition() {
|
|
543
|
+
return A(
|
|
544
|
+
() => Array.from({ length: this.control.length }, (e, s) => this.control.at(s))
|
|
2484
545
|
);
|
|
2485
|
-
return;
|
|
2486
|
-
}
|
|
2487
|
-
if (Array.isArray(s)) {
|
|
2488
|
-
for (const a of s) M(a, e, t, i);
|
|
2489
|
-
return;
|
|
2490
|
-
}
|
|
2491
|
-
const r = s;
|
|
2492
|
-
if (typeof r.item == "function") {
|
|
2493
|
-
const a = L(r.model) ?? L(r.array);
|
|
2494
|
-
if (a) {
|
|
2495
|
-
t.set(a, r.item), r.initialValue !== void 0 && se(a, r.initialValue);
|
|
2496
|
-
return;
|
|
2497
|
-
}
|
|
2498
|
-
if (r.array != null) {
|
|
2499
|
-
process.env.NODE_ENV !== "production" && console.warn(
|
|
2500
|
-
"[reformer] createForm({ schema }): у узла-массива `array` — не массив модели, узел пропущен. Ожидается `model: model.$.<массив>`."
|
|
2501
|
-
);
|
|
2502
|
-
return;
|
|
2503
|
-
}
|
|
2504
546
|
}
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
const u = Y(a), d = X(r.part, u);
|
|
2509
|
-
M(d, e, t, i);
|
|
2510
|
-
return;
|
|
2511
|
-
}
|
|
547
|
+
/** Остановить синхронизацию и диспозить формы всех строк. @internal */
|
|
548
|
+
dispose() {
|
|
549
|
+
this.stopSync(), this.rows.peek().forEach((e) => k(e).dispose()), this.mounted = [];
|
|
2512
550
|
}
|
|
2513
|
-
const n = D(r.model) ? r.model : r.value;
|
|
2514
|
-
D(n) ? e.set(n, {
|
|
2515
|
-
component: r.component,
|
|
2516
|
-
componentProps: r.componentProps,
|
|
2517
|
-
updateOn: r.updateOn,
|
|
2518
|
-
disabled: r.disabled,
|
|
2519
|
-
debounce: r.debounce
|
|
2520
|
-
}) : process.env.NODE_ENV !== "production" && r.component !== void 0 && ("valueSignal" in r || r.value != null || W(r.model) || j(r.model)) && console.warn(
|
|
2521
|
-
"[reformer] createForm({ schema }): узел с `component` не распознан как поле — привязка не является сигналом модели. Ожидается `model: model.$.<path>` (лист или массив); проверьте, что не передан `model: model.<path>` (value-прокси), группа без `part` или `valueSignal:`."
|
|
2522
|
-
);
|
|
2523
|
-
for (const a of Object.values(r)) M(a, e, t, i);
|
|
2524
551
|
}
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
if (ie(d)) {
|
|
2530
|
-
const l = i.get(d);
|
|
2531
|
-
if (l) {
|
|
2532
|
-
r.arrays.push({ path: u, handle: d, item: l });
|
|
2533
|
-
continue;
|
|
2534
|
-
}
|
|
2535
|
-
const c = t.get(d);
|
|
2536
|
-
if (c !== void 0) {
|
|
2537
|
-
const o = d;
|
|
2538
|
-
n[a] = { ...c, valueSignal: o }, r.fields.push({ path: u, signal: o });
|
|
2539
|
-
}
|
|
2540
|
-
continue;
|
|
2541
|
-
}
|
|
2542
|
-
if (d instanceof ae) {
|
|
2543
|
-
n[a] = { ...t.get(d) ?? {}, valueSignal: d }, r.fields.push({ path: u, signal: d });
|
|
2544
|
-
continue;
|
|
2545
|
-
}
|
|
2546
|
-
r.groups.push({ path: u, handle: d }), n[a] = Z(
|
|
2547
|
-
d,
|
|
2548
|
-
u,
|
|
2549
|
-
t,
|
|
2550
|
-
i,
|
|
2551
|
-
r
|
|
2552
|
-
);
|
|
2553
|
-
}
|
|
2554
|
-
return n;
|
|
552
|
+
const q = /* @__PURE__ */ new WeakMap();
|
|
553
|
+
function ee(t, e) {
|
|
554
|
+
let s = q.get(e);
|
|
555
|
+
return s || (s = /* @__PURE__ */ new Map(), q.set(e, s)), s.has(t) || s.set(t, t(e)), s.get(t);
|
|
2555
556
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
557
|
+
const b = "[@reformer/core] compileSchema", Re = /* @__PURE__ */ new Set([
|
|
558
|
+
"model",
|
|
559
|
+
"item",
|
|
560
|
+
"part",
|
|
561
|
+
"children",
|
|
562
|
+
"component",
|
|
563
|
+
"componentProps",
|
|
564
|
+
"selector",
|
|
565
|
+
"disabled",
|
|
566
|
+
"initialValue"
|
|
567
|
+
]), G = {
|
|
568
|
+
value: "привязка поля — `model: model.$.<поле>`",
|
|
569
|
+
array: "привязка массива под-форм — `model: model.$.<массив>`"
|
|
570
|
+
}, Ke = Object.freeze({}), te = /* @__PURE__ */ new WeakSet();
|
|
571
|
+
function at(t) {
|
|
572
|
+
return t !== null && typeof t == "object" && te.has(t);
|
|
2561
573
|
}
|
|
2562
|
-
function
|
|
2563
|
-
|
|
2564
|
-
t !== void 0 && M(t, r, n, /* @__PURE__ */ new WeakSet());
|
|
2565
|
-
const a = e.$, u = { fields: [], groups: [], arrays: [] }, d = Z(a, "", r, n, u), l = new _(d);
|
|
2566
|
-
for (const { path: o, handle: f, item: v } of u.arrays) {
|
|
2567
|
-
const p = z(l, o);
|
|
2568
|
-
if (!p) continue;
|
|
2569
|
-
const F = (V) => ee({ model: V, schema: X(v, V) }), T = Y(f), A = new Ve(T, F);
|
|
2570
|
-
p.fields.set(o.slice(o.lastIndexOf(".") + 1), A), N(f, A);
|
|
2571
|
-
}
|
|
2572
|
-
const c = l.getProxy();
|
|
2573
|
-
N(e.$, l);
|
|
2574
|
-
for (const { path: o, handle: f } of u.groups) {
|
|
2575
|
-
const v = l.getFieldByPath(o);
|
|
2576
|
-
v && N(f, v);
|
|
2577
|
-
}
|
|
2578
|
-
for (const { path: o, signal: f } of u.fields) {
|
|
2579
|
-
const v = l.getFieldByPath(o);
|
|
2580
|
-
v && (N(f, v), z(l, o)?.registerFieldSignal(v, f));
|
|
2581
|
-
}
|
|
2582
|
-
return i && l.attachBehaviorCleanup(i.__run(e, c, s.schemaController)), c;
|
|
574
|
+
function se(t) {
|
|
575
|
+
return de(t)?.path;
|
|
2583
576
|
}
|
|
2584
|
-
function
|
|
2585
|
-
|
|
577
|
+
function ie(t, e, s) {
|
|
578
|
+
const i = se(t);
|
|
579
|
+
if (i === void 0) return s;
|
|
580
|
+
const n = e.basePath;
|
|
581
|
+
return n === void 0 || n === "" ? i : i.startsWith(`${n}.`) ? i.slice(n.length + 1) : i;
|
|
2586
582
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
function Se(s, e) {
|
|
2590
|
-
if (!e) return;
|
|
2591
|
-
const t = typeof e == "function" ? { schema: e } : e, i = Object.keys(t.steps ?? {}), r = i.map((o) => t.steps?.[o]).filter((o) => o != null), n = t.schema ?? ue(
|
|
2592
|
-
() => ce(...r, ...t.extras ? [t.extras] : [])
|
|
2593
|
-
), a = (o) => {
|
|
2594
|
-
const f = typeof o == "number" ? i[o - 1] : o;
|
|
2595
|
-
return (f != null ? t.steps?.[f] : null) ?? null;
|
|
2596
|
-
}, { strategy: u, debounce: d, liveAfterSubmit: l } = t, c = I(s, n, {
|
|
2597
|
-
strategy: u,
|
|
2598
|
-
debounce: d,
|
|
2599
|
-
liveAfterSubmit: l
|
|
2600
|
-
});
|
|
2601
|
-
return {
|
|
2602
|
-
schema: n,
|
|
2603
|
-
stepSelectors: i,
|
|
2604
|
-
validate: () => c.validate(),
|
|
2605
|
-
validateAll: () => c.validate(),
|
|
2606
|
-
validateStep: (o) => de(s, a(o) ?? ke, { touch: !0 }),
|
|
2607
|
-
createStepController: (o) => {
|
|
2608
|
-
if (!u || u === "submit") return null;
|
|
2609
|
-
const f = a(o);
|
|
2610
|
-
return f ? I(s, f, { strategy: u, debounce: d, liveAfterSubmit: l }) : null;
|
|
2611
|
-
},
|
|
2612
|
-
controller: c,
|
|
2613
|
-
validating: c.validating
|
|
2614
|
-
};
|
|
583
|
+
function We(t) {
|
|
584
|
+
return typeof t == "string" || typeof t == "number" || J(t);
|
|
2615
585
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
const { schema: t, registry: i } = s;
|
|
2619
|
-
if (t == null) return {};
|
|
2620
|
-
if (typeof t == "function")
|
|
2621
|
-
return { tree: t(e) };
|
|
2622
|
-
if (i == null)
|
|
2623
|
-
throw new Error(
|
|
2624
|
-
`${C}: \`schema\` — готовое дерево, а не билдер \`(model) => узел\`. Передайте билдер; документу схемы (JSON) нужен \`registry\`. Форму из модели и готового дерева без сборки собирает \`createFormFromModel({ model, schema })\`.`
|
|
2625
|
-
);
|
|
2626
|
-
if (typeof i.resolveSchema != "function")
|
|
2627
|
-
throw new Error(
|
|
2628
|
-
`${C}: \`registry\` не умеет собирать дерево из документа схемы — у него нет метода \`resolveSchema\`. Возьмите реестр \`@reformer/renderer-json\` (\`createComponentRegistry\`).`
|
|
2629
|
-
);
|
|
2630
|
-
return i.resolveSchema(t, e);
|
|
586
|
+
function Be(t) {
|
|
587
|
+
return t == null || typeof t == "boolean";
|
|
2631
588
|
}
|
|
2632
|
-
function
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
if (process.env.NODE_ENV !== "production") {
|
|
2645
|
-
const o = le(u.__overrideMaps, i);
|
|
2646
|
-
o.length > 0 && console.warn(
|
|
2647
|
-
`${C}: поведение обращается к узлам схемы, которых нет в корневом дереве: ${o.map((f) => `"${f}"`).join(", ")}. Правило ничего не сделает. Узлы строки массива и подформы адресуются из их поведения — через \`applyEach\` / \`apply\`.`
|
|
2648
|
-
);
|
|
589
|
+
function g(t) {
|
|
590
|
+
process.env.NODE_ENV !== "production" && typeof console < "u" && console.warn(t);
|
|
591
|
+
}
|
|
592
|
+
function F(t, e, s) {
|
|
593
|
+
const i = typeof t.selector == "string" && t.selector !== "" ? t.selector : void 0;
|
|
594
|
+
if (i !== void 0) {
|
|
595
|
+
const n = s.selectorPaths.get(i);
|
|
596
|
+
if (n !== void 0)
|
|
597
|
+
throw new Error(
|
|
598
|
+
`${b}: в области «${s.scope}» селектор «${i}» повторяется — узлы ${n} и ${e}. По \`selector\` узел адресуют поведение и шаги, поэтому в области он уникален. Дайте узлам разные селекторы; в строках массива и подформах селекторы живут в своих областях и с корнем не пересекаются.`
|
|
599
|
+
);
|
|
600
|
+
s.selectorPaths.set(i, e);
|
|
2649
601
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
...r !== void 0 ? { fieldWrapper: r } : {},
|
|
2659
|
-
...n !== void 0 ? { errorBoundary: n } : {}
|
|
2660
|
-
}
|
|
602
|
+
return {
|
|
603
|
+
id: i ?? e,
|
|
604
|
+
path: e,
|
|
605
|
+
selector: i,
|
|
606
|
+
scope: s.scope,
|
|
607
|
+
component: t.component,
|
|
608
|
+
props: t.componentProps ?? Ke,
|
|
609
|
+
source: t
|
|
2661
610
|
};
|
|
2662
|
-
return s.setup?.(c), c;
|
|
2663
611
|
}
|
|
2664
|
-
function
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
return O(s) && "validators" in s && "asyncValidators" in s && // FieldNode имеет markAsTouched метод
|
|
2672
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2673
|
-
typeof s.markAsTouched == "function" && // У FieldNode нет fields или items
|
|
2674
|
-
!("fields" in s) && !("items" in s);
|
|
2675
|
-
}
|
|
2676
|
-
function Ce(s) {
|
|
2677
|
-
return O(s) && "getFieldByPath" in s && "fields" in s && // GroupNode НЕ имеет items/push/removeAt (это ArrayNode/ModelArrayNode)
|
|
2678
|
-
!("items" in s) && !("push" in s) && !("removeAt" in s);
|
|
2679
|
-
}
|
|
2680
|
-
function Pe(s) {
|
|
2681
|
-
return O(s) && "items" in s && "length" in s && "push" in s && "removeAt" in s && "at" in s && // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2682
|
-
typeof s.push == "function" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2683
|
-
typeof s.removeAt == "function";
|
|
2684
|
-
}
|
|
2685
|
-
function Qe(s) {
|
|
2686
|
-
return Me(s) ? "FieldNode" : Ce(s) ? "GroupNode" : Pe(s) ? "ArrayNode" : O(s) ? "FormNode" : "Unknown";
|
|
612
|
+
function I(t, e) {
|
|
613
|
+
const s = e.nodes.get(t.id);
|
|
614
|
+
if (s !== void 0)
|
|
615
|
+
throw new Error(
|
|
616
|
+
`${b}: в области «${e.scope}» идентификатор «${t.id}» занят — узлы ${s.path} и ${t.path}. Селектор не может совпадать с позиционным путём узла (\`#/…\`): дайте узлу другой селектор.`
|
|
617
|
+
);
|
|
618
|
+
return te.add(t), e.nodes.set(t.id, t), t;
|
|
2687
619
|
}
|
|
2688
|
-
function
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
return !0;
|
|
620
|
+
function oe(t, e, s) {
|
|
621
|
+
const i = t.get(e);
|
|
622
|
+
i !== void 0 && g(
|
|
623
|
+
`${b}: ручка «${s.handle.__path}» привязана к двум полям — узлы ${i.path} и ${s.path}. Нода формы у ручки одна и возьмёт конфиг последнего узла (component, componentProps, disabled). Значение второй раз показывают текстом — сигналом ручки в \`children\`.`
|
|
624
|
+
), t.set(e, s);
|
|
2694
625
|
}
|
|
2695
|
-
function
|
|
2696
|
-
const
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
r.current = { ...c, __snapshot: null };
|
|
2706
|
-
}
|
|
2707
|
-
const d = y(
|
|
2708
|
-
(c) => {
|
|
2709
|
-
let o = !0;
|
|
2710
|
-
return g(() => {
|
|
2711
|
-
const v = n.current;
|
|
2712
|
-
for (const p in v)
|
|
2713
|
-
v[p].value;
|
|
2714
|
-
if (o) {
|
|
2715
|
-
o = !1;
|
|
2716
|
-
return;
|
|
626
|
+
function V(t, e, s) {
|
|
627
|
+
const i = F(t, e, s), n = [], a = t.children;
|
|
628
|
+
if (Array.isArray(a)) {
|
|
629
|
+
let o = null;
|
|
630
|
+
for (let c = 0; c < a.length; c++) {
|
|
631
|
+
const l = a[c], h = `${e}/${c}`;
|
|
632
|
+
if (!Be(l)) {
|
|
633
|
+
if (We(l)) {
|
|
634
|
+
o === null && (o = { id: h, parts: [] }, n.push({ kind: "text", id: o.id, parts: o.parts })), o.parts.push(l);
|
|
635
|
+
continue;
|
|
2717
636
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
// навсегда: подмена контрола на другой узел перестанет пересоздавать effect. Тот же приём ниже
|
|
2724
|
-
// у getSnapshot.
|
|
2725
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2726
|
-
[i]
|
|
2727
|
-
), l = y(() => {
|
|
2728
|
-
const c = r.current, o = n.current, f = {};
|
|
2729
|
-
for (const p in o)
|
|
2730
|
-
f[p] = o[p].value;
|
|
2731
|
-
let v = !1;
|
|
2732
|
-
for (const p of a.current) {
|
|
2733
|
-
const { key: F, useShallowArrayEqual: T } = p, A = f[F], V = c[F];
|
|
2734
|
-
if (T) {
|
|
2735
|
-
if (!xe(V, A)) {
|
|
2736
|
-
v = !0;
|
|
2737
|
-
break;
|
|
637
|
+
if (o = null, typeof l != "object" || X(l) || ve(l)) {
|
|
638
|
+
g(
|
|
639
|
+
`${b}: ребёнок ${h} — не узел и не текст (группа модели, под-модель либо значение другого типа); он пропущен. Текст — строка, число или сигнал; группу подключают узлом \`{ model: model.$.<группа>, part }\`.`
|
|
640
|
+
);
|
|
641
|
+
continue;
|
|
2738
642
|
}
|
|
2739
|
-
|
|
2740
|
-
v = !0;
|
|
2741
|
-
break;
|
|
643
|
+
n.push(ne(l, h, s));
|
|
2742
644
|
}
|
|
2743
645
|
}
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
}, [i]);
|
|
2750
|
-
return P.useSyncExternalStore(d, l, l);
|
|
646
|
+
}
|
|
647
|
+
const r = Object.keys(t).every((o) => o === "selector" || o === "componentProps");
|
|
648
|
+
return i.component === void 0 && a === void 0 && r && g(
|
|
649
|
+
`${b}: узел ${i.id} — без \`component\` и без \`children\`: он ничего не рисует, и \`componentProps\` ему некому отдать. Пустую секцию объявляют с \`children: []\`, узел с разметкой — с \`component\`.`
|
|
650
|
+
), I({ ...i, kind: "container", children: n }, s);
|
|
2751
651
|
}
|
|
2752
|
-
function
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
dirty: s.dirty,
|
|
2762
|
-
shouldShowError: s.shouldShowError,
|
|
2763
|
-
componentProps: s.componentProps
|
|
2764
|
-
}, t = [
|
|
2765
|
-
{ key: "value" },
|
|
2766
|
-
{ key: "disabled" },
|
|
2767
|
-
{ key: "errors", useShallowArrayEqual: !0 },
|
|
2768
|
-
{ key: "pending" },
|
|
2769
|
-
{ key: "valid" },
|
|
2770
|
-
{ key: "invalid" },
|
|
2771
|
-
{ key: "touched" },
|
|
2772
|
-
{ key: "dirty" },
|
|
2773
|
-
{ key: "shouldShowError" },
|
|
2774
|
-
{ key: "componentProps" }
|
|
2775
|
-
], i = y(
|
|
2776
|
-
(r) => ({
|
|
2777
|
-
value: r.value,
|
|
2778
|
-
pending: r.pending,
|
|
2779
|
-
disabled: r.disabled,
|
|
2780
|
-
errors: r.errors,
|
|
2781
|
-
valid: r.valid,
|
|
2782
|
-
invalid: r.invalid,
|
|
2783
|
-
touched: r.touched,
|
|
2784
|
-
dirty: r.dirty,
|
|
2785
|
-
shouldShowError: r.shouldShowError,
|
|
2786
|
-
componentProps: r.componentProps
|
|
2787
|
-
}),
|
|
2788
|
-
[]
|
|
2789
|
-
);
|
|
2790
|
-
return B(e, t, i, s);
|
|
652
|
+
function ne(t, e, s) {
|
|
653
|
+
if (s.ancestors.has(t))
|
|
654
|
+
throw new Error(`${b}: узел ${e} содержит сам себя — дерево схемы зациклено.`);
|
|
655
|
+
s.ancestors.add(t);
|
|
656
|
+
try {
|
|
657
|
+
return Ue(t, e, s);
|
|
658
|
+
} finally {
|
|
659
|
+
s.ancestors.delete(t);
|
|
660
|
+
}
|
|
2791
661
|
}
|
|
2792
|
-
function
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
pending: r.pending,
|
|
2818
|
-
errors: r.errors,
|
|
2819
|
-
valid: r.valid,
|
|
2820
|
-
invalid: r.invalid,
|
|
2821
|
-
touched: r.touched,
|
|
2822
|
-
dirty: r.dirty,
|
|
2823
|
-
disabled: r.disabled
|
|
2824
|
-
}),
|
|
2825
|
-
[]
|
|
2826
|
-
);
|
|
2827
|
-
return B(e, t, i, s);
|
|
662
|
+
function Ue(t, e, s) {
|
|
663
|
+
if (process.env.NODE_ENV !== "production")
|
|
664
|
+
for (const i of Object.keys(t)) Re.has(i) || s.unknownKeys.add(i);
|
|
665
|
+
if (typeof t.item == "function") {
|
|
666
|
+
const i = he(t.model);
|
|
667
|
+
return i ? Le(t, i, e, s) : (g(
|
|
668
|
+
`${b}: у узла с \`item\` привязка — не массив модели (узел ${e}); он стал контейнером без строк. Ожидается \`model: model.$.<массив>\`; начальное значение поля должно быть массивом, а не \`null\`.`
|
|
669
|
+
), V(t, e, s));
|
|
670
|
+
}
|
|
671
|
+
if (typeof t.part == "function") {
|
|
672
|
+
const i = fe(t.model);
|
|
673
|
+
return i ? ze(t, i, e, s) : (g(
|
|
674
|
+
`${b}: у узла с \`part\` привязка — не группа модели (узел ${e}); он стал контейнером. Ожидается \`model: model.$.<группа>\`.`
|
|
675
|
+
), V(t, e, s));
|
|
676
|
+
}
|
|
677
|
+
if (J(t.model)) {
|
|
678
|
+
const i = t.model, n = I(
|
|
679
|
+
{ ...F(t, e, s), kind: "field", handle: i, disabled: t.disabled === !0 },
|
|
680
|
+
s
|
|
681
|
+
);
|
|
682
|
+
return oe(s.fields, i, n), n;
|
|
683
|
+
}
|
|
684
|
+
return t.model != null && g(
|
|
685
|
+
`${b}: привязка узла не распознана (узел ${e}) — он не стал полем. Ожидается \`model: model.$.<path>\` (лист или массив); проверьте, что не передан \`model: model.<path>\` (value-фасад) или группа без \`part\`.`
|
|
686
|
+
), V(t, e, s);
|
|
2828
687
|
}
|
|
2829
|
-
function
|
|
2830
|
-
const s =
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
688
|
+
function Le(t, e, s, i) {
|
|
689
|
+
const n = t.item, a = ie(e, i, s), r = `${i.scope}/${a}`, o = /* @__PURE__ */ new WeakMap(), c = /* @__PURE__ */ new Set(), l = I(
|
|
690
|
+
{
|
|
691
|
+
...F(t, s, i),
|
|
692
|
+
kind: "array",
|
|
693
|
+
handle: e,
|
|
694
|
+
control: _(e),
|
|
695
|
+
initialValue: t.initialValue,
|
|
696
|
+
row(h) {
|
|
697
|
+
let u = o.get(h);
|
|
698
|
+
if (!u) {
|
|
699
|
+
const f = /* @__PURE__ */ new Set();
|
|
700
|
+
u = B(
|
|
701
|
+
ee(n, h),
|
|
702
|
+
h,
|
|
703
|
+
`${r}[${Z(h)}]`,
|
|
704
|
+
f
|
|
705
|
+
);
|
|
706
|
+
const p = [...f].filter((y) => !c.has(y));
|
|
707
|
+
if (p.length > 0) {
|
|
708
|
+
for (const y of p) c.add(y);
|
|
709
|
+
re(new Set(p));
|
|
710
|
+
}
|
|
711
|
+
o.set(h, u);
|
|
712
|
+
}
|
|
713
|
+
return u;
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
i
|
|
2843
717
|
);
|
|
2844
|
-
return
|
|
718
|
+
return i.arrays.set(e, l), l;
|
|
2845
719
|
}
|
|
2846
|
-
function
|
|
2847
|
-
const
|
|
2848
|
-
|
|
720
|
+
function ze(t, e, s, i) {
|
|
721
|
+
const n = F(t, s, i), a = _(e), r = `${i.scope}/${ie(e, i, s)}`, o = B(
|
|
722
|
+
ee(t.part, a),
|
|
723
|
+
a,
|
|
724
|
+
r,
|
|
725
|
+
i.unknownKeys,
|
|
726
|
+
i.ancestors
|
|
727
|
+
);
|
|
728
|
+
for (const [c, l] of o.fields) oe(i.fields, c, l);
|
|
729
|
+
for (const [c, l] of o.arrays) i.arrays.set(c, l);
|
|
730
|
+
return I({ ...n, kind: "part", handle: e, model: a, content: o }, i);
|
|
2849
731
|
}
|
|
2850
|
-
function
|
|
2851
|
-
|
|
2852
|
-
(
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
732
|
+
function B(t, e, s, i, n = /* @__PURE__ */ new Set()) {
|
|
733
|
+
if (t === null || typeof t != "object")
|
|
734
|
+
throw new Error(`${b}: корень области «${s}» — не узел схемы: ${String(t)}.`);
|
|
735
|
+
const a = e !== void 0 ? e.$ : void 0, r = {
|
|
736
|
+
scope: s,
|
|
737
|
+
basePath: se(a),
|
|
738
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
739
|
+
fields: /* @__PURE__ */ new Map(),
|
|
740
|
+
arrays: /* @__PURE__ */ new Map(),
|
|
741
|
+
selectorPaths: /* @__PURE__ */ new Map(),
|
|
742
|
+
ancestors: n,
|
|
743
|
+
unknownKeys: i
|
|
744
|
+
}, o = ne(t, "#", r);
|
|
745
|
+
return {
|
|
746
|
+
scope: s,
|
|
747
|
+
model: e,
|
|
748
|
+
root: o,
|
|
749
|
+
nodes: r.nodes,
|
|
750
|
+
fields: r.fields,
|
|
751
|
+
arrays: r.arrays
|
|
752
|
+
};
|
|
2868
753
|
}
|
|
2869
|
-
function
|
|
2870
|
-
const
|
|
2871
|
-
|
|
2872
|
-
let n = !0;
|
|
2873
|
-
return g(() => {
|
|
2874
|
-
if (s.length.value, n) {
|
|
2875
|
-
n = !1;
|
|
2876
|
-
return;
|
|
2877
|
-
}
|
|
2878
|
-
r();
|
|
2879
|
-
});
|
|
2880
|
-
},
|
|
2881
|
-
[s]
|
|
2882
|
-
), i = y(() => {
|
|
2883
|
-
const r = s.length.value;
|
|
2884
|
-
return e.current.length === r ? e.current.length : (e.current.length = r, r);
|
|
2885
|
-
}, [s]);
|
|
2886
|
-
return P.useSyncExternalStore(t, i, i);
|
|
754
|
+
function qe(t, e, s) {
|
|
755
|
+
const i = /* @__PURE__ */ new Set(), n = B(t, e, s, i);
|
|
756
|
+
return i.size > 0 && re(i), n;
|
|
2887
757
|
}
|
|
2888
|
-
function
|
|
2889
|
-
const
|
|
2890
|
-
|
|
2891
|
-
|
|
758
|
+
function re(t) {
|
|
759
|
+
const e = [...t], s = e.filter((i) => i in G).map((i) => `\`${i}\`: ${G[i]}`);
|
|
760
|
+
g(
|
|
761
|
+
`${b}: у узлов схемы есть ключи, которые сборка не читает: ${e.map((i) => `\`${i}\``).join(", ")}. Узел описывают \`model\`, \`item\`, \`part\`, \`children\`, \`component\`, \`componentProps\`, \`selector\`, \`disabled\` и \`initialValue\`; вложенные узлы читаются только из \`children\`.` + (s.length > 0 ? ` ${s.join("; ")}.` : "")
|
|
2892
762
|
);
|
|
2893
|
-
Q(() => a.start(), [a]);
|
|
2894
|
-
const u = P.useSyncExternalStore(
|
|
2895
|
-
y((l) => a.validating.subscribe(l), [a]),
|
|
2896
|
-
() => a.validating.peek(),
|
|
2897
|
-
() => !1
|
|
2898
|
-
// SSR: прогон не идёт
|
|
2899
|
-
), d = y(() => a.validate(), [a]);
|
|
2900
|
-
return { submit: d, validate: d, isValidating: u };
|
|
2901
763
|
}
|
|
2902
|
-
function
|
|
2903
|
-
|
|
2904
|
-
|
|
764
|
+
function ae(t, e, s) {
|
|
765
|
+
return qe(t, e, s?.scope ?? "#");
|
|
766
|
+
}
|
|
767
|
+
const Ge = { fields: /* @__PURE__ */ new Map(), arrays: /* @__PURE__ */ new Map() }, Ye = (t) => (
|
|
768
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
769
|
+
((e) => U({ model: e, compiled: t.row(e) }))
|
|
770
|
+
);
|
|
771
|
+
function le(t, e) {
|
|
772
|
+
const s = /* @__PURE__ */ new Map(), i = [], n = (r, o) => {
|
|
773
|
+
const c = e.fields.get(o), l = c ? {
|
|
774
|
+
component: c.component,
|
|
775
|
+
componentProps: c.source.componentProps,
|
|
776
|
+
disabled: c.disabled
|
|
777
|
+
} : {}, h = new Ve({ ...l, valueSignal: o });
|
|
778
|
+
s.set(r, h), O(o, h), i.push([h, o]);
|
|
779
|
+
};
|
|
780
|
+
for (const r of Object.keys(t)) {
|
|
781
|
+
const o = t[r];
|
|
782
|
+
if (me(o)) {
|
|
783
|
+
const c = e.arrays.get(o);
|
|
784
|
+
if (c) {
|
|
785
|
+
const l = _(o), h = new je(l, Ye(c));
|
|
786
|
+
s.set(r, h), O(o, h);
|
|
787
|
+
} else e.fields.has(o) && n(r, o);
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (X(o)) {
|
|
791
|
+
const c = le(o, e);
|
|
792
|
+
s.set(r, c), O(o, c.proxy);
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
n(r, o);
|
|
796
|
+
}
|
|
797
|
+
const a = new De(s);
|
|
798
|
+
for (const [r, o] of i) a.registerLeafSignal(r, o);
|
|
799
|
+
return a;
|
|
800
|
+
}
|
|
801
|
+
function lt(t) {
|
|
802
|
+
const { schema: e, ...s } = t;
|
|
803
|
+
return U({
|
|
804
|
+
...s,
|
|
805
|
+
...e !== void 0 ? { compiled: ae(e, t.model) } : {}
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
function xe(t) {
|
|
809
|
+
k(t).dispose();
|
|
2905
810
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
811
|
+
function U(t) {
|
|
812
|
+
const { model: e, compiled: s, behavior: i } = t, n = s ?? Ge;
|
|
813
|
+
for (const [o, c] of n.arrays)
|
|
814
|
+
c.initialValue !== void 0 && pe(o, c.initialValue);
|
|
815
|
+
const a = le(
|
|
816
|
+
e.$,
|
|
817
|
+
n
|
|
818
|
+
), r = a.proxy;
|
|
819
|
+
if (O(e.$, r), i)
|
|
820
|
+
try {
|
|
821
|
+
a.attachBehaviorCleanup(
|
|
822
|
+
i.__run(e, r, t.schemaController, t.flow)
|
|
823
|
+
);
|
|
824
|
+
} catch (o) {
|
|
825
|
+
throw a.dispose(), o;
|
|
826
|
+
}
|
|
827
|
+
return r;
|
|
2909
828
|
}
|
|
2910
|
-
const
|
|
2911
|
-
function
|
|
2912
|
-
|
|
2913
|
-
|
|
829
|
+
const C = "[@reformer/core] FormFlow";
|
|
830
|
+
function He(t) {
|
|
831
|
+
const e = [...t.steps];
|
|
832
|
+
if (e.length === 0) throw new Error(`${C}: у потока нет шагов.`);
|
|
833
|
+
const s = e.filter((u, f) => e.indexOf(u) !== f);
|
|
834
|
+
if (s.length > 0)
|
|
835
|
+
throw new Error(`${C}: шаги повторяются: ${[...new Set(s)].join(", ")}.`);
|
|
836
|
+
const i = w(0), n = w(/* @__PURE__ */ new Set()), a = w(0);
|
|
837
|
+
let r = 0;
|
|
838
|
+
const o = (u) => e.indexOf(u), c = (u) => {
|
|
839
|
+
const f = o(u);
|
|
840
|
+
if (f < 0)
|
|
841
|
+
throw new Error(`${C}: шага «${u}» нет. Шаги потока: ${e.join(", ")}.`);
|
|
842
|
+
return f;
|
|
843
|
+
}, l = (u) => {
|
|
844
|
+
const f = e[i.peek()];
|
|
845
|
+
return r++, D(() => {
|
|
846
|
+
u < i.peek() && (n.value = new Set([...n.peek()].filter((p) => o(p) < u))), i.value = u;
|
|
847
|
+
}), { status: "moved", from: f, to: e[u] };
|
|
848
|
+
}, h = (u) => {
|
|
849
|
+
const f = o(u);
|
|
850
|
+
return f < 0 ? !1 : f === 0 || n.value.has(e[f - 1]);
|
|
851
|
+
};
|
|
852
|
+
return {
|
|
853
|
+
steps: e,
|
|
854
|
+
current: d(() => e[i.value]),
|
|
855
|
+
// Наружу — только чтение: шаг меняют переходы потока.
|
|
856
|
+
index: d(() => i.value),
|
|
857
|
+
completed: d(() => n.value),
|
|
858
|
+
isFirst: d(() => i.value === 0),
|
|
859
|
+
isLast: d(() => i.value === e.length - 1),
|
|
860
|
+
validating: d(() => a.value > 0),
|
|
861
|
+
canGoTo: h,
|
|
862
|
+
async next() {
|
|
863
|
+
const u = i.peek(), f = r, p = e[u];
|
|
864
|
+
let y;
|
|
865
|
+
if (t.validateStep) {
|
|
866
|
+
a.value++;
|
|
867
|
+
try {
|
|
868
|
+
y = await t.validateStep(p);
|
|
869
|
+
} finally {
|
|
870
|
+
a.value--;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const S = y !== void 0 ? { validation: y } : {};
|
|
874
|
+
return r !== f ? { status: "stayed", step: e[i.peek()], reason: "cancelled", ...S } : y !== void 0 && y.status !== "valid" ? { status: "stayed", step: p, reason: y.status, ...S } : (n.value = /* @__PURE__ */ new Set([...n.peek(), p]), u === e.length - 1 ? { status: "stayed", step: p, reason: "edge", ...S } : (r++, i.value = u + 1, { status: "moved", from: p, to: e[u + 1] }));
|
|
875
|
+
},
|
|
876
|
+
previous() {
|
|
877
|
+
const u = i.peek();
|
|
878
|
+
return u === 0 ? { status: "stayed", step: e[0], reason: "edge" } : l(u - 1);
|
|
879
|
+
},
|
|
880
|
+
goTo(u) {
|
|
881
|
+
const f = c(u);
|
|
882
|
+
return f === i.peek() ? { status: "stayed", step: u, reason: "current" } : h(u) ? l(f) : { status: "stayed", step: e[i.peek()], reason: "locked" };
|
|
883
|
+
},
|
|
884
|
+
reset() {
|
|
885
|
+
r++, D(() => {
|
|
886
|
+
i.value = 0, n.value = /* @__PURE__ */ new Set();
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
};
|
|
2914
890
|
}
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
const i = { ...t };
|
|
2918
|
-
for (const n of $e) delete i[n];
|
|
2919
|
-
for (const n of s?.strip ?? []) delete i[n];
|
|
2920
|
-
if (!s)
|
|
2921
|
-
return { ...i, value: e.value, onChange: e.onChange, onBlur: e.onBlur };
|
|
2922
|
-
const r = s.fromEmit ?? J;
|
|
891
|
+
function Je(t) {
|
|
892
|
+
const e = w(0);
|
|
2923
893
|
return {
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
894
|
+
submitting: d(() => e.value > 0),
|
|
895
|
+
async submit(s) {
|
|
896
|
+
const i = await t.check?.();
|
|
897
|
+
if (i) {
|
|
898
|
+
const { result: a } = i;
|
|
899
|
+
if (a.status !== "valid") return { status: a.status, validation: a };
|
|
900
|
+
if (i.stale)
|
|
901
|
+
return { status: "cancelled", validation: { ...a, status: "cancelled" } };
|
|
902
|
+
}
|
|
903
|
+
const n = i ? i.values : t.values();
|
|
904
|
+
e.value++;
|
|
905
|
+
try {
|
|
906
|
+
return { status: "submitted", result: await s(n) };
|
|
907
|
+
} catch (a) {
|
|
908
|
+
return { status: "failed", error: a };
|
|
909
|
+
} finally {
|
|
910
|
+
e.value--;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
2928
913
|
};
|
|
2929
914
|
}
|
|
2930
|
-
|
|
915
|
+
const M = Object.freeze({
|
|
916
|
+
status: "valid",
|
|
917
|
+
issues: /* @__PURE__ */ new Map(),
|
|
918
|
+
failures: []
|
|
919
|
+
}), Y = "[@reformer/core] createForm";
|
|
920
|
+
function Xe(t, e) {
|
|
921
|
+
const s = typeof e.validation == "function" ? { rules: e.validation } : e.validation ?? {}, i = /* @__PURE__ */ new Map();
|
|
922
|
+
for (const v of e.flow?.steps ?? []) {
|
|
923
|
+
if (i.has(v.id)) throw new Error(`${Y}: шаг потока «${v.id}» повторяется.`);
|
|
924
|
+
i.set(v.id, v.rules ?? null);
|
|
925
|
+
}
|
|
926
|
+
const n = [...i.values()].filter(
|
|
927
|
+
(v) => v != null
|
|
928
|
+
), a = s.rules ?? (n.length > 0 || s.extras ? Ee(
|
|
929
|
+
() => Oe(...n, ...s.extras ? [s.extras] : [])
|
|
930
|
+
) : void 0), r = a !== void 0 ? Pe(t, a, {
|
|
931
|
+
strategy: s.strategy,
|
|
932
|
+
debounce: s.debounce,
|
|
933
|
+
liveAfterSubmit: s.liveAfterSubmit
|
|
934
|
+
}) : void 0, o = w(0), c = (v) => {
|
|
935
|
+
const m = we();
|
|
936
|
+
return m ? (o.value++, m.then(v).finally(() => {
|
|
937
|
+
o.value--;
|
|
938
|
+
})) : v();
|
|
939
|
+
}, l = async (v) => {
|
|
940
|
+
let m;
|
|
941
|
+
const $ = await c(() => (m = t.get(), v())), P = t.get();
|
|
942
|
+
return { result: $, values: P, stale: $.status === "valid" && !ye(m, P) };
|
|
943
|
+
}, h = () => r ? c(() => r.run()) : Promise.resolve(M), u = (v) => {
|
|
944
|
+
if (!i.has(v)) {
|
|
945
|
+
const m = [...i.keys()];
|
|
946
|
+
throw new Error(
|
|
947
|
+
`${Y}: шага «${v}» нет в потоке. ` + (m.length > 0 ? `Шаги потока: ${m.join(", ")}.` : "Поток не задан: `flow`.")
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return i.get(v) ?? null;
|
|
951
|
+
}, f = async (v) => {
|
|
952
|
+
o.value++;
|
|
953
|
+
try {
|
|
954
|
+
return await Fe(t, v, { touch: !0, skipDisabled: !0 });
|
|
955
|
+
} finally {
|
|
956
|
+
o.value--;
|
|
957
|
+
}
|
|
958
|
+
}, p = async (v) => {
|
|
959
|
+
const m = u(v);
|
|
960
|
+
return m ? c(() => f(m)) : M;
|
|
961
|
+
}, y = async (v) => {
|
|
962
|
+
const m = u(v);
|
|
963
|
+
if (!m) return M;
|
|
964
|
+
const { result: $, stale: P } = await l(() => f(m));
|
|
965
|
+
return P ? { ...$, status: "cancelled" } : $;
|
|
966
|
+
}, S = e.flow !== void 0 ? He({ steps: [...i.keys()], validateStep: y }) : void 0, ce = Je({
|
|
967
|
+
check: r ? () => l(() => r.run({ touch: "all", submit: !0 })) : void 0,
|
|
968
|
+
values: () => t.get()
|
|
969
|
+
});
|
|
2931
970
|
return {
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
971
|
+
flow: S,
|
|
972
|
+
validating: d(() => (r?.validating.value ?? !1) || o.value > 0),
|
|
973
|
+
validation: a !== void 0 && r ? { rules: a, controller: r } : void 0,
|
|
974
|
+
submission: ce,
|
|
975
|
+
validate: h,
|
|
976
|
+
validateStep: p,
|
|
977
|
+
dispose() {
|
|
978
|
+
r?.dispose();
|
|
979
|
+
for (const v of n) Ae(t, v);
|
|
980
|
+
}
|
|
2936
981
|
};
|
|
2937
982
|
}
|
|
2938
|
-
|
|
2939
|
-
|
|
983
|
+
const E = "[@reformer/core] createForm", x = (t) => t.map((e) => `"${e}"`).join(", ");
|
|
984
|
+
function Qe(t, e, s) {
|
|
985
|
+
if (t === void 0) return;
|
|
986
|
+
const i = $e(e, t);
|
|
987
|
+
if (i.length > 0 && console.warn(
|
|
988
|
+
`${E}: поведение обращается к узлам схемы, которых нет в корневом дереве: ${x(i)}. Правило ничего не сделает. Узлы строки массива и подформы адресуются из их поведения — через \`applyEach\` / \`apply\`.`
|
|
989
|
+
), s) {
|
|
990
|
+
const n = s.steps.map((r) => r.id), a = n.filter((r) => t.nodes.get(r)?.selector !== r);
|
|
991
|
+
a.length > 0 && a.length < n.length && console.warn(
|
|
992
|
+
`${E}: в \`flow.steps\` есть шаги, для которых в дереве схемы нет узла с таким \`selector\`: ${x(a)}. Визард не свяжет их с разметкой шага.`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
2940
995
|
}
|
|
2941
|
-
function
|
|
2942
|
-
const
|
|
2943
|
-
|
|
996
|
+
function Ze(t, e) {
|
|
997
|
+
const { schema: s, registry: i } = t;
|
|
998
|
+
if (s == null) return {};
|
|
999
|
+
if (typeof s == "function")
|
|
1000
|
+
return { tree: s(e) };
|
|
1001
|
+
if (i == null)
|
|
1002
|
+
throw new Error(
|
|
1003
|
+
`${E}: \`schema\` — готовое дерево, а не билдер \`(model) => узел\`. Передайте билдер; документу схемы (JSON) нужен \`registry\`. Форму из модели и готового дерева без сборки собирает \`createFormFromModel({ model, schema })\`.`
|
|
1004
|
+
);
|
|
1005
|
+
if (typeof i.resolveSchema != "function")
|
|
1006
|
+
throw new Error(
|
|
1007
|
+
`${E}: \`registry\` не умеет собирать дерево из документа схемы — у него нет метода \`resolveSchema\`. Возьмите реестр \`@reformer/renderer-json\` (\`createComponentRegistry\`).`
|
|
1008
|
+
);
|
|
1009
|
+
return i.resolveSchema(s, e);
|
|
2944
1010
|
}
|
|
2945
|
-
function
|
|
2946
|
-
|
|
2947
|
-
|
|
1011
|
+
function ct(t) {
|
|
1012
|
+
if (typeof t?.model?.signalAt != "function" && t?.initial === void 0)
|
|
1013
|
+
throw new Error(
|
|
1014
|
+
`${E}: нужна модель — \`model\` либо \`initial\`. Значения формы принадлежат модели: \`createForm({ initial: { email: '' }, schema })\`.`
|
|
1015
|
+
);
|
|
1016
|
+
const s = t.model ?? ue(t.initial);
|
|
1017
|
+
t.seed?.(s), t.model === void 0 && s.captureInitial();
|
|
1018
|
+
const { tree: i, fieldWrapper: n, errorBoundary: a } = Ze(t, s), r = i !== void 0 ? ae(i, s) : void 0, o = Se(), c = o.scopeOf(s), l = Xe(s, { validation: t.validation, flow: t.flow }), h = U({
|
|
1019
|
+
model: s,
|
|
1020
|
+
...r !== void 0 ? { compiled: r } : {},
|
|
1021
|
+
...t.behavior ? { behavior: t.behavior, schemaController: o } : {},
|
|
1022
|
+
...l.flow !== void 0 ? { flow: l.flow } : {}
|
|
1023
|
+
});
|
|
1024
|
+
process.env.NODE_ENV !== "production" && Qe(r, c.__overrideMaps, t.flow);
|
|
1025
|
+
const u = {
|
|
1026
|
+
...l,
|
|
1027
|
+
model: s,
|
|
1028
|
+
form: h,
|
|
1029
|
+
render: {
|
|
1030
|
+
tree: r,
|
|
1031
|
+
controller: o,
|
|
1032
|
+
node: (f) => c.node(f),
|
|
1033
|
+
...n !== void 0 ? { fieldWrapper: n } : {},
|
|
1034
|
+
...a !== void 0 ? { errorBoundary: a } : {}
|
|
1035
|
+
},
|
|
1036
|
+
// После `...level`: уровень снимает только себя, сборка — ещё и форму.
|
|
1037
|
+
dispose() {
|
|
1038
|
+
xe(h), l.dispose();
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
try {
|
|
1042
|
+
t.setup?.(u);
|
|
1043
|
+
} catch (f) {
|
|
1044
|
+
throw u.dispose(), f;
|
|
1045
|
+
}
|
|
1046
|
+
return u;
|
|
2948
1047
|
}
|
|
2949
|
-
const
|
|
1048
|
+
const ut = Object.freeze({ runtime: "reformer-core", v: 1 });
|
|
2950
1049
|
export {
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
_ as GroupNode,
|
|
2961
|
-
Ve as ModelArrayNode,
|
|
2962
|
-
Ae as NodeFactory,
|
|
2963
|
-
$ as SubscriptionManager,
|
|
2964
|
-
ft as arrayOf,
|
|
2965
|
-
nt as bindFieldProps,
|
|
2966
|
-
Se as buildValidation,
|
|
2967
|
-
yt as computeFrom,
|
|
2968
|
-
gt as copyFrom,
|
|
2969
|
-
Ye as createCoreForm,
|
|
2970
|
-
Ne as createForm,
|
|
2971
|
-
ee as createFormFromModel,
|
|
2972
|
-
Je as createLegacyForm,
|
|
2973
|
-
te as createModel,
|
|
2974
|
-
oe as createSchemaController,
|
|
2975
|
-
Ct as createSchemaOverrideMaps,
|
|
1050
|
+
ut as CORE_RUNTIME_TOKEN,
|
|
1051
|
+
mt as arrayOf,
|
|
1052
|
+
ae as compileSchema,
|
|
1053
|
+
ct as createForm,
|
|
1054
|
+
He as createFormFlow,
|
|
1055
|
+
lt as createFormFromModel,
|
|
1056
|
+
Xe as createFormLevel,
|
|
1057
|
+
ue as createModel,
|
|
1058
|
+
Se as createSchemaController,
|
|
2976
1059
|
Pt as createSchemaScope,
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
At as safeDebouncedCallback,
|
|
3000
|
-
X as schemaSubtree,
|
|
3001
|
-
Vt as syncFields,
|
|
3002
|
-
kt as transformValue,
|
|
3003
|
-
le as unknownSchemaSelectors,
|
|
3004
|
-
mt as unmarkDerived,
|
|
3005
|
-
et as useArrayLength,
|
|
3006
|
-
at as useFieldHandleRef,
|
|
3007
|
-
st as useFormBundle,
|
|
3008
|
-
rt as useFormBundleContext,
|
|
3009
|
-
Xe as useFormControl,
|
|
3010
|
-
Ze as useFormControlValue,
|
|
3011
|
-
tt as useFormValidation,
|
|
3012
|
-
St as watchField
|
|
1060
|
+
Je as createSubmission,
|
|
1061
|
+
xe as disposeForm,
|
|
1062
|
+
ft as eachLeafSignal,
|
|
1063
|
+
vt as eachValueSignal,
|
|
1064
|
+
$t as getNodeForSignal,
|
|
1065
|
+
at as isCompiledNode,
|
|
1066
|
+
T as isDerived,
|
|
1067
|
+
Ot as isFormArray,
|
|
1068
|
+
Ft as isFormControl,
|
|
1069
|
+
It as isFormGroup,
|
|
1070
|
+
Nt as isFormNode,
|
|
1071
|
+
X as isModelContainerSignal,
|
|
1072
|
+
J as isValueSignal,
|
|
1073
|
+
wt as markDerived,
|
|
1074
|
+
_ as modelOf,
|
|
1075
|
+
O as registerSignalNode,
|
|
1076
|
+
yt as runOutsideEffect,
|
|
1077
|
+
bt as safeCallback,
|
|
1078
|
+
gt as safeDebouncedCallback,
|
|
1079
|
+
ee as schemaSubtree,
|
|
1080
|
+
$e as unknownSchemaSelectors,
|
|
1081
|
+
kt as unmarkDerived
|
|
3013
1082
|
};
|