attaform 0.16.4 → 0.17.0

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