attaform 0.16.4 → 0.17.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 (84) hide show
  1. package/README.md +4 -2
  2. package/dist/chunks/devtools.cjs +19 -12
  3. package/dist/chunks/devtools.cjs.map +1 -1
  4. package/dist/chunks/devtools.mjs +19 -12
  5. package/dist/chunks/devtools.mjs.map +1 -1
  6. package/dist/chunks/indexeddb.cjs +1 -1
  7. package/dist/chunks/indexeddb.mjs +1 -1
  8. package/dist/chunks/local-storage.cjs +1 -1
  9. package/dist/chunks/local-storage.mjs +1 -1
  10. package/dist/chunks/session-storage.cjs +1 -1
  11. package/dist/chunks/session-storage.mjs +1 -1
  12. package/dist/index.cjs +26 -7
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +52 -9
  15. package/dist/index.d.mts +52 -9
  16. package/dist/index.d.ts +52 -9
  17. package/dist/index.mjs +28 -9
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/nuxt.d.cts +1 -1
  20. package/dist/nuxt.d.mts +1 -1
  21. package/dist/nuxt.d.ts +1 -1
  22. package/dist/runtime/plugins/attaform.cjs +3 -3
  23. package/dist/runtime/plugins/attaform.cjs.map +1 -1
  24. package/dist/runtime/plugins/attaform.mjs +3 -3
  25. package/dist/runtime/plugins/attaform.mjs.map +1 -1
  26. package/dist/shared/{attaform.CMRmwGDt.d.cts → attaform.B1jvxsOF.d.mts} +1 -1
  27. package/dist/shared/{attaform.DyV1O4tI.mjs → attaform.B3ZaPIzS.mjs} +1436 -391
  28. package/dist/shared/attaform.B3ZaPIzS.mjs.map +1 -0
  29. package/dist/shared/{attaform.Dd_pWnmn.cjs → attaform.B5qiXQwN.cjs} +59 -10
  30. package/dist/shared/attaform.B5qiXQwN.cjs.map +1 -0
  31. package/dist/shared/{attaform.CIwZtbGV.cjs → attaform.BBM2muQ9.cjs} +2 -2
  32. package/dist/shared/{attaform.CIwZtbGV.cjs.map → attaform.BBM2muQ9.cjs.map} +1 -1
  33. package/dist/shared/{attaform.keLBaHB6.cjs → attaform.BV40t5y2.cjs} +240 -115
  34. package/dist/shared/attaform.BV40t5y2.cjs.map +1 -0
  35. package/dist/shared/attaform.C0iFnTN0.d.ts +165 -0
  36. package/dist/shared/{attaform.CXMOheyZ.d.mts → attaform.C6qzEdIM.d.cts} +1 -1
  37. package/dist/shared/{attaform.CJttVxRj.cjs → attaform.C8LVFVVe.cjs} +2 -2
  38. package/dist/shared/{attaform.CJttVxRj.cjs.map → attaform.C8LVFVVe.cjs.map} +1 -1
  39. package/dist/shared/attaform.CHorcsIU.d.cts +165 -0
  40. package/dist/shared/{attaform.BfMxsfmE.mjs → attaform.CIEQgJnM.mjs} +143 -78
  41. package/dist/shared/attaform.CIEQgJnM.mjs.map +1 -0
  42. package/dist/shared/{attaform.CCQkY4Ta.d.ts → attaform.CTwNcpLE.d.ts} +1 -1
  43. package/dist/shared/{attaform.UA19EF3J.mjs → attaform.CVCmBKZX.mjs} +59 -10
  44. package/dist/shared/attaform.CVCmBKZX.mjs.map +1 -0
  45. package/dist/shared/{attaform.CU3JperC.d.cts → attaform.C_5aB6EQ.d.cts} +657 -135
  46. package/dist/shared/{attaform.CU3JperC.d.mts → attaform.C_5aB6EQ.d.mts} +657 -135
  47. package/dist/shared/{attaform.CU3JperC.d.ts → attaform.C_5aB6EQ.d.ts} +657 -135
  48. package/dist/shared/{attaform.fegmBJaq.cjs → attaform.Cer8JO_P.cjs} +1435 -389
  49. package/dist/shared/attaform.Cer8JO_P.cjs.map +1 -0
  50. package/dist/shared/{attaform.g7rfuXdz.mjs → attaform.CpERWz3u.mjs} +240 -115
  51. package/dist/shared/attaform.CpERWz3u.mjs.map +1 -0
  52. package/dist/shared/attaform.CuE-bS1C.d.mts +165 -0
  53. package/dist/shared/{attaform.rIRYSUI1.cjs → attaform.Dee2rU1P.cjs} +145 -77
  54. package/dist/shared/attaform.Dee2rU1P.cjs.map +1 -0
  55. package/dist/shared/{attaform.CINUMjPq.mjs → attaform.Vo-Kft0t.mjs} +2 -2
  56. package/dist/shared/{attaform.CINUMjPq.mjs.map → attaform.Vo-Kft0t.mjs.map} +1 -1
  57. package/dist/shared/{attaform.DZRj9s0s.mjs → attaform.h1sq3BFu.mjs} +2 -2
  58. package/dist/shared/{attaform.DZRj9s0s.mjs.map → attaform.h1sq3BFu.mjs.map} +1 -1
  59. package/dist/zod-v3.cjs +3 -3
  60. package/dist/zod-v3.d.cts +27 -5
  61. package/dist/zod-v3.d.mts +27 -5
  62. package/dist/zod-v3.d.ts +27 -5
  63. package/dist/zod-v3.mjs +3 -3
  64. package/dist/zod-v4.cjs +3 -3
  65. package/dist/zod-v4.d.cts +16 -42
  66. package/dist/zod-v4.d.mts +16 -42
  67. package/dist/zod-v4.d.ts +16 -42
  68. package/dist/zod-v4.mjs +3 -3
  69. package/dist/zod.cjs +4 -4
  70. package/dist/zod.cjs.map +1 -1
  71. package/dist/zod.d.cts +7 -5
  72. package/dist/zod.d.mts +7 -5
  73. package/dist/zod.d.ts +7 -5
  74. package/dist/zod.mjs +5 -5
  75. package/dist/zod.mjs.map +1 -1
  76. package/package.json +6 -11
  77. package/dist/shared/attaform.BfMxsfmE.mjs.map +0 -1
  78. package/dist/shared/attaform.Dd_pWnmn.cjs.map +0 -1
  79. package/dist/shared/attaform.DyV1O4tI.mjs.map +0 -1
  80. package/dist/shared/attaform.UA19EF3J.mjs.map +0 -1
  81. package/dist/shared/attaform.fegmBJaq.cjs.map +0 -1
  82. package/dist/shared/attaform.g7rfuXdz.mjs.map +0 -1
  83. package/dist/shared/attaform.keLBaHB6.cjs.map +0 -1
  84. package/dist/shared/attaform.rIRYSUI1.cjs.map +0 -1
@@ -1,173 +1,6 @@
1
- import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, reactive, watch, markRaw, shallowRef, getCurrentInstance, provide, useId, toRaw, inject } from 'vue';
2
- import { s as segmentsForPathKey, i as isPathPrefix, F as FORM_ERRORS_PATH_KEY, c as canonicalizePath, R as ROOT_PATH, b as FORM_ERRORS_PATH } from './attaform.DZRj9s0s.mjs';
3
- import { _ as __DEV__, e as SubmitErrorHandlerError, A as AnonPersistError, m as captureUserCallSite, n as enforceSensitiveCheck, o as createPersistOptInRegistry, b as InvalidUseFormConfigError, p as ensureAttaformInstalled, l as useRegistry, q as kFormContext, r as kFormInstanceId, d as ReservedFormKeyError } from './attaform.BfMxsfmE.mjs';
4
-
5
- function isDescendable(value) {
6
- if (value === null || typeof value !== "object") return false;
7
- if (Array.isArray(value)) return true;
8
- const proto = Object.getPrototypeOf(value);
9
- return proto === null || proto === Object.prototype;
10
- }
11
- function appendSegment(prefix, segment) {
12
- const next = new Array(prefix.length + 1);
13
- for (let i = 0; i < prefix.length; i++) {
14
- const s = prefix[i];
15
- next[i] = s;
16
- }
17
- next[prefix.length] = segment;
18
- return next;
19
- }
20
- function diffAndApply(oldValue, newValue, prefix, visit) {
21
- if (Object.is(oldValue, newValue)) return;
22
- const oldIsDescendable = isDescendable(oldValue);
23
- const newIsDescendable = isDescendable(newValue);
24
- if (oldValue === void 0 && newIsDescendable) {
25
- if (Array.isArray(newValue)) {
26
- for (let i = 0; i < newValue.length; i++) {
27
- diffAndApply(void 0, newValue[i], appendSegment(prefix, i), visit);
28
- }
29
- } else {
30
- const rec = newValue;
31
- for (const k of Object.keys(rec)) {
32
- diffAndApply(void 0, rec[k], appendSegment(prefix, k), visit);
33
- }
34
- }
35
- return;
36
- }
37
- if (oldIsDescendable && newValue === void 0) {
38
- if (Array.isArray(oldValue)) {
39
- for (let i = 0; i < oldValue.length; i++) {
40
- diffAndApply(oldValue[i], void 0, appendSegment(prefix, i), visit);
41
- }
42
- } else {
43
- const rec = oldValue;
44
- for (const k of Object.keys(rec)) {
45
- diffAndApply(rec[k], void 0, appendSegment(prefix, k), visit);
46
- }
47
- }
48
- return;
49
- }
50
- if (oldIsDescendable && newIsDescendable) {
51
- const oldIsArray = Array.isArray(oldValue);
52
- const newIsArray = Array.isArray(newValue);
53
- if (oldIsArray && newIsArray) {
54
- const oldArr = oldValue;
55
- const newArr = newValue;
56
- const max = Math.max(oldArr.length, newArr.length);
57
- for (let i = 0; i < max; i++) {
58
- diffAndApply(oldArr[i], newArr[i], appendSegment(prefix, i), visit);
59
- }
60
- return;
61
- }
62
- if (!oldIsArray && !newIsArray) {
63
- const oldRec = oldValue;
64
- const newRec = newValue;
65
- const seen = /* @__PURE__ */ new Set();
66
- for (const k of Object.keys(oldRec)) {
67
- seen.add(k);
68
- diffAndApply(oldRec[k], newRec[k], appendSegment(prefix, k), visit);
69
- }
70
- for (const k of Object.keys(newRec)) {
71
- if (seen.has(k)) continue;
72
- diffAndApply(oldRec[k], newRec[k], appendSegment(prefix, k), visit);
73
- }
74
- return;
75
- }
76
- visit({ kind: "changed", path: prefix, oldValue, newValue });
77
- return;
78
- }
79
- if (oldIsDescendable && !newIsDescendable) {
80
- visit({ kind: "changed", path: prefix, oldValue, newValue });
81
- return;
82
- }
83
- if (!oldIsDescendable && newIsDescendable) {
84
- visit({ kind: "changed", path: prefix, oldValue, newValue });
85
- return;
86
- }
87
- if (oldValue === void 0) {
88
- visit({ kind: "added", path: prefix, newValue });
89
- return;
90
- }
91
- if (newValue === void 0) {
92
- visit({ kind: "removed", path: prefix, oldValue });
93
- return;
94
- }
95
- visit({ kind: "changed", path: prefix, oldValue, newValue });
96
- }
97
- function applyChangedKeys(target, source) {
98
- if (!isDescendable(target) || !isDescendable(source)) return false;
99
- const targetIsArray = Array.isArray(target);
100
- const sourceIsArray = Array.isArray(source);
101
- if (targetIsArray !== sourceIsArray) return false;
102
- const ROOT_SENTINEL = Symbol.for("attaform.applyChangedKeys.rootMismatch");
103
- const changedFirstSegments = /* @__PURE__ */ new Set();
104
- diffAndApply(target, source, [], (patch) => {
105
- if (patch.path.length === 0) {
106
- changedFirstSegments.add(ROOT_SENTINEL);
107
- return;
108
- }
109
- changedFirstSegments.add(patch.path[0]);
110
- });
111
- if (changedFirstSegments.has(ROOT_SENTINEL)) return false;
112
- if (targetIsArray) {
113
- const t = target;
114
- const s = source;
115
- if (t.length > s.length) t.length = s.length;
116
- for (const idx of changedFirstSegments) {
117
- if (typeof idx === "symbol") continue;
118
- const i = typeof idx === "number" ? idx : Number(idx);
119
- t[i] = s[i];
120
- }
121
- } else {
122
- const t = target;
123
- const s = source;
124
- const sourceKeys = new Set(Object.keys(s));
125
- for (const k of Object.keys(t)) {
126
- if (!sourceKeys.has(k)) delete t[k];
127
- }
128
- for (const k of changedFirstSegments) {
129
- if (typeof k === "symbol") continue;
130
- t[String(k)] = s[String(k)];
131
- }
132
- }
133
- return true;
134
- }
135
- function structuralSnapshot(value) {
136
- if (!isDescendable(value)) return value;
137
- if (Array.isArray(value)) {
138
- const out2 = new Array(value.length);
139
- for (let i = 0; i < value.length; i++) {
140
- out2[i] = structuralSnapshot(value[i]);
141
- }
142
- return out2;
143
- }
144
- const src = value;
145
- const out = {};
146
- for (const k of Object.keys(src)) {
147
- out[k] = structuralSnapshot(src[k]);
148
- }
149
- return out;
150
- }
151
-
152
- const EMPTY_RESOLVED_FIELD_META = Object.freeze({
153
- label: "",
154
- description: void 0,
155
- placeholder: void 0,
156
- meta: Object.freeze({})
157
- });
158
-
159
- function humanize(segment) {
160
- if (typeof segment === "number") return "";
161
- const str = String(segment);
162
- if (str.length === 0) return "";
163
- if (/^\d+$/.test(str)) return "";
164
- const tokens = str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().split(" ").filter((part) => part.length > 0);
165
- if (tokens.length === 0) return "";
166
- return tokens.map((part) => {
167
- const head = part[0];
168
- return head === void 0 ? part : head.toUpperCase() + part.slice(1).toLowerCase();
169
- }).join(" ");
170
- }
1
+ import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, reactive, watch, markRaw, toRaw, shallowRef, getCurrentInstance, provide, useId, inject } from 'vue';
2
+ import { _ as __DEV__, e as SubmitErrorHandlerError, A as AnonPersistError, l as captureUserCallSite, m as enforceSensitiveCheck, s as segmentMatchesSensitive, n as isSensitivePath, o as createPersistOptInRegistry, b as InvalidUseFormConfigError, p as ensureAttaformInstalled, j as useRegistry, q as kFormContext, r as kFormInstanceId, d as ReservedFormKeyError, t as createIsSensitivePath, w as createSegmentMatchesSensitive } from './attaform.CIEQgJnM.mjs';
3
+ import { c as canonicalizePath, s as segmentsForPathKey, i as isPathPrefix, F as FORM_ERRORS_PATH_KEY, R as ROOT_PATH, b as FORM_ERRORS_PATH } from './attaform.h1sq3BFu.mjs';
171
4
 
172
5
  const NOT_FOUND = Symbol("NOT_FOUND");
173
6
  function descendStep(value, segment) {
@@ -413,14 +246,230 @@ function setAtPathWithSchemaFillImpl(root, schema, fullPath, value, startIdx) {
413
246
  return rec;
414
247
  }
415
248
 
416
- function buildFieldStateAccessor(state, getFormMetaBase) {
249
+ function isDescendable(value) {
250
+ if (value === null || typeof value !== "object") return false;
251
+ if (Array.isArray(value)) return true;
252
+ const proto = Object.getPrototypeOf(value);
253
+ return proto === null || proto === Object.prototype;
254
+ }
255
+ function appendSegment(prefix, segment) {
256
+ const next = new Array(prefix.length + 1);
257
+ for (let i = 0; i < prefix.length; i++) {
258
+ const s = prefix[i];
259
+ next[i] = s;
260
+ }
261
+ next[prefix.length] = segment;
262
+ return next;
263
+ }
264
+ function diffAndApply(oldValue, newValue, prefix, visit) {
265
+ if (Object.is(oldValue, newValue)) return;
266
+ const oldIsDescendable = isDescendable(oldValue);
267
+ const newIsDescendable = isDescendable(newValue);
268
+ if (oldValue === void 0 && newIsDescendable) {
269
+ if (Array.isArray(newValue)) {
270
+ for (let i = 0; i < newValue.length; i++) {
271
+ diffAndApply(void 0, newValue[i], appendSegment(prefix, i), visit);
272
+ }
273
+ } else {
274
+ const rec = newValue;
275
+ for (const k of Object.keys(rec)) {
276
+ diffAndApply(void 0, rec[k], appendSegment(prefix, k), visit);
277
+ }
278
+ }
279
+ return;
280
+ }
281
+ if (oldIsDescendable && newValue === void 0) {
282
+ if (Array.isArray(oldValue)) {
283
+ for (let i = 0; i < oldValue.length; i++) {
284
+ diffAndApply(oldValue[i], void 0, appendSegment(prefix, i), visit);
285
+ }
286
+ } else {
287
+ const rec = oldValue;
288
+ for (const k of Object.keys(rec)) {
289
+ diffAndApply(rec[k], void 0, appendSegment(prefix, k), visit);
290
+ }
291
+ }
292
+ return;
293
+ }
294
+ if (oldIsDescendable && newIsDescendable) {
295
+ const oldIsArray = Array.isArray(oldValue);
296
+ const newIsArray = Array.isArray(newValue);
297
+ if (oldIsArray && newIsArray) {
298
+ const oldArr = oldValue;
299
+ const newArr = newValue;
300
+ const max = Math.max(oldArr.length, newArr.length);
301
+ for (let i = 0; i < max; i++) {
302
+ diffAndApply(oldArr[i], newArr[i], appendSegment(prefix, i), visit);
303
+ }
304
+ return;
305
+ }
306
+ if (!oldIsArray && !newIsArray) {
307
+ const oldRec = oldValue;
308
+ const newRec = newValue;
309
+ const seen = /* @__PURE__ */ new Set();
310
+ for (const k of Object.keys(oldRec)) {
311
+ seen.add(k);
312
+ diffAndApply(oldRec[k], newRec[k], appendSegment(prefix, k), visit);
313
+ }
314
+ for (const k of Object.keys(newRec)) {
315
+ if (seen.has(k)) continue;
316
+ diffAndApply(oldRec[k], newRec[k], appendSegment(prefix, k), visit);
317
+ }
318
+ return;
319
+ }
320
+ visit({ kind: "changed", path: prefix, oldValue, newValue });
321
+ return;
322
+ }
323
+ if (oldIsDescendable && !newIsDescendable) {
324
+ visit({ kind: "changed", path: prefix, oldValue, newValue });
325
+ return;
326
+ }
327
+ if (!oldIsDescendable && newIsDescendable) {
328
+ visit({ kind: "changed", path: prefix, oldValue, newValue });
329
+ return;
330
+ }
331
+ if (oldValue === void 0) {
332
+ visit({ kind: "added", path: prefix, newValue });
333
+ return;
334
+ }
335
+ if (newValue === void 0) {
336
+ visit({ kind: "removed", path: prefix, oldValue });
337
+ return;
338
+ }
339
+ visit({ kind: "changed", path: prefix, oldValue, newValue });
340
+ }
341
+ function applyChangedKeys(target, source) {
342
+ if (!isDescendable(target) || !isDescendable(source)) return false;
343
+ const targetIsArray = Array.isArray(target);
344
+ const sourceIsArray = Array.isArray(source);
345
+ if (targetIsArray !== sourceIsArray) return false;
346
+ const ROOT_SENTINEL = Symbol.for("attaform.applyChangedKeys.rootMismatch");
347
+ const changedFirstSegments = /* @__PURE__ */ new Set();
348
+ diffAndApply(target, source, [], (patch) => {
349
+ if (patch.path.length === 0) {
350
+ changedFirstSegments.add(ROOT_SENTINEL);
351
+ return;
352
+ }
353
+ changedFirstSegments.add(patch.path[0]);
354
+ });
355
+ if (changedFirstSegments.has(ROOT_SENTINEL)) return false;
356
+ if (targetIsArray) {
357
+ const t = target;
358
+ const s = source;
359
+ if (t.length > s.length) t.length = s.length;
360
+ for (const idx of changedFirstSegments) {
361
+ if (typeof idx === "symbol") continue;
362
+ const i = typeof idx === "number" ? idx : Number(idx);
363
+ t[i] = s[i];
364
+ }
365
+ } else {
366
+ const t = target;
367
+ const s = source;
368
+ const sourceKeys = new Set(Object.keys(s));
369
+ for (const k of Object.keys(t)) {
370
+ if (!sourceKeys.has(k)) delete t[k];
371
+ }
372
+ for (const k of changedFirstSegments) {
373
+ if (typeof k === "symbol") continue;
374
+ t[String(k)] = s[String(k)];
375
+ }
376
+ }
377
+ return true;
378
+ }
379
+ function applyPatchesForward(root, patches) {
380
+ let current = root;
381
+ for (const patch of patches) {
382
+ if (patch.path.length === 0) {
383
+ current = patch.kind === "removed" ? void 0 : patch.newValue;
384
+ continue;
385
+ }
386
+ if (patch.kind === "removed") {
387
+ current = deleteAtPath(current, patch.path);
388
+ } else {
389
+ current = setAtPath(current, patch.path, patch.newValue);
390
+ }
391
+ }
392
+ return current;
393
+ }
394
+ function applyPatchesInverse(root, patches) {
395
+ let current = root;
396
+ for (let i = patches.length - 1; i >= 0; i--) {
397
+ const patch = patches[i];
398
+ if (patch.path.length === 0) {
399
+ if (patch.kind === "added") {
400
+ current = void 0;
401
+ } else {
402
+ current = patch.oldValue;
403
+ }
404
+ continue;
405
+ }
406
+ if (patch.kind === "added") {
407
+ current = deleteAtPath(current, patch.path);
408
+ } else {
409
+ current = setAtPath(current, patch.path, patch.oldValue);
410
+ }
411
+ }
412
+ return current;
413
+ }
414
+ function structuralSnapshot(value) {
415
+ if (!isDescendable(value)) return value;
416
+ if (Array.isArray(value)) {
417
+ const out2 = new Array(value.length);
418
+ for (let i = 0; i < value.length; i++) {
419
+ out2[i] = structuralSnapshot(value[i]);
420
+ }
421
+ return out2;
422
+ }
423
+ const src = value;
424
+ const out = {};
425
+ for (const k of Object.keys(src)) {
426
+ out[k] = structuralSnapshot(src[k]);
427
+ }
428
+ return out;
429
+ }
430
+
431
+ const EMPTY_RESOLVED_FIELD_META = Object.freeze({
432
+ label: "",
433
+ description: void 0,
434
+ placeholder: void 0,
435
+ meta: Object.freeze({})
436
+ });
437
+
438
+ function humanize(segment) {
439
+ if (typeof segment === "number") return "";
440
+ const str = String(segment);
441
+ if (str.length === 0) return "";
442
+ if (/^\d+$/.test(str)) return "";
443
+ const tokens = str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().split(" ").filter((part) => part.length > 0);
444
+ if (tokens.length === 0) return "";
445
+ return tokens.map((part) => {
446
+ const head = part[0];
447
+ return head === void 0 ? part : head.toUpperCase() + part.slice(1).toLowerCase();
448
+ }).join(" ");
449
+ }
450
+
451
+ function isUnderStubAncestor(state, segments) {
452
+ for (let i = 0; i < segments.length; i++) {
453
+ const ancestorPath = segments.slice(0, i);
454
+ const du = state.schema.getUnionDiscriminatorAtPath(ancestorPath);
455
+ if (du === void 0) continue;
456
+ const ancestorValue = getAtPath(state.form.value, ancestorPath);
457
+ if (ancestorValue === null || typeof ancestorValue !== "object") continue;
458
+ const discValue = ancestorValue[du.discriminatorKey];
459
+ if (discValue === void 0) return true;
460
+ if (!du.isVariantSelected(discValue)) return true;
461
+ }
462
+ return false;
463
+ }
464
+ function buildFieldStateAccessor(state, getFormMetaBase, options) {
417
465
  const cache = /* @__PURE__ */ new Map();
466
+ const predicate = options?.shouldShowErrors;
418
467
  return function getFieldState(pathInput) {
419
468
  const { segments, key } = canonicalizePath(pathInput);
420
469
  const cached = cache.get(key);
421
470
  if (cached !== void 0) return cached;
422
471
  const c = computed(
423
- () => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, getFormMetaBase) : buildContainerFieldState(state, segments, key, getFormMetaBase)
472
+ () => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, getFormMetaBase, predicate) : buildContainerFieldState(state, segments, key, getFormMetaBase, predicate)
424
473
  );
425
474
  cache.set(key, c);
426
475
  return c;
@@ -440,7 +489,8 @@ function buildLeafFieldStateBase(state, segments, key) {
440
489
  if (userForKey !== void 0) errors.push(...userForKey);
441
490
  const validating = (state.fieldValidationCounts.get(key) ?? 0) > 0;
442
491
  const gated = state.pathHasAsyncValidation(segments) && !state.firstValidationDone.value;
443
- const valid = !gated && errors.length === 0 && !validating;
492
+ const isOrphan = segments.length > 0 && !hasAtPath(state.form.value, segments) && isUnderStubAncestor(state, segments);
493
+ const valid = !gated && errors.length === 0 && !validating && !isOrphan;
444
494
  const elementRecord = state.elements.get(key);
445
495
  const elementsArr = elementRecord ? Object.freeze([...elementRecord.elements]) : EMPTY_ELEMENTS;
446
496
  const firstElement = elementsArr[0] ?? null;
@@ -470,9 +520,9 @@ function buildLeafFieldStateBase(state, segments, key) {
470
520
  meta: resolved.meta
471
521
  };
472
522
  }
473
- function buildLeafFieldState(state, segments, key, getFormMetaBase) {
523
+ function buildLeafFieldState(state, segments, key, getFormMetaBase, shouldShowErrors) {
474
524
  const base = buildLeafFieldStateBase(state, segments, key);
475
- return decorateWithDerivedProps(base, state, getFormMetaBase);
525
+ return decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors);
476
526
  }
477
527
  function buildContainerFieldStateBase(state, segments, _key) {
478
528
  const formValue = state.form.value;
@@ -540,13 +590,14 @@ function buildContainerFieldStateBase(state, segments, _key) {
540
590
  meta: resolved.meta
541
591
  };
542
592
  }
543
- function buildContainerFieldState(state, segments, key, getFormMetaBase) {
593
+ function buildContainerFieldState(state, segments, key, getFormMetaBase, shouldShowErrors) {
544
594
  const base = buildContainerFieldStateBase(state, segments);
545
- return decorateWithDerivedProps(base, state, getFormMetaBase);
595
+ return decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors);
546
596
  }
547
- function decorateWithDerivedProps(base, state, getFormMetaBase) {
597
+ function decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors) {
548
598
  const firstError = base.errors[0];
549
- const showErrors = base.errors.length > 0 && state.shouldShowErrors(base, getFormMetaBase());
599
+ const predicate = shouldShowErrors ?? state.shouldShowErrors;
600
+ const showErrors = base.errors.length > 0 && predicate(base, getFormMetaBase());
550
601
  return { ...base, showErrors, firstError };
551
602
  }
552
603
  function aggregateErrorsAt(state, prefix) {
@@ -731,14 +782,16 @@ function buildErrorsProxy(state) {
731
782
  return buildSurfaceProxy({
732
783
  schema: state.schema,
733
784
  resolveLeaf: (path) => {
734
- if (!hasAtPath(state.form.value, path)) return void 0;
735
785
  const { key } = canonicalizePath(path);
736
- const schemaForKey = state.schemaErrors.get(key);
737
- const blankForKey = state.derivedBlankErrors.value.get(key);
738
786
  const userForKey = state.userErrors.get(key);
787
+ const isActive = hasAtPath(state.form.value, path);
739
788
  const merged = [];
740
- if (schemaForKey !== void 0) merged.push(...schemaForKey);
741
- if (blankForKey !== void 0) merged.push(...blankForKey);
789
+ if (isActive) {
790
+ const schemaForKey = state.schemaErrors.get(key);
791
+ const blankForKey = state.derivedBlankErrors.value.get(key);
792
+ if (schemaForKey !== void 0) merged.push(...schemaForKey);
793
+ if (blankForKey !== void 0) merged.push(...blankForKey);
794
+ }
742
795
  if (userForKey !== void 0) merged.push(...userForKey);
743
796
  return merged.length === 0 ? void 0 : merged;
744
797
  },
@@ -763,7 +816,7 @@ function buildErrorsProxy(state) {
763
816
  function materializeErrors(state, containerSegments) {
764
817
  const liveContainer = getAtPath(state.form.value, containerSegments);
765
818
  const tree = Array.isArray(liveContainer) ? [] : {};
766
- const collect = (store) => {
819
+ const collect = (store, applyActivePathFilter) => {
767
820
  entries: for (const [pathKey, errors] of store) {
768
821
  if (errors.length === 0) continue;
769
822
  const fullPath = segmentsForPathKey(pathKey);
@@ -772,13 +825,13 @@ function materializeErrors(state, containerSegments) {
772
825
  for (let i = 0; i < containerSegments.length; i++) {
773
826
  if (fullPath[i] !== containerSegments[i]) continue entries;
774
827
  }
775
- if (!hasAtPath(state.form.value, fullPath)) continue;
828
+ if (applyActivePathFilter && !hasAtPath(state.form.value, fullPath)) continue;
776
829
  placeAt(tree, fullPath.slice(containerSegments.length), errors);
777
830
  }
778
831
  };
779
- collect(state.schemaErrors);
780
- collect(state.derivedBlankErrors.value);
781
- collect(state.userErrors);
832
+ collect(state.schemaErrors, true);
833
+ collect(state.derivedBlankErrors.value, true);
834
+ collect(state.userErrors, false);
782
835
  return tree;
783
836
  }
784
837
  function placeAt(tree, path, errors) {
@@ -809,11 +862,13 @@ function buildFieldArrayApi(state) {
809
862
  const current = state.getValueAtPath(segments);
810
863
  return Array.isArray(current) ? current.slice() : [];
811
864
  }
812
- function writeArray(path, next) {
865
+ function writeArray(path, next, arrayOp) {
813
866
  const { segments, key } = canonicalizePath(path);
814
- return state.setValueAtPath(segments, next, {
815
- persist: state.persistOptIns.hasAnyOptInForPath(key)
816
- });
867
+ const meta = {
868
+ persist: state.persistOptIns.hasAnyOptInForPath(key),
869
+ ...arrayOp !== void 0 ? { arrayOp } : {}
870
+ };
871
+ return state.setValueAtPath(segments, next, meta);
817
872
  }
818
873
  return {
819
874
  append(path, value) {
@@ -824,18 +879,19 @@ function buildFieldArrayApi(state) {
824
879
  prepend(path, value) {
825
880
  const next = readArray(path);
826
881
  next.unshift(value);
827
- return writeArray(path, next);
882
+ return writeArray(path, next, { kind: "shift-from", index: 0 });
828
883
  },
829
884
  insert(path, index, value) {
830
885
  const next = readArray(path);
831
886
  next.splice(index, 0, value);
832
- return writeArray(path, next);
887
+ const clampedIndex = Math.max(0, Math.min(index, next.length));
888
+ return writeArray(path, next, { kind: "shift-from", index: clampedIndex });
833
889
  },
834
890
  remove(path, index) {
835
891
  const next = readArray(path);
836
892
  if (index < 0 || index >= next.length) return false;
837
893
  next.splice(index, 1);
838
- return writeArray(path, next);
894
+ return writeArray(path, next, { kind: "shift-from", index });
839
895
  },
840
896
  swap(path, a, b) {
841
897
  const next = readArray(path);
@@ -845,7 +901,7 @@ function buildFieldArrayApi(state) {
845
901
  const tmp = next[a];
846
902
  next[a] = next[b];
847
903
  next[b] = tmp;
848
- return writeArray(path, next);
904
+ return writeArray(path, next, { kind: "swap", a, b });
849
905
  },
850
906
  move(path, from, to) {
851
907
  const next = readArray(path);
@@ -853,13 +909,17 @@ function buildFieldArrayApi(state) {
853
909
  const [item] = next.splice(from, 1);
854
910
  const clampedTo = Math.max(0, Math.min(to, next.length));
855
911
  next.splice(clampedTo, 0, item);
856
- return writeArray(path, next);
912
+ return writeArray(path, next, {
913
+ kind: "shift-range",
914
+ fromIndex: Math.min(from, clampedTo),
915
+ toIndex: Math.max(from, clampedTo)
916
+ });
857
917
  },
858
918
  replace(path, index, value) {
859
919
  const next = readArray(path);
860
920
  if (index < 0 || index >= next.length) return false;
861
921
  next[index] = value;
862
- return writeArray(path, next);
922
+ return writeArray(path, next, { kind: "replace-at", index });
863
923
  }
864
924
  };
865
925
  }
@@ -888,8 +948,12 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
888
948
  "placeholder",
889
949
  "meta"
890
950
  ]);
891
- function buildFieldStateProxy(state, getFormMetaBase) {
892
- const getFieldStateAt = buildFieldStateAccessor(state, getFormMetaBase);
951
+ function buildFieldStateProxy(state, getFormMetaBase, options) {
952
+ const getFieldStateAt = buildFieldStateAccessor(
953
+ state,
954
+ getFormMetaBase,
955
+ options?.shouldShowErrors !== void 0 ? { shouldShowErrors: options.shouldShowErrors } : void 0
956
+ );
893
957
  const snapshotFieldStateAt = (path) => {
894
958
  const view = getFieldStateAt(path).value;
895
959
  const snapshot = {};
@@ -980,10 +1044,25 @@ function walk$2(value, basePath, schema, snapshotFieldStateAt) {
980
1044
 
981
1045
  const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
982
1046
  const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
983
- const DEFAULT_HISTORY_MAX_SNAPSHOTS = 50;
1047
+ const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
984
1048
  const PERSISTENCE_KEY_PREFIX = "attaform:";
985
1049
  const RESERVED_KEY_PREFIX = "__atta:";
986
1050
  const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
1051
+ const DEFAULT_MAX_RECURSION_DEPTH = 64;
1052
+ function normalizeNumericOption(config) {
1053
+ const { value, source, allowInfinity, min, defaultValue } = config;
1054
+ if (allowInfinity && value === Infinity) return Infinity;
1055
+ if (typeof value !== "number" || Number.isNaN(value) || value === Infinity || value === -Infinity) {
1056
+ if (__DEV__) {
1057
+ const acceptedDescription = allowInfinity ? "a non-negative integer or Infinity" : "a non-negative finite integer";
1058
+ console.warn(
1059
+ `[attaform] ${source} must be ${acceptedDescription}; got ${String(value)}. Falling back to ${String(defaultValue)}.`
1060
+ );
1061
+ }
1062
+ return defaultValue;
1063
+ }
1064
+ return Math.max(min, Math.floor(value));
1065
+ }
987
1066
 
988
1067
  const PERSISTENCE_MODULE_KEY = "persistence";
989
1068
  async function getStorageAdapter(storage) {
@@ -1158,20 +1237,29 @@ function mergeDeep(target, source, path, schema) {
1158
1237
  if (source === null || typeof source !== "object") return source;
1159
1238
  if (Array.isArray(source)) return source;
1160
1239
  if (!isPlainRecord(source)) return source;
1161
- let mergeTarget = target;
1162
1240
  if (schema !== void 0) {
1163
1241
  const du = schema.getUnionDiscriminatorAtPath(path);
1164
1242
  if (du !== void 0) {
1165
- const sourceDisc = source[du.discriminatorKey];
1166
- const targetDisc = isPlainRecord(target) ? target[du.discriminatorKey] : void 0;
1167
- if (sourceDisc !== void 0 && !Object.is(sourceDisc, targetDisc)) {
1243
+ const sourceRecord = source;
1244
+ const sourceDisc = sourceRecord[du.discriminatorKey];
1245
+ if (sourceDisc !== void 0 && !du.isVariantSelected(sourceDisc)) {
1246
+ return { [du.discriminatorKey]: sourceDisc };
1247
+ }
1248
+ if (sourceDisc !== void 0) {
1168
1249
  const variantDefault = du.getVariantDefault(sourceDisc);
1169
1250
  if (isPlainRecord(variantDefault)) {
1170
- mergeTarget = variantDefault;
1251
+ const out2 = { ...variantDefault };
1252
+ for (const key of Object.keys(sourceRecord)) {
1253
+ if (!(key in variantDefault) && key !== du.discriminatorKey) continue;
1254
+ out2[key] = mergeDeep(out2[key], sourceRecord[key], [...path, key], schema);
1255
+ }
1256
+ return out2;
1171
1257
  }
1172
1258
  }
1259
+ return {};
1173
1260
  }
1174
1261
  }
1262
+ const mergeTarget = target;
1175
1263
  const out = isPlainRecord(mergeTarget) ? { ...mergeTarget } : {};
1176
1264
  for (const key of Object.keys(source)) {
1177
1265
  out[key] = mergeDeep(out[key], source[key], [...path, key], schema);
@@ -1200,14 +1288,14 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1200
1288
  });
1201
1289
  let gen = 0;
1202
1290
  async function kickoff(data, path, captured) {
1203
- state.activeValidations.value += 1;
1204
- result.value = {
1205
- pending: true,
1206
- errors: void 0,
1207
- success: false,
1208
- formKey: state.formKey
1209
- };
1210
1291
  try {
1292
+ state.activeValidations.value += 1;
1293
+ result.value = {
1294
+ pending: true,
1295
+ errors: void 0,
1296
+ success: false,
1297
+ formKey: state.formKey
1298
+ };
1211
1299
  const refinement = await runRefinementValidation(data, path);
1212
1300
  if (captured !== gen) return;
1213
1301
  result.value = settled(composeWithDerivedBlank(refinement, path));
@@ -1251,14 +1339,44 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1251
1339
  async function validateAsync(pathInput) {
1252
1340
  const segments = pathInput === void 0 ? void 0 : toSegments(pathInput);
1253
1341
  const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
1254
- state.activeValidations.value += 1;
1255
1342
  try {
1343
+ state.activeValidations.value += 1;
1256
1344
  const refinement = await runRefinementValidation(dataAtPath, segments);
1257
1345
  return stripData(composeWithDerivedBlank(refinement, segments));
1346
+ } catch (err) {
1347
+ return adapterThrowResponse(err);
1258
1348
  } finally {
1259
1349
  state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
1260
1350
  }
1261
1351
  }
1352
+ async function process(pathInput) {
1353
+ const segments = pathInput === void 0 ? void 0 : toSegments(pathInput);
1354
+ const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
1355
+ try {
1356
+ state.activeValidations.value += 1;
1357
+ const refinement = await runRefinementValidation(dataAtPath, segments);
1358
+ return composeWithDerivedBlank(refinement, segments);
1359
+ } catch (err) {
1360
+ return adapterThrowResponse(err);
1361
+ } finally {
1362
+ state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
1363
+ }
1364
+ }
1365
+ function adapterThrowResponse(err) {
1366
+ return {
1367
+ success: false,
1368
+ data: void 0,
1369
+ errors: [
1370
+ {
1371
+ message: adapterThrowMessage(err),
1372
+ path: [],
1373
+ formKey: state.formKey,
1374
+ code: AttaformErrorCode.AdapterThrew
1375
+ }
1376
+ ],
1377
+ formKey: state.formKey
1378
+ };
1379
+ }
1262
1380
  async function runRefinementValidation(data, path) {
1263
1381
  return await state.schema.validateAtPath(data, path);
1264
1382
  }
@@ -1280,14 +1398,17 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1280
1398
  if (event !== void 0 && "preventDefault" in event && typeof event.preventDefault === "function") {
1281
1399
  event.preventDefault();
1282
1400
  }
1401
+ if (state.activeSubmissions.value > 0) {
1402
+ return;
1403
+ }
1283
1404
  const genAtEntry = state.submissionGeneration.value;
1284
- state.activeSubmissions.value += 1;
1285
- state.submitting.value = true;
1286
- state.submitError.value = null;
1287
- state.cancelFieldValidation();
1288
- state.activeValidations.value += 1;
1289
1405
  let validationSettled = false;
1290
1406
  try {
1407
+ state.activeSubmissions.value += 1;
1408
+ state.submitting.value = true;
1409
+ state.submitError.value = null;
1410
+ state.cancelFieldValidation();
1411
+ state.activeValidations.value += 1;
1291
1412
  const refinement = await runRefinementValidation(state.form.value, void 0);
1292
1413
  const merged = composeWithDerivedBlank(refinement, void 0);
1293
1414
  state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
@@ -1336,7 +1457,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1336
1457
  };
1337
1458
  return submitHandler;
1338
1459
  };
1339
- return { validate, validateAsync, handleSubmit };
1460
+ return { validate, validateAsync, process, handleSubmit };
1340
1461
  }
1341
1462
  function toSegments(pathInput) {
1342
1463
  return canonicalizePath(pathInput).segments;
@@ -1674,11 +1795,12 @@ function coerceValue(value, accepted, elementAccepted, index) {
1674
1795
  const EMPTY_TRANSFORMS = Object.freeze([]);
1675
1796
  const INTERACTIVE_TAG_NAMES = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
1676
1797
  const attaformListenersSymbol = Symbol.for("attaform:focus-listeners");
1677
- function attachFocusListeners(state, segments, element) {
1798
+ function attachFocusListeners(state, segments, element, instanceMeta) {
1678
1799
  const target = element;
1679
1800
  if (target[attaformListenersSymbol] !== void 0) return;
1680
- const handleFocus = () => state.markFocused(segments, true);
1681
- const handleBlur = () => state.markFocused(segments, false);
1801
+ const focusMeta = instanceMeta !== void 0 ? { instance: instanceMeta } : void 0;
1802
+ const handleFocus = () => state.markFocused(segments, true, focusMeta);
1803
+ const handleBlur = () => state.markFocused(segments, false, focusMeta);
1682
1804
  element.addEventListener("focus", handleFocus);
1683
1805
  element.addEventListener("blur", handleBlur);
1684
1806
  target[attaformListenersSymbol] = { handleFocus, handleBlur };
@@ -1691,7 +1813,13 @@ function detachFocusListeners(element) {
1691
1813
  element.removeEventListener("blur", listeners.handleBlur);
1692
1814
  delete target[attaformListenersSymbol];
1693
1815
  }
1694
- function buildRegister(state, formInstanceId) {
1816
+ function buildRegister(state, formInstanceId, instanceConfig) {
1817
+ const coerceIndex = instanceConfig?.coerce !== void 0 ? resolveCoercionIndex(instanceConfig.coerce) : state.coerceIndex;
1818
+ const instanceMeta = instanceConfig?.instanceMeta;
1819
+ const withInstanceMeta = (meta) => {
1820
+ if (instanceMeta === void 0) return meta;
1821
+ return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
1822
+ };
1695
1823
  const lastTypedFormByPath = /* @__PURE__ */ new Map();
1696
1824
  return function register(pathInput, options) {
1697
1825
  const { segments, key: pathKey } = canonicalizePath(pathInput);
@@ -1714,16 +1842,23 @@ function buildRegister(state, formInstanceId) {
1714
1842
  const slimDefault = state.schema.getDefaultAtPath(segments);
1715
1843
  const persist = options?.persist === true;
1716
1844
  const acknowledgeSensitive = options?.acknowledgeSensitive === true;
1845
+ const multiTab = options?.multiTab !== false;
1717
1846
  const transforms = options?.transforms ?? EMPTY_TRANSFORMS;
1847
+ const markNoSync = !multiTab ? () => {
1848
+ state.incrementNoSyncOptOut(pathKey);
1849
+ } : void 0;
1850
+ const unmarkNoSync = !multiTab ? () => {
1851
+ state.decrementNoSyncOptOut(pathKey);
1852
+ } : void 0;
1718
1853
  const coerce = buildCoerceFn(
1719
1854
  state.schema,
1720
1855
  segments,
1721
- state.coerceIndex
1856
+ coerceIndex
1722
1857
  );
1723
1858
  const coerceElement = buildElementCoerceFn(
1724
1859
  state.schema,
1725
1860
  segments,
1726
- state.coerceIndex
1861
+ coerceIndex
1727
1862
  );
1728
1863
  if (persist && !state.ssr && !state.modules.has(PERSISTENCE_MODULE_KEY)) {
1729
1864
  throw new AnonPersistError({
@@ -1737,22 +1872,26 @@ function buildRegister(state, formInstanceId) {
1737
1872
  displayValue,
1738
1873
  lastTypedForm,
1739
1874
  markBlank: () => {
1740
- return state.setValueAtPath(segments, slimDefault, {
1741
- blank: true,
1742
- persist
1743
- });
1875
+ return state.setValueAtPath(
1876
+ segments,
1877
+ slimDefault,
1878
+ withInstanceMeta({
1879
+ blank: true,
1880
+ persist
1881
+ })
1882
+ );
1744
1883
  },
1745
1884
  registerElement: (element) => {
1746
1885
  if (!INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
1747
1886
  const added = state.registerElement(segments, element, formInstanceId);
1748
- if (added) attachFocusListeners(state, segments, element);
1887
+ if (added) attachFocusListeners(state, segments, element, instanceMeta);
1749
1888
  },
1750
1889
  deregisterElement: (element) => {
1751
1890
  detachFocusListeners(element);
1752
1891
  state.deregisterElement(segments, element);
1753
1892
  },
1754
1893
  setValueWithInternalPath: (value, meta) => {
1755
- return state.setValueAtPath(segments, value, meta);
1894
+ return state.setValueAtPath(segments, value, withInstanceMeta(meta));
1756
1895
  },
1757
1896
  // Called by the `vRegisterHint` compile-time transform's wrapping
1758
1897
  // IIFE on every server-side render of `<element v-register="…">`.
@@ -1778,6 +1917,10 @@ function buildRegister(state, formInstanceId) {
1778
1917
  persist,
1779
1918
  acknowledgeSensitive,
1780
1919
  persistOptIns: state.persistOptIns,
1920
+ isSensitivePath: state.isSensitivePath,
1921
+ multiTab,
1922
+ ...markNoSync !== void 0 ? { markNoSync } : {},
1923
+ ...unmarkNoSync !== void 0 ? { unmarkNoSync } : {},
1781
1924
  transforms,
1782
1925
  coerce,
1783
1926
  ...coerceElement !== void 0 ? { coerceElement } : {}
@@ -1935,6 +2078,8 @@ function buildValuesProxy(form) {
1935
2078
  const inner = computed(() => readonly(form.value));
1936
2079
  const target = (() => {
1937
2080
  });
2081
+ const valuesToString = () => JSON.stringify(inner.value);
2082
+ const valuesToPrimitive = (hint) => hint === "number" ? NaN : valuesToString();
1938
2083
  return new Proxy(target, {
1939
2084
  apply(_, __, args) {
1940
2085
  const arg = args[0];
@@ -1948,8 +2093,16 @@ function buildValuesProxy(form) {
1948
2093
  return cursor;
1949
2094
  },
1950
2095
  get(_, key) {
1951
- if (typeof key === "symbol") return Reflect.get(target, key);
2096
+ if (typeof key === "symbol") {
2097
+ if (key === Symbol.toPrimitive) return valuesToPrimitive;
2098
+ return Reflect.get(target, key);
2099
+ }
1952
2100
  if (key === "toJSON") return () => inner.value;
2101
+ if (key === "toString") return valuesToString;
2102
+ if (key === "valueOf")
2103
+ return function() {
2104
+ return this;
2105
+ };
1953
2106
  return inner.value[key];
1954
2107
  },
1955
2108
  has(_, key) {
@@ -1988,6 +2141,14 @@ function buildValuesProxy(form) {
1988
2141
  });
1989
2142
  }
1990
2143
 
2144
+ function blankForKind(slimDefault) {
2145
+ if (typeof slimDefault === "string") return "";
2146
+ if (typeof slimDefault === "number") return 0;
2147
+ if (typeof slimDefault === "bigint") return 0n;
2148
+ if (typeof slimDefault === "boolean") return false;
2149
+ if (slimDefault === null) return null;
2150
+ return void 0;
2151
+ }
1991
2152
  function readonlySetSnapshot(source) {
1992
2153
  const snapshot = new Set(source);
1993
2154
  return new Proxy(snapshot, {
@@ -2003,15 +2164,36 @@ function readonlySetSnapshot(source) {
2003
2164
  });
2004
2165
  }
2005
2166
  function buildFormApi(state, formInstanceId, options = {}) {
2006
- const register = buildRegister(state, formInstanceId);
2167
+ const instanceMeta = (() => {
2168
+ const bag = {};
2169
+ if (options.validateOn !== void 0) bag.validateOn = options.validateOn;
2170
+ if (options.debounceMs !== void 0) bag.debounceMs = options.debounceMs;
2171
+ if (options.rememberVariants !== void 0) bag.rememberVariants = options.rememberVariants;
2172
+ return Object.keys(bag).length > 0 ? bag : void 0;
2173
+ })();
2174
+ const withInstanceMeta = (meta) => {
2175
+ if (instanceMeta === void 0) return meta;
2176
+ return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
2177
+ };
2178
+ const registerConfig = {
2179
+ ...instanceMeta !== void 0 ? { instanceMeta } : {},
2180
+ ...options.coerce !== void 0 ? { coerce: options.coerce } : {}
2181
+ };
2182
+ const register = buildRegister(
2183
+ state,
2184
+ formInstanceId,
2185
+ Object.keys(registerConfig).length > 0 ? registerConfig : void 0
2186
+ );
2007
2187
  const processOptions = options.onInvalidSubmit !== void 0 ? { onInvalidSubmit: options.onInvalidSubmit } : {};
2008
2188
  const {
2009
2189
  validate: validateBuilt,
2010
2190
  validateAsync: validateAsyncBuilt,
2191
+ process: processBuilt,
2011
2192
  handleSubmit
2012
2193
  } = buildProcessForm(state, formInstanceId, processOptions);
2013
2194
  const validate = (pathInput) => validateBuilt(pathInput);
2014
2195
  const validateAsync = (pathInput) => validateAsyncBuilt(pathInput);
2196
+ const process = (pathInput) => processBuilt(pathInput);
2015
2197
  function pathToRef(pathInput) {
2016
2198
  const segments = canonicalizePath(pathInput).segments;
2017
2199
  return computed(() => getAtPath(state.form.value, segments));
@@ -2023,22 +2205,36 @@ function buildFormApi(state, formInstanceId, options = {}) {
2023
2205
  next,
2024
2206
  state.schema
2025
2207
  );
2026
- const ok2 = state.setValueAtPath([], walked2.cleanedValues);
2208
+ const ok2 = state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
2027
2209
  if (!ok2) return false;
2028
2210
  for (const pathKey of walked2.paths) {
2029
2211
  const segments2 = segmentsForPathKey(pathKey);
2030
2212
  if (segments2 === null) continue;
2031
- state.setValueAtPath(segments2, state.schema.getDefaultAtPath(segments2), {
2032
- blank: true
2033
- });
2213
+ state.setValueAtPath(
2214
+ segments2,
2215
+ state.schema.getDefaultAtPath(segments2),
2216
+ withInstanceMeta({ blank: true })
2217
+ );
2034
2218
  }
2035
2219
  return true;
2036
2220
  }
2037
2221
  const segments = canonicalizePath(pathOrValue).segments;
2038
2222
  if (isUnset(maybeValue)) {
2039
- return state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
2040
- blank: true
2041
- });
2223
+ const last = segments.length > 0 ? segments[segments.length - 1] : void 0;
2224
+ if (typeof last === "string") {
2225
+ const parent = segments.slice(0, -1);
2226
+ const parentDU = state.schema.getUnionDiscriminatorAtPath(parent);
2227
+ if (parentDU?.discriminatorKey === last) {
2228
+ const slimDefault = state.schema.getDefaultAtPath(segments);
2229
+ const blank = blankForKind(slimDefault);
2230
+ return state.setValueAtPath(segments, blank, withInstanceMeta({ blank: true }));
2231
+ }
2232
+ }
2233
+ return state.setValueAtPath(
2234
+ segments,
2235
+ state.schema.getDefaultAtPath(segments),
2236
+ withInstanceMeta({ blank: true })
2237
+ );
2042
2238
  }
2043
2239
  let resolvedValue;
2044
2240
  if (typeof maybeValue === "function") {
@@ -2046,9 +2242,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
2046
2242
  const prev = current === void 0 ? state.schema.getDefaultAtPath(segments) : current;
2047
2243
  resolvedValue = maybeValue(prev);
2048
2244
  if (isUnset(resolvedValue)) {
2049
- return state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
2050
- blank: true
2051
- });
2245
+ return state.setValueAtPath(
2246
+ segments,
2247
+ state.schema.getDefaultAtPath(segments),
2248
+ withInstanceMeta({ blank: true })
2249
+ );
2052
2250
  }
2053
2251
  } else {
2054
2252
  resolvedValue = maybeValue;
@@ -2058,28 +2256,52 @@ function buildFormApi(state, formInstanceId, options = {}) {
2058
2256
  segments,
2059
2257
  state.schema
2060
2258
  );
2061
- const ok = state.setValueAtPath(segments, walked.cleanedValues);
2259
+ const ok = state.setValueAtPath(segments, walked.cleanedValues, withInstanceMeta());
2062
2260
  if (!ok) return false;
2063
2261
  for (const pathKey of walked.paths) {
2064
2262
  const blankSegments = segmentsForPathKey(pathKey);
2065
2263
  if (blankSegments === null) continue;
2066
- state.setValueAtPath(blankSegments, state.schema.getDefaultAtPath(blankSegments), {
2067
- blank: true
2068
- });
2264
+ state.setValueAtPath(
2265
+ blankSegments,
2266
+ state.schema.getDefaultAtPath(blankSegments),
2267
+ withInstanceMeta({ blank: true })
2268
+ );
2069
2269
  }
2070
2270
  return true;
2071
2271
  }
2072
2272
  const errorsProxy = buildErrorsProxy(state);
2273
+ function filterToOwnFormKey(errors, op) {
2274
+ const own = [];
2275
+ let dropped = 0;
2276
+ for (const e of errors) {
2277
+ if (e.formKey === state.formKey) own.push(e);
2278
+ else dropped++;
2279
+ }
2280
+ if (__DEV__ && dropped > 0) {
2281
+ console.warn(
2282
+ `[attaform] ${op}: dropped ${dropped} error(s) with non-matching formKey (this form's key is "${String(state.formKey)}"). Errors are scoped to the form that produced them \u2014 pass them to the matching form instance.`
2283
+ );
2284
+ }
2285
+ return own;
2286
+ }
2073
2287
  function setFieldErrors(errors) {
2074
- state.setAllUserErrors(errors);
2288
+ const preserved = state.userErrors.get(FORM_ERRORS_PATH_KEY);
2289
+ state.setAllUserErrors(filterToOwnFormKey(errors, "setFieldErrors"));
2290
+ if (preserved !== void 0 && preserved.length > 0) {
2291
+ state.userErrors.set(FORM_ERRORS_PATH_KEY, preserved);
2292
+ }
2075
2293
  }
2076
2294
  function addFieldErrors(errors) {
2077
- state.addUserErrors(errors);
2295
+ state.addUserErrors(filterToOwnFormKey(errors, "addFieldErrors"));
2078
2296
  }
2079
2297
  function clearFieldErrors(path) {
2080
2298
  if (path === void 0) {
2299
+ const preserved = state.userErrors.get(FORM_ERRORS_PATH_KEY);
2081
2300
  state.clearSchemaErrors();
2082
2301
  state.clearUserErrors();
2302
+ if (preserved !== void 0 && preserved.length > 0) {
2303
+ state.userErrors.set(FORM_ERRORS_PATH_KEY, preserved);
2304
+ }
2083
2305
  return;
2084
2306
  }
2085
2307
  const segments = canonicalizePath(path).segments;
@@ -2112,11 +2334,17 @@ function buildFormApi(state, formInstanceId, options = {}) {
2112
2334
  () => state.firstValidationDone.value && state.schemaErrors.size === 0 && state.userErrors.size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
2113
2335
  );
2114
2336
  const history = options.history;
2115
- const undo = history?.undo ?? (() => false);
2116
- const redo = history?.redo ?? (() => false);
2117
- const canUndo = history?.canUndo ?? computed(() => false);
2118
- const canRedo = history?.canRedo ?? computed(() => false);
2119
- const historySize = history?.historySize ?? computed(() => 0);
2337
+ const formHistory = readonly(
2338
+ reactive({
2339
+ undo: history?.undo ?? (() => false),
2340
+ redo: history?.redo ?? (() => false),
2341
+ clear: history?.clear ?? (() => {
2342
+ }),
2343
+ canUndo: history?.canUndo ?? computed(() => false),
2344
+ canRedo: history?.canRedo ?? computed(() => false),
2345
+ size: history?.historySize ?? computed(() => 0)
2346
+ })
2347
+ );
2120
2348
  const metaErrors = computed(
2121
2349
  () => aggregateErrorsAt(state, [])
2122
2350
  );
@@ -2127,13 +2355,15 @@ function buildFormApi(state, formInstanceId, options = {}) {
2127
2355
  submitting: state.submitting.value,
2128
2356
  submitCount: state.submitCount.value,
2129
2357
  submitError: state.submitError.value,
2130
- canUndo: canUndo.value,
2131
- canRedo: canRedo.value,
2132
- historySize: historySize.value,
2133
2358
  instanceId: formInstanceId
2134
2359
  };
2135
2360
  };
2136
- const getRootFieldStateAt = buildFieldStateAccessor(state, getFormMetaBase);
2361
+ const fieldStateAccessorOptions = options.shouldShowErrors !== void 0 ? { shouldShowErrors: options.shouldShowErrors } : void 0;
2362
+ const getRootFieldStateAt = buildFieldStateAccessor(
2363
+ state,
2364
+ getFormMetaBase,
2365
+ fieldStateAccessorOptions
2366
+ );
2137
2367
  const rootFieldState = getRootFieldStateAt([]);
2138
2368
  const formMeta = readonly(
2139
2369
  reactive({
@@ -2186,9 +2416,6 @@ function buildFormApi(state, formInstanceId, options = {}) {
2186
2416
  submitting,
2187
2417
  submitCount,
2188
2418
  submitError,
2189
- canUndo,
2190
- canRedo,
2191
- historySize,
2192
2419
  // Per-`useForm()`-call identity. Stable for one mount; new on
2193
2420
  // re-mount; orthogonal to `form.key` (which is the user-supplied
2194
2421
  // shared identifier). Useful for devtools panels disambiguating
@@ -2210,9 +2437,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
2210
2437
  for (const pathKey of walked.paths) {
2211
2438
  const segments = segmentsForPathKey(pathKey);
2212
2439
  if (segments === null) continue;
2213
- state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
2214
- blank: true
2215
- });
2440
+ state.setValueAtPath(
2441
+ segments,
2442
+ state.schema.getDefaultAtPath(segments),
2443
+ withInstanceMeta({ blank: true })
2444
+ );
2216
2445
  state.originalBlankPaths.add(pathKey);
2217
2446
  }
2218
2447
  }
@@ -2227,9 +2456,13 @@ function buildFormApi(state, formInstanceId, options = {}) {
2227
2456
  void persistence.clearPersistedDraft(segments).catch(() => void 0);
2228
2457
  }
2229
2458
  };
2459
+ function clear(pathInput) {
2460
+ const segments = pathInput === void 0 ? ROOT_PATH : canonicalizePath(pathInput).segments;
2461
+ return state.clear(segments);
2462
+ }
2230
2463
  const persist = async (pathInput, options2) => {
2231
2464
  const segments = canonicalizePath(pathInput).segments;
2232
- enforceSensitiveCheck(segments, options2?.acknowledgeSensitive === true);
2465
+ enforceSensitiveCheck(segments, options2?.acknowledgeSensitive === true, state.isSensitivePath);
2233
2466
  if (persistence === void 0) return;
2234
2467
  await persistence.writePathImmediately(segments);
2235
2468
  };
@@ -2263,7 +2496,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2263
2496
  return readonlySetSnapshot(state.blankPaths);
2264
2497
  });
2265
2498
  const valuesProxy = buildValuesProxy(state.form);
2266
- const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase);
2499
+ const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase, fieldStateAccessorOptions);
2267
2500
  return {
2268
2501
  handleSubmit,
2269
2502
  // `values` is the callable readonly Proxy. Each `get` trap reads
@@ -2276,6 +2509,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2276
2509
  setValue: setValueImpl,
2277
2510
  validate,
2278
2511
  validateAsync,
2512
+ process,
2279
2513
  register,
2280
2514
  key: state.formKey,
2281
2515
  errors: errorsProxy,
@@ -2288,13 +2522,13 @@ function buildFormApi(state, formInstanceId, options = {}) {
2288
2522
  meta: formMeta,
2289
2523
  reset,
2290
2524
  resetField,
2525
+ clear,
2291
2526
  persist,
2292
2527
  clearPersistedDraft,
2293
2528
  focusFirstError,
2294
2529
  scrollToFirstError,
2295
2530
  touch,
2296
- undo,
2297
- redo,
2531
+ history: formHistory,
2298
2532
  append: fieldArrays.append,
2299
2533
  prepend: fieldArrays.prepend,
2300
2534
  insert: fieldArrays.insert,
@@ -2339,6 +2573,42 @@ function warnMalformedHydration(formKey, kind, rawKey) {
2339
2573
  `[attaform] hydration: skipping malformed ${kind} entry at key '${rawKey}' on form '${formKey}'. This usually means the SSR bundle is on a different version than the client (rolling deploy / stale cache).`
2340
2574
  );
2341
2575
  }
2576
+ function applyDuStubs(schema, data, options = {}) {
2577
+ const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
2578
+ return walkDuStubs(schema, data, options.basePath ?? [], warned);
2579
+ }
2580
+ function walkDuStubs(schema, value, path, warned) {
2581
+ if (value === null || value === void 0 || typeof value !== "object") return value;
2582
+ if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || typeof value === "function") {
2583
+ return value;
2584
+ }
2585
+ if (Array.isArray(value)) {
2586
+ return value.map((item, i) => walkDuStubs(schema, item, [...path, i], warned));
2587
+ }
2588
+ const rec = value;
2589
+ const du = schema.getUnionDiscriminatorAtPath(path);
2590
+ if (du !== void 0) {
2591
+ const discValue = rec[du.discriminatorKey];
2592
+ if (discValue !== void 0 && !du.isVariantSelected(discValue)) {
2593
+ if (warned !== void 0 && __DEV__) {
2594
+ const dotted = path.map((s) => String(s)).join(".") || "(root)";
2595
+ const key = `${dotted}::${String(discValue)}`;
2596
+ if (!warned.has(key)) {
2597
+ warned.add(key);
2598
+ console.warn(
2599
+ `[attaform] defaultValues at '${dotted}' carries discriminator '${du.discriminatorKey}=${JSON.stringify(discValue)}' which isn't a known variant. Form mounts in a stub holding only the discriminator key. Validation will surface the mismatch.`
2600
+ );
2601
+ }
2602
+ }
2603
+ return { [du.discriminatorKey]: discValue };
2604
+ }
2605
+ }
2606
+ const out = {};
2607
+ for (const k of Object.keys(rec)) {
2608
+ out[k] = walkDuStubs(schema, rec[k], [...path, k], warned);
2609
+ }
2610
+ return out;
2611
+ }
2342
2612
  function isPathKeyUnder(existingKey, parentPath) {
2343
2613
  const parsed = segmentsForPathKey(existingKey);
2344
2614
  if (parsed === null) return false;
@@ -2348,21 +2618,96 @@ function isPathKeyUnder(existingKey, parentPath) {
2348
2618
  }
2349
2619
  return true;
2350
2620
  }
2621
+ function stripSymbolsDeep(value) {
2622
+ if (value === null || typeof value !== "object") return value;
2623
+ if (Array.isArray(value)) {
2624
+ let mutated2 = false;
2625
+ const out2 = new Array(value.length);
2626
+ for (let i = 0; i < value.length; i++) {
2627
+ const cleaned = stripSymbolsDeep(value[i]);
2628
+ out2[i] = cleaned;
2629
+ if (cleaned !== value[i]) mutated2 = true;
2630
+ }
2631
+ return mutated2 ? out2 : value;
2632
+ }
2633
+ const proto = Object.getPrototypeOf(value);
2634
+ if (proto !== Object.prototype && proto !== null) return value;
2635
+ const symKeys = Object.getOwnPropertySymbols(value);
2636
+ const stringKeys = Object.keys(value);
2637
+ let mutated = symKeys.length > 0;
2638
+ const out = {};
2639
+ const src = value;
2640
+ for (const k of stringKeys) {
2641
+ const cleaned = stripSymbolsDeep(src[k]);
2642
+ out[k] = cleaned;
2643
+ if (cleaned !== src[k]) mutated = true;
2644
+ }
2645
+ return mutated ? out : value;
2646
+ }
2647
+ function cloneVariantSnapshot(value) {
2648
+ if (value === null || typeof value !== "object") return value;
2649
+ const raw = toRaw(value);
2650
+ if (raw instanceof Date) return new Date(raw.getTime());
2651
+ if (raw instanceof Map) {
2652
+ const out2 = /* @__PURE__ */ new Map();
2653
+ for (const [k, v] of raw.entries()) out2.set(cloneVariantSnapshot(k), cloneVariantSnapshot(v));
2654
+ return out2;
2655
+ }
2656
+ if (raw instanceof Set) {
2657
+ const out2 = /* @__PURE__ */ new Set();
2658
+ for (const v of raw) out2.add(cloneVariantSnapshot(v));
2659
+ return out2;
2660
+ }
2661
+ if (raw instanceof RegExp) return new RegExp(raw.source, raw.flags);
2662
+ if (Array.isArray(raw)) {
2663
+ const out2 = new Array(raw.length);
2664
+ for (let i = 0; i < raw.length; i++) out2[i] = cloneVariantSnapshot(raw[i]);
2665
+ return out2;
2666
+ }
2667
+ const src = raw;
2668
+ const out = {};
2669
+ for (const k of Object.keys(src)) out[k] = cloneVariantSnapshot(src[k]);
2670
+ return out;
2671
+ }
2351
2672
  function createFormStore(options) {
2352
2673
  const { formKey, schema, defaultValues, strict = true, hydration } = options;
2353
2674
  const ssr = options.ssr === true;
2354
2675
  const rememberVariants = options.rememberVariants !== false;
2355
2676
  const fieldValidationMode = options.validateOn ?? "change";
2356
- const fieldValidationDebounceMs = options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS;
2677
+ const fieldValidationDebounceMs = normalizeNumericOption({
2678
+ value: options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS,
2679
+ source: "useForm.debounceMs",
2680
+ allowInfinity: false,
2681
+ min: 0,
2682
+ defaultValue: DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS
2683
+ });
2357
2684
  const fieldValidationState = /* @__PURE__ */ new Map();
2358
2685
  const formChangeListeners = /* @__PURE__ */ new Set();
2359
2686
  const submitSuccessListeners = /* @__PURE__ */ new Set();
2360
2687
  const resetListeners = /* @__PURE__ */ new Set();
2361
2688
  const persistOptIns = createPersistOptInRegistry();
2689
+ const noSyncPaths = /* @__PURE__ */ new Set();
2690
+ const noSyncPathCounts = /* @__PURE__ */ new Map();
2691
+ function incrementNoSyncOptOut(path) {
2692
+ const next = (noSyncPathCounts.get(path) ?? 0) + 1;
2693
+ noSyncPathCounts.set(path, next);
2694
+ if (next === 1) noSyncPaths.add(path);
2695
+ }
2696
+ function decrementNoSyncOptOut(path) {
2697
+ const current = noSyncPathCounts.get(path) ?? 0;
2698
+ if (current <= 1) {
2699
+ noSyncPathCounts.delete(path);
2700
+ noSyncPaths.delete(path);
2701
+ return;
2702
+ }
2703
+ noSyncPathCounts.set(path, current - 1);
2704
+ }
2362
2705
  const coerceIndex = resolveCoercionIndex(options.coerce);
2363
2706
  const resolvedShouldShowErrors = resolveShouldShowErrors(
2364
2707
  options.shouldShowErrors
2365
2708
  );
2709
+ const resolvedIsSensitivePath = options.isSensitivePath ?? isSensitivePath;
2710
+ const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ?? segmentMatchesSensitive;
2366
2711
  const cleanupHooks = [];
2367
2712
  const modules = /* @__PURE__ */ new Map();
2368
2713
  const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
@@ -2373,7 +2718,10 @@ function createFormStore(options) {
2373
2718
  });
2374
2719
  const schemaInitialData = schemaResponse.data;
2375
2720
  const initialData = hydration !== void 0 ? hydration.form : structuralSnapshot(schemaInitialData);
2376
- const form = ref(initialData);
2721
+ const stubbedInitialData = applyDuStubs(schema, initialData, {
2722
+ warn: true
2723
+ });
2724
+ const form = ref(stubbedInitialData);
2377
2725
  const fields = reactive(/* @__PURE__ */ new Map());
2378
2726
  const elements = reactive(/* @__PURE__ */ new Map());
2379
2727
  const elementToFormInstance = /* @__PURE__ */ new WeakMap();
@@ -2389,6 +2737,40 @@ function createFormStore(options) {
2389
2737
  originalBlankPaths.add(raw);
2390
2738
  }
2391
2739
  const variantMemory = /* @__PURE__ */ new Map();
2740
+ function clearVariantMemoryUnderPath(arrayPath) {
2741
+ for (const memKey of [...variantMemory.keys()]) {
2742
+ const segs = segmentsForPathKey(memKey);
2743
+ if (segs === null) continue;
2744
+ if (isPathPrefix(arrayPath, segs)) variantMemory.delete(memKey);
2745
+ }
2746
+ }
2747
+ function clearVariantMemoryAtArrayIndices(arrayPath, indexFilter) {
2748
+ for (const memKey of [...variantMemory.keys()]) {
2749
+ const segs = segmentsForPathKey(memKey);
2750
+ if (segs === null) continue;
2751
+ if (!isPathPrefix(arrayPath, segs)) continue;
2752
+ if (segs.length <= arrayPath.length) continue;
2753
+ const idxSeg = segs[arrayPath.length];
2754
+ if (typeof idxSeg !== "number") continue;
2755
+ if (indexFilter(idxSeg)) variantMemory.delete(memKey);
2756
+ }
2757
+ }
2758
+ function applyArrayOpToMemory(arrayPath, op) {
2759
+ switch (op.kind) {
2760
+ case "shift-from":
2761
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.index);
2762
+ return;
2763
+ case "shift-range":
2764
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.fromIndex && i <= op.toIndex);
2765
+ return;
2766
+ case "swap":
2767
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
2768
+ return;
2769
+ case "replace-at":
2770
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.index);
2771
+ return;
2772
+ }
2773
+ }
2392
2774
  const pathOrdinals = /* @__PURE__ */ new Map();
2393
2775
  let nextOrdinal = 0;
2394
2776
  function ensurePathOrdinal(key) {
@@ -2540,9 +2922,35 @@ function createFormStore(options) {
2540
2922
  }
2541
2923
  }
2542
2924
  function setValueAtPath(path, value, meta) {
2925
+ value = stripSymbolsDeep(value);
2926
+ value = schema.normalizeWriteValueAtPath(value, path);
2543
2927
  if (!isSlimPrimitiveValid(schema, form, path, value)) {
2544
2928
  return false;
2545
2929
  }
2930
+ if (path.length >= 2) {
2931
+ for (let i = 0; i < path.length - 1; i++) {
2932
+ const ancestorPath = path.slice(0, i + 1);
2933
+ const du = schema.getUnionDiscriminatorAtPath(ancestorPath);
2934
+ if (du === void 0) continue;
2935
+ const nextSeg = path[i + 1];
2936
+ if (nextSeg === du.discriminatorKey) continue;
2937
+ const ancestorValue = getAtPath(form.value, ancestorPath);
2938
+ if (!isPlainRecord(ancestorValue)) continue;
2939
+ const discValue = ancestorValue[du.discriminatorKey];
2940
+ if (discValue === void 0) {
2941
+ return false;
2942
+ }
2943
+ if (!du.isVariantSelected(discValue)) {
2944
+ return false;
2945
+ }
2946
+ const variantDefault = du.getVariantDefault(discValue);
2947
+ if (!isPlainRecord(variantDefault)) continue;
2948
+ if (typeof nextSeg !== "string") continue;
2949
+ if (!(nextSeg in variantDefault)) {
2950
+ return false;
2951
+ }
2952
+ }
2953
+ }
2546
2954
  if (meta?.skipDiscriminatorReshape !== true) {
2547
2955
  if (path.length > 0) {
2548
2956
  const last = path[path.length - 1];
@@ -2563,6 +2971,14 @@ function createFormStore(options) {
2563
2971
  meta
2564
2972
  );
2565
2973
  }
2974
+ return reshapeUnionVariant(
2975
+ parentPath,
2976
+ oldValue,
2977
+ value,
2978
+ { [last]: value },
2979
+ void 0,
2980
+ meta
2981
+ );
2566
2982
  }
2567
2983
  }
2568
2984
  }
@@ -2571,12 +2987,13 @@ function createFormStore(options) {
2571
2987
  const selfDU = schema.getUnionDiscriminatorAtPath(path);
2572
2988
  if (selfDU !== void 0) {
2573
2989
  const valueRecord = value;
2574
- const discValue = valueRecord[selfDU.discriminatorKey];
2990
+ const discKey = selfDU.discriminatorKey;
2991
+ const discValue = valueRecord[discKey];
2992
+ const currentUnionValue = getAtPath(form.value, path);
2993
+ const oldDiscValue = isPlainRecord(currentUnionValue) ? currentUnionValue[discKey] : void 0;
2575
2994
  if (discValue !== void 0) {
2576
2995
  const variantDefault = selfDU.getVariantDefault(discValue);
2577
2996
  if (variantDefault !== void 0 && isPlainRecord(variantDefault)) {
2578
- const currentUnionValue = getAtPath(form.value, path);
2579
- const oldDiscValue = isPlainRecord(currentUnionValue) ? currentUnionValue[selfDU.discriminatorKey] : void 0;
2580
2997
  return reshapeUnionVariant(
2581
2998
  path,
2582
2999
  oldDiscValue,
@@ -2586,7 +3003,16 @@ function createFormStore(options) {
2586
3003
  meta
2587
3004
  );
2588
3005
  }
3006
+ return reshapeUnionVariant(
3007
+ path,
3008
+ oldDiscValue,
3009
+ discValue,
3010
+ { [discKey]: discValue },
3011
+ void 0,
3012
+ meta
3013
+ );
2589
3014
  }
3015
+ return reshapeUnionVariant(path, oldDiscValue, void 0, {}, void 0, meta);
2590
3016
  }
2591
3017
  }
2592
3018
  }
@@ -2603,12 +3029,17 @@ function createFormStore(options) {
2603
3029
  }
2604
3030
  const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
2605
3031
  applyFormReplacement(nextForm, meta);
2606
- if (fieldValidationMode === "change") {
2607
- scheduleFieldValidation(
2608
- path,
2609
- false
2610
- /* debounced */
2611
- );
3032
+ if (meta?.arrayOp !== void 0) {
3033
+ applyArrayOpToMemory(path, meta.arrayOp);
3034
+ } else if (Array.isArray(value) && Array.isArray(currentValue)) {
3035
+ clearVariantMemoryUnderPath(path);
3036
+ }
3037
+ const effectiveModeAfterWrite = meta?.instance?.validateOn ?? fieldValidationMode;
3038
+ if (effectiveModeAfterWrite === "change") {
3039
+ scheduleFieldValidation(path, false, {
3040
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3041
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3042
+ });
2612
3043
  }
2613
3044
  return true;
2614
3045
  }
@@ -2617,9 +3048,10 @@ function createFormStore(options) {
2617
3048
  const parentKey = canonicalizePath(parentPath).key;
2618
3049
  let baseline = variantDefault;
2619
3050
  let restoredBlanks;
2620
- if (rememberVariants && !sameDisc) {
3051
+ const effectiveRemember = meta?.instance?.rememberVariants ?? rememberVariants;
3052
+ if (effectiveRemember && !sameDisc) {
2621
3053
  if (oldDiscValue !== void 0) {
2622
- const currentValue2 = JSON.parse(JSON.stringify(getAtPath(form.value, parentPath)));
3054
+ const currentValue2 = cloneVariantSnapshot(getAtPath(form.value, parentPath));
2623
3055
  const outgoingBlanks = [];
2624
3056
  for (const k of blankPaths) {
2625
3057
  if (isPathKeyUnder(k, parentPath)) outgoingBlanks.push(k);
@@ -2641,7 +3073,10 @@ function createFormStore(options) {
2641
3073
  restoredBlanks = [...restored.blankPaths];
2642
3074
  }
2643
3075
  }
2644
- const finalValue = consumerOverrides !== void 0 ? { ...baseline, ...consumerOverrides } : baseline;
3076
+ const layered = consumerOverrides !== void 0 ? { ...baseline, ...consumerOverrides } : baseline;
3077
+ const finalValue = applyDuStubs(schema, layered, {
3078
+ basePath: parentPath
3079
+ });
2645
3080
  let newBlankPaths;
2646
3081
  if (restoredBlanks !== void 0) {
2647
3082
  newBlankPaths = restoredBlanks;
@@ -2660,9 +3095,10 @@ function createFormStore(options) {
2660
3095
  for (const k of newBlankPaths) blankPaths.add(k);
2661
3096
  return true;
2662
3097
  }
2663
- const nextForm = parentPath.length === 0 ? finalValue : setAtPath(form.value, parentPath, finalValue);
3098
+ const nextForm = parentPath.length === 0 ? finalValue : setAtPathWithSchemaFill(form.value, schema, parentPath, finalValue);
2664
3099
  let appliedSync = false;
2665
- if (fieldValidationMode === "change") {
3100
+ const reshapeMode = meta?.instance?.validateOn ?? fieldValidationMode;
3101
+ if (reshapeMode === "change") {
2666
3102
  const syncOrPromise = schema.validateAtPath(finalValue, parentPath, { sync: true });
2667
3103
  if (!(syncOrPromise instanceof Promise)) {
2668
3104
  const reStamped = syncOrPromise.success ? [] : syncOrPromise.errors.map((err) => ({
@@ -2682,17 +3118,18 @@ function createFormStore(options) {
2682
3118
  }
2683
3119
  applyFormReplacement(nextForm, meta);
2684
3120
  for (const k of newBlankPaths) blankPaths.add(k);
2685
- if (fieldValidationMode === "change" && !appliedSync) {
2686
- scheduleFieldValidation(
2687
- parentPath,
2688
- false
2689
- /* debounced */
2690
- );
3121
+ if (reshapeMode === "change" && !appliedSync) {
3122
+ scheduleFieldValidation(parentPath, false, {
3123
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3124
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3125
+ });
2691
3126
  }
2692
3127
  return true;
2693
3128
  }
2694
- function scheduleFieldValidation(path, immediate) {
2695
- if (fieldValidationMode === "submit") return;
3129
+ function scheduleFieldValidation(path, immediate, override) {
3130
+ const effectiveMode = override?.mode ?? fieldValidationMode;
3131
+ if (effectiveMode === "submit") return;
3132
+ const effectiveDebounce = override?.debounceMs ?? fieldValidationDebounceMs;
2696
3133
  const { key } = canonicalizePath(path);
2697
3134
  const prev = fieldValidationState.get(key);
2698
3135
  if (prev !== void 0) {
@@ -2706,8 +3143,17 @@ function createFormStore(options) {
2706
3143
  fresh.timer = null;
2707
3144
  if (controller.signal.aborted) return;
2708
3145
  const data = getAtPath(form.value, path);
2709
- activeValidations.value += 1;
2710
- incFieldValidation(key);
3146
+ let activeIncremented = false;
3147
+ try {
3148
+ activeValidations.value += 1;
3149
+ activeIncremented = true;
3150
+ incFieldValidation(key);
3151
+ } catch (err) {
3152
+ if (activeIncremented) {
3153
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
3154
+ }
3155
+ throw err;
3156
+ }
2711
3157
  void Promise.resolve().then(() => schema.validateAtPath(data, path)).then((response) => {
2712
3158
  if (controller.signal.aborted) return;
2713
3159
  const reStamped = response.success ? [] : response.errors.map((err) => ({
@@ -2721,10 +3167,10 @@ function createFormStore(options) {
2721
3167
  decFieldValidation(key);
2722
3168
  });
2723
3169
  };
2724
- if (immediate || fieldValidationDebounceMs === 0) {
3170
+ if (immediate || effectiveDebounce === 0) {
2725
3171
  run();
2726
3172
  } else {
2727
- fresh.timer = setTimeout(run, fieldValidationDebounceMs);
3173
+ fresh.timer = setTimeout(run, effectiveDebounce);
2728
3174
  }
2729
3175
  }
2730
3176
  function cancelFieldValidation() {
@@ -2788,6 +3234,8 @@ function createFormStore(options) {
2788
3234
  submitSuccessListeners.clear();
2789
3235
  resetListeners.clear();
2790
3236
  persistOptIns.clear();
3237
+ noSyncPaths.clear();
3238
+ noSyncPathCounts.clear();
2791
3239
  }
2792
3240
  function getValueAtPath(path) {
2793
3241
  return getAtPath(form.value, path);
@@ -2922,7 +3370,7 @@ function createFormStore(options) {
2922
3370
  if (current?.connected === true) return;
2923
3371
  touchFieldRecord(key, path, { connected: true });
2924
3372
  }
2925
- function markFocused(path, focused) {
3373
+ function markFocused(path, focused, meta) {
2926
3374
  const { key } = canonicalizePath(path);
2927
3375
  touchFieldRecord(key, path, {
2928
3376
  focused,
@@ -2931,12 +3379,12 @@ function createFormStore(options) {
2931
3379
  // a field is currently focused we keep whatever value it held.
2932
3380
  touched: focused ? fields.get(key)?.touched ?? null : true
2933
3381
  });
2934
- if (!focused && fieldValidationMode === "blur") {
2935
- scheduleFieldValidation(
2936
- path,
2937
- true
2938
- /* immediate */
2939
- );
3382
+ const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
3383
+ if (!focused && focusMode === "blur") {
3384
+ scheduleFieldValidation(path, true, {
3385
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3386
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3387
+ });
2940
3388
  }
2941
3389
  }
2942
3390
  function markTouched(path) {
@@ -2961,12 +3409,18 @@ function createFormStore(options) {
2961
3409
  );
2962
3410
  }
2963
3411
  }
3412
+ function clear(path) {
3413
+ return setValueAtPath(path, schema.getEmptyValueAtPath(path));
3414
+ }
2964
3415
  function reset(nextDefaultValues) {
2965
- const next = schema.getDefaultValues({
3416
+ const resetSource = nextDefaultValues ?? defaultValues;
3417
+ const completedResetConstraints = resetSource === void 0 ? void 0 : mergeStructural(schema, [], resetSource);
3418
+ const resetResponse = schema.getDefaultValues({
2966
3419
  useDefaultSchemaValues: true,
2967
- constraints: nextDefaultValues ?? defaultValues,
3420
+ constraints: completedResetConstraints,
2968
3421
  strict
2969
- }).data;
3422
+ });
3423
+ const next = resetResponse.data;
2970
3424
  applyFormReplacement(next);
2971
3425
  originals.clear();
2972
3426
  diffAndApply({}, next, [], (patch) => {
@@ -2985,6 +3439,24 @@ function createFormStore(options) {
2985
3439
  }
2986
3440
  schemaErrors.clear();
2987
3441
  userErrors.clear();
3442
+ if (strict && !resetResponse.success) {
3443
+ setAllSchemaErrors(resetResponse.errors);
3444
+ }
3445
+ if (strict) {
3446
+ const syncResult = schema.validateAtPath(form.value, void 0, { sync: true });
3447
+ if (!(syncResult instanceof Promise) && !syncResult.success) {
3448
+ applySchemaErrorsForSubtree([], syncResult.errors);
3449
+ }
3450
+ }
3451
+ firstValidationDone.value = !strict || schema.needsAsyncValidation?.() !== true;
3452
+ const needsAsync = !ssr && strict && schema.needsAsyncValidation?.() === true;
3453
+ if (needsAsync) {
3454
+ queueMicrotask(() => scheduleFieldValidation(
3455
+ [],
3456
+ true
3457
+ /* immediate */
3458
+ ));
3459
+ }
2988
3460
  const now = (/* @__PURE__ */ new Date()).toISOString();
2989
3461
  for (const [pathKey, record] of fields) {
2990
3462
  fields.set(pathKey, {
@@ -3028,30 +3500,28 @@ function createFormStore(options) {
3028
3500
  `[attaform] resetField: leaf write rejected for path '${targetKey}' \u2014 originals contain a value that doesn't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
3029
3501
  );
3030
3502
  }
3031
- schemaErrors.delete(targetKey);
3032
- userErrors.delete(targetKey);
3033
- clearFieldRecordFlags(targetKey);
3034
- return;
3035
- }
3036
- let subtree = void 0;
3037
- let anyMatch = false;
3038
- for (const [, entry] of originals) {
3039
- const leafSegments = entry.segments;
3040
- if (!isPathPrefix(targetSegments, leafSegments)) continue;
3041
- if (leafSegments.length === targetSegments.length) continue;
3042
- anyMatch = true;
3043
- const relative = leafSegments.slice(targetSegments.length);
3044
- if (subtree === void 0) {
3045
- subtree = typeof relative[0] === "number" ? [] : {};
3046
- }
3047
- subtree = setAtPath(subtree, relative, entry.value);
3048
- }
3049
- if (!anyMatch) return;
3050
- const wroteSubtree = setValueAtPath(targetSegments, subtree);
3051
- if (!wroteSubtree) {
3052
- console.error(
3053
- `[attaform] resetField: subtree write rejected at path '${targetKey}' \u2014 originals contain values that don't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
3054
- );
3503
+ } else {
3504
+ let subtree = void 0;
3505
+ let anyMatch = false;
3506
+ for (const [, entry] of originals) {
3507
+ const leafSegments = entry.segments;
3508
+ if (!isPathPrefix(targetSegments, leafSegments)) continue;
3509
+ if (leafSegments.length === targetSegments.length) continue;
3510
+ anyMatch = true;
3511
+ const relative = leafSegments.slice(targetSegments.length);
3512
+ if (subtree === void 0) {
3513
+ subtree = typeof relative[0] === "number" ? [] : {};
3514
+ }
3515
+ subtree = setAtPath(subtree, relative, entry.value);
3516
+ }
3517
+ if (anyMatch) {
3518
+ const wroteSubtree = setValueAtPath(targetSegments, subtree);
3519
+ if (!wroteSubtree) {
3520
+ console.error(
3521
+ `[attaform] resetField: subtree write rejected at path '${targetKey}' \u2014 originals contain values that don't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
3522
+ );
3523
+ }
3524
+ }
3055
3525
  }
3056
3526
  deleteErrorsUnderPrefix(schemaErrors, targetSegments);
3057
3527
  deleteErrorsUnderPrefix(userErrors, targetSegments);
@@ -3152,6 +3622,7 @@ function createFormStore(options) {
3152
3622
  getValueAtPath,
3153
3623
  reset,
3154
3624
  resetField,
3625
+ clear,
3155
3626
  setSchemaErrorsForPath,
3156
3627
  setAllSchemaErrors,
3157
3628
  clearSchemaErrors,
@@ -3181,6 +3652,11 @@ function createFormStore(options) {
3181
3652
  awaitPendingWrites,
3182
3653
  modules,
3183
3654
  persistOptIns,
3655
+ isSensitivePath: resolvedIsSensitivePath,
3656
+ segmentMatchesSensitive: resolvedSegmentMatchesSensitive,
3657
+ noSyncPaths,
3658
+ incrementNoSyncOptOut,
3659
+ decrementNoSyncOptOut,
3184
3660
  coerceIndex,
3185
3661
  blankPaths,
3186
3662
  originalBlankPaths,
@@ -3188,43 +3664,140 @@ function createFormStore(options) {
3188
3664
  };
3189
3665
  }
3190
3666
 
3191
- function getComputedSchema(formKey, schemaOrCallback) {
3192
- if (typeof schemaOrCallback === "function") return schemaOrCallback(formKey);
3667
+ function getComputedSchema(formKey, schemaOrCallback, options) {
3668
+ if (typeof schemaOrCallback === "function") return schemaOrCallback(formKey, options);
3193
3669
  return schemaOrCallback;
3194
3670
  }
3195
3671
 
3672
+ function captureErrorEntries(map) {
3673
+ const out = [];
3674
+ for (const [k, v] of map) out.push([k, [...v]]);
3675
+ return out;
3676
+ }
3677
+ function errorsEqual(a, b) {
3678
+ if (a.length !== b.length) return false;
3679
+ const bMap = /* @__PURE__ */ new Map();
3680
+ for (const [k, v] of b) bMap.set(k, v);
3681
+ for (const [k, v] of a) {
3682
+ const bv = bMap.get(k);
3683
+ if (bv === void 0) return false;
3684
+ if (v.length !== bv.length) return false;
3685
+ for (let i = 0; i < v.length; i++) {
3686
+ const av = v[i];
3687
+ const bvi = bv[i];
3688
+ if (av === bvi) continue;
3689
+ if (av.message !== bvi.message) return false;
3690
+ if (av.code !== bvi.code) return false;
3691
+ if (av.formKey !== bvi.formKey) return false;
3692
+ if (av.path !== bvi.path) {
3693
+ if (av.path.length !== bvi.path.length) return false;
3694
+ for (let j = 0; j < av.path.length; j++) {
3695
+ if (av.path[j] !== bvi.path[j]) return false;
3696
+ }
3697
+ }
3698
+ }
3699
+ }
3700
+ return true;
3701
+ }
3702
+ function diffBlankPaths$1(prev, curr) {
3703
+ const added = [];
3704
+ const removed = [];
3705
+ for (const k of curr) if (!prev.has(k)) added.push(k);
3706
+ for (const k of prev) if (!curr.has(k)) removed.push(k);
3707
+ return { added, removed };
3708
+ }
3709
+ function applyDeltaForward(snap, d) {
3710
+ const nextForm = applyPatchesForward(snap.form, d.formPatches);
3711
+ const nextBlank = new Set(snap.blankPaths);
3712
+ for (const k of d.blankPathsRemoved) nextBlank.delete(k);
3713
+ for (const k of d.blankPathsAdded) nextBlank.add(k);
3714
+ return {
3715
+ form: nextForm,
3716
+ blankPaths: [...nextBlank],
3717
+ schemaErrors: d.schemaErrors !== void 0 ? d.schemaErrors.after : snap.schemaErrors,
3718
+ userErrors: d.userErrors !== void 0 ? d.userErrors.after : snap.userErrors
3719
+ };
3720
+ }
3721
+ function applyDeltaInverse(snap, d) {
3722
+ const prevForm = applyPatchesInverse(snap.form, d.formPatches);
3723
+ const prevBlank = new Set(snap.blankPaths);
3724
+ for (const k of d.blankPathsAdded) prevBlank.delete(k);
3725
+ for (const k of d.blankPathsRemoved) prevBlank.add(k);
3726
+ return {
3727
+ form: prevForm,
3728
+ blankPaths: [...prevBlank],
3729
+ schemaErrors: d.schemaErrors !== void 0 ? d.schemaErrors.before : snap.schemaErrors,
3730
+ userErrors: d.userErrors !== void 0 ? d.userErrors.before : snap.userErrors
3731
+ };
3732
+ }
3196
3733
  function createHistoryModule(state, config) {
3197
- const max = typeof config === "object" ? config.max ?? DEFAULT_HISTORY_MAX_SNAPSHOTS : DEFAULT_HISTORY_MAX_SNAPSHOTS;
3198
- const undoStack = shallowRef([]);
3199
- const redoStack = shallowRef([]);
3200
- let suppressNext = false;
3734
+ const max = normalizeNumericOption({
3735
+ value: typeof config === "object" ? config.max ?? DEFAULT_HISTORY_MAX_SNAPSHOTS : DEFAULT_HISTORY_MAX_SNAPSHOTS,
3736
+ source: "useForm.history.max",
3737
+ allowInfinity: false,
3738
+ min: 0,
3739
+ defaultValue: DEFAULT_HISTORY_MAX_SNAPSHOTS
3740
+ });
3201
3741
  function captureSnapshot() {
3202
3742
  return {
3203
3743
  form: structuralSnapshot(state.form.value),
3204
3744
  blankPaths: [...state.blankPaths],
3205
- schemaErrors: [...state.schemaErrors.entries()].map(([k, v]) => [k, [...v]]),
3206
- userErrors: [...state.userErrors.entries()].map(([k, v]) => [k, [...v]])
3745
+ schemaErrors: captureErrorEntries(state.schemaErrors),
3746
+ userErrors: captureErrorEntries(state.userErrors)
3207
3747
  };
3208
3748
  }
3209
- function pushSnapshot(snap) {
3210
- const next = [...undoStack.value, snap];
3211
- undoStack.value = next.length > max ? next.slice(-max) : next;
3212
- redoStack.value = [];
3749
+ const initial = captureSnapshot();
3750
+ const base = shallowRef(initial);
3751
+ const currentSnapshot = shallowRef(initial);
3752
+ const undoDeltas = shallowRef([]);
3753
+ const redoDeltas = shallowRef([]);
3754
+ let suppressNext = false;
3755
+ function appendDelta(delta, newCurrent) {
3756
+ if (max === 0) {
3757
+ base.value = newCurrent;
3758
+ currentSnapshot.value = newCurrent;
3759
+ redoDeltas.value = [];
3760
+ return;
3761
+ }
3762
+ undoDeltas.value = [...undoDeltas.value, delta];
3763
+ redoDeltas.value = [];
3764
+ currentSnapshot.value = newCurrent;
3765
+ while (1 + undoDeltas.value.length > max && undoDeltas.value.length > 0) {
3766
+ const oldest = undoDeltas.value[0];
3767
+ base.value = applyDeltaForward(base.value, oldest);
3768
+ undoDeltas.value = undoDeltas.value.slice(1);
3769
+ }
3213
3770
  }
3214
- pushSnapshot(captureSnapshot());
3215
- const unsubscribeChange = state.onFormChange(() => {
3771
+ const unsubscribeChange = state.onFormChange((_next, meta) => {
3216
3772
  if (suppressNext) {
3217
3773
  suppressNext = false;
3218
3774
  return;
3219
3775
  }
3220
- pushSnapshot(captureSnapshot());
3221
- });
3222
- const unsubscribeReset = state.onReset(() => {
3223
- undoStack.value = [];
3224
- redoStack.value = [];
3225
- pushSnapshot(captureSnapshot());
3776
+ if (meta?.hydration === true) {
3777
+ clear();
3778
+ return;
3779
+ }
3780
+ if (meta?.crossTab === true) {
3781
+ currentSnapshot.value = captureSnapshot();
3782
+ return;
3783
+ }
3784
+ const newSnap = captureSnapshot();
3785
+ const prevSnap = currentSnapshot.value;
3786
+ const formPatches = [];
3787
+ diffAndApply(prevSnap.form, newSnap.form, [], (p) => formPatches.push(p));
3788
+ const prevBlankSet = new Set(prevSnap.blankPaths);
3789
+ const currBlankSet = new Set(newSnap.blankPaths);
3790
+ const blankDiff = diffBlankPaths$1(prevBlankSet, currBlankSet);
3791
+ const delta = {
3792
+ formPatches,
3793
+ blankPathsAdded: blankDiff.added,
3794
+ blankPathsRemoved: blankDiff.removed,
3795
+ ...errorsEqual(prevSnap.schemaErrors, newSnap.schemaErrors) ? {} : { schemaErrors: { before: prevSnap.schemaErrors, after: newSnap.schemaErrors } },
3796
+ ...errorsEqual(prevSnap.userErrors, newSnap.userErrors) ? {} : { userErrors: { before: prevSnap.userErrors, after: newSnap.userErrors } }
3797
+ };
3798
+ appendDelta(delta, newSnap);
3226
3799
  });
3227
- function restore(snap) {
3800
+ function restoreCurrent(snap) {
3228
3801
  suppressNext = true;
3229
3802
  state.blankPaths.clear();
3230
3803
  for (const key of snap.blankPaths) state.blankPaths.add(key);
@@ -3237,38 +3810,46 @@ function createHistoryModule(state, config) {
3237
3810
  state.setAllUserErrors(userFlat);
3238
3811
  }
3239
3812
  function undo() {
3240
- if (undoStack.value.length <= 1) return false;
3241
- const current = undoStack.value[undoStack.value.length - 1];
3242
- const prev = undoStack.value[undoStack.value.length - 2];
3243
- if (current === void 0 || prev === void 0) return false;
3244
- redoStack.value = [...redoStack.value, current];
3245
- undoStack.value = undoStack.value.slice(0, -1);
3246
- restore(prev);
3813
+ if (undoDeltas.value.length === 0) return false;
3814
+ const d = undoDeltas.value[undoDeltas.value.length - 1];
3815
+ const restored = applyDeltaInverse(currentSnapshot.value, d);
3816
+ redoDeltas.value = [...redoDeltas.value, d];
3817
+ undoDeltas.value = undoDeltas.value.slice(0, -1);
3818
+ currentSnapshot.value = restored;
3819
+ restoreCurrent(restored);
3247
3820
  return true;
3248
3821
  }
3249
3822
  function redo() {
3250
- if (redoStack.value.length === 0) return false;
3251
- const next = redoStack.value[redoStack.value.length - 1];
3252
- if (next === void 0) return false;
3253
- redoStack.value = redoStack.value.slice(0, -1);
3254
- undoStack.value = [...undoStack.value, next];
3255
- restore(next);
3823
+ if (redoDeltas.value.length === 0) return false;
3824
+ const d = redoDeltas.value[redoDeltas.value.length - 1];
3825
+ const next = applyDeltaForward(currentSnapshot.value, d);
3826
+ undoDeltas.value = [...undoDeltas.value, d];
3827
+ redoDeltas.value = redoDeltas.value.slice(0, -1);
3828
+ currentSnapshot.value = next;
3829
+ restoreCurrent(next);
3256
3830
  return true;
3257
3831
  }
3258
- const canUndo = computed(() => undoStack.value.length > 1);
3259
- const canRedo = computed(() => redoStack.value.length > 0);
3260
- const historySize = computed(() => undoStack.value.length + redoStack.value.length);
3832
+ function clear() {
3833
+ const fresh = captureSnapshot();
3834
+ base.value = fresh;
3835
+ currentSnapshot.value = fresh;
3836
+ undoDeltas.value = [];
3837
+ redoDeltas.value = [];
3838
+ }
3839
+ const canUndo = computed(() => undoDeltas.value.length > 0);
3840
+ const canRedo = computed(() => redoDeltas.value.length > 0);
3841
+ const historySize = computed(() => 1 + undoDeltas.value.length + redoDeltas.value.length);
3261
3842
  return {
3262
3843
  undo,
3263
3844
  redo,
3845
+ clear,
3264
3846
  canUndo,
3265
3847
  canRedo,
3266
3848
  historySize,
3267
3849
  dispose() {
3268
3850
  unsubscribeChange();
3269
- unsubscribeReset();
3270
- undoStack.value = [];
3271
- redoStack.value = [];
3851
+ undoDeltas.value = [];
3852
+ redoDeltas.value = [];
3272
3853
  }
3273
3854
  };
3274
3855
  }
@@ -3286,12 +3867,368 @@ function hashStableString(input, seed = 0) {
3286
3867
  return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
3287
3868
  }
3288
3869
 
3870
+ const PROTOCOL_VERSION = 1;
3871
+ const JOIN_COLLECTION_WINDOW_MS = 50;
3872
+ const SNAPSHOT_TIMEOUT_MS = 200;
3873
+ const MAX_LEADER_ATTEMPTS = 3;
3874
+ function isDangerousSegment(s) {
3875
+ return s === "__proto__" || s === "constructor" || s === "prototype";
3876
+ }
3877
+ function pathContainsDangerousSegment(path) {
3878
+ for (let i = 0; i < path.length; i++) {
3879
+ if (isDangerousSegment(path[i])) return true;
3880
+ }
3881
+ return false;
3882
+ }
3883
+ function diffBlankPaths(prev, curr) {
3884
+ const added = [];
3885
+ const removed = [];
3886
+ const prevSet = new Set(prev);
3887
+ for (const k of curr) if (!prevSet.has(k)) added.push(k);
3888
+ for (const k of prev) if (!curr.has(k)) removed.push(k);
3889
+ return { added, removed };
3890
+ }
3891
+ function snapshotForm(form) {
3892
+ return structuralSnapshot(form);
3893
+ }
3894
+ function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
3895
+ if (value === null || typeof value !== "object") return value;
3896
+ if (Array.isArray(value)) {
3897
+ return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
3898
+ }
3899
+ const proto = Object.getPrototypeOf(value);
3900
+ if (proto !== Object.prototype && proto !== null) return value;
3901
+ const out = {};
3902
+ const src = value;
3903
+ for (const key of Object.keys(src)) {
3904
+ const childPath = [...pathSoFar, key];
3905
+ if (isSensitivePath(childPath)) {
3906
+ out[key] = void 0;
3907
+ continue;
3908
+ }
3909
+ out[key] = stripSensitivePathsDeep(src[key], childPath, isSensitivePath);
3910
+ }
3911
+ return out;
3912
+ }
3913
+ function isValidSyncMessage(data) {
3914
+ if (data === null || typeof data !== "object") return false;
3915
+ const m = data;
3916
+ if (m["v"] !== PROTOCOL_VERSION) return false;
3917
+ if (typeof m["senderId"] !== "string") return false;
3918
+ if (typeof m["kind"] !== "string") return false;
3919
+ switch (m["kind"]) {
3920
+ case "hello":
3921
+ case "announce":
3922
+ return true;
3923
+ case "requestSnapshot":
3924
+ return typeof m["targetId"] === "string";
3925
+ case "snapshot":
3926
+ return Array.isArray(m["blankPaths"]) && "form" in m;
3927
+ case "patches":
3928
+ return Array.isArray(m["formPatches"]) && Array.isArray(m["blankPathsAdded"]) && Array.isArray(m["blankPathsRemoved"]);
3929
+ default:
3930
+ return false;
3931
+ }
3932
+ }
3933
+ function generateSenderId() {
3934
+ try {
3935
+ return globalThis.crypto.randomUUID();
3936
+ } catch {
3937
+ return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
3938
+ }
3939
+ }
3940
+ function createMultiTabSyncModule(state, channelName, options) {
3941
+ if (typeof BroadcastChannel === "undefined") {
3942
+ return {
3943
+ dispose: () => void 0,
3944
+ lifecycle: () => "established",
3945
+ senderId: "",
3946
+ channelName
3947
+ };
3948
+ }
3949
+ let channel;
3950
+ try {
3951
+ channel = new BroadcastChannel(channelName);
3952
+ } catch {
3953
+ return {
3954
+ dispose: () => void 0,
3955
+ lifecycle: () => "established",
3956
+ senderId: "",
3957
+ channelName
3958
+ };
3959
+ }
3960
+ const senderId = generateSenderId();
3961
+ let lifecycle = "joining";
3962
+ let disposed = false;
3963
+ const peerIds = /* @__PURE__ */ new Set();
3964
+ let joinCollectionTimer = null;
3965
+ let snapshotTimeoutTimer = null;
3966
+ let leaderAttempts = 0;
3967
+ let prior = {
3968
+ form: snapshotForm(state.form.value),
3969
+ blankPathsSnapshot: [...state.blankPaths]
3970
+ };
3971
+ function safePost(msg) {
3972
+ if (disposed) return;
3973
+ try {
3974
+ channel.postMessage(msg);
3975
+ } catch {
3976
+ }
3977
+ }
3978
+ function refreshPrior() {
3979
+ prior = {
3980
+ form: snapshotForm(state.form.value),
3981
+ blankPathsSnapshot: [...state.blankPaths]
3982
+ };
3983
+ }
3984
+ function isPathLocallySuppressed(path) {
3985
+ if (pathContainsDangerousSegment(path)) return true;
3986
+ if (options.isSensitivePath(path)) return true;
3987
+ const { key } = canonicalizePath([...path]);
3988
+ if (options.noSyncPaths.has(key)) return true;
3989
+ return false;
3990
+ }
3991
+ function postPatches() {
3992
+ if (lifecycle !== "established") return;
3993
+ const next = snapshotForm(state.form.value);
3994
+ const rawPatches = [];
3995
+ diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
3996
+ const safePatches = [];
3997
+ for (const p of rawPatches) {
3998
+ if (isPathLocallySuppressed(p.path)) continue;
3999
+ safePatches.push(p);
4000
+ }
4001
+ const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
4002
+ if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
4003
+ prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
4004
+ return;
4005
+ }
4006
+ safePost({
4007
+ v: PROTOCOL_VERSION,
4008
+ kind: "patches",
4009
+ senderId,
4010
+ formPatches: safePatches,
4011
+ blankPathsAdded: added,
4012
+ blankPathsRemoved: removed
4013
+ });
4014
+ prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
4015
+ }
4016
+ const unsubscribeChange = state.onFormChange((_next, meta) => {
4017
+ if (disposed) return;
4018
+ if (lifecycle !== "established") return;
4019
+ if (meta?.crossTab === true) return;
4020
+ if (meta?.hydration === true) {
4021
+ refreshPrior();
4022
+ return;
4023
+ }
4024
+ postPatches();
4025
+ });
4026
+ function applyIncomingForm(form, blankPaths) {
4027
+ state.blankPaths.clear();
4028
+ for (const k of blankPaths) state.blankPaths.add(k);
4029
+ state.applyFormReplacement(form, { crossTab: true, persist: false });
4030
+ refreshPrior();
4031
+ }
4032
+ function handlePatches(msg) {
4033
+ if (lifecycle !== "established") return;
4034
+ const safePatches = [];
4035
+ for (const p of msg.formPatches) {
4036
+ if (!Array.isArray(p.path)) continue;
4037
+ if (isPathLocallySuppressed(p.path)) continue;
4038
+ safePatches.push(p);
4039
+ }
4040
+ const safeBlankAdded = [];
4041
+ for (const k of msg.blankPathsAdded) {
4042
+ const segs = canonicalizePath(k).segments;
4043
+ if (isPathLocallySuppressed(segs)) continue;
4044
+ safeBlankAdded.push(k);
4045
+ }
4046
+ const safeBlankRemoved = [];
4047
+ for (const k of msg.blankPathsRemoved) {
4048
+ const segs = canonicalizePath(k).segments;
4049
+ if (isPathLocallySuppressed(segs)) continue;
4050
+ safeBlankRemoved.push(k);
4051
+ }
4052
+ if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
4053
+ return;
4054
+ }
4055
+ const candidate = applyPatchesForward(state.form.value, safePatches);
4056
+ try {
4057
+ options.validateForm(state.form.value);
4058
+ try {
4059
+ options.validateForm(candidate);
4060
+ } catch {
4061
+ return;
4062
+ }
4063
+ } catch {
4064
+ }
4065
+ const nextBlankPaths = new Set(state.blankPaths);
4066
+ for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
4067
+ for (const k of safeBlankAdded) nextBlankPaths.add(k);
4068
+ applyIncomingForm(candidate, [...nextBlankPaths]);
4069
+ }
4070
+ function handleSnapshot(msg) {
4071
+ if (lifecycle !== "joining") return;
4072
+ try {
4073
+ options.validateForm(msg.form);
4074
+ } catch {
4075
+ return;
4076
+ }
4077
+ if (snapshotTimeoutTimer !== null) {
4078
+ clearTimeout(snapshotTimeoutTimer);
4079
+ snapshotTimeoutTimer = null;
4080
+ }
4081
+ if (joinCollectionTimer !== null) {
4082
+ clearTimeout(joinCollectionTimer);
4083
+ joinCollectionTimer = null;
4084
+ }
4085
+ applyIncomingForm(msg.form, msg.blankPaths);
4086
+ lifecycle = "established";
4087
+ peerIds.clear();
4088
+ }
4089
+ function respondToHello() {
4090
+ safePost({ v: PROTOCOL_VERSION, kind: "announce", senderId });
4091
+ }
4092
+ function respondToSnapshotRequest() {
4093
+ const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath);
4094
+ safePost({
4095
+ v: PROTOCOL_VERSION,
4096
+ kind: "snapshot",
4097
+ senderId,
4098
+ form: scrubbedForm,
4099
+ blankPaths: [...state.blankPaths]
4100
+ });
4101
+ }
4102
+ channel.onmessage = (event) => {
4103
+ if (disposed) return;
4104
+ const data = event.data;
4105
+ if (!isValidSyncMessage(data)) return;
4106
+ const msg = data;
4107
+ if (msg.senderId === senderId) return;
4108
+ switch (msg.kind) {
4109
+ case "hello":
4110
+ if (lifecycle !== "established") return;
4111
+ respondToHello();
4112
+ break;
4113
+ case "announce":
4114
+ if (lifecycle === "joining") peerIds.add(msg.senderId);
4115
+ break;
4116
+ case "requestSnapshot":
4117
+ if (lifecycle !== "established") return;
4118
+ if (msg.targetId !== senderId) return;
4119
+ respondToSnapshotRequest();
4120
+ break;
4121
+ case "snapshot":
4122
+ handleSnapshot(msg);
4123
+ break;
4124
+ case "patches":
4125
+ handlePatches(msg);
4126
+ break;
4127
+ }
4128
+ };
4129
+ function electLeaderAndRequest() {
4130
+ if (disposed) return;
4131
+ if (peerIds.size === 0) {
4132
+ lifecycle = "established";
4133
+ refreshPrior();
4134
+ return;
4135
+ }
4136
+ const sorted = [...peerIds].sort();
4137
+ const leaderId = sorted[0];
4138
+ peerIds.delete(leaderId);
4139
+ leaderAttempts++;
4140
+ safePost({
4141
+ v: PROTOCOL_VERSION,
4142
+ kind: "requestSnapshot",
4143
+ senderId,
4144
+ targetId: leaderId
4145
+ });
4146
+ snapshotTimeoutTimer = setTimeout(() => {
4147
+ snapshotTimeoutTimer = null;
4148
+ if (disposed) return;
4149
+ if (lifecycle === "established") return;
4150
+ if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
4151
+ lifecycle = "established";
4152
+ refreshPrior();
4153
+ return;
4154
+ }
4155
+ electLeaderAndRequest();
4156
+ }, SNAPSHOT_TIMEOUT_MS);
4157
+ }
4158
+ function joinFlow() {
4159
+ safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
4160
+ joinCollectionTimer = setTimeout(() => {
4161
+ joinCollectionTimer = null;
4162
+ if (disposed) return;
4163
+ if (lifecycle === "established") return;
4164
+ electLeaderAndRequest();
4165
+ }, JOIN_COLLECTION_WINDOW_MS);
4166
+ }
4167
+ joinFlow();
4168
+ return {
4169
+ dispose: () => {
4170
+ if (disposed) return;
4171
+ disposed = true;
4172
+ if (joinCollectionTimer !== null) {
4173
+ clearTimeout(joinCollectionTimer);
4174
+ joinCollectionTimer = null;
4175
+ }
4176
+ if (snapshotTimeoutTimer !== null) {
4177
+ clearTimeout(snapshotTimeoutTimer);
4178
+ snapshotTimeoutTimer = null;
4179
+ }
4180
+ unsubscribeChange();
4181
+ try {
4182
+ channel.close();
4183
+ } catch {
4184
+ }
4185
+ },
4186
+ lifecycle: () => lifecycle,
4187
+ senderId,
4188
+ channelName
4189
+ };
4190
+ }
4191
+ const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
4192
+
4193
+ const warned = /* @__PURE__ */ new Set();
4194
+ function warnOnceInsecureContext(feature) {
4195
+ if (!__DEV__) return;
4196
+ if (warned.has(feature)) return;
4197
+ warned.add(feature);
4198
+ const message = featureMessage(feature);
4199
+ console.warn(`[attaform] ${message}`);
4200
+ }
4201
+ function featureMessage(feature) {
4202
+ switch (feature) {
4203
+ case "multiTab":
4204
+ return "Multi-tab sync requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is interceptable by network observers, so the sync module is disabled. Serve over HTTPS in production (or develop on `localhost`) to enable cross-tab synchronisation. Use `multiTab: false` on `useForm` to silence this warning.";
4205
+ case "persist:local":
4206
+ return "Built-in `persist: 'local'` storage requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is MITM-interceptable, so the persistence layer is disabled. Serve over HTTPS to enable localStorage persistence, or pass a custom storage adapter to opt out of the secure-context gate.";
4207
+ case "persist:session":
4208
+ return "Built-in `persist: 'session'` storage requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is MITM-interceptable, so the persistence layer is disabled. Serve over HTTPS to enable sessionStorage persistence, or pass a custom storage adapter to opt out of the secure-context gate.";
4209
+ }
4210
+ }
4211
+ function isSecureContext() {
4212
+ return typeof window !== "undefined" && window.isSecureContext === true;
4213
+ }
4214
+
3289
4215
  function useAbstractForm(configuration) {
3290
4216
  if (configuration === void 0 || configuration === null || configuration.schema === void 0) {
3291
4217
  throw new InvalidUseFormConfigError();
3292
4218
  }
3293
4219
  const key = resolveFormKey(configuration.key);
3294
- const resolvedSchema = getComputedSchema(key, configuration.schema);
4220
+ const instance = getCurrentInstance();
4221
+ if (instance !== null) ensureAttaformInstalled(instance.appContext.app);
4222
+ const registry = useRegistry();
4223
+ const merged = mergeWithDefaults(registry.defaults, configuration);
4224
+ const maxRecursionDepth = normalizeNumericOption({
4225
+ value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
4226
+ source: "useForm.maxRecursionDepth",
4227
+ allowInfinity: true,
4228
+ min: 0,
4229
+ defaultValue: DEFAULT_MAX_RECURSION_DEPTH
4230
+ });
4231
+ const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
3295
4232
  if (configuration.persist !== void 0 && configuration.key === void 0) {
3296
4233
  throw new AnonPersistError({
3297
4234
  cause: "no-key",
@@ -3299,13 +4236,10 @@ function useAbstractForm(configuration) {
3299
4236
  callSite: captureUserCallSite()
3300
4237
  });
3301
4238
  }
3302
- const instance = getCurrentInstance();
3303
- if (instance !== null) ensureAttaformInstalled(instance.appContext.app);
3304
- const registry = useRegistry();
3305
- const merged = mergeWithDefaults(registry.defaults, configuration);
3306
4239
  const existing = registry.forms.get(key);
3307
4240
  if (__DEV__ && existing !== void 0) {
3308
4241
  warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
4242
+ warnOnPersistDivergence(key, existing, configuration.persist);
3309
4243
  }
3310
4244
  const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
3311
4245
  if (getCurrentScope() !== void 0) {
@@ -3316,16 +4250,54 @@ function useAbstractForm(configuration) {
3316
4250
  if (existing === void 0 && !registry.ssr) {
3317
4251
  if (merged.persist !== void 0 && !persistDisabledByAnonRule) {
3318
4252
  const resolvedPersist = normalizePersistConfig(merged.persist);
3319
- const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
3320
- void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
3321
- const persistenceModule = wirePersistence(state, resolvedPersist);
3322
- state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
3323
- state.registerDrain(() => persistenceModule.awaitPendingWrites());
3324
- state.registerCleanup(() => persistenceModule.dispose());
4253
+ const storageKind = resolvedPersist.storage;
4254
+ const isBuiltinStorage = typeof storageKind === "string";
4255
+ const secureContextOk = !isBuiltinStorage || isSecureContext();
4256
+ if (!secureContextOk) {
4257
+ const feature = storageKind === "session" ? "persist:session" : "persist:local";
4258
+ warnOnceInsecureContext(feature);
4259
+ void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
4260
+ } else {
4261
+ const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
4262
+ void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
4263
+ const persistenceModule = wirePersistence(state, resolvedPersist);
4264
+ state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
4265
+ state.registerDrain(() => persistenceModule.awaitPendingWrites());
4266
+ state.registerCleanup(() => persistenceModule.dispose());
4267
+ }
3325
4268
  } else {
3326
4269
  void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
3327
4270
  }
3328
4271
  }
4272
+ if (existing === void 0 && merged.multiTab !== false && configuration.key !== void 0 && !registry.ssr) {
4273
+ const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
4274
+ const secureContext = isSecureContext();
4275
+ if (hasBroadcastChannel && secureContext) {
4276
+ let channelName;
4277
+ try {
4278
+ channelName = `attaform:sync:${state.formKey}:${hashStableString(state.schema.fingerprint())}`;
4279
+ } catch {
4280
+ channelName = null;
4281
+ }
4282
+ if (channelName !== null) {
4283
+ const syncModule = createMultiTabSyncModule(state, channelName, {
4284
+ isSensitivePath: state.isSensitivePath,
4285
+ noSyncPaths: state.noSyncPaths,
4286
+ validateForm: (form) => {
4287
+ const result = state.schema.validateAtPath(form, void 0, { sync: true });
4288
+ if (result instanceof Promise) return;
4289
+ if (!result.success) {
4290
+ throw new Error("attaform multi-tab sync: post-apply schema validation failed");
4291
+ }
4292
+ }
4293
+ });
4294
+ state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
4295
+ state.registerCleanup(() => syncModule.dispose());
4296
+ }
4297
+ } else if (hasBroadcastChannel && !secureContext) {
4298
+ warnOnceInsecureContext("multiTab");
4299
+ }
4300
+ }
3329
4301
  if (existing === void 0 && merged.history !== void 0) {
3330
4302
  const historyModule = createHistoryModule(state, merged.history);
3331
4303
  state.modules.set(HISTORY_MODULE_KEY, historyModule);
@@ -3347,7 +4319,27 @@ function useAbstractForm(configuration) {
3347
4319
  if (history !== void 0) {
3348
4320
  apiOptions.history = history;
3349
4321
  }
3350
- return buildFormApi(state, formInstanceId, apiOptions);
4322
+ if (merged.validateOn !== void 0) {
4323
+ apiOptions.validateOn = merged.validateOn;
4324
+ }
4325
+ const mergedDebounceMs = merged.debounceMs;
4326
+ if (mergedDebounceMs !== void 0) {
4327
+ apiOptions.debounceMs = mergedDebounceMs;
4328
+ }
4329
+ if (merged.shouldShowErrors !== void 0) {
4330
+ apiOptions.shouldShowErrors = resolveShouldShowErrors(merged.shouldShowErrors);
4331
+ }
4332
+ if (merged.coerce !== void 0) {
4333
+ apiOptions.coerce = merged.coerce;
4334
+ }
4335
+ if (merged.rememberVariants !== void 0) {
4336
+ apiOptions.rememberVariants = merged.rememberVariants;
4337
+ }
4338
+ return buildFormApi(
4339
+ state,
4340
+ formInstanceId,
4341
+ apiOptions
4342
+ );
3351
4343
  }
3352
4344
  function mergeWithDefaults(defaults, configuration) {
3353
4345
  const strict = configuration.strict ?? defaults.strict;
@@ -3358,6 +4350,9 @@ function mergeWithDefaults(defaults, configuration) {
3358
4350
  const validateOn = configuration.validateOn ?? defaults.validateOn;
3359
4351
  const debounceMs = configuration.debounceMs ?? defaults.debounceMs;
3360
4352
  const shouldShowErrors = configuration.shouldShowErrors ?? defaults.shouldShowErrors;
4353
+ const maxRecursionDepth = configuration.maxRecursionDepth ?? defaults.maxRecursionDepth;
4354
+ const sensitiveNames = configuration.sensitiveNames ?? defaults.sensitiveNames;
4355
+ const multiTab = configuration.multiTab ?? defaults.multiTab;
3361
4356
  return {
3362
4357
  ...configuration,
3363
4358
  ...strict === void 0 ? {} : { strict },
@@ -3367,7 +4362,10 @@ function mergeWithDefaults(defaults, configuration) {
3367
4362
  ...coerce === void 0 ? {} : { coerce },
3368
4363
  ...validateOn === void 0 ? {} : { validateOn },
3369
4364
  ...debounceMs === void 0 ? {} : { debounceMs },
3370
- ...shouldShowErrors === void 0 ? {} : { shouldShowErrors }
4365
+ ...shouldShowErrors === void 0 ? {} : { shouldShowErrors },
4366
+ ...maxRecursionDepth === void 0 ? {} : { maxRecursionDepth },
4367
+ ...sensitiveNames === void 0 ? {} : { sensitiveNames },
4368
+ ...multiTab === void 0 ? {} : { multiTab }
3371
4369
  };
3372
4370
  }
3373
4371
  const HISTORY_MODULE_KEY = "history";
@@ -3379,6 +4377,9 @@ function buildFreshState(key, schema, configuration, registry) {
3379
4377
  schema
3380
4378
  );
3381
4379
  const initialBlankPaths = pending === void 0 ? walked.paths : void 0;
4380
+ const resolvedSensitiveNames = configuration.sensitiveNames;
4381
+ const resolvedIsSensitivePath = resolvedSensitiveNames === void 0 ? void 0 : createIsSensitivePath(resolvedSensitiveNames);
4382
+ const resolvedSegmentMatchesSensitive = resolvedSensitiveNames === void 0 ? void 0 : createSegmentMatchesSensitive(resolvedSensitiveNames);
3382
4383
  const createOptions = {
3383
4384
  formKey: key,
3384
4385
  schema,
@@ -3391,7 +4392,9 @@ function buildFreshState(key, schema, configuration, registry) {
3391
4392
  ...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
3392
4393
  ...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
3393
4394
  ...configuration.shouldShowErrors !== void 0 ? { shouldShowErrors: configuration.shouldShowErrors } : {},
3394
- ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {}
4395
+ ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {},
4396
+ ...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {},
4397
+ ...resolvedSegmentMatchesSensitive !== void 0 ? { segmentMatchesSensitive: resolvedSegmentMatchesSensitive } : {}
3395
4398
  };
3396
4399
  const state = createFormStore(createOptions);
3397
4400
  registry.forms.set(
@@ -3450,11 +4453,47 @@ function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
3450
4453
  incoming: ${incomingFp}`
3451
4454
  );
3452
4455
  }
4456
+ function warnOnPersistDivergence(key, existing, incomingPersist) {
4457
+ if (incomingPersist === void 0) return;
4458
+ const wired = existing.modules.get(PERSISTENCE_MODULE_KEY);
4459
+ const incomingNormalized = normalizePersistConfig(incomingPersist);
4460
+ if (wired === void 0) {
4461
+ console.warn(
4462
+ `[attaform] useForm({ key: "${key}" }) passed a persist config but the first useForm({ key }) call didn't wire persistence; the new config is silently dropped. Pass persist on the first call, or remove persist here to make the inheritance explicit.`
4463
+ );
4464
+ return;
4465
+ }
4466
+ if (persistConfigsEquivalent(wired.wiredConfig, incomingNormalized)) return;
4467
+ console.warn(
4468
+ `[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
4469
+ wired: ${describePersist(wired.wiredConfig)}
4470
+ incoming: ${describePersist(incomingNormalized)}`
4471
+ );
4472
+ }
4473
+ function persistConfigsEquivalent(a, b) {
4474
+ if (a.storage !== b.storage) return false;
4475
+ if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
4476
+ if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
4477
+ return true;
4478
+ }
4479
+ function describePersist(config) {
4480
+ const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
4481
+ const parts = [`storage=${storage}`];
4482
+ if (config.key !== void 0) parts.push(`key=${config.key}`);
4483
+ if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
4484
+ return `{ ${parts.join(", ")} }`;
4485
+ }
3453
4486
  function wirePersistence(state, config) {
3454
4487
  const fingerprint = hashStableString(state.schema.fingerprint());
3455
4488
  const base = resolveStorageKeyBase(config, state.formKey);
3456
4489
  const key = `${base}:${fingerprint}`;
3457
- const debounceMs = config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS;
4490
+ const debounceMs = normalizeNumericOption({
4491
+ value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
4492
+ source: "useForm.persist.debounceMs",
4493
+ allowInfinity: false,
4494
+ min: 0,
4495
+ defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
4496
+ });
3458
4497
  const include = config.include ?? "form";
3459
4498
  const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
3460
4499
  const adapterPromise = getStorageAdapter(config.storage);
@@ -3489,6 +4528,7 @@ function wirePersistence(state, config) {
3489
4528
  }, debounceMs);
3490
4529
  const unsubscribeChange = state.onFormChange((_next, meta) => {
3491
4530
  if (disposed || inFlightFinalFlush !== null) return;
4531
+ if (meta?.crossTab === true) return;
3492
4532
  if (meta?.persist !== true) return;
3493
4533
  pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
3494
4534
  writer.schedule();
@@ -3522,7 +4562,7 @@ function wirePersistence(state, config) {
3522
4562
  payload.data.form,
3523
4563
  state.schema
3524
4564
  );
3525
- state.applyFormReplacement(merged);
4565
+ state.applyFormReplacement(merged, { hydration: true });
3526
4566
  const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
3527
4567
  for (const k of persistedLeafPaths) {
3528
4568
  state.blankPaths.delete(k);
@@ -3653,6 +4693,7 @@ function wirePersistence(state, config) {
3653
4693
  });
3654
4694
  }
3655
4695
  return {
4696
+ wiredConfig: config,
3656
4697
  writePathImmediately,
3657
4698
  clearPersistedDraft,
3658
4699
  awaitPendingWrites,
@@ -3727,7 +4768,11 @@ function injectForm(key) {
3727
4768
  }
3728
4769
  const ambientInstanceId = getCurrentInstance() !== null ? inject(kFormInstanceId, null) : null;
3729
4770
  const formInstanceId = ambientInstanceId ?? (getCurrentInstance() !== null ? useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`);
3730
- return buildFormApi(state, formInstanceId, apiOptions);
4771
+ return buildFormApi(
4772
+ state,
4773
+ formInstanceId,
4774
+ apiOptions
4775
+ );
3731
4776
  }
3732
4777
  function resolveState(key, registry) {
3733
4778
  if (key !== void 0) {
@@ -3771,5 +4816,5 @@ function warnIfAmbientProviderHadDuplicates() {
3771
4816
  }
3772
4817
  }
3773
4818
 
3774
- export { AttaformErrorCode as A, isUnset as a, defaultShouldShowErrors as b, defineCoercion as c, defaultCoercionRules as d, useAbstractForm as e, setAtPath as f, getAtPath as g, humanize as h, injectForm as i, isPlainRecord as j, slimKindOf as s, unset as u };
3775
- //# sourceMappingURL=attaform.DyV1O4tI.mjs.map
4819
+ export { AttaformErrorCode as A, isUnset as a, defaultShouldShowErrors as b, defineCoercion as c, defaultCoercionRules as d, useAbstractForm as e, setAtPath as f, getAtPath as g, humanize as h, injectForm as i, isPlainRecord as j, normalizeNumericOption as n, slimKindOf as s, unset as u };
4820
+ //# sourceMappingURL=attaform.B3ZaPIzS.mjs.map