@native-form/core 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +6 -3
  2. package/dist/index.cjs +2097 -0
  3. package/dist/index.d.cts +517 -0
  4. package/dist/index.d.ts +517 -9
  5. package/dist/index.js +2054 -7
  6. package/package.json +5 -3
  7. package/dist/Discovery.d.ts +0 -2
  8. package/dist/Discovery.d.ts.map +0 -1
  9. package/dist/Discovery.js +0 -2
  10. package/dist/Discovery.js.map +0 -1
  11. package/dist/FieldErrorRenderer.d.ts +0 -2
  12. package/dist/FieldErrorRenderer.d.ts.map +0 -1
  13. package/dist/FieldErrorRenderer.js +0 -2
  14. package/dist/FieldErrorRenderer.js.map +0 -1
  15. package/dist/FieldNode.d.ts +0 -2
  16. package/dist/FieldNode.d.ts.map +0 -1
  17. package/dist/FieldNode.js +0 -2
  18. package/dist/FieldNode.js.map +0 -1
  19. package/dist/FieldRegistry.d.ts +0 -2
  20. package/dist/FieldRegistry.d.ts.map +0 -1
  21. package/dist/FieldRegistry.js +0 -2
  22. package/dist/FieldRegistry.js.map +0 -1
  23. package/dist/FieldState.d.ts +0 -2
  24. package/dist/FieldState.d.ts.map +0 -1
  25. package/dist/FieldState.js +0 -2
  26. package/dist/FieldState.js.map +0 -1
  27. package/dist/Path.d.ts +0 -78
  28. package/dist/Path.d.ts.map +0 -1
  29. package/dist/Path.js +0 -219
  30. package/dist/Path.js.map +0 -1
  31. package/dist/Runtime.d.ts +0 -2
  32. package/dist/Runtime.d.ts.map +0 -1
  33. package/dist/Runtime.js +0 -2
  34. package/dist/Runtime.js.map +0 -1
  35. package/dist/RuntimeMutationPolicy.d.ts +0 -2
  36. package/dist/RuntimeMutationPolicy.d.ts.map +0 -1
  37. package/dist/RuntimeMutationPolicy.js +0 -2
  38. package/dist/RuntimeMutationPolicy.js.map +0 -1
  39. package/dist/Serializer.d.ts +0 -2
  40. package/dist/Serializer.d.ts.map +0 -1
  41. package/dist/Serializer.js +0 -2
  42. package/dist/Serializer.js.map +0 -1
  43. package/dist/Validation.d.ts +0 -2
  44. package/dist/Validation.d.ts.map +0 -1
  45. package/dist/Validation.js +0 -2
  46. package/dist/Validation.js.map +0 -1
  47. package/dist/ValidationPipeline.d.ts +0 -2
  48. package/dist/ValidationPipeline.d.ts.map +0 -1
  49. package/dist/ValidationPipeline.js +0 -2
  50. package/dist/ValidationPipeline.js.map +0 -1
  51. package/dist/discovery/Discovery.d.ts +0 -5
  52. package/dist/discovery/Discovery.d.ts.map +0 -1
  53. package/dist/discovery/Discovery.js +0 -63
  54. package/dist/discovery/Discovery.js.map +0 -1
  55. package/dist/errors/FieldErrorRenderer.d.ts +0 -17
  56. package/dist/errors/FieldErrorRenderer.d.ts.map +0 -1
  57. package/dist/errors/FieldErrorRenderer.js +0 -115
  58. package/dist/errors/FieldErrorRenderer.js.map +0 -1
  59. package/dist/field/FieldNode.d.ts +0 -38
  60. package/dist/field/FieldNode.d.ts.map +0 -1
  61. package/dist/field/FieldNode.js +0 -154
  62. package/dist/field/FieldNode.js.map +0 -1
  63. package/dist/field/FieldRegistry.d.ts +0 -19
  64. package/dist/field/FieldRegistry.d.ts.map +0 -1
  65. package/dist/field/FieldRegistry.js +0 -46
  66. package/dist/field/FieldRegistry.js.map +0 -1
  67. package/dist/field/FieldState.d.ts +0 -13
  68. package/dist/field/FieldState.d.ts.map +0 -1
  69. package/dist/field/FieldState.js +0 -2
  70. package/dist/field/FieldState.js.map +0 -1
  71. package/dist/index.d.ts.map +0 -1
  72. package/dist/index.js.map +0 -1
  73. package/dist/runtime/Runtime.d.ts +0 -137
  74. package/dist/runtime/Runtime.d.ts.map +0 -1
  75. package/dist/runtime/Runtime.js +0 -1374
  76. package/dist/runtime/Runtime.js.map +0 -1
  77. package/dist/runtime/RuntimeMutationPolicy.d.ts +0 -3
  78. package/dist/runtime/RuntimeMutationPolicy.d.ts.map +0 -1
  79. package/dist/runtime/RuntimeMutationPolicy.js +0 -34
  80. package/dist/runtime/RuntimeMutationPolicy.js.map +0 -1
  81. package/dist/serialization/Serializer.d.ts +0 -52
  82. package/dist/serialization/Serializer.d.ts.map +0 -1
  83. package/dist/serialization/Serializer.js +0 -115
  84. package/dist/serialization/Serializer.js.map +0 -1
  85. package/dist/shared/types.d.ts +0 -66
  86. package/dist/shared/types.d.ts.map +0 -1
  87. package/dist/shared/types.js +0 -2
  88. package/dist/shared/types.js.map +0 -1
  89. package/dist/tsdoc-metadata.json +0 -11
  90. package/dist/types.d.ts +0 -2
  91. package/dist/types.d.ts.map +0 -1
  92. package/dist/types.js +0 -2
  93. package/dist/types.js.map +0 -1
  94. package/dist/validation/Validation.d.ts +0 -61
  95. package/dist/validation/Validation.d.ts.map +0 -1
  96. package/dist/validation/Validation.js +0 -34
  97. package/dist/validation/Validation.js.map +0 -1
  98. package/dist/validation/ValidationPipeline.d.ts +0 -61
  99. package/dist/validation/ValidationPipeline.d.ts.map +0 -1
  100. package/dist/validation/ValidationPipeline.js +0 -73
  101. package/dist/validation/ValidationPipeline.js.map +0 -1
package/dist/index.cjs ADDED
@@ -0,0 +1,2097 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FieldNode: () => FieldNode,
24
+ Runtime: () => Runtime,
25
+ Serializer: () => Serializer,
26
+ arrayItemPath: () => arrayItemPath,
27
+ createValidationPipeline: () => createValidationPipeline,
28
+ createValidator: () => createValidator,
29
+ formPath: () => formPath,
30
+ getByPath: () => getByPath,
31
+ isArrayItemPath: () => isArrayItemPath,
32
+ matchArrayItemPath: () => matchArrayItemPath,
33
+ normalizePath: () => normalizePath,
34
+ parseArrayItemPath: () => parseArrayItemPath,
35
+ remapArrayItemIndex: () => remapArrayItemIndex,
36
+ setByPath: () => setByPath,
37
+ shiftArrayItemIndex: () => shiftArrayItemIndex,
38
+ splitPath: () => splitPath,
39
+ toNestedValues: () => toNestedValues
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+
43
+ // src/Path.ts
44
+ var BLOCKED_PROPERTIES = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
45
+ function formPath(...segments) {
46
+ return segments.map((segment) => String(segment).trim()).filter(Boolean).join(".");
47
+ }
48
+ function arrayItemPath(arrayName, index, ...segments) {
49
+ if (!Number.isInteger(index) || index < 0) {
50
+ throw new Error("arrayItemPath index must be a non-negative integer.");
51
+ }
52
+ return formPath(arrayName, index, ...segments);
53
+ }
54
+ function splitPath(path) {
55
+ if (!path) return [];
56
+ return path.replace(/\[/g, ".").replace(/\]/g, "").split(".").map((segment) => segment.replace(/['"]/g, "").trim()).filter(Boolean);
57
+ }
58
+ function normalizePath(path) {
59
+ return splitPath(path).join(".");
60
+ }
61
+ function parseArrayItemPath(path) {
62
+ const parts = splitPath(path);
63
+ for (let i = 0; i < parts.length - 1; i++) {
64
+ const next = parts[i + 1];
65
+ if (/^\d+$/.test(next)) {
66
+ const arrayName = parts.slice(0, i + 1).join(".");
67
+ const index = Number(next);
68
+ const subPath = parts.slice(i + 2).join(".");
69
+ return { arrayName, index, subPath };
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ function isArrayItemPath(path, arrayName) {
75
+ const parsed = parseArrayItemPath(path);
76
+ if (!parsed) {
77
+ return false;
78
+ }
79
+ if (arrayName !== void 0 && parsed.arrayName !== arrayName) {
80
+ return false;
81
+ }
82
+ return true;
83
+ }
84
+ function matchArrayItemPath(path, arrayName) {
85
+ const parsed = parseArrayItemPath(path);
86
+ if (!parsed || parsed.arrayName !== arrayName) {
87
+ return null;
88
+ }
89
+ return { index: parsed.index, subPath: parsed.subPath };
90
+ }
91
+ function shiftArrayItemIndex(path, arrayName, fromIndex, delta) {
92
+ const parsed = parseArrayItemPath(path);
93
+ if (!parsed || parsed.arrayName !== arrayName || parsed.index < fromIndex) {
94
+ return path;
95
+ }
96
+ const nextIndex = parsed.index + delta;
97
+ if (nextIndex < 0) {
98
+ throw new Error("shiftArrayItemIndex resulted in a negative index.");
99
+ }
100
+ return parsed.subPath ? arrayItemPath(arrayName, nextIndex, parsed.subPath) : arrayItemPath(arrayName, nextIndex);
101
+ }
102
+ function remapArrayItemIndex(path, arrayName, oldIndex, newIndex) {
103
+ if (!Number.isInteger(newIndex) || newIndex < 0) {
104
+ throw new Error(
105
+ "remapArrayItemIndex newIndex must be a non-negative integer."
106
+ );
107
+ }
108
+ const parsed = parseArrayItemPath(path);
109
+ if (!parsed || parsed.arrayName !== arrayName || parsed.index !== oldIndex) {
110
+ return path;
111
+ }
112
+ return parsed.subPath ? arrayItemPath(arrayName, newIndex, parsed.subPath) : arrayItemPath(arrayName, newIndex);
113
+ }
114
+ function getByPath(target, path, defaultValue) {
115
+ const parts = typeof path === "string" ? splitPath(path) : path;
116
+ if (parts.length === 0) {
117
+ return target !== void 0 ? target : defaultValue;
118
+ }
119
+ let current = target;
120
+ for (const part of parts) {
121
+ if (current === null || current === void 0) {
122
+ return defaultValue;
123
+ }
124
+ if (BLOCKED_PROPERTIES.has(part)) {
125
+ return defaultValue;
126
+ }
127
+ if (Array.isArray(current)) {
128
+ const index = Number(part);
129
+ if (!Number.isInteger(index) || index < 0) {
130
+ return defaultValue;
131
+ }
132
+ current = current[index];
133
+ } else if (typeof current === "object") {
134
+ current = current[part];
135
+ } else {
136
+ return defaultValue;
137
+ }
138
+ }
139
+ return current !== void 0 ? current : defaultValue;
140
+ }
141
+ function setByPath(target, path, value) {
142
+ const parts = typeof path === "string" ? splitPath(path) : path;
143
+ if (parts.length === 0) {
144
+ return;
145
+ }
146
+ let current = target;
147
+ for (let i = 0; i < parts.length; i++) {
148
+ const part = parts[i];
149
+ if (BLOCKED_PROPERTIES.has(part)) {
150
+ return;
151
+ }
152
+ const isLast = i === parts.length - 1;
153
+ const nextPart = parts[i + 1];
154
+ const nextIsIndex = nextPart !== void 0 && /^\d+$/.test(nextPart);
155
+ if (Array.isArray(current)) {
156
+ const index = Number(part);
157
+ if (!Number.isInteger(index) || index < 0) {
158
+ return;
159
+ }
160
+ if (isLast) {
161
+ current[index] = value;
162
+ return;
163
+ }
164
+ if (current[index] === void 0 || current[index] === null) {
165
+ current[index] = nextIsIndex ? [] : {};
166
+ }
167
+ current = current[index];
168
+ continue;
169
+ }
170
+ if (typeof current === "object" && current !== null) {
171
+ if (isLast) {
172
+ current[part] = value;
173
+ return;
174
+ }
175
+ if (current[part] === void 0 || current[part] === null) {
176
+ current[part] = nextIsIndex ? [] : {};
177
+ }
178
+ current = current[part];
179
+ continue;
180
+ }
181
+ return;
182
+ }
183
+ }
184
+ function toNestedValues(flatValues) {
185
+ const nested = {};
186
+ for (const [path, value] of Object.entries(flatValues)) {
187
+ const parts = splitPath(path);
188
+ if (parts.length === 0) {
189
+ continue;
190
+ }
191
+ setByPath(nested, parts, value);
192
+ }
193
+ return nested;
194
+ }
195
+
196
+ // src/serialization/Serializer.ts
197
+ var Serializer = class {
198
+ /**
199
+ * Native FormData serialization baseline (100% backward compatible with browser form submission).
200
+ */
201
+ serialize(form) {
202
+ const formData = new FormData(form);
203
+ const values = {};
204
+ for (const key of new Set(formData.keys())) {
205
+ const entries = formData.getAll(key);
206
+ values[key] = entries.length === 1 ? entries[0] : entries;
207
+ }
208
+ return values;
209
+ }
210
+ /**
211
+ * Field-node-driven serialization supporting type preservation, unchecked checkbox coercion,
212
+ * disabled filtering, and custom element serializers.
213
+ */
214
+ serializeFields(fields, options = {}) {
215
+ const {
216
+ includeDisabled = false,
217
+ coerceUncheckedCheckboxes = true,
218
+ valueMode = "typed",
219
+ customSerializers = {},
220
+ nesting = "flat"
221
+ } = options;
222
+ const values = {};
223
+ const groupedArrays = /* @__PURE__ */ new Map();
224
+ for (const node of fields) {
225
+ const element = node.element;
226
+ const name = node.name;
227
+ if (!name) continue;
228
+ if (!includeDisabled && "disabled" in element && element.disabled) continue;
229
+ let value;
230
+ const tagName = element.tagName.toLowerCase();
231
+ const inputType = element instanceof HTMLInputElement ? element.type : void 0;
232
+ const customExtractor = customSerializers[tagName] ?? (inputType ? customSerializers[inputType] : void 0);
233
+ if (customExtractor) {
234
+ value = customExtractor(element, node);
235
+ } else if (valueMode === "raw") {
236
+ if (element instanceof HTMLSelectElement && element.multiple) {
237
+ value = Array.from(element.selectedOptions).map((o) => o.value);
238
+ } else {
239
+ value = element.value;
240
+ }
241
+ } else {
242
+ value = node.value;
243
+ }
244
+ if (!customExtractor && element instanceof HTMLInputElement && element.type === "radio") {
245
+ if (element.checked) {
246
+ values[name] = valueMode === "raw" ? element.value || "on" : node.value;
247
+ } else if (!(name in values)) {
248
+ values[name] = void 0;
249
+ }
250
+ continue;
251
+ }
252
+ if (!customExtractor && element instanceof HTMLInputElement && element.type === "checkbox") {
253
+ const hasExplicitValue = element.hasAttribute("value") && element.getAttribute("value") !== "on";
254
+ if (hasExplicitValue) {
255
+ if (!element.checked) {
256
+ if (!(name in values)) {
257
+ values[name] = [];
258
+ } else if (!Array.isArray(values[name])) {
259
+ const arr = [values[name]];
260
+ groupedArrays.set(name, arr);
261
+ values[name] = arr;
262
+ }
263
+ continue;
264
+ }
265
+ value = element.value;
266
+ } else {
267
+ if (element.checked) {
268
+ value = valueMode === "raw" ? element.value || "on" : true;
269
+ } else {
270
+ if (coerceUncheckedCheckboxes) {
271
+ values[name] = false;
272
+ }
273
+ continue;
274
+ }
275
+ }
276
+ }
277
+ if (name in values) {
278
+ if (!groupedArrays.has(name)) {
279
+ const initial = values[name];
280
+ const arr = Array.isArray(initial) ? [...initial] : [initial];
281
+ groupedArrays.set(name, arr);
282
+ values[name] = arr;
283
+ }
284
+ groupedArrays.get(name).push(value);
285
+ } else {
286
+ values[name] = value;
287
+ }
288
+ }
289
+ if (nesting === "nested") {
290
+ return toNestedValues(values);
291
+ }
292
+ return values;
293
+ }
294
+ };
295
+
296
+ // src/field/FieldNode.ts
297
+ var FieldNode = class {
298
+ constructor(element) {
299
+ this.element = element;
300
+ }
301
+ element;
302
+ dirty = false;
303
+ touched = false;
304
+ mounted = true;
305
+ initialValue = void 0;
306
+ stateValue = void 0;
307
+ error;
308
+ validating = false;
309
+ get name() {
310
+ return this.element.name;
311
+ }
312
+ get tag() {
313
+ return this.element.tagName.toLowerCase();
314
+ }
315
+ get type() {
316
+ if (this.element instanceof HTMLInputElement) {
317
+ return this.element.type;
318
+ }
319
+ if (this.element instanceof HTMLSelectElement) {
320
+ return this.element.multiple ? "select-multiple" : "select";
321
+ }
322
+ return "textarea";
323
+ }
324
+ get value() {
325
+ if (this.element instanceof HTMLInputElement) {
326
+ switch (this.element.type) {
327
+ case "checkbox":
328
+ return this.element.checked;
329
+ case "radio":
330
+ return this.element.checked ? this.element.value : void 0;
331
+ case "number":
332
+ case "range":
333
+ return this.element.value === "" || Number.isNaN(this.element.valueAsNumber) ? void 0 : this.element.valueAsNumber;
334
+ case "file":
335
+ return this.element.files;
336
+ default:
337
+ return this.element.value;
338
+ }
339
+ }
340
+ if (this.element instanceof HTMLSelectElement) {
341
+ return this.element.multiple ? Array.from(this.element.selectedOptions).map((o) => o.value) : this.element.value;
342
+ }
343
+ return this.element.value;
344
+ }
345
+ focus() {
346
+ this.element.focus();
347
+ }
348
+ blur() {
349
+ this.element.blur();
350
+ }
351
+ get isDirty() {
352
+ return this.dirty;
353
+ }
354
+ get isTouched() {
355
+ return this.touched;
356
+ }
357
+ get isMounted() {
358
+ return this.mounted;
359
+ }
360
+ get isValidating() {
361
+ return this.validating;
362
+ }
363
+ setValidating(validating) {
364
+ this.validating = validating;
365
+ }
366
+ // --- New state API (replaces FieldStateStore) ---
367
+ register(initialValue) {
368
+ this.mounted = true;
369
+ this.initialValue = initialValue;
370
+ this.stateValue = initialValue;
371
+ this.dirty = false;
372
+ this.touched = false;
373
+ this.error = void 0;
374
+ this.validating = false;
375
+ }
376
+ unregister() {
377
+ this.mounted = false;
378
+ }
379
+ getState() {
380
+ return {
381
+ name: this.name,
382
+ dirty: this.dirty,
383
+ touched: this.touched,
384
+ mounted: this.mounted,
385
+ initialValue: this.initialValue,
386
+ value: this.stateValue,
387
+ error: this.error,
388
+ validating: this.validating
389
+ };
390
+ }
391
+ updateValue(value) {
392
+ const next = value === void 0 ? this.value : value;
393
+ this.stateValue = next;
394
+ this.dirty = !isEqualValue(this.initialValue, next);
395
+ }
396
+ markTouched() {
397
+ this.touched = true;
398
+ }
399
+ setError(message) {
400
+ this.error = message;
401
+ }
402
+ reset(value) {
403
+ this.initialValue = value;
404
+ this.stateValue = value;
405
+ this.dirty = false;
406
+ this.touched = false;
407
+ this.error = void 0;
408
+ this.validating = false;
409
+ }
410
+ // --- Lifecycle helpers ---
411
+ // Called when the browser emits input on the control
412
+ handleInput(value) {
413
+ this.updateValue(value);
414
+ }
415
+ // Called when the browser emits change on the control
416
+ handleChange(value) {
417
+ this.updateValue(value);
418
+ }
419
+ // Called when the control is blurred
420
+ handleBlur() {
421
+ this.markTouched();
422
+ }
423
+ // Called when the form is reset
424
+ handleReset(value) {
425
+ const initial = value ?? this.initialValue ?? this.value;
426
+ this.reset(initial);
427
+ this.setError(void 0);
428
+ }
429
+ // Called when the form is submitted (mark touched)
430
+ handleSubmit() {
431
+ this.markTouched();
432
+ }
433
+ };
434
+ function isEqualValue(left, right) {
435
+ if (Array.isArray(left) || Array.isArray(right)) {
436
+ if (!Array.isArray(left) || !Array.isArray(right)) {
437
+ return false;
438
+ }
439
+ if (left.length !== right.length) {
440
+ return false;
441
+ }
442
+ return left.every((value, index) => value === right[index]);
443
+ }
444
+ return left === right;
445
+ }
446
+
447
+ // src/discovery/Discovery.ts
448
+ var Discovery = class {
449
+ discover(form, registry) {
450
+ const seen = /* @__PURE__ */ new Set();
451
+ for (const control of Array.from(form.elements)) {
452
+ if (control instanceof HTMLInputElement || control instanceof HTMLSelectElement || control instanceof HTMLTextAreaElement) {
453
+ if (!control.name) {
454
+ continue;
455
+ }
456
+ const name = control.name;
457
+ seen.add(name);
458
+ if (registry.has(name)) {
459
+ const existing = registry.get(name);
460
+ if (existing.element === control) {
461
+ continue;
462
+ }
463
+ if (control instanceof HTMLInputElement && control.type === "radio" && existing.element instanceof HTMLInputElement && existing.element.type === "radio") {
464
+ if (control.checked && !existing.element.checked) {
465
+ registry.unregister(name);
466
+ registry.register(new FieldNode(control));
467
+ }
468
+ continue;
469
+ }
470
+ if (control instanceof HTMLInputElement && control.type === "checkbox" && existing.element instanceof HTMLInputElement && existing.element.type === "checkbox" && existing.element.form === form) {
471
+ continue;
472
+ }
473
+ registry.unregister(name);
474
+ registry.register(new FieldNode(control));
475
+ continue;
476
+ }
477
+ registry.register(new FieldNode(control));
478
+ }
479
+ }
480
+ for (const [key, field] of registry.entries()) {
481
+ if (!seen.has(key) || field.element.name !== key) {
482
+ registry.unregister(key);
483
+ }
484
+ }
485
+ }
486
+ };
487
+
488
+ // src/errors/FieldErrorRenderer.ts
489
+ var FieldErrorRenderer = class _FieldErrorRenderer {
490
+ static nextErrorId = 0;
491
+ errorIds = /* @__PURE__ */ new WeakMap();
492
+ errorElements = /* @__PURE__ */ new WeakMap();
493
+ applyErrors(fields, errors, renderDomErrors = true) {
494
+ for (const field of fields) {
495
+ this.applyFieldError(field, errors[field.name], renderDomErrors);
496
+ }
497
+ }
498
+ normalizeFields(fields) {
499
+ for (const field of fields) {
500
+ this.normalizeField(field);
501
+ }
502
+ }
503
+ applyFieldError(field, message, renderDomErrors = true) {
504
+ const element = field.element;
505
+ try {
506
+ element.setCustomValidity(message ?? "");
507
+ } catch (_e) {
508
+ }
509
+ if (!renderDomErrors) {
510
+ this.removeErrorElements(this.findExistingErrorElements(element));
511
+ this.errorElements.delete(element);
512
+ return;
513
+ }
514
+ if (!message) {
515
+ this.removeErrorElements(this.findExistingErrorElements(element));
516
+ this.errorElements.delete(element);
517
+ return;
518
+ }
519
+ let errorElement = this.getPrimaryErrorElement(element);
520
+ if (!errorElement) {
521
+ errorElement = this.createErrorElement(field);
522
+ element.insertAdjacentElement("afterend", errorElement);
523
+ }
524
+ errorElement.textContent = message;
525
+ this.errorElements.set(element, errorElement);
526
+ }
527
+ normalizeField(field) {
528
+ const element = field.element;
529
+ const existing = this.findExistingErrorElements(element);
530
+ if (existing.length === 0) {
531
+ this.errorElements.delete(element);
532
+ return;
533
+ }
534
+ const keep = existing[0];
535
+ this.removeErrorElements(existing.slice(1));
536
+ this.decorateErrorElement(field, keep);
537
+ this.errorElements.set(element, keep);
538
+ }
539
+ getPrimaryErrorElement(element) {
540
+ const stored = this.errorElements.get(element);
541
+ if (stored?.isConnected) {
542
+ const existing2 = this.findExistingErrorElements(element);
543
+ if (existing2.length > 1) {
544
+ this.removeErrorElements(existing2.slice(1));
545
+ }
546
+ return existing2[0] ?? stored;
547
+ }
548
+ const existing = this.findExistingErrorElements(element);
549
+ if (existing.length === 0) {
550
+ return void 0;
551
+ }
552
+ const keep = existing[0];
553
+ this.removeErrorElements(existing.slice(1));
554
+ this.errorElements.set(element, keep);
555
+ return keep;
556
+ }
557
+ findExistingErrorElements(element) {
558
+ const results = [];
559
+ let node = element.nextElementSibling;
560
+ while (node) {
561
+ if (node instanceof HTMLElement) {
562
+ const id = node.id ?? "";
563
+ const aria = node.getAttribute("aria-live");
564
+ const isRuntimeLabel = node.dataset.nativeFormError === "true" || id.startsWith("native-form-error-") || aria === "polite";
565
+ if (isRuntimeLabel) {
566
+ results.push(node);
567
+ node = node.nextElementSibling;
568
+ continue;
569
+ }
570
+ }
571
+ break;
572
+ }
573
+ return results;
574
+ }
575
+ removeErrorElements(elements) {
576
+ for (const element of elements) {
577
+ element.remove();
578
+ }
579
+ }
580
+ createErrorElement(field) {
581
+ const error = document.createElement("div");
582
+ this.decorateErrorElement(field, error);
583
+ error.style.color = "red";
584
+ error.style.fontSize = "0.875rem";
585
+ error.style.marginTop = "0.25rem";
586
+ return error;
587
+ }
588
+ decorateErrorElement(field, element) {
589
+ element.id = this.getOrCreateErrorId(field.element);
590
+ element.dataset.nativeFormError = "true";
591
+ element.setAttribute("data-for", field.name);
592
+ element.setAttribute("aria-live", "polite");
593
+ }
594
+ getOrCreateErrorId(element) {
595
+ const existing = this.errorIds.get(element);
596
+ if (existing) {
597
+ return existing;
598
+ }
599
+ const created = `native-form-error-${_FieldErrorRenderer.nextErrorId++}`;
600
+ this.errorIds.set(element, created);
601
+ return created;
602
+ }
603
+ };
604
+
605
+ // src/field/FieldRegistry.ts
606
+ var FieldRegistry = class {
607
+ fields = /* @__PURE__ */ new Map();
608
+ // Optional change callback used by Runtime to observe mount/unmount events.
609
+ onChange;
610
+ register(field) {
611
+ this.fields.set(field.name, field);
612
+ this.onChange?.({ type: "mount", field });
613
+ }
614
+ unregister(name) {
615
+ const field = this.fields.get(name);
616
+ if (field) {
617
+ field.unregister();
618
+ this.onChange?.({ type: "unmount", field });
619
+ }
620
+ this.fields.delete(name);
621
+ }
622
+ get(name) {
623
+ return this.fields.get(name);
624
+ }
625
+ has(name) {
626
+ return this.fields.has(name);
627
+ }
628
+ values() {
629
+ return [...this.fields.values()];
630
+ }
631
+ keys() {
632
+ return Array.from(this.fields.keys());
633
+ }
634
+ entries() {
635
+ return Array.from(this.fields.entries());
636
+ }
637
+ clear() {
638
+ for (const field of this.fields.values()) {
639
+ field.unregister();
640
+ this.onChange?.({ type: "unmount", field });
641
+ }
642
+ this.fields.clear();
643
+ }
644
+ get size() {
645
+ return this.fields.size;
646
+ }
647
+ getAll() {
648
+ return Array.from(this.fields.values());
649
+ }
650
+ };
651
+
652
+ // src/runtime/RuntimeMutationPolicy.ts
653
+ var runtimeMutationObserverOptions = {
654
+ childList: true,
655
+ subtree: true,
656
+ attributes: true,
657
+ attributeFilter: ["name", "disabled"]
658
+ };
659
+ function isRuntimeMutationRelevant(mutation) {
660
+ if (mutation.type === "attributes") {
661
+ return isNativeFormControl(mutation.target);
662
+ }
663
+ if (mutation.type === "childList") {
664
+ const nodes = [...mutation.addedNodes, ...mutation.removedNodes];
665
+ return nodes.some((node) => {
666
+ if (isErrorLabel(node)) {
667
+ return false;
668
+ }
669
+ return isNativeFormControl(node) || containsNativeFormControl(node);
670
+ });
671
+ }
672
+ return false;
673
+ }
674
+ function isNativeFormControl(node) {
675
+ return node instanceof HTMLInputElement || node instanceof HTMLSelectElement || node instanceof HTMLTextAreaElement;
676
+ }
677
+ function containsNativeFormControl(node) {
678
+ return node instanceof HTMLElement && Boolean(node.querySelector("input[name], select[name], textarea[name]"));
679
+ }
680
+ function isErrorLabel(node) {
681
+ return node instanceof HTMLElement && node.dataset.nativeFormError === "true";
682
+ }
683
+
684
+ // src/validation/Validation.ts
685
+ function createValidator(schema) {
686
+ if ("~standard" in schema) {
687
+ return {
688
+ async validate(values) {
689
+ const result = await schema["~standard"].validate(values);
690
+ if (result.issues) {
691
+ return {
692
+ success: false,
693
+ issues: result.issues.map((issue) => ({
694
+ name: schemaIssuePathToName(issue.path),
695
+ message: issue.message
696
+ }))
697
+ };
698
+ }
699
+ return {
700
+ success: true,
701
+ values: result.value
702
+ };
703
+ }
704
+ };
705
+ }
706
+ return schema;
707
+ }
708
+ function schemaIssuePathToName(path) {
709
+ if (!path || path.length === 0) {
710
+ return "";
711
+ }
712
+ return path.map(
713
+ (segment) => typeof segment === "object" && segment !== null ? segment.key : segment
714
+ ).map(String).join(".");
715
+ }
716
+
717
+ // src/validation/ValidationPipeline.ts
718
+ function createValidationPipeline(options = {}) {
719
+ const schemaOrValidator = options.schema ?? options.validator;
720
+ const validator = schemaOrValidator ? createValidator(schemaOrValidator) : void 0;
721
+ return {
722
+ capabilities: {
723
+ supportsFieldScopedValidation: !validator
724
+ },
725
+ async validate(values, context) {
726
+ if (!validator) {
727
+ const nativeIssues = getNativeValidationIssues(context.fields);
728
+ if (nativeIssues.length > 0) {
729
+ return {
730
+ success: false,
731
+ issues: nativeIssues
732
+ };
733
+ }
734
+ return {
735
+ success: true,
736
+ values
737
+ };
738
+ }
739
+ return validator.validate(values, context);
740
+ },
741
+ async validateField(field, values, context) {
742
+ if (!validator) {
743
+ const issue = getNativeValidationIssue(field);
744
+ if (issue) {
745
+ return {
746
+ success: false,
747
+ issues: [issue]
748
+ };
749
+ }
750
+ return {
751
+ success: true,
752
+ values
753
+ };
754
+ }
755
+ return validator.validate(values, context);
756
+ }
757
+ };
758
+ }
759
+ function getNativeValidationIssue(field) {
760
+ const validationMessage = field.validationMessage;
761
+ if (!validationMessage) {
762
+ return void 0;
763
+ }
764
+ return {
765
+ name: field.name,
766
+ message: validationMessage
767
+ };
768
+ }
769
+ function getNativeValidationIssues(fields) {
770
+ const issues = [];
771
+ for (const field of fields) {
772
+ const validationMessage = field.validationMessage;
773
+ if (validationMessage) {
774
+ issues.push({
775
+ name: field.name,
776
+ message: validationMessage
777
+ });
778
+ }
779
+ }
780
+ return issues;
781
+ }
782
+
783
+ // src/runtime/Runtime.ts
784
+ var Runtime = class {
785
+ constructor(options = {}) {
786
+ this.options = options;
787
+ this.pipeline = options.pipeline ?? createValidationPipeline({
788
+ schema: options.schema,
789
+ validator: options.validator
790
+ });
791
+ this.registry.onChange = (e) => {
792
+ if (e.type === "mount") {
793
+ this.emitFieldEvent({ type: "field:mount", field: e.field.name });
794
+ } else {
795
+ this.emitFieldEvent({ type: "field:unmount", field: e.field.name });
796
+ }
797
+ };
798
+ }
799
+ options;
800
+ registry = new FieldRegistry();
801
+ discovery = new Discovery();
802
+ serializer = new Serializer();
803
+ fieldErrorRenderer = new FieldErrorRenderer();
804
+ form;
805
+ listeners = /* @__PURE__ */ new Set();
806
+ // Field state is now managed on each FieldNode instance.
807
+ observer;
808
+ validationController;
809
+ debounceTimers = /* @__PURE__ */ new Map();
810
+ fieldValidationControllers = /* @__PURE__ */ new Map();
811
+ submitting = false;
812
+ validating = false;
813
+ errors = {};
814
+ preservedValuesStore = /* @__PURE__ */ Object.create(null);
815
+ isSyncingFields = false;
816
+ pipeline;
817
+ mount(form) {
818
+ this.form = form;
819
+ this.syncFields();
820
+ this.fieldErrorRenderer.normalizeFields(this.registry.values());
821
+ const values = this.extractCurrentValues();
822
+ for (const field of this.registry.getAll()) {
823
+ field.register(values[field.name]);
824
+ }
825
+ form.addEventListener("submit", this.handleSubmit);
826
+ form.addEventListener("input", this.handleFieldUpdate);
827
+ form.addEventListener("change", this.handleFieldUpdate);
828
+ form.addEventListener("blur", this.handleFieldBlur, true);
829
+ form.addEventListener("reset", this.handleReset);
830
+ this.observer = new MutationObserver(this.handleMutation);
831
+ this.observer.observe(form, runtimeMutationObserverOptions);
832
+ this.emitFormEvent({
833
+ type: "form:mount"
834
+ });
835
+ }
836
+ unmount() {
837
+ this.clearDebounceTimers();
838
+ this.abortAllValidations();
839
+ if (this.form) {
840
+ this.form.removeEventListener("submit", this.handleSubmit);
841
+ this.form.removeEventListener("input", this.handleFieldUpdate);
842
+ this.form.removeEventListener("change", this.handleFieldUpdate);
843
+ this.form.removeEventListener("blur", this.handleFieldBlur, true);
844
+ this.form.removeEventListener("reset", this.handleReset);
845
+ }
846
+ this.observer?.disconnect();
847
+ this.observer = void 0;
848
+ this.fieldErrorRenderer.applyErrors(this.registry.values(), {});
849
+ this.registry.clear();
850
+ this.form = void 0;
851
+ this.errors = {};
852
+ this.preservedValuesStore = /* @__PURE__ */ Object.create(null);
853
+ this.submitting = false;
854
+ this.validating = false;
855
+ this.emitFormEvent({
856
+ type: "form:unmount"
857
+ });
858
+ this.listeners.clear();
859
+ }
860
+ clearDebounceTimers() {
861
+ for (const timer of this.debounceTimers.values()) {
862
+ clearTimeout(timer);
863
+ }
864
+ this.debounceTimers.clear();
865
+ }
866
+ abortAllValidations() {
867
+ this.validationController?.abort();
868
+ this.validationController = void 0;
869
+ for (const controller of this.fieldValidationControllers.values()) {
870
+ controller.abort();
871
+ }
872
+ this.fieldValidationControllers.clear();
873
+ for (const field of this.registry.values()) {
874
+ field.setValidating(false);
875
+ }
876
+ }
877
+ getAsyncValidator(name) {
878
+ if (!this.options.asyncValidators) {
879
+ return void 0;
880
+ }
881
+ if (this.options.asyncValidators[name]) {
882
+ return this.options.asyncValidators[name];
883
+ }
884
+ const normalized = normalizePath(name);
885
+ if (this.options.asyncValidators[normalized]) {
886
+ return this.options.asyncValidators[normalized];
887
+ }
888
+ for (const [key, validator] of Object.entries(this.options.asyncValidators)) {
889
+ if (normalizePath(key) === normalized) {
890
+ return validator;
891
+ }
892
+ }
893
+ return void 0;
894
+ }
895
+ serialize() {
896
+ if (!this.form) {
897
+ if (this.options.preserveUnmountedValues) {
898
+ return { ...this.preservedValuesStore };
899
+ }
900
+ throw new Error("Runtime is not mounted.");
901
+ }
902
+ if (this.options.preserveUnmountedValues) {
903
+ if (!this.isSyncingFields) {
904
+ this.syncFields();
905
+ }
906
+ const live = this.serializer.serialize(this.form);
907
+ return {
908
+ ...this.preservedValuesStore,
909
+ ...live
910
+ };
911
+ }
912
+ return this.serializer.serialize(this.form);
913
+ }
914
+ serializeFields(options) {
915
+ if (this.options.preserveUnmountedValues && this.form && !this.isSyncingFields) {
916
+ this.syncFields();
917
+ }
918
+ const effectiveOptions = {
919
+ ...this.options.serializeFieldsOptions,
920
+ ...options
921
+ };
922
+ const nesting = effectiveOptions.nesting ?? this.options.nesting ?? "flat";
923
+ let nodes = this.registry.values();
924
+ if (this.form) {
925
+ const nodeList = [];
926
+ const nodeMap = new Map(this.registry.values().map((n) => [n.element, n]));
927
+ for (const control of Array.from(this.form.elements)) {
928
+ if ((control instanceof HTMLInputElement || control instanceof HTMLSelectElement || control instanceof HTMLTextAreaElement) && control.name) {
929
+ nodeList.push(nodeMap.get(control) ?? new FieldNode(control));
930
+ }
931
+ }
932
+ nodes = nodeList;
933
+ }
934
+ if (this.options.preserveUnmountedValues) {
935
+ const live = this.serializer.serializeFields(nodes, {
936
+ ...effectiveOptions,
937
+ nesting: "flat"
938
+ });
939
+ const merged = {
940
+ ...this.preservedValuesStore,
941
+ ...live
942
+ };
943
+ if (nesting === "nested") {
944
+ return toNestedValues(merged);
945
+ }
946
+ return merged;
947
+ }
948
+ return this.serializer.serializeFields(nodes, options);
949
+ }
950
+ get fields() {
951
+ return this.getFieldStates();
952
+ }
953
+ get serializationMode() {
954
+ return this.options.serializationMode ?? "native";
955
+ }
956
+ get preservedValues() {
957
+ if (this.options.preserveUnmountedValues && this.form && !this.isSyncingFields) {
958
+ this.syncFields();
959
+ }
960
+ return Object.assign(/* @__PURE__ */ Object.create(null), this.preservedValuesStore);
961
+ }
962
+ extractCurrentValues() {
963
+ if (!this.form) {
964
+ if (this.options.preserveUnmountedValues) {
965
+ let res = { ...this.preservedValuesStore };
966
+ if (this.options.nesting === "nested") {
967
+ res = toNestedValues(res);
968
+ }
969
+ return res;
970
+ }
971
+ return {};
972
+ }
973
+ const serializeOpts = {
974
+ ...this.options.serializeFieldsOptions,
975
+ ...this.options.nesting ? { nesting: this.options.nesting } : {}
976
+ };
977
+ let values;
978
+ if (this.serializationMode === "typed") {
979
+ values = this.serializeFields(serializeOpts);
980
+ } else {
981
+ values = this.serialize();
982
+ if (this.options.nesting === "nested") {
983
+ values = toNestedValues(values);
984
+ }
985
+ }
986
+ return values;
987
+ }
988
+ subscribe(listener) {
989
+ this.listeners.add(listener);
990
+ return () => {
991
+ this.unsubscribe(listener);
992
+ };
993
+ }
994
+ unsubscribe(listener) {
995
+ this.listeners.delete(listener);
996
+ }
997
+ notify(event) {
998
+ for (const listener of this.listeners) {
999
+ listener(event);
1000
+ }
1001
+ }
1002
+ emitFieldEvent(event) {
1003
+ this.notify(event);
1004
+ }
1005
+ emitValidationEvent(event) {
1006
+ this.notify(event);
1007
+ }
1008
+ emitFormEvent(event) {
1009
+ this.notify(event);
1010
+ }
1011
+ submit(event) {
1012
+ return this.processSubmitFlow(event);
1013
+ }
1014
+ async validate() {
1015
+ const validation = await this.runValidation(this.extractCurrentValues());
1016
+ return validation.result;
1017
+ }
1018
+ async validateFields(fieldNames) {
1019
+ if (!this.form) {
1020
+ throw new Error("Runtime is not mounted.");
1021
+ }
1022
+ this.clearDebounceTimers();
1023
+ this.abortAllValidations();
1024
+ for (const name of fieldNames) {
1025
+ delete this.errors[name];
1026
+ const normalized = normalizePath(name);
1027
+ if (this.errors[normalized]) {
1028
+ delete this.errors[normalized];
1029
+ }
1030
+ const node = this.registry.get(name);
1031
+ node?.setError(void 0);
1032
+ }
1033
+ this.applyNativeErrors();
1034
+ const values = this.extractCurrentValues();
1035
+ const normalizedTargetNames = new Set(fieldNames.map(normalizePath));
1036
+ const controller = new AbortController();
1037
+ this.validationController = controller;
1038
+ this.validating = true;
1039
+ this.emitValidationEvent({
1040
+ type: "validation:start",
1041
+ values
1042
+ });
1043
+ const context = {
1044
+ form: this.form,
1045
+ signal: controller.signal,
1046
+ fields: this.getValidationFields()
1047
+ };
1048
+ const result = await this.pipeline.validate(values, context);
1049
+ if (this.validationController === controller) {
1050
+ this.validating = false;
1051
+ }
1052
+ if (controller.signal.aborted) {
1053
+ return {
1054
+ success: false,
1055
+ issues: []
1056
+ };
1057
+ }
1058
+ const filteredIssues = [];
1059
+ if (!result.success && result.issues) {
1060
+ for (const issue of result.issues) {
1061
+ if (normalizedTargetNames.has(normalizePath(issue.name))) {
1062
+ filteredIssues.push(issue);
1063
+ }
1064
+ }
1065
+ }
1066
+ for (const name of fieldNames) {
1067
+ const issue = filteredIssues.find(
1068
+ (iss) => normalizePath(iss.name) === normalizePath(name)
1069
+ );
1070
+ if (issue) {
1071
+ this.errors[name] = issue.message;
1072
+ const node = this.registry.get(name);
1073
+ node?.setError(issue.message);
1074
+ } else {
1075
+ delete this.errors[name];
1076
+ const normalized = normalizePath(name);
1077
+ if (this.errors[normalized]) {
1078
+ delete this.errors[normalized];
1079
+ }
1080
+ const node = this.registry.get(name);
1081
+ node?.setError(void 0);
1082
+ }
1083
+ }
1084
+ this.applyNativeErrors();
1085
+ if (this.options.asyncValidators) {
1086
+ const asyncPromises = [];
1087
+ const flat = this.serializationMode === "typed" ? this.serialize() : values;
1088
+ for (const name of fieldNames) {
1089
+ const issue = filteredIssues.find(
1090
+ (iss) => normalizePath(iss.name) === normalizePath(name)
1091
+ );
1092
+ if (issue) continue;
1093
+ const asyncValidator = this.getAsyncValidator(name);
1094
+ if (!asyncValidator) continue;
1095
+ const node = this.registry.get(name);
1096
+ node?.setValidating(true);
1097
+ const rawValue = values[name] ?? flat[name];
1098
+ asyncPromises.push(
1099
+ (async () => {
1100
+ try {
1101
+ const err = await asyncValidator(rawValue, {
1102
+ form: this.form,
1103
+ signal: controller.signal
1104
+ });
1105
+ if (err) {
1106
+ filteredIssues.push({ name, message: err });
1107
+ this.errors[name] = err;
1108
+ node?.setError(err);
1109
+ }
1110
+ } catch (error) {
1111
+ const msg = error instanceof Error ? error.message : String(error);
1112
+ filteredIssues.push({ name, message: msg });
1113
+ this.errors[name] = msg;
1114
+ node?.setError(msg);
1115
+ } finally {
1116
+ node?.setValidating(false);
1117
+ }
1118
+ })()
1119
+ );
1120
+ }
1121
+ if (asyncPromises.length > 0) {
1122
+ await Promise.all(asyncPromises);
1123
+ this.applyNativeErrors();
1124
+ }
1125
+ }
1126
+ this.emitValidationEvent({
1127
+ type: "validation:end",
1128
+ errors: { ...this.errors },
1129
+ values
1130
+ });
1131
+ if (filteredIssues.length > 0) {
1132
+ return {
1133
+ success: false,
1134
+ issues: filteredIssues
1135
+ };
1136
+ }
1137
+ return {
1138
+ success: true,
1139
+ values
1140
+ };
1141
+ }
1142
+ handleSubmit = (event) => {
1143
+ event.preventDefault();
1144
+ void this.processSubmitFlow(event);
1145
+ };
1146
+ async processSubmitFlow(event) {
1147
+ if (!this.form) {
1148
+ throw new Error("Runtime is not mounted.");
1149
+ }
1150
+ this.clearDebounceTimers();
1151
+ this.abortAllValidations();
1152
+ const values = this.extractCurrentValues();
1153
+ for (const field of this.registry.values()) {
1154
+ field.handleSubmit();
1155
+ this.emitFieldEvent({
1156
+ type: "field:submit",
1157
+ field: field.name
1158
+ });
1159
+ }
1160
+ const validation = await this.runValidation(values);
1161
+ const isStaleValidation = this.validationController !== void 0 && validation.signal !== this.validationController.signal;
1162
+ if (validation.aborted || isStaleValidation) {
1163
+ return validation.result;
1164
+ }
1165
+ if (!validation.result.success) {
1166
+ this.options.onInvalid?.(this.errors, values, event);
1167
+ this.emitFormEvent({
1168
+ type: "form:invalid",
1169
+ errors: this.errors,
1170
+ values
1171
+ });
1172
+ return validation.result;
1173
+ }
1174
+ if (this.options.asyncValidators && Object.keys(this.options.asyncValidators).length > 0) {
1175
+ const asyncIssues = [];
1176
+ const flat = this.serializationMode === "typed" ? this.serialize() : values;
1177
+ const promises = [];
1178
+ for (const [name, validator] of Object.entries(
1179
+ this.options.asyncValidators
1180
+ )) {
1181
+ const rawValue = values[name] ?? flat[name];
1182
+ const fieldNode = this.registry.get(name);
1183
+ if (fieldNode) {
1184
+ fieldNode.setValidating(true);
1185
+ }
1186
+ promises.push(
1187
+ (async () => {
1188
+ try {
1189
+ const err = await validator(rawValue, {
1190
+ form: this.form,
1191
+ signal: validation.signal
1192
+ });
1193
+ if (err) {
1194
+ asyncIssues.push({ name, message: err });
1195
+ }
1196
+ } catch (error) {
1197
+ const msg = error instanceof Error ? error.message : String(error);
1198
+ asyncIssues.push({ name, message: msg });
1199
+ } finally {
1200
+ fieldNode?.setValidating(false);
1201
+ }
1202
+ })()
1203
+ );
1204
+ }
1205
+ await Promise.all(promises);
1206
+ if (asyncIssues.length > 0) {
1207
+ for (const issue of asyncIssues) {
1208
+ this.errors[issue.name] = issue.message;
1209
+ const node = this.registry.get(issue.name);
1210
+ node?.setError(issue.message);
1211
+ }
1212
+ this.applyNativeErrors();
1213
+ this.options.onInvalid?.(this.errors, values, event);
1214
+ this.emitFormEvent({
1215
+ type: "form:invalid",
1216
+ errors: this.errors,
1217
+ values
1218
+ });
1219
+ return {
1220
+ success: false,
1221
+ issues: asyncIssues
1222
+ };
1223
+ }
1224
+ }
1225
+ this.submitting = true;
1226
+ this.emitFormEvent({
1227
+ type: "form:submit",
1228
+ values: validation.result.values
1229
+ });
1230
+ try {
1231
+ await this.options.onSubmit?.(validation.result.values, event);
1232
+ } finally {
1233
+ this.submitting = false;
1234
+ }
1235
+ return validation.result;
1236
+ }
1237
+ async runValidation(values) {
1238
+ if (!this.form) {
1239
+ throw new Error("Runtime is not mounted.");
1240
+ }
1241
+ this.validationController?.abort();
1242
+ const controller = new AbortController();
1243
+ this.validationController = controller;
1244
+ this.validating = true;
1245
+ this.emitValidationEvent({
1246
+ type: "validation:start",
1247
+ values
1248
+ });
1249
+ this.errors = {};
1250
+ for (const field of this.registry.values()) {
1251
+ field.setError(this.errors[field.name]);
1252
+ }
1253
+ this.applyNativeErrors();
1254
+ const result = await this.pipeline.validate(values, {
1255
+ form: this.form,
1256
+ signal: controller.signal,
1257
+ fields: this.getValidationFields()
1258
+ });
1259
+ if (!controller.signal.aborted) {
1260
+ this.applyValidationResult(result, values);
1261
+ if (this.validationController === controller) {
1262
+ this.validating = false;
1263
+ }
1264
+ }
1265
+ return {
1266
+ result,
1267
+ signal: controller.signal,
1268
+ aborted: controller.signal.aborted
1269
+ };
1270
+ }
1271
+ handleFieldUpdate = (event) => {
1272
+ const element = toNativeFormElement(event.target);
1273
+ if (!element?.name || !this.form) return;
1274
+ if (event.type === "input") {
1275
+ this.processInputFlow(element);
1276
+ return;
1277
+ }
1278
+ if (event.type === "change") {
1279
+ this.processChangeFlow(element);
1280
+ return;
1281
+ }
1282
+ this.processUpdateFlow(element);
1283
+ };
1284
+ processInputFlow(element) {
1285
+ const snapshot = this.prepareFieldFlow(element);
1286
+ if (!snapshot) {
1287
+ return;
1288
+ }
1289
+ const { node, values, value } = snapshot;
1290
+ node.handleInput(value);
1291
+ const mode = this.options.mode ?? "onChange";
1292
+ const reValidateMode = this.options.reValidateMode ?? "onChange";
1293
+ const hasExistingError = Boolean(
1294
+ resolveFieldError(this.errors, element.name)
1295
+ );
1296
+ const shouldValidate = mode === "onChange" || mode === "all" || hasExistingError && reValidateMode === "onChange" || mode === "onTouched" && node.isTouched;
1297
+ if (shouldValidate) {
1298
+ this.triggerFieldValidation(node, values);
1299
+ }
1300
+ this.emitFieldEvent({
1301
+ type: "field:input",
1302
+ field: element.name,
1303
+ value,
1304
+ values
1305
+ });
1306
+ this.emitFieldEvent({
1307
+ type: "field:update",
1308
+ field: element.name,
1309
+ value,
1310
+ values
1311
+ });
1312
+ }
1313
+ processChangeFlow(element) {
1314
+ const snapshot = this.prepareFieldFlow(element);
1315
+ if (!snapshot) {
1316
+ return;
1317
+ }
1318
+ const { node, values, value } = snapshot;
1319
+ node.handleChange(value);
1320
+ const mode = this.options.mode ?? "onChange";
1321
+ const reValidateMode = this.options.reValidateMode ?? "onChange";
1322
+ const hasExistingError = Boolean(
1323
+ resolveFieldError(this.errors, element.name)
1324
+ );
1325
+ const isDiscreteControl = element instanceof HTMLSelectElement || element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio");
1326
+ const shouldValidate = mode === "onChange" || mode === "all" || isDiscreteControl || hasExistingError && reValidateMode === "onChange" || mode === "onTouched" && node.isTouched;
1327
+ if (shouldValidate) {
1328
+ this.triggerFieldValidation(node, values);
1329
+ }
1330
+ this.emitFieldEvent({
1331
+ type: "field:change",
1332
+ field: element.name,
1333
+ value,
1334
+ values
1335
+ });
1336
+ this.emitFieldEvent({
1337
+ type: "field:update",
1338
+ field: element.name,
1339
+ value,
1340
+ values
1341
+ });
1342
+ }
1343
+ processUpdateFlow(element) {
1344
+ const snapshot = this.prepareFieldFlow(element);
1345
+ if (!snapshot) {
1346
+ return;
1347
+ }
1348
+ const { node, values, value } = snapshot;
1349
+ node.updateValue(value);
1350
+ if (this.options.preserveUnmountedValues) {
1351
+ if (element.name !== "__proto__" && element.name !== "constructor" && element.name !== "prototype") {
1352
+ if (value !== void 0) {
1353
+ this.preservedValuesStore[element.name] = value;
1354
+ } else {
1355
+ delete this.preservedValuesStore[element.name];
1356
+ }
1357
+ }
1358
+ }
1359
+ const mode = this.options.mode ?? "onChange";
1360
+ const shouldValidate = mode === "onChange" || mode === "all" || mode === "onTouched" && node.isTouched;
1361
+ if (shouldValidate) {
1362
+ this.triggerFieldValidation(node, values);
1363
+ }
1364
+ this.emitFieldEvent({
1365
+ type: "field:update",
1366
+ field: element.name,
1367
+ value,
1368
+ values
1369
+ });
1370
+ }
1371
+ prepareFieldFlow(element) {
1372
+ this.ensureField(element);
1373
+ const values = this.extractCurrentValues();
1374
+ const value = values[element.name];
1375
+ const node = this.registry.get(element.name);
1376
+ if (!node) {
1377
+ return void 0;
1378
+ }
1379
+ return { node, values, value };
1380
+ }
1381
+ triggerFieldValidation(node, values, immediate = false) {
1382
+ const debounceMs = this.options.debounceMs ?? 0;
1383
+ if (!immediate && debounceMs > 0) {
1384
+ const existing = this.debounceTimers.get(node.name);
1385
+ if (existing) {
1386
+ clearTimeout(existing);
1387
+ }
1388
+ const timer = setTimeout(() => {
1389
+ this.debounceTimers.delete(node.name);
1390
+ void this.executeFieldValidation(node, values);
1391
+ }, debounceMs);
1392
+ this.debounceTimers.set(node.name, timer);
1393
+ } else {
1394
+ const existing = this.debounceTimers.get(node.name);
1395
+ if (existing) {
1396
+ clearTimeout(existing);
1397
+ this.debounceTimers.delete(node.name);
1398
+ }
1399
+ void this.executeFieldValidation(node, values);
1400
+ }
1401
+ }
1402
+ async executeFieldValidation(node, values) {
1403
+ const fieldName = node.name;
1404
+ const previousController = this.fieldValidationControllers.get(fieldName);
1405
+ if (previousController) {
1406
+ previousController.abort();
1407
+ this.fieldValidationControllers.delete(fieldName);
1408
+ }
1409
+ const fieldController = new AbortController();
1410
+ this.fieldValidationControllers.set(fieldName, fieldController);
1411
+ try {
1412
+ const validation = await this.runFieldValidation(node, values);
1413
+ const isStaleValidation = this.validationController !== void 0 && validation.signal !== this.validationController.signal;
1414
+ if (validation.aborted || fieldController.signal.aborted || isStaleValidation) {
1415
+ return;
1416
+ }
1417
+ if (!validation.result.success) {
1418
+ this.options.onInvalid?.(this.errors, values);
1419
+ return;
1420
+ }
1421
+ const asyncValidator = this.getAsyncValidator(fieldName);
1422
+ if (asyncValidator && !fieldController.signal.aborted) {
1423
+ node.setValidating(true);
1424
+ this.emitFieldEvent({
1425
+ type: "field:update",
1426
+ field: fieldName,
1427
+ value: values[fieldName],
1428
+ values
1429
+ });
1430
+ try {
1431
+ const context = {
1432
+ form: this.form,
1433
+ signal: fieldController.signal
1434
+ };
1435
+ const rawValue = values[fieldName];
1436
+ const asyncError = await asyncValidator(rawValue, context);
1437
+ if (!fieldController.signal.aborted) {
1438
+ if (asyncError) {
1439
+ this.errors[fieldName] = asyncError;
1440
+ node.setError(asyncError);
1441
+ this.options.onInvalid?.(this.errors, values);
1442
+ } else if (this.errors[fieldName]) {
1443
+ delete this.errors[fieldName];
1444
+ node.setError(void 0);
1445
+ }
1446
+ this.applyNativeErrors();
1447
+ }
1448
+ } catch (error) {
1449
+ if (!fieldController.signal.aborted) {
1450
+ const message = error instanceof Error ? error.message : String(error);
1451
+ this.errors[fieldName] = message;
1452
+ node.setError(message);
1453
+ this.applyNativeErrors();
1454
+ this.options.onInvalid?.(this.errors, values);
1455
+ }
1456
+ } finally {
1457
+ if (!fieldController.signal.aborted) {
1458
+ node.setValidating(false);
1459
+ this.emitValidationEvent({
1460
+ type: "validation:end",
1461
+ errors: { ...this.errors },
1462
+ values
1463
+ });
1464
+ this.emitFieldEvent({
1465
+ type: "field:update",
1466
+ field: fieldName,
1467
+ value: values[fieldName],
1468
+ values
1469
+ });
1470
+ }
1471
+ }
1472
+ }
1473
+ } finally {
1474
+ if (this.fieldValidationControllers.get(fieldName) === fieldController) {
1475
+ this.fieldValidationControllers.delete(fieldName);
1476
+ }
1477
+ }
1478
+ }
1479
+ handleFieldBlur = (event) => {
1480
+ const element = toNativeFormElement(event.target);
1481
+ if (!element?.name || !this.form) return;
1482
+ this.ensureField(element);
1483
+ const values = this.extractCurrentValues();
1484
+ const value = values[element.name];
1485
+ const node = this.registry.get(element.name);
1486
+ node?.handleBlur();
1487
+ const mode = this.options.mode ?? "onChange";
1488
+ const reValidateMode = this.options.reValidateMode ?? "onChange";
1489
+ const hasExistingError = Boolean(
1490
+ resolveFieldError(this.errors, element.name)
1491
+ );
1492
+ const hasPendingDebounce = this.debounceTimers.has(element.name);
1493
+ const shouldValidate = mode === "onBlur" || mode === "onTouched" || mode === "all" || hasExistingError && reValidateMode === "onBlur" || hasPendingDebounce;
1494
+ if (shouldValidate && node) {
1495
+ this.triggerFieldValidation(node, values, true);
1496
+ }
1497
+ this.emitFieldEvent({
1498
+ type: "field:blur",
1499
+ field: element.name,
1500
+ value,
1501
+ values
1502
+ });
1503
+ };
1504
+ async runFieldValidation(field, values) {
1505
+ if (!this.form) {
1506
+ throw new Error("Runtime is not mounted.");
1507
+ }
1508
+ this.validationController?.abort();
1509
+ const controller = new AbortController();
1510
+ this.validationController = controller;
1511
+ this.validating = true;
1512
+ this.emitValidationEvent({
1513
+ type: "validation:start",
1514
+ values
1515
+ });
1516
+ const context = {
1517
+ form: this.form,
1518
+ signal: controller.signal,
1519
+ fields: this.getValidationFields()
1520
+ };
1521
+ const activeFieldMeta = this.toValidationField(field);
1522
+ const supportsFieldScopedValidation = this.pipeline.capabilities?.supportsFieldScopedValidation ?? true;
1523
+ const result = supportsFieldScopedValidation ? await this.pipeline.validateField(activeFieldMeta, values, context) : await this.pipeline.validate(values, context);
1524
+ if (!controller.signal.aborted) {
1525
+ this.applyFieldValidationResult(field, result, values);
1526
+ if (this.validationController === controller) {
1527
+ this.validating = false;
1528
+ }
1529
+ }
1530
+ return {
1531
+ result,
1532
+ signal: controller.signal,
1533
+ aborted: controller.signal.aborted
1534
+ };
1535
+ }
1536
+ handleReset = () => {
1537
+ this.clearDebounceTimers();
1538
+ this.abortAllValidations();
1539
+ queueMicrotask(() => {
1540
+ if (!this.form) return;
1541
+ const values = this.extractCurrentValues();
1542
+ this.processResetFlow(values);
1543
+ });
1544
+ };
1545
+ processResetFlow(values) {
1546
+ for (const field of this.registry.values()) {
1547
+ field.handleReset(values[field.name]);
1548
+ this.emitFieldEvent({
1549
+ type: "field:reset",
1550
+ field: field.name,
1551
+ value: values[field.name],
1552
+ values
1553
+ });
1554
+ }
1555
+ this.errors = {};
1556
+ this.preservedValuesStore = /* @__PURE__ */ Object.create(null);
1557
+ this.applyNativeErrors();
1558
+ this.emitFormEvent({
1559
+ type: "form:reset",
1560
+ values
1561
+ });
1562
+ }
1563
+ getFieldState(name) {
1564
+ let node = this.registry.get(name);
1565
+ if (!node) {
1566
+ const normalized = normalizePath(name);
1567
+ for (const field of this.registry.values()) {
1568
+ if (normalizePath(field.name) === normalized) {
1569
+ node = field;
1570
+ break;
1571
+ }
1572
+ }
1573
+ }
1574
+ return node?.getState();
1575
+ }
1576
+ getFieldStates() {
1577
+ return this.registry.values().map((f) => f.getState());
1578
+ }
1579
+ setOptions(options) {
1580
+ Object.assign(this.options, options);
1581
+ if (options.schema !== void 0 || options.validator !== void 0 || options.pipeline !== void 0) {
1582
+ this.pipeline = options.pipeline ?? createValidationPipeline({
1583
+ schema: options.schema,
1584
+ validator: options.validator
1585
+ });
1586
+ }
1587
+ }
1588
+ removeArrayItem(arrayName, index) {
1589
+ const nextErrors = {};
1590
+ let changed = false;
1591
+ for (const [path, error] of Object.entries(this.errors)) {
1592
+ const match = matchArrayItemPath(path, arrayName);
1593
+ if (!match) {
1594
+ nextErrors[path] = error;
1595
+ continue;
1596
+ }
1597
+ if (match.index === index) {
1598
+ changed = true;
1599
+ continue;
1600
+ }
1601
+ if (match.index > index) {
1602
+ const shiftedPath = shiftArrayItemIndex(path, arrayName, index + 1, -1);
1603
+ nextErrors[shiftedPath] = error;
1604
+ changed = true;
1605
+ } else {
1606
+ nextErrors[path] = error;
1607
+ }
1608
+ }
1609
+ if (changed) {
1610
+ this.errors = nextErrors;
1611
+ this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
1612
+ this.emitValidationEvent({
1613
+ type: "validation:end",
1614
+ errors: { ...this.errors },
1615
+ values: this.extractCurrentValues()
1616
+ });
1617
+ }
1618
+ if (this.options.preserveUnmountedValues) {
1619
+ const nextPreserved = {};
1620
+ let pChanged = false;
1621
+ for (const [path, val] of Object.entries(this.preservedValuesStore)) {
1622
+ const match = matchArrayItemPath(path, arrayName);
1623
+ if (!match) {
1624
+ nextPreserved[path] = val;
1625
+ continue;
1626
+ }
1627
+ if (match.index === index) {
1628
+ pChanged = true;
1629
+ continue;
1630
+ }
1631
+ if (match.index > index) {
1632
+ const shiftedPath = shiftArrayItemIndex(path, arrayName, index + 1, -1);
1633
+ nextPreserved[shiftedPath] = val;
1634
+ pChanged = true;
1635
+ } else {
1636
+ nextPreserved[path] = val;
1637
+ }
1638
+ }
1639
+ if (pChanged) {
1640
+ this.preservedValuesStore = nextPreserved;
1641
+ }
1642
+ }
1643
+ }
1644
+ swapArrayItems(arrayName, indexA, indexB) {
1645
+ if (indexA === indexB) return;
1646
+ const nextErrors = {};
1647
+ let changed = false;
1648
+ for (const [path, error] of Object.entries(this.errors)) {
1649
+ const match = matchArrayItemPath(path, arrayName);
1650
+ if (!match) {
1651
+ nextErrors[path] = error;
1652
+ continue;
1653
+ }
1654
+ if (match.index === indexA) {
1655
+ const newPath = remapArrayItemIndex(path, arrayName, indexA, indexB);
1656
+ nextErrors[newPath] = error;
1657
+ changed = true;
1658
+ } else if (match.index === indexB) {
1659
+ const newPath = remapArrayItemIndex(path, arrayName, indexB, indexA);
1660
+ nextErrors[newPath] = error;
1661
+ changed = true;
1662
+ } else {
1663
+ nextErrors[path] = error;
1664
+ }
1665
+ }
1666
+ if (changed) {
1667
+ this.errors = nextErrors;
1668
+ this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
1669
+ this.emitValidationEvent({
1670
+ type: "validation:end",
1671
+ errors: { ...this.errors },
1672
+ values: this.extractCurrentValues()
1673
+ });
1674
+ }
1675
+ if (this.options.preserveUnmountedValues) {
1676
+ const nextPreserved = {};
1677
+ let pChanged = false;
1678
+ for (const [path, val] of Object.entries(this.preservedValuesStore)) {
1679
+ const match = matchArrayItemPath(path, arrayName);
1680
+ if (!match) {
1681
+ nextPreserved[path] = val;
1682
+ continue;
1683
+ }
1684
+ if (match.index === indexA) {
1685
+ const newPath = remapArrayItemIndex(path, arrayName, indexA, indexB);
1686
+ nextPreserved[newPath] = val;
1687
+ pChanged = true;
1688
+ } else if (match.index === indexB) {
1689
+ const newPath = remapArrayItemIndex(path, arrayName, indexB, indexA);
1690
+ nextPreserved[newPath] = val;
1691
+ pChanged = true;
1692
+ } else {
1693
+ nextPreserved[path] = val;
1694
+ }
1695
+ }
1696
+ if (pChanged) {
1697
+ this.preservedValuesStore = nextPreserved;
1698
+ }
1699
+ }
1700
+ }
1701
+ moveArrayItem(arrayName, fromIndex, toIndex) {
1702
+ if (fromIndex === toIndex) return;
1703
+ const nextErrors = {};
1704
+ let changed = false;
1705
+ for (const [path, error] of Object.entries(this.errors)) {
1706
+ const match = matchArrayItemPath(path, arrayName);
1707
+ if (!match) {
1708
+ nextErrors[path] = error;
1709
+ continue;
1710
+ }
1711
+ const idx = match.index;
1712
+ if (idx === fromIndex) {
1713
+ const newPath = remapArrayItemIndex(path, arrayName, fromIndex, toIndex);
1714
+ nextErrors[newPath] = error;
1715
+ changed = true;
1716
+ } else if (fromIndex < toIndex && idx > fromIndex && idx <= toIndex) {
1717
+ const newPath = remapArrayItemIndex(path, arrayName, idx, idx - 1);
1718
+ nextErrors[newPath] = error;
1719
+ changed = true;
1720
+ } else if (fromIndex > toIndex && idx >= toIndex && idx < fromIndex) {
1721
+ const newPath = remapArrayItemIndex(path, arrayName, idx, idx + 1);
1722
+ nextErrors[newPath] = error;
1723
+ changed = true;
1724
+ } else {
1725
+ nextErrors[path] = error;
1726
+ }
1727
+ }
1728
+ if (changed) {
1729
+ this.errors = nextErrors;
1730
+ this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
1731
+ this.emitValidationEvent({
1732
+ type: "validation:end",
1733
+ errors: { ...this.errors },
1734
+ values: this.extractCurrentValues()
1735
+ });
1736
+ }
1737
+ if (this.options.preserveUnmountedValues) {
1738
+ const nextPreserved = {};
1739
+ let pChanged = false;
1740
+ for (const [path, val] of Object.entries(this.preservedValuesStore)) {
1741
+ const match = matchArrayItemPath(path, arrayName);
1742
+ if (!match) {
1743
+ nextPreserved[path] = val;
1744
+ continue;
1745
+ }
1746
+ const idx = match.index;
1747
+ if (idx === fromIndex) {
1748
+ const newPath = remapArrayItemIndex(path, arrayName, fromIndex, toIndex);
1749
+ nextPreserved[newPath] = val;
1750
+ pChanged = true;
1751
+ } else if (fromIndex < toIndex && idx > fromIndex && idx <= toIndex) {
1752
+ const newPath = remapArrayItemIndex(path, arrayName, idx, idx - 1);
1753
+ nextPreserved[newPath] = val;
1754
+ pChanged = true;
1755
+ } else if (fromIndex > toIndex && idx >= toIndex && idx < fromIndex) {
1756
+ const newPath = remapArrayItemIndex(path, arrayName, idx, idx + 1);
1757
+ nextPreserved[newPath] = val;
1758
+ pChanged = true;
1759
+ } else {
1760
+ nextPreserved[path] = val;
1761
+ }
1762
+ }
1763
+ if (pChanged) {
1764
+ this.preservedValuesStore = nextPreserved;
1765
+ }
1766
+ }
1767
+ }
1768
+ insertArrayItem(arrayName, index) {
1769
+ const nextErrors = {};
1770
+ let changed = false;
1771
+ for (const [path, error] of Object.entries(this.errors)) {
1772
+ const match = matchArrayItemPath(path, arrayName);
1773
+ if (!match) {
1774
+ nextErrors[path] = error;
1775
+ continue;
1776
+ }
1777
+ if (match.index >= index) {
1778
+ const shiftedPath = shiftArrayItemIndex(path, arrayName, index, 1);
1779
+ nextErrors[shiftedPath] = error;
1780
+ changed = true;
1781
+ } else {
1782
+ nextErrors[path] = error;
1783
+ }
1784
+ }
1785
+ if (changed) {
1786
+ this.errors = nextErrors;
1787
+ this.fieldErrorRenderer.applyErrors(this.registry.values(), this.errors);
1788
+ this.emitValidationEvent({
1789
+ type: "validation:end",
1790
+ errors: { ...this.errors },
1791
+ values: this.extractCurrentValues()
1792
+ });
1793
+ }
1794
+ if (this.options.preserveUnmountedValues) {
1795
+ const nextPreserved = {};
1796
+ let pChanged = false;
1797
+ for (const [path, val] of Object.entries(this.preservedValuesStore)) {
1798
+ const match = matchArrayItemPath(path, arrayName);
1799
+ if (!match) {
1800
+ nextPreserved[path] = val;
1801
+ continue;
1802
+ }
1803
+ if (match.index >= index) {
1804
+ const shiftedPath = shiftArrayItemIndex(path, arrayName, index, 1);
1805
+ nextPreserved[shiftedPath] = val;
1806
+ pChanged = true;
1807
+ } else {
1808
+ nextPreserved[path] = val;
1809
+ }
1810
+ }
1811
+ if (pChanged) {
1812
+ this.preservedValuesStore = nextPreserved;
1813
+ }
1814
+ }
1815
+ }
1816
+ getFormState() {
1817
+ const fields = this.getFieldStates();
1818
+ return {
1819
+ dirty: fields.some((field) => field.dirty),
1820
+ touched: fields.some((field) => field.touched),
1821
+ valid: Object.keys(this.errors).length === 0,
1822
+ submitting: this.submitting,
1823
+ validating: this.validating || fields.some((field) => field.validating),
1824
+ errors: { ...this.errors },
1825
+ fields,
1826
+ values: this.extractCurrentValues()
1827
+ };
1828
+ }
1829
+ ensureField(element) {
1830
+ if (this.registry.has(element.name)) {
1831
+ return;
1832
+ }
1833
+ this.registry.register(new FieldNode(element));
1834
+ const node = this.registry.get(element.name);
1835
+ node.register(this.getFieldValue(element.name));
1836
+ }
1837
+ getFieldValue(name) {
1838
+ if (!this.form) {
1839
+ if (this.options.preserveUnmountedValues) {
1840
+ return this.preservedValuesStore[name];
1841
+ }
1842
+ return void 0;
1843
+ }
1844
+ if (this.options.preserveUnmountedValues && !this.isSyncingFields) {
1845
+ this.syncFields();
1846
+ }
1847
+ let node = this.registry.get(name);
1848
+ if (!node) {
1849
+ const control = this.form.elements.namedItem(name);
1850
+ if (control instanceof HTMLInputElement || control instanceof HTMLSelectElement || control instanceof HTMLTextAreaElement) {
1851
+ this.ensureField(control);
1852
+ node = this.registry.get(name);
1853
+ }
1854
+ }
1855
+ if (!node) {
1856
+ const normalizedTarget = normalizePath(name);
1857
+ for (const field of this.registry.values()) {
1858
+ if (normalizePath(field.name) === normalizedTarget) {
1859
+ node = field;
1860
+ break;
1861
+ }
1862
+ }
1863
+ }
1864
+ if (!node) {
1865
+ if (this.options.preserveUnmountedValues) {
1866
+ if (name in this.preservedValuesStore) {
1867
+ return this.preservedValuesStore[name];
1868
+ }
1869
+ const normalizedTarget = normalizePath(name);
1870
+ for (const [key, val] of Object.entries(this.preservedValuesStore)) {
1871
+ if (normalizePath(key) === normalizedTarget) {
1872
+ return val;
1873
+ }
1874
+ }
1875
+ }
1876
+ if (this.options.nesting === "nested") {
1877
+ const nested = this.extractCurrentValues();
1878
+ const resolved = getByPath(nested, name);
1879
+ if (resolved !== void 0) {
1880
+ return resolved;
1881
+ }
1882
+ }
1883
+ return void 0;
1884
+ }
1885
+ if (node.element instanceof HTMLInputElement && node.element.type === "radio") {
1886
+ const item = this.form.elements.namedItem(name);
1887
+ if (item) {
1888
+ if ("value" in item && typeof item.value === "string" && item.value !== "") {
1889
+ return item.value;
1890
+ }
1891
+ if ("length" in item) {
1892
+ for (const el of Array.from(item)) {
1893
+ if (el instanceof HTMLInputElement && el.checked) {
1894
+ return el.value;
1895
+ }
1896
+ }
1897
+ }
1898
+ }
1899
+ return node.element.checked ? node.element.value : void 0;
1900
+ }
1901
+ if (node.element instanceof HTMLInputElement && node.element.type === "checkbox") {
1902
+ const item = this.form.elements.namedItem(name);
1903
+ if (item && ("length" in item || item instanceof RadioNodeList) && item.length > 1) {
1904
+ const checkedValues = [];
1905
+ for (const el of Array.from(item)) {
1906
+ if (el instanceof HTMLInputElement && el.checked) {
1907
+ checkedValues.push(el.value || "on");
1908
+ }
1909
+ }
1910
+ return checkedValues;
1911
+ }
1912
+ return node.element.checked;
1913
+ }
1914
+ return node.value ?? void 0;
1915
+ }
1916
+ getValidationFields() {
1917
+ return this.registry.values().map((field) => this.toValidationField(field));
1918
+ }
1919
+ toValidationField(field) {
1920
+ return {
1921
+ name: field.name,
1922
+ validationMessage: field.element.validationMessage
1923
+ };
1924
+ }
1925
+ syncFields() {
1926
+ if (!this.form || this.isSyncingFields) {
1927
+ return;
1928
+ }
1929
+ this.isSyncingFields = true;
1930
+ try {
1931
+ const previousFields = this.registry.values();
1932
+ const previousFieldMap = new Map(
1933
+ previousFields.map((field) => [field.name, field])
1934
+ );
1935
+ const activeControlNames = /* @__PURE__ */ new Set();
1936
+ for (const control of Array.from(this.form.elements)) {
1937
+ if ((control instanceof HTMLInputElement || control instanceof HTMLSelectElement || control instanceof HTMLTextAreaElement) && control.name) {
1938
+ activeControlNames.add(control.name);
1939
+ }
1940
+ }
1941
+ for (const field of previousFields) {
1942
+ if (!activeControlNames.has(field.name)) {
1943
+ if (this.options.preserveUnmountedValues) {
1944
+ const val = field.value;
1945
+ if (field.name !== "__proto__" && field.name !== "constructor" && field.name !== "prototype") {
1946
+ if (val !== void 0) {
1947
+ this.preservedValuesStore[field.name] = val;
1948
+ } else {
1949
+ delete this.preservedValuesStore[field.name];
1950
+ }
1951
+ }
1952
+ }
1953
+ this.fieldErrorRenderer.applyErrors([field], {});
1954
+ delete this.errors[field.name];
1955
+ }
1956
+ }
1957
+ this.discovery.discover(this.form, this.registry);
1958
+ const values = this.extractCurrentValues();
1959
+ for (const field of this.registry.values()) {
1960
+ if (!previousFieldMap.has(field.name) || previousFieldMap.get(field.name) !== field) {
1961
+ field.register(values[field.name]);
1962
+ }
1963
+ }
1964
+ } finally {
1965
+ this.isSyncingFields = false;
1966
+ }
1967
+ }
1968
+ handleMutation = (mutations) => {
1969
+ if (!mutations.some(isRuntimeMutationRelevant)) {
1970
+ return;
1971
+ }
1972
+ this.syncFields();
1973
+ };
1974
+ applyFieldValidationResult(targetField, result, values) {
1975
+ const issueMap = result.success ? {} : issuesToErrors(result.issues);
1976
+ const targetName = targetField.name;
1977
+ const targetIssue = resolveFieldError(issueMap, targetName);
1978
+ if (targetIssue) {
1979
+ this.errors[targetName] = targetIssue;
1980
+ targetField.setError(targetIssue);
1981
+ } else {
1982
+ delete this.errors[targetName];
1983
+ const normalizedTarget = normalizePath(targetName);
1984
+ if (this.errors[normalizedTarget]) {
1985
+ delete this.errors[normalizedTarget];
1986
+ }
1987
+ targetField.setError(void 0);
1988
+ }
1989
+ for (const otherField of this.registry.values()) {
1990
+ if (otherField === targetField) continue;
1991
+ const name = otherField.name;
1992
+ const hadError = Boolean(resolveFieldError(this.errors, name));
1993
+ const isTouched = otherField.isTouched;
1994
+ if (hadError || isTouched) {
1995
+ const issue = resolveFieldError(issueMap, name);
1996
+ if (issue) {
1997
+ this.errors[name] = issue;
1998
+ otherField.setError(issue);
1999
+ } else {
2000
+ delete this.errors[name];
2001
+ const normalizedOther = normalizePath(name);
2002
+ if (this.errors[normalizedOther]) {
2003
+ delete this.errors[normalizedOther];
2004
+ }
2005
+ otherField.setError(void 0);
2006
+ }
2007
+ }
2008
+ }
2009
+ this.fieldErrorRenderer.normalizeFields(this.registry.values());
2010
+ this.applyNativeErrors();
2011
+ this.emitValidationEvent({
2012
+ type: "validation:end",
2013
+ errors: { ...this.errors },
2014
+ values
2015
+ });
2016
+ }
2017
+ applyValidationResult(result, values) {
2018
+ this.fieldErrorRenderer.normalizeFields(this.registry.values());
2019
+ this.errors = result.success ? {} : issuesToErrors(result.issues);
2020
+ for (const field of this.registry.values()) {
2021
+ const error = resolveFieldError(this.errors, field.name);
2022
+ if (error && this.errors[field.name] === void 0) {
2023
+ this.errors[field.name] = error;
2024
+ }
2025
+ field.setError(error);
2026
+ }
2027
+ this.applyNativeErrors();
2028
+ this.emitValidationEvent({
2029
+ type: "validation:end",
2030
+ errors: this.errors,
2031
+ values
2032
+ });
2033
+ }
2034
+ applyNativeErrors() {
2035
+ const observer = this.observer;
2036
+ if (observer) {
2037
+ observer.disconnect();
2038
+ }
2039
+ const renderDomErrors = this.options.renderDomErrors ?? true;
2040
+ this.fieldErrorRenderer.applyErrors(
2041
+ this.registry.values(),
2042
+ this.errors,
2043
+ renderDomErrors
2044
+ );
2045
+ if (observer && this.form) {
2046
+ observer.observe(this.form, runtimeMutationObserverOptions);
2047
+ }
2048
+ }
2049
+ };
2050
+ function issuesToErrors(issues) {
2051
+ const errors = {};
2052
+ for (const issue of issues) {
2053
+ errors[issue.name] ??= issue.message;
2054
+ }
2055
+ return errors;
2056
+ }
2057
+ function resolveFieldError(errors, name) {
2058
+ if (errors[name] !== void 0) {
2059
+ return errors[name];
2060
+ }
2061
+ const normalized = normalizePath(name);
2062
+ if (errors[normalized] !== void 0) {
2063
+ return errors[normalized];
2064
+ }
2065
+ for (const [key, msg] of Object.entries(errors)) {
2066
+ if (normalizePath(key) === normalized) {
2067
+ return msg;
2068
+ }
2069
+ }
2070
+ return void 0;
2071
+ }
2072
+ function toNativeFormElement(target) {
2073
+ if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) {
2074
+ return target;
2075
+ }
2076
+ return null;
2077
+ }
2078
+ // Annotate the CommonJS export names for ESM import in node:
2079
+ 0 && (module.exports = {
2080
+ FieldNode,
2081
+ Runtime,
2082
+ Serializer,
2083
+ arrayItemPath,
2084
+ createValidationPipeline,
2085
+ createValidator,
2086
+ formPath,
2087
+ getByPath,
2088
+ isArrayItemPath,
2089
+ matchArrayItemPath,
2090
+ normalizePath,
2091
+ parseArrayItemPath,
2092
+ remapArrayItemIndex,
2093
+ setByPath,
2094
+ shiftArrayItemIndex,
2095
+ splitPath,
2096
+ toNestedValues
2097
+ });