attaform 0.16.4 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +4 -2
  2. package/dist/chunks/devtools.cjs +19 -12
  3. package/dist/chunks/devtools.cjs.map +1 -1
  4. package/dist/chunks/devtools.mjs +19 -12
  5. package/dist/chunks/devtools.mjs.map +1 -1
  6. package/dist/chunks/indexeddb.cjs +1 -1
  7. package/dist/chunks/indexeddb.mjs +1 -1
  8. package/dist/chunks/local-storage.cjs +1 -1
  9. package/dist/chunks/local-storage.mjs +1 -1
  10. package/dist/chunks/session-storage.cjs +1 -1
  11. package/dist/chunks/session-storage.mjs +1 -1
  12. package/dist/index.cjs +26 -7
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +52 -9
  15. package/dist/index.d.mts +52 -9
  16. package/dist/index.d.ts +52 -9
  17. package/dist/index.mjs +28 -9
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/nuxt.d.cts +1 -1
  20. package/dist/nuxt.d.mts +1 -1
  21. package/dist/nuxt.d.ts +1 -1
  22. package/dist/runtime/plugins/attaform.cjs +3 -3
  23. package/dist/runtime/plugins/attaform.cjs.map +1 -1
  24. package/dist/runtime/plugins/attaform.mjs +3 -3
  25. package/dist/runtime/plugins/attaform.mjs.map +1 -1
  26. package/dist/shared/{attaform.CMRmwGDt.d.cts → attaform.B1jvxsOF.d.mts} +1 -1
  27. package/dist/shared/{attaform.DyV1O4tI.mjs → attaform.B3ZaPIzS.mjs} +1436 -391
  28. package/dist/shared/attaform.B3ZaPIzS.mjs.map +1 -0
  29. package/dist/shared/{attaform.Dd_pWnmn.cjs → attaform.B5qiXQwN.cjs} +59 -10
  30. package/dist/shared/attaform.B5qiXQwN.cjs.map +1 -0
  31. package/dist/shared/{attaform.CIwZtbGV.cjs → attaform.BBM2muQ9.cjs} +2 -2
  32. package/dist/shared/{attaform.CIwZtbGV.cjs.map → attaform.BBM2muQ9.cjs.map} +1 -1
  33. package/dist/shared/{attaform.keLBaHB6.cjs → attaform.BV40t5y2.cjs} +240 -115
  34. package/dist/shared/attaform.BV40t5y2.cjs.map +1 -0
  35. package/dist/shared/attaform.C0iFnTN0.d.ts +165 -0
  36. package/dist/shared/{attaform.CXMOheyZ.d.mts → attaform.C6qzEdIM.d.cts} +1 -1
  37. package/dist/shared/{attaform.CJttVxRj.cjs → attaform.C8LVFVVe.cjs} +2 -2
  38. package/dist/shared/{attaform.CJttVxRj.cjs.map → attaform.C8LVFVVe.cjs.map} +1 -1
  39. package/dist/shared/attaform.CHorcsIU.d.cts +165 -0
  40. package/dist/shared/{attaform.BfMxsfmE.mjs → attaform.CIEQgJnM.mjs} +143 -78
  41. package/dist/shared/attaform.CIEQgJnM.mjs.map +1 -0
  42. package/dist/shared/{attaform.CCQkY4Ta.d.ts → attaform.CTwNcpLE.d.ts} +1 -1
  43. package/dist/shared/{attaform.UA19EF3J.mjs → attaform.CVCmBKZX.mjs} +59 -10
  44. package/dist/shared/attaform.CVCmBKZX.mjs.map +1 -0
  45. package/dist/shared/{attaform.CU3JperC.d.cts → attaform.C_5aB6EQ.d.cts} +657 -135
  46. package/dist/shared/{attaform.CU3JperC.d.mts → attaform.C_5aB6EQ.d.mts} +657 -135
  47. package/dist/shared/{attaform.CU3JperC.d.ts → attaform.C_5aB6EQ.d.ts} +657 -135
  48. package/dist/shared/{attaform.fegmBJaq.cjs → attaform.Cer8JO_P.cjs} +1435 -389
  49. package/dist/shared/attaform.Cer8JO_P.cjs.map +1 -0
  50. package/dist/shared/{attaform.g7rfuXdz.mjs → attaform.CpERWz3u.mjs} +240 -115
  51. package/dist/shared/attaform.CpERWz3u.mjs.map +1 -0
  52. package/dist/shared/attaform.CuE-bS1C.d.mts +165 -0
  53. package/dist/shared/{attaform.rIRYSUI1.cjs → attaform.Dee2rU1P.cjs} +145 -77
  54. package/dist/shared/attaform.Dee2rU1P.cjs.map +1 -0
  55. package/dist/shared/{attaform.CINUMjPq.mjs → attaform.Vo-Kft0t.mjs} +2 -2
  56. package/dist/shared/{attaform.CINUMjPq.mjs.map → attaform.Vo-Kft0t.mjs.map} +1 -1
  57. package/dist/shared/{attaform.DZRj9s0s.mjs → attaform.h1sq3BFu.mjs} +2 -2
  58. package/dist/shared/{attaform.DZRj9s0s.mjs.map → attaform.h1sq3BFu.mjs.map} +1 -1
  59. package/dist/zod-v3.cjs +3 -3
  60. package/dist/zod-v3.d.cts +27 -5
  61. package/dist/zod-v3.d.mts +27 -5
  62. package/dist/zod-v3.d.ts +27 -5
  63. package/dist/zod-v3.mjs +3 -3
  64. package/dist/zod-v4.cjs +3 -3
  65. package/dist/zod-v4.d.cts +16 -42
  66. package/dist/zod-v4.d.mts +16 -42
  67. package/dist/zod-v4.d.ts +16 -42
  68. package/dist/zod-v4.mjs +3 -3
  69. package/dist/zod.cjs +4 -4
  70. package/dist/zod.cjs.map +1 -1
  71. package/dist/zod.d.cts +7 -5
  72. package/dist/zod.d.mts +7 -5
  73. package/dist/zod.d.ts +7 -5
  74. package/dist/zod.mjs +5 -5
  75. package/dist/zod.mjs.map +1 -1
  76. package/package.json +6 -11
  77. package/dist/shared/attaform.BfMxsfmE.mjs.map +0 -1
  78. package/dist/shared/attaform.Dd_pWnmn.cjs.map +0 -1
  79. package/dist/shared/attaform.DyV1O4tI.mjs.map +0 -1
  80. package/dist/shared/attaform.UA19EF3J.mjs.map +0 -1
  81. package/dist/shared/attaform.fegmBJaq.cjs.map +0 -1
  82. package/dist/shared/attaform.g7rfuXdz.mjs.map +0 -1
  83. package/dist/shared/attaform.keLBaHB6.cjs.map +0 -1
  84. package/dist/shared/attaform.rIRYSUI1.cjs.map +0 -1
@@ -1,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);
1260
1350
  } finally {
1261
1351
  state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
1262
1352
  }
1263
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);
1363
+ } finally {
1364
+ state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
1365
+ }
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
  }
@@ -2229,9 +2458,13 @@ function buildFormApi(state, formInstanceId, options = {}) {
2229
2458
  void persistence.clearPersistedDraft(segments).catch(() => void 0);
2230
2459
  }
2231
2460
  };
2461
+ function clear(pathInput) {
2462
+ const segments = pathInput === void 0 ? paths.ROOT_PATH : paths.canonicalizePath(pathInput).segments;
2463
+ return state.clear(segments);
2464
+ }
2232
2465
  const persist = async (pathInput, options2) => {
2233
2466
  const segments = paths.canonicalizePath(pathInput).segments;
2234
- plugin.enforceSensitiveCheck(segments, options2?.acknowledgeSensitive === true);
2467
+ plugin.enforceSensitiveCheck(segments, options2?.acknowledgeSensitive === true, state.isSensitivePath);
2235
2468
  if (persistence === void 0) return;
2236
2469
  await persistence.writePathImmediately(segments);
2237
2470
  };
@@ -2265,7 +2498,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2265
2498
  return readonlySetSnapshot(state.blankPaths);
2266
2499
  });
2267
2500
  const valuesProxy = buildValuesProxy(state.form);
2268
- const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase);
2501
+ const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase, fieldStateAccessorOptions);
2269
2502
  return {
2270
2503
  handleSubmit,
2271
2504
  // `values` is the callable readonly Proxy. Each `get` trap reads
@@ -2278,6 +2511,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2278
2511
  setValue: setValueImpl,
2279
2512
  validate,
2280
2513
  validateAsync,
2514
+ process,
2281
2515
  register,
2282
2516
  key: state.formKey,
2283
2517
  errors: errorsProxy,
@@ -2290,13 +2524,13 @@ function buildFormApi(state, formInstanceId, options = {}) {
2290
2524
  meta: formMeta,
2291
2525
  reset,
2292
2526
  resetField,
2527
+ clear,
2293
2528
  persist,
2294
2529
  clearPersistedDraft,
2295
2530
  focusFirstError,
2296
2531
  scrollToFirstError,
2297
2532
  touch,
2298
- undo,
2299
- redo,
2533
+ history: formHistory,
2300
2534
  append: fieldArrays.append,
2301
2535
  prepend: fieldArrays.prepend,
2302
2536
  insert: fieldArrays.insert,
@@ -2341,6 +2575,42 @@ function warnMalformedHydration(formKey, kind, rawKey) {
2341
2575
  `[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
2576
  );
2343
2577
  }
2578
+ function applyDuStubs(schema, data, options = {}) {
2579
+ const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
2580
+ return walkDuStubs(schema, data, options.basePath ?? [], warned);
2581
+ }
2582
+ function walkDuStubs(schema, value, path, warned) {
2583
+ if (value === null || value === void 0 || typeof value !== "object") return value;
2584
+ if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || typeof value === "function") {
2585
+ return value;
2586
+ }
2587
+ if (Array.isArray(value)) {
2588
+ return value.map((item, i) => walkDuStubs(schema, item, [...path, i], warned));
2589
+ }
2590
+ const rec = value;
2591
+ const du = schema.getUnionDiscriminatorAtPath(path);
2592
+ if (du !== void 0) {
2593
+ const discValue = rec[du.discriminatorKey];
2594
+ if (discValue !== void 0 && !du.isVariantSelected(discValue)) {
2595
+ if (warned !== void 0 && plugin.__DEV__) {
2596
+ const dotted = path.map((s) => String(s)).join(".") || "(root)";
2597
+ const key = `${dotted}::${String(discValue)}`;
2598
+ if (!warned.has(key)) {
2599
+ warned.add(key);
2600
+ console.warn(
2601
+ `[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.`
2602
+ );
2603
+ }
2604
+ }
2605
+ return { [du.discriminatorKey]: discValue };
2606
+ }
2607
+ }
2608
+ const out = {};
2609
+ for (const k of Object.keys(rec)) {
2610
+ out[k] = walkDuStubs(schema, rec[k], [...path, k], warned);
2611
+ }
2612
+ return out;
2613
+ }
2344
2614
  function isPathKeyUnder(existingKey, parentPath) {
2345
2615
  const parsed = paths.segmentsForPathKey(existingKey);
2346
2616
  if (parsed === null) return false;
@@ -2350,21 +2620,96 @@ function isPathKeyUnder(existingKey, parentPath) {
2350
2620
  }
2351
2621
  return true;
2352
2622
  }
2623
+ function stripSymbolsDeep(value) {
2624
+ if (value === null || typeof value !== "object") return value;
2625
+ if (Array.isArray(value)) {
2626
+ let mutated2 = false;
2627
+ const out2 = new Array(value.length);
2628
+ for (let i = 0; i < value.length; i++) {
2629
+ const cleaned = stripSymbolsDeep(value[i]);
2630
+ out2[i] = cleaned;
2631
+ if (cleaned !== value[i]) mutated2 = true;
2632
+ }
2633
+ return mutated2 ? out2 : value;
2634
+ }
2635
+ const proto = Object.getPrototypeOf(value);
2636
+ if (proto !== Object.prototype && proto !== null) return value;
2637
+ const symKeys = Object.getOwnPropertySymbols(value);
2638
+ const stringKeys = Object.keys(value);
2639
+ let mutated = symKeys.length > 0;
2640
+ const out = {};
2641
+ const src = value;
2642
+ for (const k of stringKeys) {
2643
+ const cleaned = stripSymbolsDeep(src[k]);
2644
+ out[k] = cleaned;
2645
+ if (cleaned !== src[k]) mutated = true;
2646
+ }
2647
+ return mutated ? out : value;
2648
+ }
2649
+ function cloneVariantSnapshot(value) {
2650
+ if (value === null || typeof value !== "object") return value;
2651
+ const raw = vue.toRaw(value);
2652
+ if (raw instanceof Date) return new Date(raw.getTime());
2653
+ if (raw instanceof Map) {
2654
+ const out2 = /* @__PURE__ */ new Map();
2655
+ for (const [k, v] of raw.entries()) out2.set(cloneVariantSnapshot(k), cloneVariantSnapshot(v));
2656
+ return out2;
2657
+ }
2658
+ if (raw instanceof Set) {
2659
+ const out2 = /* @__PURE__ */ new Set();
2660
+ for (const v of raw) out2.add(cloneVariantSnapshot(v));
2661
+ return out2;
2662
+ }
2663
+ if (raw instanceof RegExp) return new RegExp(raw.source, raw.flags);
2664
+ if (Array.isArray(raw)) {
2665
+ const out2 = new Array(raw.length);
2666
+ for (let i = 0; i < raw.length; i++) out2[i] = cloneVariantSnapshot(raw[i]);
2667
+ return out2;
2668
+ }
2669
+ const src = raw;
2670
+ const out = {};
2671
+ for (const k of Object.keys(src)) out[k] = cloneVariantSnapshot(src[k]);
2672
+ return out;
2673
+ }
2353
2674
  function createFormStore(options) {
2354
2675
  const { formKey, schema, defaultValues, strict = true, hydration } = options;
2355
2676
  const ssr = options.ssr === true;
2356
2677
  const rememberVariants = options.rememberVariants !== false;
2357
2678
  const fieldValidationMode = options.validateOn ?? "change";
2358
- const fieldValidationDebounceMs = options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS;
2679
+ const fieldValidationDebounceMs = normalizeNumericOption({
2680
+ value: options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS,
2681
+ source: "useForm.debounceMs",
2682
+ allowInfinity: false,
2683
+ min: 0,
2684
+ defaultValue: DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS
2685
+ });
2359
2686
  const fieldValidationState = /* @__PURE__ */ new Map();
2360
2687
  const formChangeListeners = /* @__PURE__ */ new Set();
2361
2688
  const submitSuccessListeners = /* @__PURE__ */ new Set();
2362
2689
  const resetListeners = /* @__PURE__ */ new Set();
2363
2690
  const persistOptIns = plugin.createPersistOptInRegistry();
2691
+ const noSyncPaths = /* @__PURE__ */ new Set();
2692
+ const noSyncPathCounts = /* @__PURE__ */ new Map();
2693
+ function incrementNoSyncOptOut(path) {
2694
+ const next = (noSyncPathCounts.get(path) ?? 0) + 1;
2695
+ noSyncPathCounts.set(path, next);
2696
+ if (next === 1) noSyncPaths.add(path);
2697
+ }
2698
+ function decrementNoSyncOptOut(path) {
2699
+ const current = noSyncPathCounts.get(path) ?? 0;
2700
+ if (current <= 1) {
2701
+ noSyncPathCounts.delete(path);
2702
+ noSyncPaths.delete(path);
2703
+ return;
2704
+ }
2705
+ noSyncPathCounts.set(path, current - 1);
2706
+ }
2364
2707
  const coerceIndex = resolveCoercionIndex(options.coerce);
2365
2708
  const resolvedShouldShowErrors = resolveShouldShowErrors(
2366
2709
  options.shouldShowErrors
2367
2710
  );
2711
+ const resolvedIsSensitivePath = options.isSensitivePath ?? plugin.isSensitivePath;
2712
+ const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ?? plugin.segmentMatchesSensitive;
2368
2713
  const cleanupHooks = [];
2369
2714
  const modules = /* @__PURE__ */ new Map();
2370
2715
  const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
@@ -2375,7 +2720,10 @@ function createFormStore(options) {
2375
2720
  });
2376
2721
  const schemaInitialData = schemaResponse.data;
2377
2722
  const initialData = hydration !== void 0 ? hydration.form : structuralSnapshot(schemaInitialData);
2378
- const form = vue.ref(initialData);
2723
+ const stubbedInitialData = applyDuStubs(schema, initialData, {
2724
+ warn: true
2725
+ });
2726
+ const form = vue.ref(stubbedInitialData);
2379
2727
  const fields = vue.reactive(/* @__PURE__ */ new Map());
2380
2728
  const elements = vue.reactive(/* @__PURE__ */ new Map());
2381
2729
  const elementToFormInstance = /* @__PURE__ */ new WeakMap();
@@ -2391,6 +2739,40 @@ function createFormStore(options) {
2391
2739
  originalBlankPaths.add(raw);
2392
2740
  }
2393
2741
  const variantMemory = /* @__PURE__ */ new Map();
2742
+ function clearVariantMemoryUnderPath(arrayPath) {
2743
+ for (const memKey of [...variantMemory.keys()]) {
2744
+ const segs = paths.segmentsForPathKey(memKey);
2745
+ if (segs === null) continue;
2746
+ if (isPathPrefix(arrayPath, segs)) variantMemory.delete(memKey);
2747
+ }
2748
+ }
2749
+ function clearVariantMemoryAtArrayIndices(arrayPath, indexFilter) {
2750
+ for (const memKey of [...variantMemory.keys()]) {
2751
+ const segs = paths.segmentsForPathKey(memKey);
2752
+ if (segs === null) continue;
2753
+ if (!isPathPrefix(arrayPath, segs)) continue;
2754
+ if (segs.length <= arrayPath.length) continue;
2755
+ const idxSeg = segs[arrayPath.length];
2756
+ if (typeof idxSeg !== "number") continue;
2757
+ if (indexFilter(idxSeg)) variantMemory.delete(memKey);
2758
+ }
2759
+ }
2760
+ function applyArrayOpToMemory(arrayPath, op) {
2761
+ switch (op.kind) {
2762
+ case "shift-from":
2763
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.index);
2764
+ return;
2765
+ case "shift-range":
2766
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.fromIndex && i <= op.toIndex);
2767
+ return;
2768
+ case "swap":
2769
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
2770
+ return;
2771
+ case "replace-at":
2772
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.index);
2773
+ return;
2774
+ }
2775
+ }
2394
2776
  const pathOrdinals = /* @__PURE__ */ new Map();
2395
2777
  let nextOrdinal = 0;
2396
2778
  function ensurePathOrdinal(key) {
@@ -2542,9 +2924,35 @@ function createFormStore(options) {
2542
2924
  }
2543
2925
  }
2544
2926
  function setValueAtPath(path, value, meta) {
2927
+ value = stripSymbolsDeep(value);
2928
+ value = schema.normalizeWriteValueAtPath(value, path);
2545
2929
  if (!isSlimPrimitiveValid(schema, form, path, value)) {
2546
2930
  return false;
2547
2931
  }
2932
+ if (path.length >= 2) {
2933
+ for (let i = 0; i < path.length - 1; i++) {
2934
+ const ancestorPath = path.slice(0, i + 1);
2935
+ const du = schema.getUnionDiscriminatorAtPath(ancestorPath);
2936
+ if (du === void 0) continue;
2937
+ const nextSeg = path[i + 1];
2938
+ if (nextSeg === du.discriminatorKey) continue;
2939
+ const ancestorValue = getAtPath(form.value, ancestorPath);
2940
+ if (!isPlainRecord(ancestorValue)) continue;
2941
+ const discValue = ancestorValue[du.discriminatorKey];
2942
+ if (discValue === void 0) {
2943
+ return false;
2944
+ }
2945
+ if (!du.isVariantSelected(discValue)) {
2946
+ return false;
2947
+ }
2948
+ const variantDefault = du.getVariantDefault(discValue);
2949
+ if (!isPlainRecord(variantDefault)) continue;
2950
+ if (typeof nextSeg !== "string") continue;
2951
+ if (!(nextSeg in variantDefault)) {
2952
+ return false;
2953
+ }
2954
+ }
2955
+ }
2548
2956
  if (meta?.skipDiscriminatorReshape !== true) {
2549
2957
  if (path.length > 0) {
2550
2958
  const last = path[path.length - 1];
@@ -2565,6 +2973,14 @@ function createFormStore(options) {
2565
2973
  meta
2566
2974
  );
2567
2975
  }
2976
+ return reshapeUnionVariant(
2977
+ parentPath,
2978
+ oldValue,
2979
+ value,
2980
+ { [last]: value },
2981
+ void 0,
2982
+ meta
2983
+ );
2568
2984
  }
2569
2985
  }
2570
2986
  }
@@ -2573,12 +2989,13 @@ function createFormStore(options) {
2573
2989
  const selfDU = schema.getUnionDiscriminatorAtPath(path);
2574
2990
  if (selfDU !== void 0) {
2575
2991
  const valueRecord = value;
2576
- const discValue = valueRecord[selfDU.discriminatorKey];
2992
+ const discKey = selfDU.discriminatorKey;
2993
+ const discValue = valueRecord[discKey];
2994
+ const currentUnionValue = getAtPath(form.value, path);
2995
+ const oldDiscValue = isPlainRecord(currentUnionValue) ? currentUnionValue[discKey] : void 0;
2577
2996
  if (discValue !== void 0) {
2578
2997
  const variantDefault = selfDU.getVariantDefault(discValue);
2579
2998
  if (variantDefault !== void 0 && isPlainRecord(variantDefault)) {
2580
- const currentUnionValue = getAtPath(form.value, path);
2581
- const oldDiscValue = isPlainRecord(currentUnionValue) ? currentUnionValue[selfDU.discriminatorKey] : void 0;
2582
2999
  return reshapeUnionVariant(
2583
3000
  path,
2584
3001
  oldDiscValue,
@@ -2588,7 +3005,16 @@ function createFormStore(options) {
2588
3005
  meta
2589
3006
  );
2590
3007
  }
3008
+ return reshapeUnionVariant(
3009
+ path,
3010
+ oldDiscValue,
3011
+ discValue,
3012
+ { [discKey]: discValue },
3013
+ void 0,
3014
+ meta
3015
+ );
2591
3016
  }
3017
+ return reshapeUnionVariant(path, oldDiscValue, void 0, {}, void 0, meta);
2592
3018
  }
2593
3019
  }
2594
3020
  }
@@ -2605,12 +3031,17 @@ function createFormStore(options) {
2605
3031
  }
2606
3032
  const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
2607
3033
  applyFormReplacement(nextForm, meta);
2608
- if (fieldValidationMode === "change") {
2609
- scheduleFieldValidation(
2610
- path,
2611
- false
2612
- /* debounced */
2613
- );
3034
+ if (meta?.arrayOp !== void 0) {
3035
+ applyArrayOpToMemory(path, meta.arrayOp);
3036
+ } else if (Array.isArray(value) && Array.isArray(currentValue)) {
3037
+ clearVariantMemoryUnderPath(path);
3038
+ }
3039
+ const effectiveModeAfterWrite = meta?.instance?.validateOn ?? fieldValidationMode;
3040
+ if (effectiveModeAfterWrite === "change") {
3041
+ scheduleFieldValidation(path, false, {
3042
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3043
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3044
+ });
2614
3045
  }
2615
3046
  return true;
2616
3047
  }
@@ -2619,9 +3050,10 @@ function createFormStore(options) {
2619
3050
  const parentKey = paths.canonicalizePath(parentPath).key;
2620
3051
  let baseline = variantDefault;
2621
3052
  let restoredBlanks;
2622
- if (rememberVariants && !sameDisc) {
3053
+ const effectiveRemember = meta?.instance?.rememberVariants ?? rememberVariants;
3054
+ if (effectiveRemember && !sameDisc) {
2623
3055
  if (oldDiscValue !== void 0) {
2624
- const currentValue2 = JSON.parse(JSON.stringify(getAtPath(form.value, parentPath)));
3056
+ const currentValue2 = cloneVariantSnapshot(getAtPath(form.value, parentPath));
2625
3057
  const outgoingBlanks = [];
2626
3058
  for (const k of blankPaths) {
2627
3059
  if (isPathKeyUnder(k, parentPath)) outgoingBlanks.push(k);
@@ -2643,7 +3075,10 @@ function createFormStore(options) {
2643
3075
  restoredBlanks = [...restored.blankPaths];
2644
3076
  }
2645
3077
  }
2646
- const finalValue = consumerOverrides !== void 0 ? { ...baseline, ...consumerOverrides } : baseline;
3078
+ const layered = consumerOverrides !== void 0 ? { ...baseline, ...consumerOverrides } : baseline;
3079
+ const finalValue = applyDuStubs(schema, layered, {
3080
+ basePath: parentPath
3081
+ });
2647
3082
  let newBlankPaths;
2648
3083
  if (restoredBlanks !== void 0) {
2649
3084
  newBlankPaths = restoredBlanks;
@@ -2662,9 +3097,10 @@ function createFormStore(options) {
2662
3097
  for (const k of newBlankPaths) blankPaths.add(k);
2663
3098
  return true;
2664
3099
  }
2665
- const nextForm = parentPath.length === 0 ? finalValue : setAtPath(form.value, parentPath, finalValue);
3100
+ const nextForm = parentPath.length === 0 ? finalValue : setAtPathWithSchemaFill(form.value, schema, parentPath, finalValue);
2666
3101
  let appliedSync = false;
2667
- if (fieldValidationMode === "change") {
3102
+ const reshapeMode = meta?.instance?.validateOn ?? fieldValidationMode;
3103
+ if (reshapeMode === "change") {
2668
3104
  const syncOrPromise = schema.validateAtPath(finalValue, parentPath, { sync: true });
2669
3105
  if (!(syncOrPromise instanceof Promise)) {
2670
3106
  const reStamped = syncOrPromise.success ? [] : syncOrPromise.errors.map((err) => ({
@@ -2684,17 +3120,18 @@ function createFormStore(options) {
2684
3120
  }
2685
3121
  applyFormReplacement(nextForm, meta);
2686
3122
  for (const k of newBlankPaths) blankPaths.add(k);
2687
- if (fieldValidationMode === "change" && !appliedSync) {
2688
- scheduleFieldValidation(
2689
- parentPath,
2690
- false
2691
- /* debounced */
2692
- );
3123
+ if (reshapeMode === "change" && !appliedSync) {
3124
+ scheduleFieldValidation(parentPath, false, {
3125
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3126
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3127
+ });
2693
3128
  }
2694
3129
  return true;
2695
3130
  }
2696
- function scheduleFieldValidation(path, immediate) {
2697
- if (fieldValidationMode === "submit") return;
3131
+ function scheduleFieldValidation(path, immediate, override) {
3132
+ const effectiveMode = override?.mode ?? fieldValidationMode;
3133
+ if (effectiveMode === "submit") return;
3134
+ const effectiveDebounce = override?.debounceMs ?? fieldValidationDebounceMs;
2698
3135
  const { key } = paths.canonicalizePath(path);
2699
3136
  const prev = fieldValidationState.get(key);
2700
3137
  if (prev !== void 0) {
@@ -2708,8 +3145,17 @@ function createFormStore(options) {
2708
3145
  fresh.timer = null;
2709
3146
  if (controller.signal.aborted) return;
2710
3147
  const data = getAtPath(form.value, path);
2711
- activeValidations.value += 1;
2712
- incFieldValidation(key);
3148
+ let activeIncremented = false;
3149
+ try {
3150
+ activeValidations.value += 1;
3151
+ activeIncremented = true;
3152
+ incFieldValidation(key);
3153
+ } catch (err) {
3154
+ if (activeIncremented) {
3155
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
3156
+ }
3157
+ throw err;
3158
+ }
2713
3159
  void Promise.resolve().then(() => schema.validateAtPath(data, path)).then((response) => {
2714
3160
  if (controller.signal.aborted) return;
2715
3161
  const reStamped = response.success ? [] : response.errors.map((err) => ({
@@ -2723,10 +3169,10 @@ function createFormStore(options) {
2723
3169
  decFieldValidation(key);
2724
3170
  });
2725
3171
  };
2726
- if (immediate || fieldValidationDebounceMs === 0) {
3172
+ if (immediate || effectiveDebounce === 0) {
2727
3173
  run();
2728
3174
  } else {
2729
- fresh.timer = setTimeout(run, fieldValidationDebounceMs);
3175
+ fresh.timer = setTimeout(run, effectiveDebounce);
2730
3176
  }
2731
3177
  }
2732
3178
  function cancelFieldValidation() {
@@ -2790,6 +3236,8 @@ function createFormStore(options) {
2790
3236
  submitSuccessListeners.clear();
2791
3237
  resetListeners.clear();
2792
3238
  persistOptIns.clear();
3239
+ noSyncPaths.clear();
3240
+ noSyncPathCounts.clear();
2793
3241
  }
2794
3242
  function getValueAtPath(path) {
2795
3243
  return getAtPath(form.value, path);
@@ -2924,7 +3372,7 @@ function createFormStore(options) {
2924
3372
  if (current?.connected === true) return;
2925
3373
  touchFieldRecord(key, path, { connected: true });
2926
3374
  }
2927
- function markFocused(path, focused) {
3375
+ function markFocused(path, focused, meta) {
2928
3376
  const { key } = paths.canonicalizePath(path);
2929
3377
  touchFieldRecord(key, path, {
2930
3378
  focused,
@@ -2933,12 +3381,12 @@ function createFormStore(options) {
2933
3381
  // a field is currently focused we keep whatever value it held.
2934
3382
  touched: focused ? fields.get(key)?.touched ?? null : true
2935
3383
  });
2936
- if (!focused && fieldValidationMode === "blur") {
2937
- scheduleFieldValidation(
2938
- path,
2939
- true
2940
- /* immediate */
2941
- );
3384
+ const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
3385
+ if (!focused && focusMode === "blur") {
3386
+ scheduleFieldValidation(path, true, {
3387
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3388
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3389
+ });
2942
3390
  }
2943
3391
  }
2944
3392
  function markTouched(path) {
@@ -2963,12 +3411,18 @@ function createFormStore(options) {
2963
3411
  );
2964
3412
  }
2965
3413
  }
3414
+ function clear(path) {
3415
+ return setValueAtPath(path, schema.getEmptyValueAtPath(path));
3416
+ }
2966
3417
  function reset(nextDefaultValues) {
2967
- const next = schema.getDefaultValues({
3418
+ const resetSource = nextDefaultValues ?? defaultValues;
3419
+ const completedResetConstraints = resetSource === void 0 ? void 0 : mergeStructural(schema, [], resetSource);
3420
+ const resetResponse = schema.getDefaultValues({
2968
3421
  useDefaultSchemaValues: true,
2969
- constraints: nextDefaultValues ?? defaultValues,
3422
+ constraints: completedResetConstraints,
2970
3423
  strict
2971
- }).data;
3424
+ });
3425
+ const next = resetResponse.data;
2972
3426
  applyFormReplacement(next);
2973
3427
  originals.clear();
2974
3428
  diffAndApply({}, next, [], (patch) => {
@@ -2987,6 +3441,24 @@ function createFormStore(options) {
2987
3441
  }
2988
3442
  schemaErrors.clear();
2989
3443
  userErrors.clear();
3444
+ if (strict && !resetResponse.success) {
3445
+ setAllSchemaErrors(resetResponse.errors);
3446
+ }
3447
+ if (strict) {
3448
+ const syncResult = schema.validateAtPath(form.value, void 0, { sync: true });
3449
+ if (!(syncResult instanceof Promise) && !syncResult.success) {
3450
+ applySchemaErrorsForSubtree([], syncResult.errors);
3451
+ }
3452
+ }
3453
+ firstValidationDone.value = !strict || schema.needsAsyncValidation?.() !== true;
3454
+ const needsAsync = !ssr && strict && schema.needsAsyncValidation?.() === true;
3455
+ if (needsAsync) {
3456
+ queueMicrotask(() => scheduleFieldValidation(
3457
+ [],
3458
+ true
3459
+ /* immediate */
3460
+ ));
3461
+ }
2990
3462
  const now = (/* @__PURE__ */ new Date()).toISOString();
2991
3463
  for (const [pathKey, record] of fields) {
2992
3464
  fields.set(pathKey, {
@@ -3030,30 +3502,28 @@ function createFormStore(options) {
3030
3502
  `[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
3503
  );
3032
3504
  }
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
- );
3505
+ } else {
3506
+ let subtree = void 0;
3507
+ let anyMatch = false;
3508
+ for (const [, entry] of originals) {
3509
+ const leafSegments = entry.segments;
3510
+ if (!isPathPrefix(targetSegments, leafSegments)) continue;
3511
+ if (leafSegments.length === targetSegments.length) continue;
3512
+ anyMatch = true;
3513
+ const relative = leafSegments.slice(targetSegments.length);
3514
+ if (subtree === void 0) {
3515
+ subtree = typeof relative[0] === "number" ? [] : {};
3516
+ }
3517
+ subtree = setAtPath(subtree, relative, entry.value);
3518
+ }
3519
+ if (anyMatch) {
3520
+ const wroteSubtree = setValueAtPath(targetSegments, subtree);
3521
+ if (!wroteSubtree) {
3522
+ console.error(
3523
+ `[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.`
3524
+ );
3525
+ }
3526
+ }
3057
3527
  }
3058
3528
  deleteErrorsUnderPrefix(schemaErrors, targetSegments);
3059
3529
  deleteErrorsUnderPrefix(userErrors, targetSegments);
@@ -3154,6 +3624,7 @@ function createFormStore(options) {
3154
3624
  getValueAtPath,
3155
3625
  reset,
3156
3626
  resetField,
3627
+ clear,
3157
3628
  setSchemaErrorsForPath,
3158
3629
  setAllSchemaErrors,
3159
3630
  clearSchemaErrors,
@@ -3183,6 +3654,11 @@ function createFormStore(options) {
3183
3654
  awaitPendingWrites,
3184
3655
  modules,
3185
3656
  persistOptIns,
3657
+ isSensitivePath: resolvedIsSensitivePath,
3658
+ segmentMatchesSensitive: resolvedSegmentMatchesSensitive,
3659
+ noSyncPaths,
3660
+ incrementNoSyncOptOut,
3661
+ decrementNoSyncOptOut,
3186
3662
  coerceIndex,
3187
3663
  blankPaths,
3188
3664
  originalBlankPaths,
@@ -3190,43 +3666,140 @@ function createFormStore(options) {
3190
3666
  };
3191
3667
  }
3192
3668
 
3193
- function getComputedSchema(formKey, schemaOrCallback) {
3194
- if (typeof schemaOrCallback === "function") return schemaOrCallback(formKey);
3669
+ function getComputedSchema(formKey, schemaOrCallback, options) {
3670
+ if (typeof schemaOrCallback === "function") return schemaOrCallback(formKey, options);
3195
3671
  return schemaOrCallback;
3196
3672
  }
3197
3673
 
3674
+ function captureErrorEntries(map) {
3675
+ const out = [];
3676
+ for (const [k, v] of map) out.push([k, [...v]]);
3677
+ return out;
3678
+ }
3679
+ function errorsEqual(a, b) {
3680
+ if (a.length !== b.length) return false;
3681
+ const bMap = /* @__PURE__ */ new Map();
3682
+ for (const [k, v] of b) bMap.set(k, v);
3683
+ for (const [k, v] of a) {
3684
+ const bv = bMap.get(k);
3685
+ if (bv === void 0) return false;
3686
+ if (v.length !== bv.length) return false;
3687
+ for (let i = 0; i < v.length; i++) {
3688
+ const av = v[i];
3689
+ const bvi = bv[i];
3690
+ if (av === bvi) continue;
3691
+ if (av.message !== bvi.message) return false;
3692
+ if (av.code !== bvi.code) return false;
3693
+ if (av.formKey !== bvi.formKey) return false;
3694
+ if (av.path !== bvi.path) {
3695
+ if (av.path.length !== bvi.path.length) return false;
3696
+ for (let j = 0; j < av.path.length; j++) {
3697
+ if (av.path[j] !== bvi.path[j]) return false;
3698
+ }
3699
+ }
3700
+ }
3701
+ }
3702
+ return true;
3703
+ }
3704
+ function diffBlankPaths$1(prev, curr) {
3705
+ const added = [];
3706
+ const removed = [];
3707
+ for (const k of curr) if (!prev.has(k)) added.push(k);
3708
+ for (const k of prev) if (!curr.has(k)) removed.push(k);
3709
+ return { added, removed };
3710
+ }
3711
+ function applyDeltaForward(snap, d) {
3712
+ const nextForm = applyPatchesForward(snap.form, d.formPatches);
3713
+ const nextBlank = new Set(snap.blankPaths);
3714
+ for (const k of d.blankPathsRemoved) nextBlank.delete(k);
3715
+ for (const k of d.blankPathsAdded) nextBlank.add(k);
3716
+ return {
3717
+ form: nextForm,
3718
+ blankPaths: [...nextBlank],
3719
+ schemaErrors: d.schemaErrors !== void 0 ? d.schemaErrors.after : snap.schemaErrors,
3720
+ userErrors: d.userErrors !== void 0 ? d.userErrors.after : snap.userErrors
3721
+ };
3722
+ }
3723
+ function applyDeltaInverse(snap, d) {
3724
+ const prevForm = applyPatchesInverse(snap.form, d.formPatches);
3725
+ const prevBlank = new Set(snap.blankPaths);
3726
+ for (const k of d.blankPathsAdded) prevBlank.delete(k);
3727
+ for (const k of d.blankPathsRemoved) prevBlank.add(k);
3728
+ return {
3729
+ form: prevForm,
3730
+ blankPaths: [...prevBlank],
3731
+ schemaErrors: d.schemaErrors !== void 0 ? d.schemaErrors.before : snap.schemaErrors,
3732
+ userErrors: d.userErrors !== void 0 ? d.userErrors.before : snap.userErrors
3733
+ };
3734
+ }
3198
3735
  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;
3736
+ const max = normalizeNumericOption({
3737
+ value: typeof config === "object" ? config.max ?? DEFAULT_HISTORY_MAX_SNAPSHOTS : DEFAULT_HISTORY_MAX_SNAPSHOTS,
3738
+ source: "useForm.history.max",
3739
+ allowInfinity: false,
3740
+ min: 0,
3741
+ defaultValue: DEFAULT_HISTORY_MAX_SNAPSHOTS
3742
+ });
3203
3743
  function captureSnapshot() {
3204
3744
  return {
3205
3745
  form: structuralSnapshot(state.form.value),
3206
3746
  blankPaths: [...state.blankPaths],
3207
- schemaErrors: [...state.schemaErrors.entries()].map(([k, v]) => [k, [...v]]),
3208
- userErrors: [...state.userErrors.entries()].map(([k, v]) => [k, [...v]])
3747
+ schemaErrors: captureErrorEntries(state.schemaErrors),
3748
+ userErrors: captureErrorEntries(state.userErrors)
3209
3749
  };
3210
3750
  }
3211
- function pushSnapshot(snap) {
3212
- const next = [...undoStack.value, snap];
3213
- undoStack.value = next.length > max ? next.slice(-max) : next;
3214
- redoStack.value = [];
3751
+ const initial = captureSnapshot();
3752
+ const base = vue.shallowRef(initial);
3753
+ const currentSnapshot = vue.shallowRef(initial);
3754
+ const undoDeltas = vue.shallowRef([]);
3755
+ const redoDeltas = vue.shallowRef([]);
3756
+ let suppressNext = false;
3757
+ function appendDelta(delta, newCurrent) {
3758
+ if (max === 0) {
3759
+ base.value = newCurrent;
3760
+ currentSnapshot.value = newCurrent;
3761
+ redoDeltas.value = [];
3762
+ return;
3763
+ }
3764
+ undoDeltas.value = [...undoDeltas.value, delta];
3765
+ redoDeltas.value = [];
3766
+ currentSnapshot.value = newCurrent;
3767
+ while (1 + undoDeltas.value.length > max && undoDeltas.value.length > 0) {
3768
+ const oldest = undoDeltas.value[0];
3769
+ base.value = applyDeltaForward(base.value, oldest);
3770
+ undoDeltas.value = undoDeltas.value.slice(1);
3771
+ }
3215
3772
  }
3216
- pushSnapshot(captureSnapshot());
3217
- const unsubscribeChange = state.onFormChange(() => {
3773
+ const unsubscribeChange = state.onFormChange((_next, meta) => {
3218
3774
  if (suppressNext) {
3219
3775
  suppressNext = false;
3220
3776
  return;
3221
3777
  }
3222
- pushSnapshot(captureSnapshot());
3223
- });
3224
- const unsubscribeReset = state.onReset(() => {
3225
- undoStack.value = [];
3226
- redoStack.value = [];
3227
- pushSnapshot(captureSnapshot());
3778
+ if (meta?.hydration === true) {
3779
+ clear();
3780
+ return;
3781
+ }
3782
+ if (meta?.crossTab === true) {
3783
+ currentSnapshot.value = captureSnapshot();
3784
+ return;
3785
+ }
3786
+ const newSnap = captureSnapshot();
3787
+ const prevSnap = currentSnapshot.value;
3788
+ const formPatches = [];
3789
+ diffAndApply(prevSnap.form, newSnap.form, [], (p) => formPatches.push(p));
3790
+ const prevBlankSet = new Set(prevSnap.blankPaths);
3791
+ const currBlankSet = new Set(newSnap.blankPaths);
3792
+ const blankDiff = diffBlankPaths$1(prevBlankSet, currBlankSet);
3793
+ const delta = {
3794
+ formPatches,
3795
+ blankPathsAdded: blankDiff.added,
3796
+ blankPathsRemoved: blankDiff.removed,
3797
+ ...errorsEqual(prevSnap.schemaErrors, newSnap.schemaErrors) ? {} : { schemaErrors: { before: prevSnap.schemaErrors, after: newSnap.schemaErrors } },
3798
+ ...errorsEqual(prevSnap.userErrors, newSnap.userErrors) ? {} : { userErrors: { before: prevSnap.userErrors, after: newSnap.userErrors } }
3799
+ };
3800
+ appendDelta(delta, newSnap);
3228
3801
  });
3229
- function restore(snap) {
3802
+ function restoreCurrent(snap) {
3230
3803
  suppressNext = true;
3231
3804
  state.blankPaths.clear();
3232
3805
  for (const key of snap.blankPaths) state.blankPaths.add(key);
@@ -3239,38 +3812,46 @@ function createHistoryModule(state, config) {
3239
3812
  state.setAllUserErrors(userFlat);
3240
3813
  }
3241
3814
  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);
3815
+ if (undoDeltas.value.length === 0) return false;
3816
+ const d = undoDeltas.value[undoDeltas.value.length - 1];
3817
+ const restored = applyDeltaInverse(currentSnapshot.value, d);
3818
+ redoDeltas.value = [...redoDeltas.value, d];
3819
+ undoDeltas.value = undoDeltas.value.slice(0, -1);
3820
+ currentSnapshot.value = restored;
3821
+ restoreCurrent(restored);
3249
3822
  return true;
3250
3823
  }
3251
3824
  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);
3825
+ if (redoDeltas.value.length === 0) return false;
3826
+ const d = redoDeltas.value[redoDeltas.value.length - 1];
3827
+ const next = applyDeltaForward(currentSnapshot.value, d);
3828
+ undoDeltas.value = [...undoDeltas.value, d];
3829
+ redoDeltas.value = redoDeltas.value.slice(0, -1);
3830
+ currentSnapshot.value = next;
3831
+ restoreCurrent(next);
3258
3832
  return true;
3259
3833
  }
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);
3834
+ function clear() {
3835
+ const fresh = captureSnapshot();
3836
+ base.value = fresh;
3837
+ currentSnapshot.value = fresh;
3838
+ undoDeltas.value = [];
3839
+ redoDeltas.value = [];
3840
+ }
3841
+ const canUndo = vue.computed(() => undoDeltas.value.length > 0);
3842
+ const canRedo = vue.computed(() => redoDeltas.value.length > 0);
3843
+ const historySize = vue.computed(() => 1 + undoDeltas.value.length + redoDeltas.value.length);
3263
3844
  return {
3264
3845
  undo,
3265
3846
  redo,
3847
+ clear,
3266
3848
  canUndo,
3267
3849
  canRedo,
3268
3850
  historySize,
3269
3851
  dispose() {
3270
3852
  unsubscribeChange();
3271
- unsubscribeReset();
3272
- undoStack.value = [];
3273
- redoStack.value = [];
3853
+ undoDeltas.value = [];
3854
+ redoDeltas.value = [];
3274
3855
  }
3275
3856
  };
3276
3857
  }
@@ -3288,12 +3869,368 @@ function hashStableString(input, seed = 0) {
3288
3869
  return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
3289
3870
  }
3290
3871
 
3872
+ const PROTOCOL_VERSION = 1;
3873
+ const JOIN_COLLECTION_WINDOW_MS = 50;
3874
+ const SNAPSHOT_TIMEOUT_MS = 200;
3875
+ const MAX_LEADER_ATTEMPTS = 3;
3876
+ function isDangerousSegment(s) {
3877
+ return s === "__proto__" || s === "constructor" || s === "prototype";
3878
+ }
3879
+ function pathContainsDangerousSegment(path) {
3880
+ for (let i = 0; i < path.length; i++) {
3881
+ if (isDangerousSegment(path[i])) return true;
3882
+ }
3883
+ return false;
3884
+ }
3885
+ function diffBlankPaths(prev, curr) {
3886
+ const added = [];
3887
+ const removed = [];
3888
+ const prevSet = new Set(prev);
3889
+ for (const k of curr) if (!prevSet.has(k)) added.push(k);
3890
+ for (const k of prev) if (!curr.has(k)) removed.push(k);
3891
+ return { added, removed };
3892
+ }
3893
+ function snapshotForm(form) {
3894
+ return structuralSnapshot(form);
3895
+ }
3896
+ function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
3897
+ if (value === null || typeof value !== "object") return value;
3898
+ if (Array.isArray(value)) {
3899
+ return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
3900
+ }
3901
+ const proto = Object.getPrototypeOf(value);
3902
+ if (proto !== Object.prototype && proto !== null) return value;
3903
+ const out = {};
3904
+ const src = value;
3905
+ for (const key of Object.keys(src)) {
3906
+ const childPath = [...pathSoFar, key];
3907
+ if (isSensitivePath(childPath)) {
3908
+ out[key] = void 0;
3909
+ continue;
3910
+ }
3911
+ out[key] = stripSensitivePathsDeep(src[key], childPath, isSensitivePath);
3912
+ }
3913
+ return out;
3914
+ }
3915
+ function isValidSyncMessage(data) {
3916
+ if (data === null || typeof data !== "object") return false;
3917
+ const m = data;
3918
+ if (m["v"] !== PROTOCOL_VERSION) return false;
3919
+ if (typeof m["senderId"] !== "string") return false;
3920
+ if (typeof m["kind"] !== "string") return false;
3921
+ switch (m["kind"]) {
3922
+ case "hello":
3923
+ case "announce":
3924
+ return true;
3925
+ case "requestSnapshot":
3926
+ return typeof m["targetId"] === "string";
3927
+ case "snapshot":
3928
+ return Array.isArray(m["blankPaths"]) && "form" in m;
3929
+ case "patches":
3930
+ return Array.isArray(m["formPatches"]) && Array.isArray(m["blankPathsAdded"]) && Array.isArray(m["blankPathsRemoved"]);
3931
+ default:
3932
+ return false;
3933
+ }
3934
+ }
3935
+ function generateSenderId() {
3936
+ try {
3937
+ return globalThis.crypto.randomUUID();
3938
+ } catch {
3939
+ return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
3940
+ }
3941
+ }
3942
+ function createMultiTabSyncModule(state, channelName, options) {
3943
+ if (typeof BroadcastChannel === "undefined") {
3944
+ return {
3945
+ dispose: () => void 0,
3946
+ lifecycle: () => "established",
3947
+ senderId: "",
3948
+ channelName
3949
+ };
3950
+ }
3951
+ let channel;
3952
+ try {
3953
+ channel = new BroadcastChannel(channelName);
3954
+ } catch {
3955
+ return {
3956
+ dispose: () => void 0,
3957
+ lifecycle: () => "established",
3958
+ senderId: "",
3959
+ channelName
3960
+ };
3961
+ }
3962
+ const senderId = generateSenderId();
3963
+ let lifecycle = "joining";
3964
+ let disposed = false;
3965
+ const peerIds = /* @__PURE__ */ new Set();
3966
+ let joinCollectionTimer = null;
3967
+ let snapshotTimeoutTimer = null;
3968
+ let leaderAttempts = 0;
3969
+ let prior = {
3970
+ form: snapshotForm(state.form.value),
3971
+ blankPathsSnapshot: [...state.blankPaths]
3972
+ };
3973
+ function safePost(msg) {
3974
+ if (disposed) return;
3975
+ try {
3976
+ channel.postMessage(msg);
3977
+ } catch {
3978
+ }
3979
+ }
3980
+ function refreshPrior() {
3981
+ prior = {
3982
+ form: snapshotForm(state.form.value),
3983
+ blankPathsSnapshot: [...state.blankPaths]
3984
+ };
3985
+ }
3986
+ function isPathLocallySuppressed(path) {
3987
+ if (pathContainsDangerousSegment(path)) return true;
3988
+ if (options.isSensitivePath(path)) return true;
3989
+ const { key } = paths.canonicalizePath([...path]);
3990
+ if (options.noSyncPaths.has(key)) return true;
3991
+ return false;
3992
+ }
3993
+ function postPatches() {
3994
+ if (lifecycle !== "established") return;
3995
+ const next = snapshotForm(state.form.value);
3996
+ const rawPatches = [];
3997
+ diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
3998
+ const safePatches = [];
3999
+ for (const p of rawPatches) {
4000
+ if (isPathLocallySuppressed(p.path)) continue;
4001
+ safePatches.push(p);
4002
+ }
4003
+ const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
4004
+ if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
4005
+ prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
4006
+ return;
4007
+ }
4008
+ safePost({
4009
+ v: PROTOCOL_VERSION,
4010
+ kind: "patches",
4011
+ senderId,
4012
+ formPatches: safePatches,
4013
+ blankPathsAdded: added,
4014
+ blankPathsRemoved: removed
4015
+ });
4016
+ prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
4017
+ }
4018
+ const unsubscribeChange = state.onFormChange((_next, meta) => {
4019
+ if (disposed) return;
4020
+ if (lifecycle !== "established") return;
4021
+ if (meta?.crossTab === true) return;
4022
+ if (meta?.hydration === true) {
4023
+ refreshPrior();
4024
+ return;
4025
+ }
4026
+ postPatches();
4027
+ });
4028
+ function applyIncomingForm(form, blankPaths) {
4029
+ state.blankPaths.clear();
4030
+ for (const k of blankPaths) state.blankPaths.add(k);
4031
+ state.applyFormReplacement(form, { crossTab: true, persist: false });
4032
+ refreshPrior();
4033
+ }
4034
+ function handlePatches(msg) {
4035
+ if (lifecycle !== "established") return;
4036
+ const safePatches = [];
4037
+ for (const p of msg.formPatches) {
4038
+ if (!Array.isArray(p.path)) continue;
4039
+ if (isPathLocallySuppressed(p.path)) continue;
4040
+ safePatches.push(p);
4041
+ }
4042
+ const safeBlankAdded = [];
4043
+ for (const k of msg.blankPathsAdded) {
4044
+ const segs = paths.canonicalizePath(k).segments;
4045
+ if (isPathLocallySuppressed(segs)) continue;
4046
+ safeBlankAdded.push(k);
4047
+ }
4048
+ const safeBlankRemoved = [];
4049
+ for (const k of msg.blankPathsRemoved) {
4050
+ const segs = paths.canonicalizePath(k).segments;
4051
+ if (isPathLocallySuppressed(segs)) continue;
4052
+ safeBlankRemoved.push(k);
4053
+ }
4054
+ if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
4055
+ return;
4056
+ }
4057
+ const candidate = applyPatchesForward(state.form.value, safePatches);
4058
+ try {
4059
+ options.validateForm(state.form.value);
4060
+ try {
4061
+ options.validateForm(candidate);
4062
+ } catch {
4063
+ return;
4064
+ }
4065
+ } catch {
4066
+ }
4067
+ const nextBlankPaths = new Set(state.blankPaths);
4068
+ for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
4069
+ for (const k of safeBlankAdded) nextBlankPaths.add(k);
4070
+ applyIncomingForm(candidate, [...nextBlankPaths]);
4071
+ }
4072
+ function handleSnapshot(msg) {
4073
+ if (lifecycle !== "joining") return;
4074
+ try {
4075
+ options.validateForm(msg.form);
4076
+ } catch {
4077
+ return;
4078
+ }
4079
+ if (snapshotTimeoutTimer !== null) {
4080
+ clearTimeout(snapshotTimeoutTimer);
4081
+ snapshotTimeoutTimer = null;
4082
+ }
4083
+ if (joinCollectionTimer !== null) {
4084
+ clearTimeout(joinCollectionTimer);
4085
+ joinCollectionTimer = null;
4086
+ }
4087
+ applyIncomingForm(msg.form, msg.blankPaths);
4088
+ lifecycle = "established";
4089
+ peerIds.clear();
4090
+ }
4091
+ function respondToHello() {
4092
+ safePost({ v: PROTOCOL_VERSION, kind: "announce", senderId });
4093
+ }
4094
+ function respondToSnapshotRequest() {
4095
+ const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath);
4096
+ safePost({
4097
+ v: PROTOCOL_VERSION,
4098
+ kind: "snapshot",
4099
+ senderId,
4100
+ form: scrubbedForm,
4101
+ blankPaths: [...state.blankPaths]
4102
+ });
4103
+ }
4104
+ channel.onmessage = (event) => {
4105
+ if (disposed) return;
4106
+ const data = event.data;
4107
+ if (!isValidSyncMessage(data)) return;
4108
+ const msg = data;
4109
+ if (msg.senderId === senderId) return;
4110
+ switch (msg.kind) {
4111
+ case "hello":
4112
+ if (lifecycle !== "established") return;
4113
+ respondToHello();
4114
+ break;
4115
+ case "announce":
4116
+ if (lifecycle === "joining") peerIds.add(msg.senderId);
4117
+ break;
4118
+ case "requestSnapshot":
4119
+ if (lifecycle !== "established") return;
4120
+ if (msg.targetId !== senderId) return;
4121
+ respondToSnapshotRequest();
4122
+ break;
4123
+ case "snapshot":
4124
+ handleSnapshot(msg);
4125
+ break;
4126
+ case "patches":
4127
+ handlePatches(msg);
4128
+ break;
4129
+ }
4130
+ };
4131
+ function electLeaderAndRequest() {
4132
+ if (disposed) return;
4133
+ if (peerIds.size === 0) {
4134
+ lifecycle = "established";
4135
+ refreshPrior();
4136
+ return;
4137
+ }
4138
+ const sorted = [...peerIds].sort();
4139
+ const leaderId = sorted[0];
4140
+ peerIds.delete(leaderId);
4141
+ leaderAttempts++;
4142
+ safePost({
4143
+ v: PROTOCOL_VERSION,
4144
+ kind: "requestSnapshot",
4145
+ senderId,
4146
+ targetId: leaderId
4147
+ });
4148
+ snapshotTimeoutTimer = setTimeout(() => {
4149
+ snapshotTimeoutTimer = null;
4150
+ if (disposed) return;
4151
+ if (lifecycle === "established") return;
4152
+ if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
4153
+ lifecycle = "established";
4154
+ refreshPrior();
4155
+ return;
4156
+ }
4157
+ electLeaderAndRequest();
4158
+ }, SNAPSHOT_TIMEOUT_MS);
4159
+ }
4160
+ function joinFlow() {
4161
+ safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
4162
+ joinCollectionTimer = setTimeout(() => {
4163
+ joinCollectionTimer = null;
4164
+ if (disposed) return;
4165
+ if (lifecycle === "established") return;
4166
+ electLeaderAndRequest();
4167
+ }, JOIN_COLLECTION_WINDOW_MS);
4168
+ }
4169
+ joinFlow();
4170
+ return {
4171
+ dispose: () => {
4172
+ if (disposed) return;
4173
+ disposed = true;
4174
+ if (joinCollectionTimer !== null) {
4175
+ clearTimeout(joinCollectionTimer);
4176
+ joinCollectionTimer = null;
4177
+ }
4178
+ if (snapshotTimeoutTimer !== null) {
4179
+ clearTimeout(snapshotTimeoutTimer);
4180
+ snapshotTimeoutTimer = null;
4181
+ }
4182
+ unsubscribeChange();
4183
+ try {
4184
+ channel.close();
4185
+ } catch {
4186
+ }
4187
+ },
4188
+ lifecycle: () => lifecycle,
4189
+ senderId,
4190
+ channelName
4191
+ };
4192
+ }
4193
+ const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
4194
+
4195
+ const warned = /* @__PURE__ */ new Set();
4196
+ function warnOnceInsecureContext(feature) {
4197
+ if (!plugin.__DEV__) return;
4198
+ if (warned.has(feature)) return;
4199
+ warned.add(feature);
4200
+ const message = featureMessage(feature);
4201
+ console.warn(`[attaform] ${message}`);
4202
+ }
4203
+ function featureMessage(feature) {
4204
+ switch (feature) {
4205
+ case "multiTab":
4206
+ 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.";
4207
+ case "persist:local":
4208
+ 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.";
4209
+ case "persist:session":
4210
+ 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.";
4211
+ }
4212
+ }
4213
+ function isSecureContext() {
4214
+ return typeof window !== "undefined" && window.isSecureContext === true;
4215
+ }
4216
+
3291
4217
  function useAbstractForm(configuration) {
3292
4218
  if (configuration === void 0 || configuration === null || configuration.schema === void 0) {
3293
4219
  throw new plugin.InvalidUseFormConfigError();
3294
4220
  }
3295
4221
  const key = resolveFormKey(configuration.key);
3296
- const resolvedSchema = getComputedSchema(key, configuration.schema);
4222
+ const instance = vue.getCurrentInstance();
4223
+ if (instance !== null) plugin.ensureAttaformInstalled(instance.appContext.app);
4224
+ const registry = plugin.useRegistry();
4225
+ const merged = mergeWithDefaults(registry.defaults, configuration);
4226
+ const maxRecursionDepth = normalizeNumericOption({
4227
+ value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
4228
+ source: "useForm.maxRecursionDepth",
4229
+ allowInfinity: true,
4230
+ min: 0,
4231
+ defaultValue: DEFAULT_MAX_RECURSION_DEPTH
4232
+ });
4233
+ const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
3297
4234
  if (configuration.persist !== void 0 && configuration.key === void 0) {
3298
4235
  throw new plugin.AnonPersistError({
3299
4236
  cause: "no-key",
@@ -3301,13 +4238,10 @@ function useAbstractForm(configuration) {
3301
4238
  callSite: plugin.captureUserCallSite()
3302
4239
  });
3303
4240
  }
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
4241
  const existing = registry.forms.get(key);
3309
4242
  if (plugin.__DEV__ && existing !== void 0) {
3310
4243
  warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
4244
+ warnOnPersistDivergence(key, existing, configuration.persist);
3311
4245
  }
3312
4246
  const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
3313
4247
  if (vue.getCurrentScope() !== void 0) {
@@ -3318,16 +4252,54 @@ function useAbstractForm(configuration) {
3318
4252
  if (existing === void 0 && !registry.ssr) {
3319
4253
  if (merged.persist !== void 0 && !persistDisabledByAnonRule) {
3320
4254
  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());
4255
+ const storageKind = resolvedPersist.storage;
4256
+ const isBuiltinStorage = typeof storageKind === "string";
4257
+ const secureContextOk = !isBuiltinStorage || isSecureContext();
4258
+ if (!secureContextOk) {
4259
+ const feature = storageKind === "session" ? "persist:session" : "persist:local";
4260
+ warnOnceInsecureContext(feature);
4261
+ void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
4262
+ } else {
4263
+ const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
4264
+ void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
4265
+ const persistenceModule = wirePersistence(state, resolvedPersist);
4266
+ state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
4267
+ state.registerDrain(() => persistenceModule.awaitPendingWrites());
4268
+ state.registerCleanup(() => persistenceModule.dispose());
4269
+ }
3327
4270
  } else {
3328
4271
  void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
3329
4272
  }
3330
4273
  }
4274
+ if (existing === void 0 && merged.multiTab !== false && configuration.key !== void 0 && !registry.ssr) {
4275
+ const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
4276
+ const secureContext = isSecureContext();
4277
+ if (hasBroadcastChannel && secureContext) {
4278
+ let channelName;
4279
+ try {
4280
+ channelName = `attaform:sync:${state.formKey}:${hashStableString(state.schema.fingerprint())}`;
4281
+ } catch {
4282
+ channelName = null;
4283
+ }
4284
+ if (channelName !== null) {
4285
+ const syncModule = createMultiTabSyncModule(state, channelName, {
4286
+ isSensitivePath: state.isSensitivePath,
4287
+ noSyncPaths: state.noSyncPaths,
4288
+ validateForm: (form) => {
4289
+ const result = state.schema.validateAtPath(form, void 0, { sync: true });
4290
+ if (result instanceof Promise) return;
4291
+ if (!result.success) {
4292
+ throw new Error("attaform multi-tab sync: post-apply schema validation failed");
4293
+ }
4294
+ }
4295
+ });
4296
+ state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
4297
+ state.registerCleanup(() => syncModule.dispose());
4298
+ }
4299
+ } else if (hasBroadcastChannel && !secureContext) {
4300
+ warnOnceInsecureContext("multiTab");
4301
+ }
4302
+ }
3331
4303
  if (existing === void 0 && merged.history !== void 0) {
3332
4304
  const historyModule = createHistoryModule(state, merged.history);
3333
4305
  state.modules.set(HISTORY_MODULE_KEY, historyModule);
@@ -3349,7 +4321,27 @@ function useAbstractForm(configuration) {
3349
4321
  if (history !== void 0) {
3350
4322
  apiOptions.history = history;
3351
4323
  }
3352
- return buildFormApi(state, formInstanceId, apiOptions);
4324
+ if (merged.validateOn !== void 0) {
4325
+ apiOptions.validateOn = merged.validateOn;
4326
+ }
4327
+ const mergedDebounceMs = merged.debounceMs;
4328
+ if (mergedDebounceMs !== void 0) {
4329
+ apiOptions.debounceMs = mergedDebounceMs;
4330
+ }
4331
+ if (merged.shouldShowErrors !== void 0) {
4332
+ apiOptions.shouldShowErrors = resolveShouldShowErrors(merged.shouldShowErrors);
4333
+ }
4334
+ if (merged.coerce !== void 0) {
4335
+ apiOptions.coerce = merged.coerce;
4336
+ }
4337
+ if (merged.rememberVariants !== void 0) {
4338
+ apiOptions.rememberVariants = merged.rememberVariants;
4339
+ }
4340
+ return buildFormApi(
4341
+ state,
4342
+ formInstanceId,
4343
+ apiOptions
4344
+ );
3353
4345
  }
3354
4346
  function mergeWithDefaults(defaults, configuration) {
3355
4347
  const strict = configuration.strict ?? defaults.strict;
@@ -3360,6 +4352,9 @@ function mergeWithDefaults(defaults, configuration) {
3360
4352
  const validateOn = configuration.validateOn ?? defaults.validateOn;
3361
4353
  const debounceMs = configuration.debounceMs ?? defaults.debounceMs;
3362
4354
  const shouldShowErrors = configuration.shouldShowErrors ?? defaults.shouldShowErrors;
4355
+ const maxRecursionDepth = configuration.maxRecursionDepth ?? defaults.maxRecursionDepth;
4356
+ const sensitiveNames = configuration.sensitiveNames ?? defaults.sensitiveNames;
4357
+ const multiTab = configuration.multiTab ?? defaults.multiTab;
3363
4358
  return {
3364
4359
  ...configuration,
3365
4360
  ...strict === void 0 ? {} : { strict },
@@ -3369,7 +4364,10 @@ function mergeWithDefaults(defaults, configuration) {
3369
4364
  ...coerce === void 0 ? {} : { coerce },
3370
4365
  ...validateOn === void 0 ? {} : { validateOn },
3371
4366
  ...debounceMs === void 0 ? {} : { debounceMs },
3372
- ...shouldShowErrors === void 0 ? {} : { shouldShowErrors }
4367
+ ...shouldShowErrors === void 0 ? {} : { shouldShowErrors },
4368
+ ...maxRecursionDepth === void 0 ? {} : { maxRecursionDepth },
4369
+ ...sensitiveNames === void 0 ? {} : { sensitiveNames },
4370
+ ...multiTab === void 0 ? {} : { multiTab }
3373
4371
  };
3374
4372
  }
3375
4373
  const HISTORY_MODULE_KEY = "history";
@@ -3381,6 +4379,9 @@ function buildFreshState(key, schema, configuration, registry) {
3381
4379
  schema
3382
4380
  );
3383
4381
  const initialBlankPaths = pending === void 0 ? walked.paths : void 0;
4382
+ const resolvedSensitiveNames = configuration.sensitiveNames;
4383
+ const resolvedIsSensitivePath = resolvedSensitiveNames === void 0 ? void 0 : plugin.createIsSensitivePath(resolvedSensitiveNames);
4384
+ const resolvedSegmentMatchesSensitive = resolvedSensitiveNames === void 0 ? void 0 : plugin.createSegmentMatchesSensitive(resolvedSensitiveNames);
3384
4385
  const createOptions = {
3385
4386
  formKey: key,
3386
4387
  schema,
@@ -3393,7 +4394,9 @@ function buildFreshState(key, schema, configuration, registry) {
3393
4394
  ...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
3394
4395
  ...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
3395
4396
  ...configuration.shouldShowErrors !== void 0 ? { shouldShowErrors: configuration.shouldShowErrors } : {},
3396
- ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {}
4397
+ ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {},
4398
+ ...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {},
4399
+ ...resolvedSegmentMatchesSensitive !== void 0 ? { segmentMatchesSensitive: resolvedSegmentMatchesSensitive } : {}
3397
4400
  };
3398
4401
  const state = createFormStore(createOptions);
3399
4402
  registry.forms.set(
@@ -3452,11 +4455,47 @@ function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
3452
4455
  incoming: ${incomingFp}`
3453
4456
  );
3454
4457
  }
4458
+ function warnOnPersistDivergence(key, existing, incomingPersist) {
4459
+ if (incomingPersist === void 0) return;
4460
+ const wired = existing.modules.get(PERSISTENCE_MODULE_KEY);
4461
+ const incomingNormalized = normalizePersistConfig(incomingPersist);
4462
+ if (wired === void 0) {
4463
+ console.warn(
4464
+ `[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.`
4465
+ );
4466
+ return;
4467
+ }
4468
+ if (persistConfigsEquivalent(wired.wiredConfig, incomingNormalized)) return;
4469
+ console.warn(
4470
+ `[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
4471
+ wired: ${describePersist(wired.wiredConfig)}
4472
+ incoming: ${describePersist(incomingNormalized)}`
4473
+ );
4474
+ }
4475
+ function persistConfigsEquivalent(a, b) {
4476
+ if (a.storage !== b.storage) return false;
4477
+ if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
4478
+ if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
4479
+ return true;
4480
+ }
4481
+ function describePersist(config) {
4482
+ const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
4483
+ const parts = [`storage=${storage}`];
4484
+ if (config.key !== void 0) parts.push(`key=${config.key}`);
4485
+ if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
4486
+ return `{ ${parts.join(", ")} }`;
4487
+ }
3455
4488
  function wirePersistence(state, config) {
3456
4489
  const fingerprint = hashStableString(state.schema.fingerprint());
3457
4490
  const base = resolveStorageKeyBase(config, state.formKey);
3458
4491
  const key = `${base}:${fingerprint}`;
3459
- const debounceMs = config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS;
4492
+ const debounceMs = normalizeNumericOption({
4493
+ value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
4494
+ source: "useForm.persist.debounceMs",
4495
+ allowInfinity: false,
4496
+ min: 0,
4497
+ defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
4498
+ });
3460
4499
  const include = config.include ?? "form";
3461
4500
  const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
3462
4501
  const adapterPromise = getStorageAdapter(config.storage);
@@ -3491,6 +4530,7 @@ function wirePersistence(state, config) {
3491
4530
  }, debounceMs);
3492
4531
  const unsubscribeChange = state.onFormChange((_next, meta) => {
3493
4532
  if (disposed || inFlightFinalFlush !== null) return;
4533
+ if (meta?.crossTab === true) return;
3494
4534
  if (meta?.persist !== true) return;
3495
4535
  pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
3496
4536
  writer.schedule();
@@ -3524,7 +4564,7 @@ function wirePersistence(state, config) {
3524
4564
  payload.data.form,
3525
4565
  state.schema
3526
4566
  );
3527
- state.applyFormReplacement(merged);
4567
+ state.applyFormReplacement(merged, { hydration: true });
3528
4568
  const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
3529
4569
  for (const k of persistedLeafPaths) {
3530
4570
  state.blankPaths.delete(k);
@@ -3655,6 +4695,7 @@ function wirePersistence(state, config) {
3655
4695
  });
3656
4696
  }
3657
4697
  return {
4698
+ wiredConfig: config,
3658
4699
  writePathImmediately,
3659
4700
  clearPersistedDraft,
3660
4701
  awaitPendingWrites,
@@ -3729,7 +4770,11 @@ function injectForm(key) {
3729
4770
  }
3730
4771
  const ambientInstanceId = vue.getCurrentInstance() !== null ? vue.inject(plugin.kFormInstanceId, null) : null;
3731
4772
  const formInstanceId = ambientInstanceId ?? (vue.getCurrentInstance() !== null ? vue.useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`);
3732
- return buildFormApi(state, formInstanceId, apiOptions);
4773
+ return buildFormApi(
4774
+ state,
4775
+ formInstanceId,
4776
+ apiOptions
4777
+ );
3733
4778
  }
3734
4779
  function resolveState(key, registry) {
3735
4780
  if (key !== void 0) {
@@ -3782,8 +4827,9 @@ exports.humanize = humanize;
3782
4827
  exports.injectForm = injectForm;
3783
4828
  exports.isPlainRecord = isPlainRecord;
3784
4829
  exports.isUnset = isUnset;
4830
+ exports.normalizeNumericOption = normalizeNumericOption;
3785
4831
  exports.setAtPath = setAtPath;
3786
4832
  exports.slimKindOf = slimKindOf;
3787
4833
  exports.unset = unset;
3788
4834
  exports.useAbstractForm = useAbstractForm;
3789
- //# sourceMappingURL=attaform.fegmBJaq.cjs.map
4835
+ //# sourceMappingURL=attaform.Cer8JO_P.cjs.map