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