@native-form/core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2073 -0
- package/dist/index.d.cts +495 -0
- package/dist/index.d.ts +495 -9
- package/dist/index.js +2030 -7
- package/package.json +5 -3
- package/dist/Discovery.d.ts +0 -2
- package/dist/Discovery.d.ts.map +0 -1
- package/dist/Discovery.js +0 -2
- package/dist/Discovery.js.map +0 -1
- package/dist/FieldErrorRenderer.d.ts +0 -2
- package/dist/FieldErrorRenderer.d.ts.map +0 -1
- package/dist/FieldErrorRenderer.js +0 -2
- package/dist/FieldErrorRenderer.js.map +0 -1
- package/dist/FieldNode.d.ts +0 -2
- package/dist/FieldNode.d.ts.map +0 -1
- package/dist/FieldNode.js +0 -2
- package/dist/FieldNode.js.map +0 -1
- package/dist/FieldRegistry.d.ts +0 -2
- package/dist/FieldRegistry.d.ts.map +0 -1
- package/dist/FieldRegistry.js +0 -2
- package/dist/FieldRegistry.js.map +0 -1
- package/dist/FieldState.d.ts +0 -2
- package/dist/FieldState.d.ts.map +0 -1
- package/dist/FieldState.js +0 -2
- package/dist/FieldState.js.map +0 -1
- package/dist/Path.d.ts +0 -78
- package/dist/Path.d.ts.map +0 -1
- package/dist/Path.js +0 -219
- package/dist/Path.js.map +0 -1
- package/dist/Runtime.d.ts +0 -2
- package/dist/Runtime.d.ts.map +0 -1
- package/dist/Runtime.js +0 -2
- package/dist/Runtime.js.map +0 -1
- package/dist/RuntimeMutationPolicy.d.ts +0 -2
- package/dist/RuntimeMutationPolicy.d.ts.map +0 -1
- package/dist/RuntimeMutationPolicy.js +0 -2
- package/dist/RuntimeMutationPolicy.js.map +0 -1
- package/dist/Serializer.d.ts +0 -2
- package/dist/Serializer.d.ts.map +0 -1
- package/dist/Serializer.js +0 -2
- package/dist/Serializer.js.map +0 -1
- package/dist/Validation.d.ts +0 -2
- package/dist/Validation.d.ts.map +0 -1
- package/dist/Validation.js +0 -2
- package/dist/Validation.js.map +0 -1
- package/dist/ValidationPipeline.d.ts +0 -2
- package/dist/ValidationPipeline.d.ts.map +0 -1
- package/dist/ValidationPipeline.js +0 -2
- package/dist/ValidationPipeline.js.map +0 -1
- package/dist/discovery/Discovery.d.ts +0 -5
- package/dist/discovery/Discovery.d.ts.map +0 -1
- package/dist/discovery/Discovery.js +0 -63
- package/dist/discovery/Discovery.js.map +0 -1
- package/dist/errors/FieldErrorRenderer.d.ts +0 -17
- package/dist/errors/FieldErrorRenderer.d.ts.map +0 -1
- package/dist/errors/FieldErrorRenderer.js +0 -115
- package/dist/errors/FieldErrorRenderer.js.map +0 -1
- package/dist/field/FieldNode.d.ts +0 -38
- package/dist/field/FieldNode.d.ts.map +0 -1
- package/dist/field/FieldNode.js +0 -154
- package/dist/field/FieldNode.js.map +0 -1
- package/dist/field/FieldRegistry.d.ts +0 -19
- package/dist/field/FieldRegistry.d.ts.map +0 -1
- package/dist/field/FieldRegistry.js +0 -46
- package/dist/field/FieldRegistry.js.map +0 -1
- package/dist/field/FieldState.d.ts +0 -13
- package/dist/field/FieldState.d.ts.map +0 -1
- package/dist/field/FieldState.js +0 -2
- package/dist/field/FieldState.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/runtime/Runtime.d.ts +0 -137
- package/dist/runtime/Runtime.d.ts.map +0 -1
- package/dist/runtime/Runtime.js +0 -1374
- package/dist/runtime/Runtime.js.map +0 -1
- package/dist/runtime/RuntimeMutationPolicy.d.ts +0 -3
- package/dist/runtime/RuntimeMutationPolicy.d.ts.map +0 -1
- package/dist/runtime/RuntimeMutationPolicy.js +0 -34
- package/dist/runtime/RuntimeMutationPolicy.js.map +0 -1
- package/dist/serialization/Serializer.d.ts +0 -52
- package/dist/serialization/Serializer.d.ts.map +0 -1
- package/dist/serialization/Serializer.js +0 -115
- package/dist/serialization/Serializer.js.map +0 -1
- package/dist/shared/types.d.ts +0 -66
- package/dist/shared/types.d.ts.map +0 -1
- package/dist/shared/types.js +0 -2
- package/dist/shared/types.js.map +0 -1
- package/dist/tsdoc-metadata.json +0 -11
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/validation/Validation.d.ts +0 -61
- package/dist/validation/Validation.d.ts.map +0 -1
- package/dist/validation/Validation.js +0 -34
- package/dist/validation/Validation.js.map +0 -1
- package/dist/validation/ValidationPipeline.d.ts +0 -61
- package/dist/validation/ValidationPipeline.d.ts.map +0 -1
- package/dist/validation/ValidationPipeline.js +0 -73
- package/dist/validation/ValidationPipeline.js.map +0 -1
package/dist/runtime/Runtime.js
DELETED
|
@@ -1,1374 +0,0 @@
|
|
|
1
|
-
import { Discovery } from "../discovery/Discovery";
|
|
2
|
-
import { FieldErrorRenderer } from "../errors/FieldErrorRenderer";
|
|
3
|
-
import { FieldNode } from "../field/FieldNode";
|
|
4
|
-
import { FieldRegistry } from "../field/FieldRegistry";
|
|
5
|
-
import { isRuntimeMutationRelevant, runtimeMutationObserverOptions, } from "./RuntimeMutationPolicy";
|
|
6
|
-
import { getByPath, matchArrayItemPath, normalizePath, remapArrayItemIndex, shiftArrayItemIndex, toNestedValues, } from "../Path";
|
|
7
|
-
import { Serializer } from "../serialization/Serializer";
|
|
8
|
-
import { createValidationPipeline, } from "../validation/ValidationPipeline";
|
|
9
|
-
/** @public */
|
|
10
|
-
export class Runtime {
|
|
11
|
-
options;
|
|
12
|
-
registry = new FieldRegistry();
|
|
13
|
-
discovery = new Discovery();
|
|
14
|
-
serializer = new Serializer();
|
|
15
|
-
fieldErrorRenderer = new FieldErrorRenderer();
|
|
16
|
-
form;
|
|
17
|
-
listeners = new Set();
|
|
18
|
-
// Field state is now managed on each FieldNode instance.
|
|
19
|
-
observer;
|
|
20
|
-
validationController;
|
|
21
|
-
debounceTimers = new Map();
|
|
22
|
-
fieldValidationControllers = new Map();
|
|
23
|
-
submitting = false;
|
|
24
|
-
validating = false;
|
|
25
|
-
errors = {};
|
|
26
|
-
preservedValuesStore = Object.create(null);
|
|
27
|
-
isSyncingFields = false;
|
|
28
|
-
pipeline;
|
|
29
|
-
constructor(options = {}) {
|
|
30
|
-
this.options = options;
|
|
31
|
-
this.pipeline =
|
|
32
|
-
options.pipeline ??
|
|
33
|
-
createValidationPipeline({
|
|
34
|
-
schema: options.schema,
|
|
35
|
-
validator: options.validator,
|
|
36
|
-
});
|
|
37
|
-
// Subscribe to registry lifecycle changes so Runtime can emit events.
|
|
38
|
-
this.registry.onChange = (e) => {
|
|
39
|
-
if (e.type === "mount") {
|
|
40
|
-
this.emitFieldEvent({ type: "field:mount", field: e.field.name });
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
this.emitFieldEvent({ type: "field:unmount", field: e.field.name });
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
mount(form) {
|
|
48
|
-
this.form = form;
|
|
49
|
-
this.syncFields();
|
|
50
|
-
this.fieldErrorRenderer.normalizeFields(this.registry.values());
|
|
51
|
-
const values = this.extractCurrentValues();
|
|
52
|
-
for (const field of this.registry.getAll()) {
|
|
53
|
-
field.register(values[field.name]);
|
|
54
|
-
}
|
|
55
|
-
form.addEventListener("submit", this.handleSubmit);
|
|
56
|
-
form.addEventListener("input", this.handleFieldUpdate);
|
|
57
|
-
form.addEventListener("change", this.handleFieldUpdate);
|
|
58
|
-
form.addEventListener("blur", this.handleFieldBlur, true);
|
|
59
|
-
form.addEventListener("reset", this.handleReset);
|
|
60
|
-
this.observer = new MutationObserver(this.handleMutation);
|
|
61
|
-
this.observer.observe(form, runtimeMutationObserverOptions);
|
|
62
|
-
this.emitFormEvent({
|
|
63
|
-
type: "form:mount",
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
unmount() {
|
|
67
|
-
this.clearDebounceTimers();
|
|
68
|
-
this.abortAllValidations();
|
|
69
|
-
if (this.form) {
|
|
70
|
-
this.form.removeEventListener("submit", this.handleSubmit);
|
|
71
|
-
this.form.removeEventListener("input", this.handleFieldUpdate);
|
|
72
|
-
this.form.removeEventListener("change", this.handleFieldUpdate);
|
|
73
|
-
this.form.removeEventListener("blur", this.handleFieldBlur, true);
|
|
74
|
-
this.form.removeEventListener("reset", this.handleReset);
|
|
75
|
-
}
|
|
76
|
-
this.observer?.disconnect();
|
|
77
|
-
this.observer = undefined;
|
|
78
|
-
this.fieldErrorRenderer.applyErrors(this.registry.values(), {});
|
|
79
|
-
this.registry.clear();
|
|
80
|
-
this.form = undefined;
|
|
81
|
-
this.errors = {};
|
|
82
|
-
this.preservedValuesStore = Object.create(null);
|
|
83
|
-
this.submitting = false;
|
|
84
|
-
this.validating = false;
|
|
85
|
-
this.emitFormEvent({
|
|
86
|
-
type: "form:unmount",
|
|
87
|
-
});
|
|
88
|
-
this.listeners.clear();
|
|
89
|
-
}
|
|
90
|
-
clearDebounceTimers() {
|
|
91
|
-
for (const timer of this.debounceTimers.values()) {
|
|
92
|
-
clearTimeout(timer);
|
|
93
|
-
}
|
|
94
|
-
this.debounceTimers.clear();
|
|
95
|
-
}
|
|
96
|
-
abortAllValidations() {
|
|
97
|
-
this.validationController?.abort();
|
|
98
|
-
this.validationController = undefined;
|
|
99
|
-
for (const controller of this.fieldValidationControllers.values()) {
|
|
100
|
-
controller.abort();
|
|
101
|
-
}
|
|
102
|
-
this.fieldValidationControllers.clear();
|
|
103
|
-
for (const field of this.registry.values()) {
|
|
104
|
-
field.setValidating(false);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
getAsyncValidator(name) {
|
|
108
|
-
if (!this.options.asyncValidators) {
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
if (this.options.asyncValidators[name]) {
|
|
112
|
-
return this.options.asyncValidators[name];
|
|
113
|
-
}
|
|
114
|
-
const normalized = normalizePath(name);
|
|
115
|
-
if (this.options.asyncValidators[normalized]) {
|
|
116
|
-
return this.options.asyncValidators[normalized];
|
|
117
|
-
}
|
|
118
|
-
for (const [key, validator] of Object.entries(this.options.asyncValidators)) {
|
|
119
|
-
if (normalizePath(key) === normalized) {
|
|
120
|
-
return validator;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
serialize() {
|
|
126
|
-
if (!this.form) {
|
|
127
|
-
if (this.options.preserveUnmountedValues) {
|
|
128
|
-
return { ...this.preservedValuesStore };
|
|
129
|
-
}
|
|
130
|
-
throw new Error("Runtime is not mounted.");
|
|
131
|
-
}
|
|
132
|
-
if (this.options.preserveUnmountedValues) {
|
|
133
|
-
if (!this.isSyncingFields) {
|
|
134
|
-
this.syncFields();
|
|
135
|
-
}
|
|
136
|
-
const live = this.serializer.serialize(this.form);
|
|
137
|
-
return {
|
|
138
|
-
...this.preservedValuesStore,
|
|
139
|
-
...live,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
return this.serializer.serialize(this.form);
|
|
143
|
-
}
|
|
144
|
-
serializeFields(options) {
|
|
145
|
-
if (this.options.preserveUnmountedValues && this.form && !this.isSyncingFields) {
|
|
146
|
-
this.syncFields();
|
|
147
|
-
}
|
|
148
|
-
const effectiveOptions = {
|
|
149
|
-
...this.options.serializeFieldsOptions,
|
|
150
|
-
...options,
|
|
151
|
-
};
|
|
152
|
-
const nesting = effectiveOptions.nesting ?? this.options.nesting ?? "flat";
|
|
153
|
-
let nodes = this.registry.values();
|
|
154
|
-
if (this.form) {
|
|
155
|
-
const nodeList = [];
|
|
156
|
-
const nodeMap = new Map(this.registry.values().map((n) => [n.element, n]));
|
|
157
|
-
for (const control of Array.from(this.form.elements)) {
|
|
158
|
-
if ((control instanceof HTMLInputElement ||
|
|
159
|
-
control instanceof HTMLSelectElement ||
|
|
160
|
-
control instanceof HTMLTextAreaElement) &&
|
|
161
|
-
control.name) {
|
|
162
|
-
nodeList.push(nodeMap.get(control) ?? new FieldNode(control));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
nodes = nodeList;
|
|
166
|
-
}
|
|
167
|
-
if (this.options.preserveUnmountedValues) {
|
|
168
|
-
const live = this.serializer.serializeFields(nodes, {
|
|
169
|
-
...effectiveOptions,
|
|
170
|
-
nesting: "flat",
|
|
171
|
-
});
|
|
172
|
-
const merged = {
|
|
173
|
-
...this.preservedValuesStore,
|
|
174
|
-
...live,
|
|
175
|
-
};
|
|
176
|
-
if (nesting === "nested") {
|
|
177
|
-
return toNestedValues(merged);
|
|
178
|
-
}
|
|
179
|
-
return merged;
|
|
180
|
-
}
|
|
181
|
-
return this.serializer.serializeFields(nodes, options);
|
|
182
|
-
}
|
|
183
|
-
get fields() {
|
|
184
|
-
return this.getFieldStates();
|
|
185
|
-
}
|
|
186
|
-
get serializationMode() {
|
|
187
|
-
return this.options.serializationMode ?? "native";
|
|
188
|
-
}
|
|
189
|
-
get preservedValues() {
|
|
190
|
-
if (this.options.preserveUnmountedValues && this.form && !this.isSyncingFields) {
|
|
191
|
-
this.syncFields();
|
|
192
|
-
}
|
|
193
|
-
return Object.assign(Object.create(null), this.preservedValuesStore);
|
|
194
|
-
}
|
|
195
|
-
extractCurrentValues() {
|
|
196
|
-
if (!this.form) {
|
|
197
|
-
if (this.options.preserveUnmountedValues) {
|
|
198
|
-
let res = { ...this.preservedValuesStore };
|
|
199
|
-
if (this.options.nesting === "nested") {
|
|
200
|
-
res = toNestedValues(res);
|
|
201
|
-
}
|
|
202
|
-
return res;
|
|
203
|
-
}
|
|
204
|
-
return {};
|
|
205
|
-
}
|
|
206
|
-
const serializeOpts = {
|
|
207
|
-
...this.options.serializeFieldsOptions,
|
|
208
|
-
...(this.options.nesting ? { nesting: this.options.nesting } : {}),
|
|
209
|
-
};
|
|
210
|
-
let values;
|
|
211
|
-
if (this.serializationMode === "typed") {
|
|
212
|
-
values = this.serializeFields(serializeOpts);
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
values = this.serialize();
|
|
216
|
-
if (this.options.nesting === "nested") {
|
|
217
|
-
values = toNestedValues(values);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return values;
|
|
221
|
-
}
|
|
222
|
-
subscribe(listener) {
|
|
223
|
-
this.listeners.add(listener);
|
|
224
|
-
return () => {
|
|
225
|
-
this.unsubscribe(listener);
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
unsubscribe(listener) {
|
|
229
|
-
this.listeners.delete(listener);
|
|
230
|
-
}
|
|
231
|
-
notify(event) {
|
|
232
|
-
for (const listener of this.listeners) {
|
|
233
|
-
listener(event);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
emitFieldEvent(event) {
|
|
237
|
-
this.notify(event);
|
|
238
|
-
}
|
|
239
|
-
emitValidationEvent(event) {
|
|
240
|
-
this.notify(event);
|
|
241
|
-
}
|
|
242
|
-
emitFormEvent(event) {
|
|
243
|
-
this.notify(event);
|
|
244
|
-
}
|
|
245
|
-
submit(event) {
|
|
246
|
-
return this.processSubmitFlow(event);
|
|
247
|
-
}
|
|
248
|
-
async validate() {
|
|
249
|
-
const validation = await this.runValidation(this.extractCurrentValues());
|
|
250
|
-
return validation.result;
|
|
251
|
-
}
|
|
252
|
-
async validateFields(fieldNames) {
|
|
253
|
-
if (!this.form) {
|
|
254
|
-
throw new Error("Runtime is not mounted.");
|
|
255
|
-
}
|
|
256
|
-
this.clearDebounceTimers();
|
|
257
|
-
this.abortAllValidations();
|
|
258
|
-
// Clear prior errors on targeted fields before running validation
|
|
259
|
-
for (const name of fieldNames) {
|
|
260
|
-
delete this.errors[name];
|
|
261
|
-
const normalized = normalizePath(name);
|
|
262
|
-
if (this.errors[normalized]) {
|
|
263
|
-
delete this.errors[normalized];
|
|
264
|
-
}
|
|
265
|
-
const node = this.registry.get(name);
|
|
266
|
-
node?.setError(undefined);
|
|
267
|
-
}
|
|
268
|
-
this.applyNativeErrors();
|
|
269
|
-
const values = this.extractCurrentValues();
|
|
270
|
-
const normalizedTargetNames = new Set(fieldNames.map(normalizePath));
|
|
271
|
-
const controller = new AbortController();
|
|
272
|
-
this.validationController = controller;
|
|
273
|
-
this.validating = true;
|
|
274
|
-
this.emitValidationEvent({
|
|
275
|
-
type: "validation:start",
|
|
276
|
-
values,
|
|
277
|
-
});
|
|
278
|
-
const context = {
|
|
279
|
-
form: this.form,
|
|
280
|
-
signal: controller.signal,
|
|
281
|
-
fields: this.getValidationFields(),
|
|
282
|
-
};
|
|
283
|
-
const result = await this.pipeline.validate(values, context);
|
|
284
|
-
if (this.validationController === controller) {
|
|
285
|
-
this.validating = false;
|
|
286
|
-
}
|
|
287
|
-
if (controller.signal.aborted) {
|
|
288
|
-
return {
|
|
289
|
-
success: false,
|
|
290
|
-
issues: [],
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
// Filter issues strictly to requested field names
|
|
294
|
-
const filteredIssues = [];
|
|
295
|
-
if (!result.success && result.issues) {
|
|
296
|
-
for (const issue of result.issues) {
|
|
297
|
-
if (normalizedTargetNames.has(normalizePath(issue.name))) {
|
|
298
|
-
filteredIssues.push(issue);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
// Update errors for the targeted fields
|
|
303
|
-
for (const name of fieldNames) {
|
|
304
|
-
const issue = filteredIssues.find((iss) => normalizePath(iss.name) === normalizePath(name));
|
|
305
|
-
if (issue) {
|
|
306
|
-
this.errors[name] = issue.message;
|
|
307
|
-
const node = this.registry.get(name);
|
|
308
|
-
node?.setError(issue.message);
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
delete this.errors[name];
|
|
312
|
-
const normalized = normalizePath(name);
|
|
313
|
-
if (this.errors[normalized]) {
|
|
314
|
-
delete this.errors[normalized];
|
|
315
|
-
}
|
|
316
|
-
const node = this.registry.get(name);
|
|
317
|
-
node?.setError(undefined);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
this.applyNativeErrors();
|
|
321
|
-
// Two-tier async validation for requested fields that passed sync validation
|
|
322
|
-
if (this.options.asyncValidators) {
|
|
323
|
-
const asyncPromises = [];
|
|
324
|
-
const flat = (this.serializationMode === "typed" ? this.serialize() : values);
|
|
325
|
-
for (const name of fieldNames) {
|
|
326
|
-
const issue = filteredIssues.find((iss) => normalizePath(iss.name) === normalizePath(name));
|
|
327
|
-
if (issue)
|
|
328
|
-
continue; // Skip async check if sync check failed
|
|
329
|
-
const asyncValidator = this.getAsyncValidator(name);
|
|
330
|
-
if (!asyncValidator)
|
|
331
|
-
continue;
|
|
332
|
-
const node = this.registry.get(name);
|
|
333
|
-
node?.setValidating(true);
|
|
334
|
-
const rawValue = values[name] ?? flat[name];
|
|
335
|
-
asyncPromises.push((async () => {
|
|
336
|
-
try {
|
|
337
|
-
const err = await asyncValidator(rawValue, {
|
|
338
|
-
form: this.form,
|
|
339
|
-
signal: controller.signal,
|
|
340
|
-
});
|
|
341
|
-
if (err) {
|
|
342
|
-
filteredIssues.push({ name, message: err });
|
|
343
|
-
this.errors[name] = err;
|
|
344
|
-
node?.setError(err);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
catch (error) {
|
|
348
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
349
|
-
filteredIssues.push({ name, message: msg });
|
|
350
|
-
this.errors[name] = msg;
|
|
351
|
-
node?.setError(msg);
|
|
352
|
-
}
|
|
353
|
-
finally {
|
|
354
|
-
node?.setValidating(false);
|
|
355
|
-
}
|
|
356
|
-
})());
|
|
357
|
-
}
|
|
358
|
-
if (asyncPromises.length > 0) {
|
|
359
|
-
await Promise.all(asyncPromises);
|
|
360
|
-
this.applyNativeErrors();
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
this.emitValidationEvent({
|
|
364
|
-
type: "validation:end",
|
|
365
|
-
errors: { ...this.errors },
|
|
366
|
-
values,
|
|
367
|
-
});
|
|
368
|
-
if (filteredIssues.length > 0) {
|
|
369
|
-
return {
|
|
370
|
-
success: false,
|
|
371
|
-
issues: filteredIssues,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
return {
|
|
375
|
-
success: true,
|
|
376
|
-
values: values,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
handleSubmit = (event) => {
|
|
380
|
-
event.preventDefault();
|
|
381
|
-
void this.processSubmitFlow(event);
|
|
382
|
-
};
|
|
383
|
-
async processSubmitFlow(event) {
|
|
384
|
-
if (!this.form) {
|
|
385
|
-
throw new Error("Runtime is not mounted.");
|
|
386
|
-
}
|
|
387
|
-
this.clearDebounceTimers();
|
|
388
|
-
this.abortAllValidations();
|
|
389
|
-
const values = this.extractCurrentValues();
|
|
390
|
-
// lifecycle: mark fields as submitted/touched
|
|
391
|
-
for (const field of this.registry.values()) {
|
|
392
|
-
field.handleSubmit();
|
|
393
|
-
this.emitFieldEvent({
|
|
394
|
-
type: "field:submit",
|
|
395
|
-
field: field.name,
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
const validation = await this.runValidation(values);
|
|
399
|
-
const isStaleValidation = this.validationController !== undefined &&
|
|
400
|
-
validation.signal !== this.validationController.signal;
|
|
401
|
-
if (validation.aborted || isStaleValidation) {
|
|
402
|
-
return validation.result;
|
|
403
|
-
}
|
|
404
|
-
if (!validation.result.success) {
|
|
405
|
-
this.options.onInvalid?.(this.errors, values, event);
|
|
406
|
-
this.emitFormEvent({
|
|
407
|
-
type: "form:invalid",
|
|
408
|
-
errors: this.errors,
|
|
409
|
-
values,
|
|
410
|
-
});
|
|
411
|
-
return validation.result;
|
|
412
|
-
}
|
|
413
|
-
// Step 2: Two-tier validation. Execute field-level async validators on submit
|
|
414
|
-
if (this.options.asyncValidators &&
|
|
415
|
-
Object.keys(this.options.asyncValidators).length > 0) {
|
|
416
|
-
const asyncIssues = [];
|
|
417
|
-
const flat = (this.serializationMode === "typed" ? this.serialize() : values);
|
|
418
|
-
const promises = [];
|
|
419
|
-
for (const [name, validator] of Object.entries(this.options.asyncValidators)) {
|
|
420
|
-
const rawValue = values[name] ?? flat[name];
|
|
421
|
-
const fieldNode = this.registry.get(name);
|
|
422
|
-
if (fieldNode) {
|
|
423
|
-
fieldNode.setValidating(true);
|
|
424
|
-
}
|
|
425
|
-
promises.push((async () => {
|
|
426
|
-
try {
|
|
427
|
-
const err = await validator(rawValue, {
|
|
428
|
-
form: this.form,
|
|
429
|
-
signal: validation.signal,
|
|
430
|
-
});
|
|
431
|
-
if (err) {
|
|
432
|
-
asyncIssues.push({ name, message: err });
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
catch (error) {
|
|
436
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
437
|
-
asyncIssues.push({ name, message: msg });
|
|
438
|
-
}
|
|
439
|
-
finally {
|
|
440
|
-
fieldNode?.setValidating(false);
|
|
441
|
-
}
|
|
442
|
-
})());
|
|
443
|
-
}
|
|
444
|
-
await Promise.all(promises);
|
|
445
|
-
if (asyncIssues.length > 0) {
|
|
446
|
-
for (const issue of asyncIssues) {
|
|
447
|
-
this.errors[issue.name] = issue.message;
|
|
448
|
-
const node = this.registry.get(issue.name);
|
|
449
|
-
node?.setError(issue.message);
|
|
450
|
-
}
|
|
451
|
-
this.applyNativeErrors();
|
|
452
|
-
this.options.onInvalid?.(this.errors, values, event);
|
|
453
|
-
this.emitFormEvent({
|
|
454
|
-
type: "form:invalid",
|
|
455
|
-
errors: this.errors,
|
|
456
|
-
values,
|
|
457
|
-
});
|
|
458
|
-
return {
|
|
459
|
-
success: false,
|
|
460
|
-
issues: asyncIssues,
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
this.submitting = true;
|
|
465
|
-
this.emitFormEvent({
|
|
466
|
-
type: "form:submit",
|
|
467
|
-
values: validation.result.values,
|
|
468
|
-
});
|
|
469
|
-
try {
|
|
470
|
-
await this.options.onSubmit?.(validation.result.values, event);
|
|
471
|
-
}
|
|
472
|
-
finally {
|
|
473
|
-
this.submitting = false;
|
|
474
|
-
}
|
|
475
|
-
return validation.result;
|
|
476
|
-
}
|
|
477
|
-
async runValidation(values) {
|
|
478
|
-
if (!this.form) {
|
|
479
|
-
throw new Error("Runtime is not mounted.");
|
|
480
|
-
}
|
|
481
|
-
this.validationController?.abort();
|
|
482
|
-
const controller = new AbortController();
|
|
483
|
-
this.validationController = controller;
|
|
484
|
-
this.validating = true;
|
|
485
|
-
this.emitValidationEvent({
|
|
486
|
-
type: "validation:start",
|
|
487
|
-
values,
|
|
488
|
-
});
|
|
489
|
-
this.errors = {};
|
|
490
|
-
for (const field of this.registry.values()) {
|
|
491
|
-
field.setError(this.errors[field.name]);
|
|
492
|
-
}
|
|
493
|
-
this.applyNativeErrors();
|
|
494
|
-
const result = await this.pipeline.validate(values, {
|
|
495
|
-
form: this.form,
|
|
496
|
-
signal: controller.signal,
|
|
497
|
-
fields: this.getValidationFields(),
|
|
498
|
-
});
|
|
499
|
-
if (!controller.signal.aborted) {
|
|
500
|
-
this.applyValidationResult(result, values);
|
|
501
|
-
if (this.validationController === controller) {
|
|
502
|
-
this.validating = false;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
return {
|
|
506
|
-
result,
|
|
507
|
-
signal: controller.signal,
|
|
508
|
-
aborted: controller.signal.aborted,
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
handleFieldUpdate = (event) => {
|
|
512
|
-
const element = toNativeFormElement(event.target);
|
|
513
|
-
if (!element?.name || !this.form)
|
|
514
|
-
return;
|
|
515
|
-
if (event.type === "input") {
|
|
516
|
-
this.processInputFlow(element);
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
if (event.type === "change") {
|
|
520
|
-
this.processChangeFlow(element);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
this.processUpdateFlow(element);
|
|
524
|
-
};
|
|
525
|
-
processInputFlow(element) {
|
|
526
|
-
const snapshot = this.prepareFieldFlow(element);
|
|
527
|
-
if (!snapshot) {
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
const { node, values, value } = snapshot;
|
|
531
|
-
node.handleInput(value);
|
|
532
|
-
const mode = this.options.mode ?? "onChange";
|
|
533
|
-
const shouldValidate = mode === "onChange" ||
|
|
534
|
-
mode === "all" ||
|
|
535
|
-
(mode === "onTouched" && node.isTouched);
|
|
536
|
-
if (shouldValidate) {
|
|
537
|
-
this.triggerFieldValidation(node, values);
|
|
538
|
-
}
|
|
539
|
-
this.emitFieldEvent({
|
|
540
|
-
type: "field:input",
|
|
541
|
-
field: element.name,
|
|
542
|
-
value,
|
|
543
|
-
values,
|
|
544
|
-
});
|
|
545
|
-
// Normalized generic update signal for any value change source.
|
|
546
|
-
this.emitFieldEvent({
|
|
547
|
-
type: "field:update",
|
|
548
|
-
field: element.name,
|
|
549
|
-
value,
|
|
550
|
-
values,
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
processChangeFlow(element) {
|
|
554
|
-
const snapshot = this.prepareFieldFlow(element);
|
|
555
|
-
if (!snapshot) {
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
const { node, values, value } = snapshot;
|
|
559
|
-
node.handleChange(value);
|
|
560
|
-
const mode = this.options.mode ?? "onChange";
|
|
561
|
-
const shouldValidate = mode === "onChange" ||
|
|
562
|
-
mode === "all" ||
|
|
563
|
-
(mode === "onTouched" && node.isTouched);
|
|
564
|
-
if (shouldValidate) {
|
|
565
|
-
this.triggerFieldValidation(node, values);
|
|
566
|
-
}
|
|
567
|
-
this.emitFieldEvent({
|
|
568
|
-
type: "field:change",
|
|
569
|
-
field: element.name,
|
|
570
|
-
value,
|
|
571
|
-
values,
|
|
572
|
-
});
|
|
573
|
-
// Normalized generic update signal for any value change source.
|
|
574
|
-
this.emitFieldEvent({
|
|
575
|
-
type: "field:update",
|
|
576
|
-
field: element.name,
|
|
577
|
-
value,
|
|
578
|
-
values,
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
processUpdateFlow(element) {
|
|
582
|
-
const snapshot = this.prepareFieldFlow(element);
|
|
583
|
-
if (!snapshot) {
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
const { node, values, value } = snapshot;
|
|
587
|
-
node.updateValue(value);
|
|
588
|
-
if (this.options.preserveUnmountedValues) {
|
|
589
|
-
if (element.name !== "__proto__" &&
|
|
590
|
-
element.name !== "constructor" &&
|
|
591
|
-
element.name !== "prototype") {
|
|
592
|
-
if (value !== undefined) {
|
|
593
|
-
this.preservedValuesStore[element.name] = value;
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
delete this.preservedValuesStore[element.name];
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
const mode = this.options.mode ?? "onChange";
|
|
601
|
-
const shouldValidate = mode === "onChange" ||
|
|
602
|
-
mode === "all" ||
|
|
603
|
-
(mode === "onTouched" && node.isTouched);
|
|
604
|
-
if (shouldValidate) {
|
|
605
|
-
this.triggerFieldValidation(node, values);
|
|
606
|
-
}
|
|
607
|
-
this.emitFieldEvent({
|
|
608
|
-
type: "field:update",
|
|
609
|
-
field: element.name,
|
|
610
|
-
value,
|
|
611
|
-
values,
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
prepareFieldFlow(element) {
|
|
615
|
-
this.ensureField(element);
|
|
616
|
-
const values = this.extractCurrentValues();
|
|
617
|
-
const value = values[element.name];
|
|
618
|
-
const node = this.registry.get(element.name);
|
|
619
|
-
if (!node) {
|
|
620
|
-
return undefined;
|
|
621
|
-
}
|
|
622
|
-
return { node, values, value };
|
|
623
|
-
}
|
|
624
|
-
triggerFieldValidation(node, values, immediate = false) {
|
|
625
|
-
const debounceMs = this.options.debounceMs ?? 0;
|
|
626
|
-
if (!immediate && debounceMs > 0) {
|
|
627
|
-
const existing = this.debounceTimers.get(node.name);
|
|
628
|
-
if (existing) {
|
|
629
|
-
clearTimeout(existing);
|
|
630
|
-
}
|
|
631
|
-
const timer = setTimeout(() => {
|
|
632
|
-
this.debounceTimers.delete(node.name);
|
|
633
|
-
void this.executeFieldValidation(node, values);
|
|
634
|
-
}, debounceMs);
|
|
635
|
-
this.debounceTimers.set(node.name, timer);
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
const existing = this.debounceTimers.get(node.name);
|
|
639
|
-
if (existing) {
|
|
640
|
-
clearTimeout(existing);
|
|
641
|
-
this.debounceTimers.delete(node.name);
|
|
642
|
-
}
|
|
643
|
-
void this.executeFieldValidation(node, values);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
async executeFieldValidation(node, values) {
|
|
647
|
-
const fieldName = node.name;
|
|
648
|
-
// Abort previous in-flight validation for this field
|
|
649
|
-
const previousController = this.fieldValidationControllers.get(fieldName);
|
|
650
|
-
if (previousController) {
|
|
651
|
-
previousController.abort();
|
|
652
|
-
this.fieldValidationControllers.delete(fieldName);
|
|
653
|
-
}
|
|
654
|
-
const fieldController = new AbortController();
|
|
655
|
-
this.fieldValidationControllers.set(fieldName, fieldController);
|
|
656
|
-
try {
|
|
657
|
-
const validation = await this.runFieldValidation(node, values);
|
|
658
|
-
const isStaleValidation = this.validationController !== undefined &&
|
|
659
|
-
validation.signal !== this.validationController.signal;
|
|
660
|
-
if (validation.aborted ||
|
|
661
|
-
fieldController.signal.aborted ||
|
|
662
|
-
isStaleValidation) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
if (!validation.result.success) {
|
|
666
|
-
this.options.onInvalid?.(this.errors, values);
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
// Step 2: Two-tier async validation.
|
|
670
|
-
// If sync validation succeeded and an asyncValidator is configured for this field:
|
|
671
|
-
const asyncValidator = this.getAsyncValidator(fieldName);
|
|
672
|
-
if (asyncValidator && !fieldController.signal.aborted) {
|
|
673
|
-
node.setValidating(true);
|
|
674
|
-
this.emitFieldEvent({
|
|
675
|
-
type: "field:update",
|
|
676
|
-
field: fieldName,
|
|
677
|
-
value: values[fieldName],
|
|
678
|
-
values,
|
|
679
|
-
});
|
|
680
|
-
try {
|
|
681
|
-
const context = {
|
|
682
|
-
form: this.form,
|
|
683
|
-
signal: fieldController.signal,
|
|
684
|
-
};
|
|
685
|
-
const rawValue = values[fieldName];
|
|
686
|
-
const asyncError = await asyncValidator(rawValue, context);
|
|
687
|
-
if (!fieldController.signal.aborted) {
|
|
688
|
-
if (asyncError) {
|
|
689
|
-
this.errors[fieldName] = asyncError;
|
|
690
|
-
node.setError(asyncError);
|
|
691
|
-
this.options.onInvalid?.(this.errors, values);
|
|
692
|
-
}
|
|
693
|
-
else if (this.errors[fieldName]) {
|
|
694
|
-
delete this.errors[fieldName];
|
|
695
|
-
node.setError(undefined);
|
|
696
|
-
}
|
|
697
|
-
this.applyNativeErrors();
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
catch (error) {
|
|
701
|
-
if (!fieldController.signal.aborted) {
|
|
702
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
703
|
-
this.errors[fieldName] = message;
|
|
704
|
-
node.setError(message);
|
|
705
|
-
this.applyNativeErrors();
|
|
706
|
-
this.options.onInvalid?.(this.errors, values);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
finally {
|
|
710
|
-
if (!fieldController.signal.aborted) {
|
|
711
|
-
node.setValidating(false);
|
|
712
|
-
this.emitValidationEvent({
|
|
713
|
-
type: "validation:end",
|
|
714
|
-
errors: { ...this.errors },
|
|
715
|
-
values,
|
|
716
|
-
});
|
|
717
|
-
this.emitFieldEvent({
|
|
718
|
-
type: "field:update",
|
|
719
|
-
field: fieldName,
|
|
720
|
-
value: values[fieldName],
|
|
721
|
-
values,
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
finally {
|
|
728
|
-
if (this.fieldValidationControllers.get(fieldName) === fieldController) {
|
|
729
|
-
this.fieldValidationControllers.delete(fieldName);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
handleFieldBlur = (event) => {
|
|
734
|
-
const element = toNativeFormElement(event.target);
|
|
735
|
-
if (!element?.name || !this.form)
|
|
736
|
-
return;
|
|
737
|
-
this.ensureField(element);
|
|
738
|
-
const values = this.extractCurrentValues();
|
|
739
|
-
const value = values[element.name];
|
|
740
|
-
const node = this.registry.get(element.name);
|
|
741
|
-
node?.handleBlur();
|
|
742
|
-
const mode = this.options.mode ?? "onChange";
|
|
743
|
-
const hasPendingDebounce = this.debounceTimers.has(element.name);
|
|
744
|
-
const shouldValidate = mode === "onBlur" ||
|
|
745
|
-
mode === "onTouched" ||
|
|
746
|
-
mode === "all" ||
|
|
747
|
-
hasPendingDebounce;
|
|
748
|
-
if (shouldValidate && node) {
|
|
749
|
-
this.triggerFieldValidation(node, values, true);
|
|
750
|
-
}
|
|
751
|
-
this.emitFieldEvent({
|
|
752
|
-
type: "field:blur",
|
|
753
|
-
field: element.name,
|
|
754
|
-
value,
|
|
755
|
-
values,
|
|
756
|
-
});
|
|
757
|
-
};
|
|
758
|
-
async runFieldValidation(field, values) {
|
|
759
|
-
if (!this.form) {
|
|
760
|
-
throw new Error("Runtime is not mounted.");
|
|
761
|
-
}
|
|
762
|
-
this.validationController?.abort();
|
|
763
|
-
const controller = new AbortController();
|
|
764
|
-
this.validationController = controller;
|
|
765
|
-
this.validating = true;
|
|
766
|
-
this.emitValidationEvent({
|
|
767
|
-
type: "validation:start",
|
|
768
|
-
values,
|
|
769
|
-
});
|
|
770
|
-
const context = {
|
|
771
|
-
form: this.form,
|
|
772
|
-
signal: controller.signal,
|
|
773
|
-
fields: this.getValidationFields(),
|
|
774
|
-
};
|
|
775
|
-
const activeFieldMeta = this.toValidationField(field);
|
|
776
|
-
const supportsFieldScopedValidation = this.pipeline.capabilities?.supportsFieldScopedValidation ?? true;
|
|
777
|
-
const result = supportsFieldScopedValidation
|
|
778
|
-
? await this.pipeline.validateField(activeFieldMeta, values, context)
|
|
779
|
-
: await this.pipeline.validate(values, context);
|
|
780
|
-
if (!controller.signal.aborted) {
|
|
781
|
-
this.applyFieldValidationResult(field, result, values);
|
|
782
|
-
if (this.validationController === controller) {
|
|
783
|
-
this.validating = false;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
return {
|
|
787
|
-
result,
|
|
788
|
-
signal: controller.signal,
|
|
789
|
-
aborted: controller.signal.aborted,
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
handleReset = () => {
|
|
793
|
-
this.clearDebounceTimers();
|
|
794
|
-
this.abortAllValidations();
|
|
795
|
-
queueMicrotask(() => {
|
|
796
|
-
if (!this.form)
|
|
797
|
-
return;
|
|
798
|
-
const values = this.extractCurrentValues();
|
|
799
|
-
this.processResetFlow(values);
|
|
800
|
-
});
|
|
801
|
-
};
|
|
802
|
-
processResetFlow(values) {
|
|
803
|
-
for (const field of this.registry.values()) {
|
|
804
|
-
field.handleReset(values[field.name]);
|
|
805
|
-
this.emitFieldEvent({
|
|
806
|
-
type: "field:reset",
|
|
807
|
-
field: field.name,
|
|
808
|
-
value: values[field.name],
|
|
809
|
-
values,
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
this.errors = {};
|
|
813
|
-
this.preservedValuesStore = Object.create(null);
|
|
814
|
-
this.applyNativeErrors();
|
|
815
|
-
this.emitFormEvent({
|
|
816
|
-
type: "form:reset",
|
|
817
|
-
values,
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
getFieldState(name) {
|
|
821
|
-
let node = this.registry.get(name);
|
|
822
|
-
if (!node) {
|
|
823
|
-
const normalized = normalizePath(name);
|
|
824
|
-
for (const field of this.registry.values()) {
|
|
825
|
-
if (normalizePath(field.name) === normalized) {
|
|
826
|
-
node = field;
|
|
827
|
-
break;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
return node?.getState();
|
|
832
|
-
}
|
|
833
|
-
getFieldStates() {
|
|
834
|
-
return this.registry.values().map((f) => f.getState());
|
|
835
|
-
}
|
|
836
|
-
setOptions(options) {
|
|
837
|
-
Object.assign(this.options, options);
|
|
838
|
-
if (options.schema !== undefined ||
|
|
839
|
-
options.validator !== undefined ||
|
|
840
|
-
options.pipeline !== undefined) {
|
|
841
|
-
this.pipeline =
|
|
842
|
-
options.pipeline ??
|
|
843
|
-
createValidationPipeline({
|
|
844
|
-
schema: options.schema,
|
|
845
|
-
validator: options.validator,
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
removeArrayItem(arrayName, index) {
|
|
850
|
-
const nextErrors = {};
|
|
851
|
-
let changed = false;
|
|
852
|
-
for (const [path, error] of Object.entries(this.errors)) {
|
|
853
|
-
const match = matchArrayItemPath(path, arrayName);
|
|
854
|
-
if (!match) {
|
|
855
|
-
nextErrors[path] = error;
|
|
856
|
-
continue;
|
|
857
|
-
}
|
|
858
|
-
if (match.index === index) {
|
|
859
|
-
changed = true;
|
|
860
|
-
continue;
|
|
861
|
-
}
|
|
862
|
-
if (match.index > index) {
|
|
863
|
-
const shiftedPath = shiftArrayItemIndex(path, arrayName, index + 1, -1);
|
|
864
|
-
nextErrors[shiftedPath] = error;
|
|
865
|
-
changed = true;
|
|
866
|
-
}
|
|
867
|
-
else {
|
|
868
|
-
nextErrors[path] = error;
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
if (changed) {
|
|
872
|
-
this.errors = nextErrors;
|
|
873
|
-
this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
|
|
874
|
-
this.emitValidationEvent({
|
|
875
|
-
type: "validation:end",
|
|
876
|
-
errors: { ...this.errors },
|
|
877
|
-
values: this.extractCurrentValues(),
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
if (this.options.preserveUnmountedValues) {
|
|
881
|
-
const nextPreserved = {};
|
|
882
|
-
let pChanged = false;
|
|
883
|
-
for (const [path, val] of Object.entries(this.preservedValuesStore)) {
|
|
884
|
-
const match = matchArrayItemPath(path, arrayName);
|
|
885
|
-
if (!match) {
|
|
886
|
-
nextPreserved[path] = val;
|
|
887
|
-
continue;
|
|
888
|
-
}
|
|
889
|
-
if (match.index === index) {
|
|
890
|
-
pChanged = true;
|
|
891
|
-
continue;
|
|
892
|
-
}
|
|
893
|
-
if (match.index > index) {
|
|
894
|
-
const shiftedPath = shiftArrayItemIndex(path, arrayName, index + 1, -1);
|
|
895
|
-
nextPreserved[shiftedPath] = val;
|
|
896
|
-
pChanged = true;
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
nextPreserved[path] = val;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
if (pChanged) {
|
|
903
|
-
this.preservedValuesStore = nextPreserved;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
swapArrayItems(arrayName, indexA, indexB) {
|
|
908
|
-
if (indexA === indexB)
|
|
909
|
-
return;
|
|
910
|
-
const nextErrors = {};
|
|
911
|
-
let changed = false;
|
|
912
|
-
for (const [path, error] of Object.entries(this.errors)) {
|
|
913
|
-
const match = matchArrayItemPath(path, arrayName);
|
|
914
|
-
if (!match) {
|
|
915
|
-
nextErrors[path] = error;
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
if (match.index === indexA) {
|
|
919
|
-
const newPath = remapArrayItemIndex(path, arrayName, indexA, indexB);
|
|
920
|
-
nextErrors[newPath] = error;
|
|
921
|
-
changed = true;
|
|
922
|
-
}
|
|
923
|
-
else if (match.index === indexB) {
|
|
924
|
-
const newPath = remapArrayItemIndex(path, arrayName, indexB, indexA);
|
|
925
|
-
nextErrors[newPath] = error;
|
|
926
|
-
changed = true;
|
|
927
|
-
}
|
|
928
|
-
else {
|
|
929
|
-
nextErrors[path] = error;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
if (changed) {
|
|
933
|
-
this.errors = nextErrors;
|
|
934
|
-
this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
|
|
935
|
-
this.emitValidationEvent({
|
|
936
|
-
type: "validation:end",
|
|
937
|
-
errors: { ...this.errors },
|
|
938
|
-
values: this.extractCurrentValues(),
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
if (this.options.preserveUnmountedValues) {
|
|
942
|
-
const nextPreserved = {};
|
|
943
|
-
let pChanged = false;
|
|
944
|
-
for (const [path, val] of Object.entries(this.preservedValuesStore)) {
|
|
945
|
-
const match = matchArrayItemPath(path, arrayName);
|
|
946
|
-
if (!match) {
|
|
947
|
-
nextPreserved[path] = val;
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
if (match.index === indexA) {
|
|
951
|
-
const newPath = remapArrayItemIndex(path, arrayName, indexA, indexB);
|
|
952
|
-
nextPreserved[newPath] = val;
|
|
953
|
-
pChanged = true;
|
|
954
|
-
}
|
|
955
|
-
else if (match.index === indexB) {
|
|
956
|
-
const newPath = remapArrayItemIndex(path, arrayName, indexB, indexA);
|
|
957
|
-
nextPreserved[newPath] = val;
|
|
958
|
-
pChanged = true;
|
|
959
|
-
}
|
|
960
|
-
else {
|
|
961
|
-
nextPreserved[path] = val;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
if (pChanged) {
|
|
965
|
-
this.preservedValuesStore = nextPreserved;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
moveArrayItem(arrayName, fromIndex, toIndex) {
|
|
970
|
-
if (fromIndex === toIndex)
|
|
971
|
-
return;
|
|
972
|
-
const nextErrors = {};
|
|
973
|
-
let changed = false;
|
|
974
|
-
for (const [path, error] of Object.entries(this.errors)) {
|
|
975
|
-
const match = matchArrayItemPath(path, arrayName);
|
|
976
|
-
if (!match) {
|
|
977
|
-
nextErrors[path] = error;
|
|
978
|
-
continue;
|
|
979
|
-
}
|
|
980
|
-
const idx = match.index;
|
|
981
|
-
if (idx === fromIndex) {
|
|
982
|
-
const newPath = remapArrayItemIndex(path, arrayName, fromIndex, toIndex);
|
|
983
|
-
nextErrors[newPath] = error;
|
|
984
|
-
changed = true;
|
|
985
|
-
}
|
|
986
|
-
else if (fromIndex < toIndex && idx > fromIndex && idx <= toIndex) {
|
|
987
|
-
const newPath = remapArrayItemIndex(path, arrayName, idx, idx - 1);
|
|
988
|
-
nextErrors[newPath] = error;
|
|
989
|
-
changed = true;
|
|
990
|
-
}
|
|
991
|
-
else if (fromIndex > toIndex && idx >= toIndex && idx < fromIndex) {
|
|
992
|
-
const newPath = remapArrayItemIndex(path, arrayName, idx, idx + 1);
|
|
993
|
-
nextErrors[newPath] = error;
|
|
994
|
-
changed = true;
|
|
995
|
-
}
|
|
996
|
-
else {
|
|
997
|
-
nextErrors[path] = error;
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
if (changed) {
|
|
1001
|
-
this.errors = nextErrors;
|
|
1002
|
-
this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
|
|
1003
|
-
this.emitValidationEvent({
|
|
1004
|
-
type: "validation:end",
|
|
1005
|
-
errors: { ...this.errors },
|
|
1006
|
-
values: this.extractCurrentValues(),
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
if (this.options.preserveUnmountedValues) {
|
|
1010
|
-
const nextPreserved = {};
|
|
1011
|
-
let pChanged = false;
|
|
1012
|
-
for (const [path, val] of Object.entries(this.preservedValuesStore)) {
|
|
1013
|
-
const match = matchArrayItemPath(path, arrayName);
|
|
1014
|
-
if (!match) {
|
|
1015
|
-
nextPreserved[path] = val;
|
|
1016
|
-
continue;
|
|
1017
|
-
}
|
|
1018
|
-
const idx = match.index;
|
|
1019
|
-
if (idx === fromIndex) {
|
|
1020
|
-
const newPath = remapArrayItemIndex(path, arrayName, fromIndex, toIndex);
|
|
1021
|
-
nextPreserved[newPath] = val;
|
|
1022
|
-
pChanged = true;
|
|
1023
|
-
}
|
|
1024
|
-
else if (fromIndex < toIndex && idx > fromIndex && idx <= toIndex) {
|
|
1025
|
-
const newPath = remapArrayItemIndex(path, arrayName, idx, idx - 1);
|
|
1026
|
-
nextPreserved[newPath] = val;
|
|
1027
|
-
pChanged = true;
|
|
1028
|
-
}
|
|
1029
|
-
else if (fromIndex > toIndex && idx >= toIndex && idx < fromIndex) {
|
|
1030
|
-
const newPath = remapArrayItemIndex(path, arrayName, idx, idx + 1);
|
|
1031
|
-
nextPreserved[newPath] = val;
|
|
1032
|
-
pChanged = true;
|
|
1033
|
-
}
|
|
1034
|
-
else {
|
|
1035
|
-
nextPreserved[path] = val;
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
if (pChanged) {
|
|
1039
|
-
this.preservedValuesStore = nextPreserved;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
insertArrayItem(arrayName, index) {
|
|
1044
|
-
const nextErrors = {};
|
|
1045
|
-
let changed = false;
|
|
1046
|
-
for (const [path, error] of Object.entries(this.errors)) {
|
|
1047
|
-
const match = matchArrayItemPath(path, arrayName);
|
|
1048
|
-
if (!match) {
|
|
1049
|
-
nextErrors[path] = error;
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
if (match.index >= index) {
|
|
1053
|
-
const shiftedPath = shiftArrayItemIndex(path, arrayName, index, 1);
|
|
1054
|
-
nextErrors[shiftedPath] = error;
|
|
1055
|
-
changed = true;
|
|
1056
|
-
}
|
|
1057
|
-
else {
|
|
1058
|
-
nextErrors[path] = error;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
if (changed) {
|
|
1062
|
-
this.errors = nextErrors;
|
|
1063
|
-
this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
|
|
1064
|
-
this.emitValidationEvent({
|
|
1065
|
-
type: "validation:end",
|
|
1066
|
-
errors: { ...this.errors },
|
|
1067
|
-
values: this.extractCurrentValues(),
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
if (this.options.preserveUnmountedValues) {
|
|
1071
|
-
const nextPreserved = {};
|
|
1072
|
-
let pChanged = false;
|
|
1073
|
-
for (const [path, val] of Object.entries(this.preservedValuesStore)) {
|
|
1074
|
-
const match = matchArrayItemPath(path, arrayName);
|
|
1075
|
-
if (!match) {
|
|
1076
|
-
nextPreserved[path] = val;
|
|
1077
|
-
continue;
|
|
1078
|
-
}
|
|
1079
|
-
if (match.index >= index) {
|
|
1080
|
-
const shiftedPath = shiftArrayItemIndex(path, arrayName, index, 1);
|
|
1081
|
-
nextPreserved[shiftedPath] = val;
|
|
1082
|
-
pChanged = true;
|
|
1083
|
-
}
|
|
1084
|
-
else {
|
|
1085
|
-
nextPreserved[path] = val;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
if (pChanged) {
|
|
1089
|
-
this.preservedValuesStore = nextPreserved;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
getFormState() {
|
|
1094
|
-
const fields = this.getFieldStates();
|
|
1095
|
-
return {
|
|
1096
|
-
dirty: fields.some((field) => field.dirty),
|
|
1097
|
-
touched: fields.some((field) => field.touched),
|
|
1098
|
-
valid: Object.keys(this.errors).length === 0,
|
|
1099
|
-
submitting: this.submitting,
|
|
1100
|
-
validating: this.validating || fields.some((field) => field.validating),
|
|
1101
|
-
errors: { ...this.errors },
|
|
1102
|
-
fields,
|
|
1103
|
-
values: this.extractCurrentValues(),
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
ensureField(element) {
|
|
1107
|
-
if (this.registry.has(element.name)) {
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
this.registry.register(new FieldNode(element));
|
|
1111
|
-
const node = this.registry.get(element.name);
|
|
1112
|
-
node.register(this.getFieldValue(element.name));
|
|
1113
|
-
}
|
|
1114
|
-
getFieldValue(name) {
|
|
1115
|
-
if (!this.form) {
|
|
1116
|
-
if (this.options.preserveUnmountedValues) {
|
|
1117
|
-
return this.preservedValuesStore[name];
|
|
1118
|
-
}
|
|
1119
|
-
return undefined;
|
|
1120
|
-
}
|
|
1121
|
-
if (this.options.preserveUnmountedValues && !this.isSyncingFields) {
|
|
1122
|
-
this.syncFields();
|
|
1123
|
-
}
|
|
1124
|
-
let node = this.registry.get(name);
|
|
1125
|
-
if (!node) {
|
|
1126
|
-
const control = this.form.elements.namedItem(name);
|
|
1127
|
-
if (control instanceof HTMLInputElement ||
|
|
1128
|
-
control instanceof HTMLSelectElement ||
|
|
1129
|
-
control instanceof HTMLTextAreaElement) {
|
|
1130
|
-
this.ensureField(control);
|
|
1131
|
-
node = this.registry.get(name);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
if (!node) {
|
|
1135
|
-
// Check for bracket/dot alias (e.g. user.profile.name vs user[profile][name])
|
|
1136
|
-
const normalizedTarget = normalizePath(name);
|
|
1137
|
-
for (const field of this.registry.values()) {
|
|
1138
|
-
if (normalizePath(field.name) === normalizedTarget) {
|
|
1139
|
-
node = field;
|
|
1140
|
-
break;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
if (!node) {
|
|
1145
|
-
if (this.options.preserveUnmountedValues) {
|
|
1146
|
-
if (name in this.preservedValuesStore) {
|
|
1147
|
-
return this.preservedValuesStore[name];
|
|
1148
|
-
}
|
|
1149
|
-
const normalizedTarget = normalizePath(name);
|
|
1150
|
-
for (const [key, val] of Object.entries(this.preservedValuesStore)) {
|
|
1151
|
-
if (normalizePath(key) === normalizedTarget) {
|
|
1152
|
-
return val;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
if (this.options.nesting === "nested") {
|
|
1157
|
-
const nested = this.extractCurrentValues();
|
|
1158
|
-
const resolved = getByPath(nested, name);
|
|
1159
|
-
if (resolved !== undefined) {
|
|
1160
|
-
return resolved;
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
return undefined;
|
|
1164
|
-
}
|
|
1165
|
-
if (node.element instanceof HTMLInputElement &&
|
|
1166
|
-
node.element.type === "radio") {
|
|
1167
|
-
const item = this.form.elements.namedItem(name);
|
|
1168
|
-
if (item) {
|
|
1169
|
-
if ("value" in item &&
|
|
1170
|
-
typeof item.value === "string" &&
|
|
1171
|
-
item.value !== "") {
|
|
1172
|
-
return item.value;
|
|
1173
|
-
}
|
|
1174
|
-
if ("length" in item) {
|
|
1175
|
-
for (const el of Array.from(item)) {
|
|
1176
|
-
if (el instanceof HTMLInputElement && el.checked) {
|
|
1177
|
-
return el.value;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
return node.element.checked ? node.element.value : undefined;
|
|
1183
|
-
}
|
|
1184
|
-
if (node.element instanceof HTMLInputElement &&
|
|
1185
|
-
node.element.type === "checkbox") {
|
|
1186
|
-
const item = this.form.elements.namedItem(name);
|
|
1187
|
-
if (item &&
|
|
1188
|
-
("length" in item || item instanceof RadioNodeList) &&
|
|
1189
|
-
item.length > 1) {
|
|
1190
|
-
const checkedValues = [];
|
|
1191
|
-
for (const el of Array.from(item)) {
|
|
1192
|
-
if (el instanceof HTMLInputElement && el.checked) {
|
|
1193
|
-
checkedValues.push(el.value || "on");
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
return checkedValues;
|
|
1197
|
-
}
|
|
1198
|
-
return node.element.checked;
|
|
1199
|
-
}
|
|
1200
|
-
return (node.value ?? undefined);
|
|
1201
|
-
}
|
|
1202
|
-
getValidationFields() {
|
|
1203
|
-
return this.registry.values().map((field) => this.toValidationField(field));
|
|
1204
|
-
}
|
|
1205
|
-
toValidationField(field) {
|
|
1206
|
-
return {
|
|
1207
|
-
name: field.name,
|
|
1208
|
-
validationMessage: field.element.validationMessage,
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
syncFields() {
|
|
1212
|
-
if (!this.form || this.isSyncingFields) {
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
this.isSyncingFields = true;
|
|
1216
|
-
try {
|
|
1217
|
-
const previousFields = this.registry.values();
|
|
1218
|
-
const previousFieldMap = new Map(previousFields.map((field) => [field.name, field]));
|
|
1219
|
-
const activeControlNames = new Set();
|
|
1220
|
-
for (const control of Array.from(this.form.elements)) {
|
|
1221
|
-
if ((control instanceof HTMLInputElement ||
|
|
1222
|
-
control instanceof HTMLSelectElement ||
|
|
1223
|
-
control instanceof HTMLTextAreaElement) &&
|
|
1224
|
-
control.name) {
|
|
1225
|
-
activeControlNames.add(control.name);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
for (const field of previousFields) {
|
|
1229
|
-
if (!activeControlNames.has(field.name)) {
|
|
1230
|
-
if (this.options.preserveUnmountedValues) {
|
|
1231
|
-
const val = field.value;
|
|
1232
|
-
if (field.name !== "__proto__" &&
|
|
1233
|
-
field.name !== "constructor" &&
|
|
1234
|
-
field.name !== "prototype") {
|
|
1235
|
-
if (val !== undefined) {
|
|
1236
|
-
this.preservedValuesStore[field.name] = val;
|
|
1237
|
-
}
|
|
1238
|
-
else {
|
|
1239
|
-
delete this.preservedValuesStore[field.name];
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
this.fieldErrorRenderer.applyErrors([field], {});
|
|
1244
|
-
delete this.errors[field.name];
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
this.discovery.discover(this.form, this.registry);
|
|
1248
|
-
const values = this.extractCurrentValues();
|
|
1249
|
-
for (const field of this.registry.values()) {
|
|
1250
|
-
if (!previousFieldMap.has(field.name) ||
|
|
1251
|
-
previousFieldMap.get(field.name) !== field) {
|
|
1252
|
-
field.register(values[field.name]);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
finally {
|
|
1257
|
-
this.isSyncingFields = false;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
handleMutation = (mutations) => {
|
|
1261
|
-
if (!mutations.some(isRuntimeMutationRelevant)) {
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
this.syncFields();
|
|
1265
|
-
};
|
|
1266
|
-
applyFieldValidationResult(targetField, result, values) {
|
|
1267
|
-
const issueMap = result.success ? {} : issuesToErrors(result.issues);
|
|
1268
|
-
const targetName = targetField.name;
|
|
1269
|
-
// 1. Update the active field being edited
|
|
1270
|
-
const targetIssue = resolveFieldError(issueMap, targetName);
|
|
1271
|
-
if (targetIssue) {
|
|
1272
|
-
this.errors[targetName] = targetIssue;
|
|
1273
|
-
targetField.setError(targetIssue);
|
|
1274
|
-
}
|
|
1275
|
-
else {
|
|
1276
|
-
delete this.errors[targetName];
|
|
1277
|
-
const normalizedTarget = normalizePath(targetName);
|
|
1278
|
-
if (this.errors[normalizedTarget]) {
|
|
1279
|
-
delete this.errors[normalizedTarget];
|
|
1280
|
-
}
|
|
1281
|
-
targetField.setError(undefined);
|
|
1282
|
-
}
|
|
1283
|
-
// 2. For other fields in the form: only update error state if they were
|
|
1284
|
-
// already touched or already had an error (e.g. cross-field validation rules).
|
|
1285
|
-
// Untouched fields must NOT show premature errors while typing elsewhere.
|
|
1286
|
-
for (const otherField of this.registry.values()) {
|
|
1287
|
-
if (otherField === targetField)
|
|
1288
|
-
continue;
|
|
1289
|
-
const name = otherField.name;
|
|
1290
|
-
const hadError = Boolean(resolveFieldError(this.errors, name));
|
|
1291
|
-
const isTouched = otherField.isTouched;
|
|
1292
|
-
if (hadError || isTouched) {
|
|
1293
|
-
const issue = resolveFieldError(issueMap, name);
|
|
1294
|
-
if (issue) {
|
|
1295
|
-
this.errors[name] = issue;
|
|
1296
|
-
otherField.setError(issue);
|
|
1297
|
-
}
|
|
1298
|
-
else {
|
|
1299
|
-
delete this.errors[name];
|
|
1300
|
-
const normalizedOther = normalizePath(name);
|
|
1301
|
-
if (this.errors[normalizedOther]) {
|
|
1302
|
-
delete this.errors[normalizedOther];
|
|
1303
|
-
}
|
|
1304
|
-
otherField.setError(undefined);
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
this.fieldErrorRenderer.normalizeFields(this.registry.values());
|
|
1309
|
-
this.applyNativeErrors();
|
|
1310
|
-
this.emitValidationEvent({
|
|
1311
|
-
type: "validation:end",
|
|
1312
|
-
errors: { ...this.errors },
|
|
1313
|
-
values,
|
|
1314
|
-
});
|
|
1315
|
-
}
|
|
1316
|
-
applyValidationResult(result, values) {
|
|
1317
|
-
this.fieldErrorRenderer.normalizeFields(this.registry.values());
|
|
1318
|
-
this.errors = result.success ? {} : issuesToErrors(result.issues);
|
|
1319
|
-
for (const field of this.registry.values()) {
|
|
1320
|
-
const error = resolveFieldError(this.errors, field.name);
|
|
1321
|
-
if (error && this.errors[field.name] === undefined) {
|
|
1322
|
-
this.errors[field.name] = error;
|
|
1323
|
-
}
|
|
1324
|
-
field.setError(error);
|
|
1325
|
-
}
|
|
1326
|
-
this.applyNativeErrors();
|
|
1327
|
-
this.emitValidationEvent({
|
|
1328
|
-
type: "validation:end",
|
|
1329
|
-
errors: this.errors,
|
|
1330
|
-
values,
|
|
1331
|
-
});
|
|
1332
|
-
}
|
|
1333
|
-
applyNativeErrors() {
|
|
1334
|
-
const observer = this.observer;
|
|
1335
|
-
if (observer) {
|
|
1336
|
-
observer.disconnect();
|
|
1337
|
-
}
|
|
1338
|
-
this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
|
|
1339
|
-
if (observer && this.form) {
|
|
1340
|
-
observer.observe(this.form, runtimeMutationObserverOptions);
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
function issuesToErrors(issues) {
|
|
1345
|
-
const errors = {};
|
|
1346
|
-
for (const issue of issues) {
|
|
1347
|
-
errors[issue.name] ??= issue.message;
|
|
1348
|
-
}
|
|
1349
|
-
return errors;
|
|
1350
|
-
}
|
|
1351
|
-
function resolveFieldError(errors, name) {
|
|
1352
|
-
if (errors[name] !== undefined) {
|
|
1353
|
-
return errors[name];
|
|
1354
|
-
}
|
|
1355
|
-
const normalized = normalizePath(name);
|
|
1356
|
-
if (errors[normalized] !== undefined) {
|
|
1357
|
-
return errors[normalized];
|
|
1358
|
-
}
|
|
1359
|
-
for (const [key, msg] of Object.entries(errors)) {
|
|
1360
|
-
if (normalizePath(key) === normalized) {
|
|
1361
|
-
return msg;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
return undefined;
|
|
1365
|
-
}
|
|
1366
|
-
function toNativeFormElement(target) {
|
|
1367
|
-
if (target instanceof HTMLInputElement ||
|
|
1368
|
-
target instanceof HTMLSelectElement ||
|
|
1369
|
-
target instanceof HTMLTextAreaElement) {
|
|
1370
|
-
return target;
|
|
1371
|
-
}
|
|
1372
|
-
return null;
|
|
1373
|
-
}
|
|
1374
|
-
//# sourceMappingURL=Runtime.js.map
|