attaform 0.16.3 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) 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 +27 -7
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +80 -8
  15. package/dist/index.d.mts +80 -8
  16. package/dist/index.d.ts +80 -8
  17. package/dist/index.mjs +28 -9
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/nuxt.d.cts +1 -1
  20. package/dist/nuxt.d.mts +1 -1
  21. package/dist/nuxt.d.ts +1 -1
  22. package/dist/runtime/plugins/attaform.cjs +3 -3
  23. package/dist/runtime/plugins/attaform.cjs.map +1 -1
  24. package/dist/runtime/plugins/attaform.mjs +3 -3
  25. package/dist/runtime/plugins/attaform.mjs.map +1 -1
  26. package/dist/shared/{attaform.KrNw10aW.cjs → attaform.0Wg7UEeX.cjs} +60 -20
  27. package/dist/shared/attaform.0Wg7UEeX.cjs.map +1 -0
  28. package/dist/shared/attaform.AOgGyRoI.d.cts +65 -0
  29. package/dist/shared/{attaform.lFNwBcA3.d.ts → attaform.B0zue7zt.d.ts} +1 -1
  30. package/dist/shared/{attaform.c_NzdRyc.cjs → attaform.BBM2muQ9.cjs} +7 -3
  31. package/dist/shared/attaform.BBM2muQ9.cjs.map +1 -0
  32. package/dist/shared/{attaform.C9Ph2SMx.cjs → attaform.BFumZXY2.cjs} +1514 -391
  33. package/dist/shared/attaform.BFumZXY2.cjs.map +1 -0
  34. package/dist/shared/attaform.BQ-iGGWd.d.mts +65 -0
  35. package/dist/shared/{attaform.DILbdvfo.mjs → attaform.BT55rDNN.mjs} +1514 -393
  36. package/dist/shared/attaform.BT55rDNN.mjs.map +1 -0
  37. package/dist/shared/{attaform._EqYNPYF.d.mts → attaform.BYbsV2Wv.d.cts} +738 -138
  38. package/dist/shared/{attaform._EqYNPYF.d.ts → attaform.BYbsV2Wv.d.mts} +738 -138
  39. package/dist/shared/{attaform._EqYNPYF.d.cts → attaform.BYbsV2Wv.d.ts} +738 -138
  40. package/dist/shared/{attaform.DGuGGNg9.cjs → attaform.C6_zOf8x.cjs} +232 -113
  41. package/dist/shared/attaform.C6_zOf8x.cjs.map +1 -0
  42. package/dist/shared/{attaform.CJttVxRj.cjs → attaform.C8LVFVVe.cjs} +2 -2
  43. package/dist/shared/{attaform.CJttVxRj.cjs.map → attaform.C8LVFVVe.cjs.map} +1 -1
  44. package/dist/shared/{attaform.BfMxsfmE.mjs → attaform.CIEQgJnM.mjs} +143 -78
  45. package/dist/shared/attaform.CIEQgJnM.mjs.map +1 -0
  46. package/dist/shared/attaform.CX9v2M8k.d.ts +65 -0
  47. package/dist/shared/{attaform.XYOMTvuO.mjs → attaform.Cj0pCNVn.mjs} +232 -113
  48. package/dist/shared/attaform.Cj0pCNVn.mjs.map +1 -0
  49. package/dist/shared/{attaform.DLnKT7wk.d.cts → attaform.ClfCi1i2.d.mts} +1 -1
  50. package/dist/shared/{attaform.CFA6y0KF.mjs → attaform.D6Q5ZP8L.mjs} +60 -20
  51. package/dist/shared/attaform.D6Q5ZP8L.mjs.map +1 -0
  52. package/dist/shared/{attaform.Bls_kFR6.d.mts → attaform.D7lomopc.d.cts} +1 -1
  53. package/dist/shared/{attaform.rIRYSUI1.cjs → attaform.Dee2rU1P.cjs} +145 -77
  54. package/dist/shared/attaform.Dee2rU1P.cjs.map +1 -0
  55. package/dist/shared/{attaform.CINUMjPq.mjs → attaform.Vo-Kft0t.mjs} +2 -2
  56. package/dist/shared/{attaform.CINUMjPq.mjs.map → attaform.Vo-Kft0t.mjs.map} +1 -1
  57. package/dist/shared/{attaform.jrxE_xZw.mjs → attaform.h1sq3BFu.mjs} +6 -4
  58. package/dist/shared/attaform.h1sq3BFu.mjs.map +1 -0
  59. package/dist/zod-v3.cjs +3 -3
  60. package/dist/zod-v3.d.cts +5 -5
  61. package/dist/zod-v3.d.mts +5 -5
  62. package/dist/zod-v3.d.ts +5 -5
  63. package/dist/zod-v3.mjs +3 -3
  64. package/dist/zod-v4.cjs +3 -3
  65. package/dist/zod-v4.d.cts +16 -42
  66. package/dist/zod-v4.d.mts +16 -42
  67. package/dist/zod-v4.d.ts +16 -42
  68. package/dist/zod-v4.mjs +3 -3
  69. package/dist/zod.cjs +4 -4
  70. package/dist/zod.cjs.map +1 -1
  71. package/dist/zod.d.cts +6 -5
  72. package/dist/zod.d.mts +6 -5
  73. package/dist/zod.d.ts +6 -5
  74. package/dist/zod.mjs +5 -5
  75. package/dist/zod.mjs.map +1 -1
  76. package/package.json +3 -8
  77. package/dist/shared/attaform.BfMxsfmE.mjs.map +0 -1
  78. package/dist/shared/attaform.C9Ph2SMx.cjs.map +0 -1
  79. package/dist/shared/attaform.CFA6y0KF.mjs.map +0 -1
  80. package/dist/shared/attaform.DGuGGNg9.cjs.map +0 -1
  81. package/dist/shared/attaform.DILbdvfo.mjs.map +0 -1
  82. package/dist/shared/attaform.KrNw10aW.cjs.map +0 -1
  83. package/dist/shared/attaform.XYOMTvuO.mjs.map +0 -1
  84. package/dist/shared/attaform.c_NzdRyc.cjs.map +0 -1
  85. package/dist/shared/attaform.jrxE_xZw.mjs.map +0 -1
  86. 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.c_NzdRyc.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,20 +248,236 @@ function setAtPathWithSchemaFillImpl(root, schema, fullPath, value, startIdx) {
415
248
  return rec;
416
249
  }
417
250
 
418
- function buildFieldStateAccessor(state) {
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) : buildContainerFieldState(state, segments)
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;
429
478
  };
430
479
  }
431
- function buildLeafFieldState(state, segments, key) {
480
+ function buildLeafFieldStateBase(state, segments, key) {
432
481
  const record = state.fields.get(key);
433
482
  const value = state.getValueAtPath(segments);
434
483
  const original = state.originals.get(key)?.value;
@@ -442,7 +491,8 @@ function buildLeafFieldState(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,7 +522,11 @@ function buildLeafFieldState(state, segments, key) {
472
522
  meta: resolved.meta
473
523
  };
474
524
  }
475
- function buildContainerFieldState(state, segments, _key) {
525
+ function buildLeafFieldState(state, segments, key, getFormMetaBase, shouldShowErrors) {
526
+ const base = buildLeafFieldStateBase(state, segments, key);
527
+ return decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors);
528
+ }
529
+ function buildContainerFieldStateBase(state, segments, _key) {
476
530
  const formValue = state.form.value;
477
531
  const value = state.getValueAtPath(segments);
478
532
  const original = state.originals.get(paths.canonicalizePath(segments).key)?.value;
@@ -538,6 +592,16 @@ function buildContainerFieldState(state, segments, _key) {
538
592
  meta: resolved.meta
539
593
  };
540
594
  }
595
+ function buildContainerFieldState(state, segments, key, getFormMetaBase, shouldShowErrors) {
596
+ const base = buildContainerFieldStateBase(state, segments);
597
+ return decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors);
598
+ }
599
+ function decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors) {
600
+ const firstError = base.errors[0];
601
+ const predicate = shouldShowErrors ?? state.shouldShowErrors;
602
+ const showErrors = base.errors.length > 0 && predicate(base, getFormMetaBase());
603
+ return { ...base, showErrors, firstError };
604
+ }
541
605
  function aggregateErrorsAt(state, prefix) {
542
606
  const formValue = state.form.value;
543
607
  const buckets = /* @__PURE__ */ new Map();
@@ -547,7 +611,7 @@ function aggregateErrorsAt(state, prefix) {
547
611
  const segs = paths.segmentsForPathKey(pathKey);
548
612
  if (segs === null) continue;
549
613
  if (!paths.isPathPrefix(prefix, segs)) continue;
550
- if (segs.length > 0 && !hasAtPath(formValue, segs)) continue;
614
+ if (pathKey === paths.FORM_ERRORS_PATH_KEY) ; else if (segs.length > 0 && !hasAtPath(formValue, segs)) continue;
551
615
  const ordinal = state.ensurePathOrdinal(pathKey);
552
616
  const existing = buckets.get(ordinal);
553
617
  if (existing === void 0) buckets.set(ordinal, [...list]);
@@ -720,14 +784,16 @@ function buildErrorsProxy(state) {
720
784
  return buildSurfaceProxy({
721
785
  schema: state.schema,
722
786
  resolveLeaf: (path) => {
723
- if (!hasAtPath(state.form.value, path)) return void 0;
724
787
  const { key } = paths.canonicalizePath(path);
725
- const schemaForKey = state.schemaErrors.get(key);
726
- const blankForKey = state.derivedBlankErrors.value.get(key);
727
788
  const userForKey = state.userErrors.get(key);
789
+ const isActive = hasAtPath(state.form.value, path);
728
790
  const merged = [];
729
- if (schemaForKey !== void 0) merged.push(...schemaForKey);
730
- 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
+ }
731
797
  if (userForKey !== void 0) merged.push(...userForKey);
732
798
  return merged.length === 0 ? void 0 : merged;
733
799
  },
@@ -752,7 +818,7 @@ function buildErrorsProxy(state) {
752
818
  function materializeErrors(state, containerSegments) {
753
819
  const liveContainer = getAtPath(state.form.value, containerSegments);
754
820
  const tree = Array.isArray(liveContainer) ? [] : {};
755
- const collect = (store) => {
821
+ const collect = (store, applyActivePathFilter) => {
756
822
  entries: for (const [pathKey, errors] of store) {
757
823
  if (errors.length === 0) continue;
758
824
  const fullPath = paths.segmentsForPathKey(pathKey);
@@ -761,13 +827,13 @@ function materializeErrors(state, containerSegments) {
761
827
  for (let i = 0; i < containerSegments.length; i++) {
762
828
  if (fullPath[i] !== containerSegments[i]) continue entries;
763
829
  }
764
- if (!hasAtPath(state.form.value, fullPath)) continue;
830
+ if (applyActivePathFilter && !hasAtPath(state.form.value, fullPath)) continue;
765
831
  placeAt(tree, fullPath.slice(containerSegments.length), errors);
766
832
  }
767
833
  };
768
- collect(state.schemaErrors);
769
- collect(state.derivedBlankErrors.value);
770
- collect(state.userErrors);
834
+ collect(state.schemaErrors, true);
835
+ collect(state.derivedBlankErrors.value, true);
836
+ collect(state.userErrors, false);
771
837
  return tree;
772
838
  }
773
839
  function placeAt(tree, path, errors) {
@@ -798,11 +864,13 @@ function buildFieldArrayApi(state) {
798
864
  const current = state.getValueAtPath(segments);
799
865
  return Array.isArray(current) ? current.slice() : [];
800
866
  }
801
- function writeArray(path, next) {
867
+ function writeArray(path, next, arrayOp) {
802
868
  const { segments, key } = paths.canonicalizePath(path);
803
- return state.setValueAtPath(segments, next, {
804
- persist: state.persistOptIns.hasAnyOptInForPath(key)
805
- });
869
+ const meta = {
870
+ persist: state.persistOptIns.hasAnyOptInForPath(key),
871
+ ...arrayOp !== void 0 ? { arrayOp } : {}
872
+ };
873
+ return state.setValueAtPath(segments, next, meta);
806
874
  }
807
875
  return {
808
876
  append(path, value) {
@@ -813,18 +881,19 @@ function buildFieldArrayApi(state) {
813
881
  prepend(path, value) {
814
882
  const next = readArray(path);
815
883
  next.unshift(value);
816
- return writeArray(path, next);
884
+ return writeArray(path, next, { kind: "shift-from", index: 0 });
817
885
  },
818
886
  insert(path, index, value) {
819
887
  const next = readArray(path);
820
888
  next.splice(index, 0, value);
821
- 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 });
822
891
  },
823
892
  remove(path, index) {
824
893
  const next = readArray(path);
825
894
  if (index < 0 || index >= next.length) return false;
826
895
  next.splice(index, 1);
827
- return writeArray(path, next);
896
+ return writeArray(path, next, { kind: "shift-from", index });
828
897
  },
829
898
  swap(path, a, b) {
830
899
  const next = readArray(path);
@@ -834,7 +903,7 @@ function buildFieldArrayApi(state) {
834
903
  const tmp = next[a];
835
904
  next[a] = next[b];
836
905
  next[b] = tmp;
837
- return writeArray(path, next);
906
+ return writeArray(path, next, { kind: "swap", a, b });
838
907
  },
839
908
  move(path, from, to) {
840
909
  const next = readArray(path);
@@ -842,13 +911,17 @@ function buildFieldArrayApi(state) {
842
911
  const [item] = next.splice(from, 1);
843
912
  const clampedTo = Math.max(0, Math.min(to, next.length));
844
913
  next.splice(clampedTo, 0, item);
845
- 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
+ });
846
919
  },
847
920
  replace(path, index, value) {
848
921
  const next = readArray(path);
849
922
  if (index < 0 || index >= next.length) return false;
850
923
  next[index] = value;
851
- return writeArray(path, next);
924
+ return writeArray(path, next, { kind: "replace-at", index });
852
925
  }
853
926
  };
854
927
  }
@@ -868,6 +941,8 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
868
941
  "errors",
869
942
  "validating",
870
943
  "valid",
944
+ "showErrors",
945
+ "firstError",
871
946
  "path",
872
947
  "blank",
873
948
  "label",
@@ -875,8 +950,12 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
875
950
  "placeholder",
876
951
  "meta"
877
952
  ]);
878
- function buildFieldStateProxy(state) {
879
- const getFieldStateAt = buildFieldStateAccessor(state);
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
+ );
880
959
  const snapshotFieldStateAt = (path) => {
881
960
  const view = getFieldStateAt(path).value;
882
961
  const snapshot = {};
@@ -967,10 +1046,25 @@ function walk$2(value, basePath, schema, snapshotFieldStateAt) {
967
1046
 
968
1047
  const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
969
1048
  const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
970
- const DEFAULT_HISTORY_MAX_SNAPSHOTS = 50;
1049
+ const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
971
1050
  const PERSISTENCE_KEY_PREFIX = "attaform:";
972
1051
  const RESERVED_KEY_PREFIX = "__atta:";
973
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
+ }
974
1068
 
975
1069
  const PERSISTENCE_MODULE_KEY = "persistence";
976
1070
  async function getStorageAdapter(storage) {
@@ -1145,20 +1239,29 @@ function mergeDeep(target, source, path, schema) {
1145
1239
  if (source === null || typeof source !== "object") return source;
1146
1240
  if (Array.isArray(source)) return source;
1147
1241
  if (!isPlainRecord(source)) return source;
1148
- let mergeTarget = target;
1149
1242
  if (schema !== void 0) {
1150
1243
  const du = schema.getUnionDiscriminatorAtPath(path);
1151
1244
  if (du !== void 0) {
1152
- const sourceDisc = source[du.discriminatorKey];
1153
- const targetDisc = isPlainRecord(target) ? target[du.discriminatorKey] : void 0;
1154
- 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) {
1155
1251
  const variantDefault = du.getVariantDefault(sourceDisc);
1156
1252
  if (isPlainRecord(variantDefault)) {
1157
- 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;
1158
1259
  }
1159
1260
  }
1261
+ return {};
1160
1262
  }
1161
1263
  }
1264
+ const mergeTarget = target;
1162
1265
  const out = isPlainRecord(mergeTarget) ? { ...mergeTarget } : {};
1163
1266
  for (const key of Object.keys(source)) {
1164
1267
  out[key] = mergeDeep(out[key], source[key], [...path, key], schema);
@@ -1187,14 +1290,14 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1187
1290
  });
1188
1291
  let gen = 0;
1189
1292
  async function kickoff(data, path, captured) {
1190
- state.activeValidations.value += 1;
1191
- result.value = {
1192
- pending: true,
1193
- errors: void 0,
1194
- success: false,
1195
- formKey: state.formKey
1196
- };
1197
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
+ };
1198
1301
  const refinement = await runRefinementValidation(data, path);
1199
1302
  if (captured !== gen) return;
1200
1303
  result.value = settled(composeWithDerivedBlank(refinement, path));
@@ -1238,14 +1341,44 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1238
1341
  async function validateAsync(pathInput) {
1239
1342
  const segments = pathInput === void 0 ? void 0 : toSegments(pathInput);
1240
1343
  const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
1241
- state.activeValidations.value += 1;
1242
1344
  try {
1345
+ state.activeValidations.value += 1;
1243
1346
  const refinement = await runRefinementValidation(dataAtPath, segments);
1244
1347
  return stripData(composeWithDerivedBlank(refinement, segments));
1348
+ } catch (err) {
1349
+ return adapterThrowResponse(err);
1350
+ } finally {
1351
+ state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
1352
+ }
1353
+ }
1354
+ async function process(pathInput) {
1355
+ const segments = pathInput === void 0 ? void 0 : toSegments(pathInput);
1356
+ const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
1357
+ try {
1358
+ state.activeValidations.value += 1;
1359
+ const refinement = await runRefinementValidation(dataAtPath, segments);
1360
+ return composeWithDerivedBlank(refinement, segments);
1361
+ } catch (err) {
1362
+ return adapterThrowResponse(err);
1245
1363
  } finally {
1246
1364
  state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
1247
1365
  }
1248
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
+ }
1249
1382
  async function runRefinementValidation(data, path) {
1250
1383
  return await state.schema.validateAtPath(data, path);
1251
1384
  }
@@ -1267,14 +1400,17 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1267
1400
  if (event !== void 0 && "preventDefault" in event && typeof event.preventDefault === "function") {
1268
1401
  event.preventDefault();
1269
1402
  }
1403
+ if (state.activeSubmissions.value > 0) {
1404
+ return;
1405
+ }
1270
1406
  const genAtEntry = state.submissionGeneration.value;
1271
- state.activeSubmissions.value += 1;
1272
- state.submitting.value = true;
1273
- state.submitError.value = null;
1274
- state.cancelFieldValidation();
1275
- state.activeValidations.value += 1;
1276
1407
  let validationSettled = false;
1277
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;
1278
1414
  const refinement = await runRefinementValidation(state.form.value, void 0);
1279
1415
  const merged = composeWithDerivedBlank(refinement, void 0);
1280
1416
  state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
@@ -1323,7 +1459,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1323
1459
  };
1324
1460
  return submitHandler;
1325
1461
  };
1326
- return { validate, validateAsync, handleSubmit };
1462
+ return { validate, validateAsync, process, handleSubmit };
1327
1463
  }
1328
1464
  function toSegments(pathInput) {
1329
1465
  return paths.canonicalizePath(pathInput).segments;
@@ -1661,11 +1797,12 @@ function coerceValue(value, accepted, elementAccepted, index) {
1661
1797
  const EMPTY_TRANSFORMS = Object.freeze([]);
1662
1798
  const INTERACTIVE_TAG_NAMES = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
1663
1799
  const attaformListenersSymbol = Symbol.for("attaform:focus-listeners");
1664
- function attachFocusListeners(state, segments, element) {
1800
+ function attachFocusListeners(state, segments, element, instanceMeta) {
1665
1801
  const target = element;
1666
1802
  if (target[attaformListenersSymbol] !== void 0) return;
1667
- const handleFocus = () => state.markFocused(segments, true);
1668
- 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);
1669
1806
  element.addEventListener("focus", handleFocus);
1670
1807
  element.addEventListener("blur", handleBlur);
1671
1808
  target[attaformListenersSymbol] = { handleFocus, handleBlur };
@@ -1678,7 +1815,13 @@ function detachFocusListeners(element) {
1678
1815
  element.removeEventListener("blur", listeners.handleBlur);
1679
1816
  delete target[attaformListenersSymbol];
1680
1817
  }
1681
- 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
+ };
1682
1825
  const lastTypedFormByPath = /* @__PURE__ */ new Map();
1683
1826
  return function register(pathInput, options) {
1684
1827
  const { segments, key: pathKey } = paths.canonicalizePath(pathInput);
@@ -1701,16 +1844,23 @@ function buildRegister(state, formInstanceId) {
1701
1844
  const slimDefault = state.schema.getDefaultAtPath(segments);
1702
1845
  const persist = options?.persist === true;
1703
1846
  const acknowledgeSensitive = options?.acknowledgeSensitive === true;
1847
+ const multiTab = options?.multiTab !== false;
1704
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;
1705
1855
  const coerce = buildCoerceFn(
1706
1856
  state.schema,
1707
1857
  segments,
1708
- state.coerceIndex
1858
+ coerceIndex
1709
1859
  );
1710
1860
  const coerceElement = buildElementCoerceFn(
1711
1861
  state.schema,
1712
1862
  segments,
1713
- state.coerceIndex
1863
+ coerceIndex
1714
1864
  );
1715
1865
  if (persist && !state.ssr && !state.modules.has(PERSISTENCE_MODULE_KEY)) {
1716
1866
  throw new plugin.AnonPersistError({
@@ -1724,22 +1874,26 @@ function buildRegister(state, formInstanceId) {
1724
1874
  displayValue,
1725
1875
  lastTypedForm,
1726
1876
  markBlank: () => {
1727
- return state.setValueAtPath(segments, slimDefault, {
1728
- blank: true,
1729
- persist
1730
- });
1877
+ return state.setValueAtPath(
1878
+ segments,
1879
+ slimDefault,
1880
+ withInstanceMeta({
1881
+ blank: true,
1882
+ persist
1883
+ })
1884
+ );
1731
1885
  },
1732
1886
  registerElement: (element) => {
1733
1887
  if (!INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
1734
1888
  const added = state.registerElement(segments, element, formInstanceId);
1735
- if (added) attachFocusListeners(state, segments, element);
1889
+ if (added) attachFocusListeners(state, segments, element, instanceMeta);
1736
1890
  },
1737
1891
  deregisterElement: (element) => {
1738
1892
  detachFocusListeners(element);
1739
1893
  state.deregisterElement(segments, element);
1740
1894
  },
1741
1895
  setValueWithInternalPath: (value, meta) => {
1742
- return state.setValueAtPath(segments, value, meta);
1896
+ return state.setValueAtPath(segments, value, withInstanceMeta(meta));
1743
1897
  },
1744
1898
  // Called by the `vRegisterHint` compile-time transform's wrapping
1745
1899
  // IIFE on every server-side render of `<element v-register="…">`.
@@ -1765,6 +1919,10 @@ function buildRegister(state, formInstanceId) {
1765
1919
  persist,
1766
1920
  acknowledgeSensitive,
1767
1921
  persistOptIns: state.persistOptIns,
1922
+ isSensitivePath: state.isSensitivePath,
1923
+ multiTab,
1924
+ ...markNoSync !== void 0 ? { markNoSync } : {},
1925
+ ...unmarkNoSync !== void 0 ? { unmarkNoSync } : {},
1768
1926
  transforms,
1769
1927
  coerce,
1770
1928
  ...coerceElement !== void 0 ? { coerceElement } : {}
@@ -1922,6 +2080,8 @@ function buildValuesProxy(form) {
1922
2080
  const inner = vue.computed(() => vue.readonly(form.value));
1923
2081
  const target = (() => {
1924
2082
  });
2083
+ const valuesToString = () => JSON.stringify(inner.value);
2084
+ const valuesToPrimitive = (hint) => hint === "number" ? NaN : valuesToString();
1925
2085
  return new Proxy(target, {
1926
2086
  apply(_, __, args) {
1927
2087
  const arg = args[0];
@@ -1935,8 +2095,16 @@ function buildValuesProxy(form) {
1935
2095
  return cursor;
1936
2096
  },
1937
2097
  get(_, key) {
1938
- 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
+ }
1939
2102
  if (key === "toJSON") return () => inner.value;
2103
+ if (key === "toString") return valuesToString;
2104
+ if (key === "valueOf")
2105
+ return function() {
2106
+ return this;
2107
+ };
1940
2108
  return inner.value[key];
1941
2109
  },
1942
2110
  has(_, key) {
@@ -1975,6 +2143,14 @@ function buildValuesProxy(form) {
1975
2143
  });
1976
2144
  }
1977
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
+ }
1978
2154
  function readonlySetSnapshot(source) {
1979
2155
  const snapshot = new Set(source);
1980
2156
  return new Proxy(snapshot, {
@@ -1990,15 +2166,36 @@ function readonlySetSnapshot(source) {
1990
2166
  });
1991
2167
  }
1992
2168
  function buildFormApi(state, formInstanceId, options = {}) {
1993
- 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
+ );
1994
2189
  const processOptions = options.onInvalidSubmit !== void 0 ? { onInvalidSubmit: options.onInvalidSubmit } : {};
1995
2190
  const {
1996
2191
  validate: validateBuilt,
1997
2192
  validateAsync: validateAsyncBuilt,
2193
+ process: processBuilt,
1998
2194
  handleSubmit
1999
2195
  } = buildProcessForm(state, formInstanceId, processOptions);
2000
2196
  const validate = (pathInput) => validateBuilt(pathInput);
2001
2197
  const validateAsync = (pathInput) => validateAsyncBuilt(pathInput);
2198
+ const process = (pathInput) => processBuilt(pathInput);
2002
2199
  function pathToRef(pathInput) {
2003
2200
  const segments = paths.canonicalizePath(pathInput).segments;
2004
2201
  return vue.computed(() => getAtPath(state.form.value, segments));
@@ -2010,22 +2207,36 @@ function buildFormApi(state, formInstanceId, options = {}) {
2010
2207
  next,
2011
2208
  state.schema
2012
2209
  );
2013
- const ok2 = state.setValueAtPath([], walked2.cleanedValues);
2210
+ const ok2 = state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
2014
2211
  if (!ok2) return false;
2015
2212
  for (const pathKey of walked2.paths) {
2016
2213
  const segments2 = paths.segmentsForPathKey(pathKey);
2017
2214
  if (segments2 === null) continue;
2018
- state.setValueAtPath(segments2, state.schema.getDefaultAtPath(segments2), {
2019
- blank: true
2020
- });
2215
+ state.setValueAtPath(
2216
+ segments2,
2217
+ state.schema.getDefaultAtPath(segments2),
2218
+ withInstanceMeta({ blank: true })
2219
+ );
2021
2220
  }
2022
2221
  return true;
2023
2222
  }
2024
2223
  const segments = paths.canonicalizePath(pathOrValue).segments;
2025
2224
  if (isUnset(maybeValue)) {
2026
- return state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
2027
- blank: true
2028
- });
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
+ );
2029
2240
  }
2030
2241
  let resolvedValue;
2031
2242
  if (typeof maybeValue === "function") {
@@ -2033,9 +2244,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
2033
2244
  const prev = current === void 0 ? state.schema.getDefaultAtPath(segments) : current;
2034
2245
  resolvedValue = maybeValue(prev);
2035
2246
  if (isUnset(resolvedValue)) {
2036
- return state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
2037
- blank: true
2038
- });
2247
+ return state.setValueAtPath(
2248
+ segments,
2249
+ state.schema.getDefaultAtPath(segments),
2250
+ withInstanceMeta({ blank: true })
2251
+ );
2039
2252
  }
2040
2253
  } else {
2041
2254
  resolvedValue = maybeValue;
@@ -2045,28 +2258,52 @@ function buildFormApi(state, formInstanceId, options = {}) {
2045
2258
  segments,
2046
2259
  state.schema
2047
2260
  );
2048
- const ok = state.setValueAtPath(segments, walked.cleanedValues);
2261
+ const ok = state.setValueAtPath(segments, walked.cleanedValues, withInstanceMeta());
2049
2262
  if (!ok) return false;
2050
2263
  for (const pathKey of walked.paths) {
2051
2264
  const blankSegments = paths.segmentsForPathKey(pathKey);
2052
2265
  if (blankSegments === null) continue;
2053
- state.setValueAtPath(blankSegments, state.schema.getDefaultAtPath(blankSegments), {
2054
- blank: true
2055
- });
2266
+ state.setValueAtPath(
2267
+ blankSegments,
2268
+ state.schema.getDefaultAtPath(blankSegments),
2269
+ withInstanceMeta({ blank: true })
2270
+ );
2056
2271
  }
2057
2272
  return true;
2058
2273
  }
2059
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
+ }
2060
2289
  function setFieldErrors(errors) {
2061
- 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
+ }
2062
2295
  }
2063
2296
  function addFieldErrors(errors) {
2064
- state.addUserErrors(errors);
2297
+ state.addUserErrors(filterToOwnFormKey(errors, "addFieldErrors"));
2065
2298
  }
2066
2299
  function clearFieldErrors(path) {
2067
2300
  if (path === void 0) {
2301
+ const preserved = state.userErrors.get(paths.FORM_ERRORS_PATH_KEY);
2068
2302
  state.clearSchemaErrors();
2069
2303
  state.clearUserErrors();
2304
+ if (preserved !== void 0 && preserved.length > 0) {
2305
+ state.userErrors.set(paths.FORM_ERRORS_PATH_KEY, preserved);
2306
+ }
2070
2307
  return;
2071
2308
  }
2072
2309
  const segments = paths.canonicalizePath(path).segments;
@@ -2075,13 +2312,13 @@ function buildFormApi(state, formInstanceId, options = {}) {
2075
2312
  }
2076
2313
  function setFormErrors(errors) {
2077
2314
  if (errors.length === 0) {
2078
- state.userErrors.delete(paths.ROOT_PATH_KEY);
2315
+ state.userErrors.delete(paths.FORM_ERRORS_PATH_KEY);
2079
2316
  return;
2080
2317
  }
2081
2318
  state.userErrors.set(
2082
- paths.ROOT_PATH_KEY,
2319
+ paths.FORM_ERRORS_PATH_KEY,
2083
2320
  errors.map((e) => ({
2084
- path: [],
2321
+ path: [...paths.FORM_ERRORS_PATH],
2085
2322
  message: e.message,
2086
2323
  formKey: state.formKey,
2087
2324
  code: e.code ?? "atta:form-error"
@@ -2089,7 +2326,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2089
2326
  );
2090
2327
  }
2091
2328
  function clearFormErrors() {
2092
- state.userErrors.delete(paths.ROOT_PATH_KEY);
2329
+ state.userErrors.delete(paths.FORM_ERRORS_PATH_KEY);
2093
2330
  }
2094
2331
  const submitting = vue.computed(() => state.submitting.value);
2095
2332
  const submitCount = vue.computed(() => state.submitCount.value);
@@ -2099,15 +2336,36 @@ function buildFormApi(state, formInstanceId, options = {}) {
2099
2336
  () => state.firstValidationDone.value && state.schemaErrors.size === 0 && state.userErrors.size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
2100
2337
  );
2101
2338
  const history = options.history;
2102
- const undo = history?.undo ?? (() => false);
2103
- const redo = history?.redo ?? (() => false);
2104
- const canUndo = history?.canUndo ?? vue.computed(() => false);
2105
- const canRedo = history?.canRedo ?? vue.computed(() => false);
2106
- 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
+ );
2107
2350
  const metaErrors = vue.computed(
2108
2351
  () => aggregateErrorsAt(state, [])
2109
2352
  );
2110
- const getRootFieldStateAt = buildFieldStateAccessor(state);
2353
+ const getFormMetaBase = () => {
2354
+ const rootBase = buildContainerFieldStateBase(state, paths.ROOT_PATH);
2355
+ return {
2356
+ ...rootBase,
2357
+ submitting: state.submitting.value,
2358
+ submitCount: state.submitCount.value,
2359
+ submitError: state.submitError.value,
2360
+ instanceId: formInstanceId
2361
+ };
2362
+ };
2363
+ const fieldStateAccessorOptions = options.shouldShowErrors !== void 0 ? { shouldShowErrors: options.shouldShowErrors } : void 0;
2364
+ const getRootFieldStateAt = buildFieldStateAccessor(
2365
+ state,
2366
+ getFormMetaBase,
2367
+ fieldStateAccessorOptions
2368
+ );
2111
2369
  const rootFieldState = getRootFieldStateAt([]);
2112
2370
  const formMeta = vue.readonly(
2113
2371
  vue.reactive({
@@ -2143,6 +2401,13 @@ function buildFormApi(state, formInstanceId, options = {}) {
2143
2401
  // keep the explicit form-level computation for the gate.
2144
2402
  valid,
2145
2403
  errors: metaErrors,
2404
+ // `showErrors` / `firstError` flow through the same root
2405
+ // field-state computed as the rest of the FieldState surface,
2406
+ // so `form.meta.showErrors` matches `form.fields().showErrors`
2407
+ // exactly — the predicate runs once at the root and the result
2408
+ // is shared.
2409
+ showErrors: vue.computed(() => rootFieldState.value.showErrors),
2410
+ firstError: vue.computed(() => rootFieldState.value.firstError),
2146
2411
  path: vue.computed(() => rootFieldState.value.path),
2147
2412
  blank: vue.computed(() => rootFieldState.value.blank),
2148
2413
  label: vue.computed(() => rootFieldState.value.label),
@@ -2153,9 +2418,6 @@ function buildFormApi(state, formInstanceId, options = {}) {
2153
2418
  submitting,
2154
2419
  submitCount,
2155
2420
  submitError,
2156
- canUndo,
2157
- canRedo,
2158
- historySize,
2159
2421
  // Per-`useForm()`-call identity. Stable for one mount; new on
2160
2422
  // re-mount; orthogonal to `form.key` (which is the user-supplied
2161
2423
  // shared identifier). Useful for devtools panels disambiguating
@@ -2177,9 +2439,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
2177
2439
  for (const pathKey of walked.paths) {
2178
2440
  const segments = paths.segmentsForPathKey(pathKey);
2179
2441
  if (segments === null) continue;
2180
- state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
2181
- blank: true
2182
- });
2442
+ state.setValueAtPath(
2443
+ segments,
2444
+ state.schema.getDefaultAtPath(segments),
2445
+ withInstanceMeta({ blank: true })
2446
+ );
2183
2447
  state.originalBlankPaths.add(pathKey);
2184
2448
  }
2185
2449
  }
@@ -2196,7 +2460,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2196
2460
  };
2197
2461
  const persist = async (pathInput, options2) => {
2198
2462
  const segments = paths.canonicalizePath(pathInput).segments;
2199
- plugin.enforceSensitiveCheck(segments, options2?.acknowledgeSensitive === true);
2463
+ plugin.enforceSensitiveCheck(segments, options2?.acknowledgeSensitive === true, state.isSensitivePath);
2200
2464
  if (persistence === void 0) return;
2201
2465
  await persistence.writePathImmediately(segments);
2202
2466
  };
@@ -2209,6 +2473,10 @@ function buildFormApi(state, formInstanceId, options = {}) {
2209
2473
  const segments = paths.canonicalizePath(pathInput).segments;
2210
2474
  await persistence.clearPersistedDraft(segments);
2211
2475
  };
2476
+ function touch(pathInput) {
2477
+ const segments = pathInput === void 0 ? paths.ROOT_PATH : paths.canonicalizePath(pathInput).segments;
2478
+ state.touchAtPath(segments);
2479
+ }
2212
2480
  const focusFirstError = (options2) => {
2213
2481
  const target = state.getFirstErrorElement(formInstanceId);
2214
2482
  if (target === null) return false;
@@ -2226,7 +2494,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2226
2494
  return readonlySetSnapshot(state.blankPaths);
2227
2495
  });
2228
2496
  const valuesProxy = buildValuesProxy(state.form);
2229
- const fieldStateProxy = buildFieldStateProxy(state);
2497
+ const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase, fieldStateAccessorOptions);
2230
2498
  return {
2231
2499
  handleSubmit,
2232
2500
  // `values` is the callable readonly Proxy. Each `get` trap reads
@@ -2239,6 +2507,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2239
2507
  setValue: setValueImpl,
2240
2508
  validate,
2241
2509
  validateAsync,
2510
+ process,
2242
2511
  register,
2243
2512
  key: state.formKey,
2244
2513
  errors: errorsProxy,
@@ -2255,8 +2524,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
2255
2524
  clearPersistedDraft,
2256
2525
  focusFirstError,
2257
2526
  scrollToFirstError,
2258
- undo,
2259
- redo,
2527
+ touch,
2528
+ history: formHistory,
2260
2529
  append: fieldArrays.append,
2261
2530
  prepend: fieldArrays.prepend,
2262
2531
  insert: fieldArrays.insert,
@@ -2268,6 +2537,16 @@ function buildFormApi(state, formInstanceId, options = {}) {
2268
2537
  };
2269
2538
  }
2270
2539
 
2540
+ const defaultShouldShowErrors = (field, formMeta) => formMeta.submitCount > 0 || field.touched === true && field.dirty;
2541
+ const SHOW_ALWAYS = () => true;
2542
+ const SHOW_NEVER = () => false;
2543
+ function resolveShouldShowErrors(config) {
2544
+ if (config === void 0) return defaultShouldShowErrors;
2545
+ if (config === true) return SHOW_ALWAYS;
2546
+ if (config === false) return SHOW_NEVER;
2547
+ return config;
2548
+ }
2549
+
2271
2550
  function isHydratedFieldRecord(value) {
2272
2551
  if (typeof value !== "object" || value === null) return false;
2273
2552
  const r = value;
@@ -2291,6 +2570,42 @@ function warnMalformedHydration(formKey, kind, rawKey) {
2291
2570
  `[attaform] hydration: skipping malformed ${kind} entry at key '${rawKey}' on form '${formKey}'. This usually means the SSR bundle is on a different version than the client (rolling deploy / stale cache).`
2292
2571
  );
2293
2572
  }
2573
+ function applyDuStubs(schema, data, options = {}) {
2574
+ const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
2575
+ return walkDuStubs(schema, data, options.basePath ?? [], warned);
2576
+ }
2577
+ function walkDuStubs(schema, value, path, warned) {
2578
+ if (value === null || value === void 0 || typeof value !== "object") return value;
2579
+ if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || typeof value === "function") {
2580
+ return value;
2581
+ }
2582
+ if (Array.isArray(value)) {
2583
+ return value.map((item, i) => walkDuStubs(schema, item, [...path, i], warned));
2584
+ }
2585
+ const rec = value;
2586
+ const du = schema.getUnionDiscriminatorAtPath(path);
2587
+ if (du !== void 0) {
2588
+ const discValue = rec[du.discriminatorKey];
2589
+ if (discValue !== void 0 && !du.isVariantSelected(discValue)) {
2590
+ if (warned !== void 0 && plugin.__DEV__) {
2591
+ const dotted = path.map((s) => String(s)).join(".") || "(root)";
2592
+ const key = `${dotted}::${String(discValue)}`;
2593
+ if (!warned.has(key)) {
2594
+ warned.add(key);
2595
+ console.warn(
2596
+ `[attaform] defaultValues at '${dotted}' carries discriminator '${du.discriminatorKey}=${JSON.stringify(discValue)}' which isn't a known variant. Form mounts in a stub holding only the discriminator key. Validation will surface the mismatch.`
2597
+ );
2598
+ }
2599
+ }
2600
+ return { [du.discriminatorKey]: discValue };
2601
+ }
2602
+ }
2603
+ const out = {};
2604
+ for (const k of Object.keys(rec)) {
2605
+ out[k] = walkDuStubs(schema, rec[k], [...path, k], warned);
2606
+ }
2607
+ return out;
2608
+ }
2294
2609
  function isPathKeyUnder(existingKey, parentPath) {
2295
2610
  const parsed = paths.segmentsForPathKey(existingKey);
2296
2611
  if (parsed === null) return false;
@@ -2300,18 +2615,96 @@ function isPathKeyUnder(existingKey, parentPath) {
2300
2615
  }
2301
2616
  return true;
2302
2617
  }
2618
+ function stripSymbolsDeep(value) {
2619
+ if (value === null || typeof value !== "object") return value;
2620
+ if (Array.isArray(value)) {
2621
+ let mutated2 = false;
2622
+ const out2 = new Array(value.length);
2623
+ for (let i = 0; i < value.length; i++) {
2624
+ const cleaned = stripSymbolsDeep(value[i]);
2625
+ out2[i] = cleaned;
2626
+ if (cleaned !== value[i]) mutated2 = true;
2627
+ }
2628
+ return mutated2 ? out2 : value;
2629
+ }
2630
+ const proto = Object.getPrototypeOf(value);
2631
+ if (proto !== Object.prototype && proto !== null) return value;
2632
+ const symKeys = Object.getOwnPropertySymbols(value);
2633
+ const stringKeys = Object.keys(value);
2634
+ let mutated = symKeys.length > 0;
2635
+ const out = {};
2636
+ const src = value;
2637
+ for (const k of stringKeys) {
2638
+ const cleaned = stripSymbolsDeep(src[k]);
2639
+ out[k] = cleaned;
2640
+ if (cleaned !== src[k]) mutated = true;
2641
+ }
2642
+ return mutated ? out : value;
2643
+ }
2644
+ function cloneVariantSnapshot(value) {
2645
+ if (value === null || typeof value !== "object") return value;
2646
+ const raw = vue.toRaw(value);
2647
+ if (raw instanceof Date) return new Date(raw.getTime());
2648
+ if (raw instanceof Map) {
2649
+ const out2 = /* @__PURE__ */ new Map();
2650
+ for (const [k, v] of raw.entries()) out2.set(cloneVariantSnapshot(k), cloneVariantSnapshot(v));
2651
+ return out2;
2652
+ }
2653
+ if (raw instanceof Set) {
2654
+ const out2 = /* @__PURE__ */ new Set();
2655
+ for (const v of raw) out2.add(cloneVariantSnapshot(v));
2656
+ return out2;
2657
+ }
2658
+ if (raw instanceof RegExp) return new RegExp(raw.source, raw.flags);
2659
+ if (Array.isArray(raw)) {
2660
+ const out2 = new Array(raw.length);
2661
+ for (let i = 0; i < raw.length; i++) out2[i] = cloneVariantSnapshot(raw[i]);
2662
+ return out2;
2663
+ }
2664
+ const src = raw;
2665
+ const out = {};
2666
+ for (const k of Object.keys(src)) out[k] = cloneVariantSnapshot(src[k]);
2667
+ return out;
2668
+ }
2303
2669
  function createFormStore(options) {
2304
2670
  const { formKey, schema, defaultValues, strict = true, hydration } = options;
2305
2671
  const ssr = options.ssr === true;
2306
2672
  const rememberVariants = options.rememberVariants !== false;
2307
2673
  const fieldValidationMode = options.validateOn ?? "change";
2308
- const fieldValidationDebounceMs = options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS;
2674
+ const fieldValidationDebounceMs = normalizeNumericOption({
2675
+ value: options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS,
2676
+ source: "useForm.debounceMs",
2677
+ allowInfinity: false,
2678
+ min: 0,
2679
+ defaultValue: DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS
2680
+ });
2309
2681
  const fieldValidationState = /* @__PURE__ */ new Map();
2310
2682
  const formChangeListeners = /* @__PURE__ */ new Set();
2311
2683
  const submitSuccessListeners = /* @__PURE__ */ new Set();
2312
2684
  const resetListeners = /* @__PURE__ */ new Set();
2313
2685
  const persistOptIns = plugin.createPersistOptInRegistry();
2686
+ const noSyncPaths = /* @__PURE__ */ new Set();
2687
+ const noSyncPathCounts = /* @__PURE__ */ new Map();
2688
+ function incrementNoSyncOptOut(path) {
2689
+ const next = (noSyncPathCounts.get(path) ?? 0) + 1;
2690
+ noSyncPathCounts.set(path, next);
2691
+ if (next === 1) noSyncPaths.add(path);
2692
+ }
2693
+ function decrementNoSyncOptOut(path) {
2694
+ const current = noSyncPathCounts.get(path) ?? 0;
2695
+ if (current <= 1) {
2696
+ noSyncPathCounts.delete(path);
2697
+ noSyncPaths.delete(path);
2698
+ return;
2699
+ }
2700
+ noSyncPathCounts.set(path, current - 1);
2701
+ }
2314
2702
  const coerceIndex = resolveCoercionIndex(options.coerce);
2703
+ const resolvedShouldShowErrors = resolveShouldShowErrors(
2704
+ options.shouldShowErrors
2705
+ );
2706
+ const resolvedIsSensitivePath = options.isSensitivePath ?? plugin.isSensitivePath;
2707
+ const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ?? plugin.segmentMatchesSensitive;
2315
2708
  const cleanupHooks = [];
2316
2709
  const modules = /* @__PURE__ */ new Map();
2317
2710
  const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
@@ -2322,7 +2715,10 @@ function createFormStore(options) {
2322
2715
  });
2323
2716
  const schemaInitialData = schemaResponse.data;
2324
2717
  const initialData = hydration !== void 0 ? hydration.form : structuralSnapshot(schemaInitialData);
2325
- const form = vue.ref(initialData);
2718
+ const stubbedInitialData = applyDuStubs(schema, initialData, {
2719
+ warn: true
2720
+ });
2721
+ const form = vue.ref(stubbedInitialData);
2326
2722
  const fields = vue.reactive(/* @__PURE__ */ new Map());
2327
2723
  const elements = vue.reactive(/* @__PURE__ */ new Map());
2328
2724
  const elementToFormInstance = /* @__PURE__ */ new WeakMap();
@@ -2338,6 +2734,40 @@ function createFormStore(options) {
2338
2734
  originalBlankPaths.add(raw);
2339
2735
  }
2340
2736
  const variantMemory = /* @__PURE__ */ new Map();
2737
+ function clearVariantMemoryUnderPath(arrayPath) {
2738
+ for (const memKey of [...variantMemory.keys()]) {
2739
+ const segs = paths.segmentsForPathKey(memKey);
2740
+ if (segs === null) continue;
2741
+ if (isPathPrefix(arrayPath, segs)) variantMemory.delete(memKey);
2742
+ }
2743
+ }
2744
+ function clearVariantMemoryAtArrayIndices(arrayPath, indexFilter) {
2745
+ for (const memKey of [...variantMemory.keys()]) {
2746
+ const segs = paths.segmentsForPathKey(memKey);
2747
+ if (segs === null) continue;
2748
+ if (!isPathPrefix(arrayPath, segs)) continue;
2749
+ if (segs.length <= arrayPath.length) continue;
2750
+ const idxSeg = segs[arrayPath.length];
2751
+ if (typeof idxSeg !== "number") continue;
2752
+ if (indexFilter(idxSeg)) variantMemory.delete(memKey);
2753
+ }
2754
+ }
2755
+ function applyArrayOpToMemory(arrayPath, op) {
2756
+ switch (op.kind) {
2757
+ case "shift-from":
2758
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.index);
2759
+ return;
2760
+ case "shift-range":
2761
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.fromIndex && i <= op.toIndex);
2762
+ return;
2763
+ case "swap":
2764
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
2765
+ return;
2766
+ case "replace-at":
2767
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.index);
2768
+ return;
2769
+ }
2770
+ }
2341
2771
  const pathOrdinals = /* @__PURE__ */ new Map();
2342
2772
  let nextOrdinal = 0;
2343
2773
  function ensurePathOrdinal(key) {
@@ -2489,9 +2919,35 @@ function createFormStore(options) {
2489
2919
  }
2490
2920
  }
2491
2921
  function setValueAtPath(path, value, meta) {
2922
+ value = stripSymbolsDeep(value);
2923
+ value = schema.normalizeWriteValueAtPath(value, path);
2492
2924
  if (!isSlimPrimitiveValid(schema, form, path, value)) {
2493
2925
  return false;
2494
2926
  }
2927
+ if (path.length >= 2) {
2928
+ for (let i = 0; i < path.length - 1; i++) {
2929
+ const ancestorPath = path.slice(0, i + 1);
2930
+ const du = schema.getUnionDiscriminatorAtPath(ancestorPath);
2931
+ if (du === void 0) continue;
2932
+ const nextSeg = path[i + 1];
2933
+ if (nextSeg === du.discriminatorKey) continue;
2934
+ const ancestorValue = getAtPath(form.value, ancestorPath);
2935
+ if (!isPlainRecord(ancestorValue)) continue;
2936
+ const discValue = ancestorValue[du.discriminatorKey];
2937
+ if (discValue === void 0) {
2938
+ return false;
2939
+ }
2940
+ if (!du.isVariantSelected(discValue)) {
2941
+ return false;
2942
+ }
2943
+ const variantDefault = du.getVariantDefault(discValue);
2944
+ if (!isPlainRecord(variantDefault)) continue;
2945
+ if (typeof nextSeg !== "string") continue;
2946
+ if (!(nextSeg in variantDefault)) {
2947
+ return false;
2948
+ }
2949
+ }
2950
+ }
2495
2951
  if (meta?.skipDiscriminatorReshape !== true) {
2496
2952
  if (path.length > 0) {
2497
2953
  const last = path[path.length - 1];
@@ -2512,6 +2968,14 @@ function createFormStore(options) {
2512
2968
  meta
2513
2969
  );
2514
2970
  }
2971
+ return reshapeUnionVariant(
2972
+ parentPath,
2973
+ oldValue,
2974
+ value,
2975
+ { [last]: value },
2976
+ void 0,
2977
+ meta
2978
+ );
2515
2979
  }
2516
2980
  }
2517
2981
  }
@@ -2520,12 +2984,13 @@ function createFormStore(options) {
2520
2984
  const selfDU = schema.getUnionDiscriminatorAtPath(path);
2521
2985
  if (selfDU !== void 0) {
2522
2986
  const valueRecord = value;
2523
- const discValue = valueRecord[selfDU.discriminatorKey];
2987
+ const discKey = selfDU.discriminatorKey;
2988
+ const discValue = valueRecord[discKey];
2989
+ const currentUnionValue = getAtPath(form.value, path);
2990
+ const oldDiscValue = isPlainRecord(currentUnionValue) ? currentUnionValue[discKey] : void 0;
2524
2991
  if (discValue !== void 0) {
2525
2992
  const variantDefault = selfDU.getVariantDefault(discValue);
2526
2993
  if (variantDefault !== void 0 && isPlainRecord(variantDefault)) {
2527
- const currentUnionValue = getAtPath(form.value, path);
2528
- const oldDiscValue = isPlainRecord(currentUnionValue) ? currentUnionValue[selfDU.discriminatorKey] : void 0;
2529
2994
  return reshapeUnionVariant(
2530
2995
  path,
2531
2996
  oldDiscValue,
@@ -2535,7 +3000,16 @@ function createFormStore(options) {
2535
3000
  meta
2536
3001
  );
2537
3002
  }
3003
+ return reshapeUnionVariant(
3004
+ path,
3005
+ oldDiscValue,
3006
+ discValue,
3007
+ { [discKey]: discValue },
3008
+ void 0,
3009
+ meta
3010
+ );
2538
3011
  }
3012
+ return reshapeUnionVariant(path, oldDiscValue, void 0, {}, void 0, meta);
2539
3013
  }
2540
3014
  }
2541
3015
  }
@@ -2552,12 +3026,17 @@ function createFormStore(options) {
2552
3026
  }
2553
3027
  const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
2554
3028
  applyFormReplacement(nextForm, meta);
2555
- if (fieldValidationMode === "change") {
2556
- scheduleFieldValidation(
2557
- path,
2558
- false
2559
- /* debounced */
2560
- );
3029
+ if (meta?.arrayOp !== void 0) {
3030
+ applyArrayOpToMemory(path, meta.arrayOp);
3031
+ } else if (Array.isArray(value) && Array.isArray(currentValue)) {
3032
+ clearVariantMemoryUnderPath(path);
3033
+ }
3034
+ const effectiveModeAfterWrite = meta?.instance?.validateOn ?? fieldValidationMode;
3035
+ if (effectiveModeAfterWrite === "change") {
3036
+ scheduleFieldValidation(path, false, {
3037
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3038
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3039
+ });
2561
3040
  }
2562
3041
  return true;
2563
3042
  }
@@ -2566,9 +3045,10 @@ function createFormStore(options) {
2566
3045
  const parentKey = paths.canonicalizePath(parentPath).key;
2567
3046
  let baseline = variantDefault;
2568
3047
  let restoredBlanks;
2569
- if (rememberVariants && !sameDisc) {
3048
+ const effectiveRemember = meta?.instance?.rememberVariants ?? rememberVariants;
3049
+ if (effectiveRemember && !sameDisc) {
2570
3050
  if (oldDiscValue !== void 0) {
2571
- const currentValue2 = JSON.parse(JSON.stringify(getAtPath(form.value, parentPath)));
3051
+ const currentValue2 = cloneVariantSnapshot(getAtPath(form.value, parentPath));
2572
3052
  const outgoingBlanks = [];
2573
3053
  for (const k of blankPaths) {
2574
3054
  if (isPathKeyUnder(k, parentPath)) outgoingBlanks.push(k);
@@ -2590,7 +3070,10 @@ function createFormStore(options) {
2590
3070
  restoredBlanks = [...restored.blankPaths];
2591
3071
  }
2592
3072
  }
2593
- const finalValue = consumerOverrides !== void 0 ? { ...baseline, ...consumerOverrides } : baseline;
3073
+ const layered = consumerOverrides !== void 0 ? { ...baseline, ...consumerOverrides } : baseline;
3074
+ const finalValue = applyDuStubs(schema, layered, {
3075
+ basePath: parentPath
3076
+ });
2594
3077
  let newBlankPaths;
2595
3078
  if (restoredBlanks !== void 0) {
2596
3079
  newBlankPaths = restoredBlanks;
@@ -2609,9 +3092,10 @@ function createFormStore(options) {
2609
3092
  for (const k of newBlankPaths) blankPaths.add(k);
2610
3093
  return true;
2611
3094
  }
2612
- const nextForm = parentPath.length === 0 ? finalValue : setAtPath(form.value, parentPath, finalValue);
3095
+ const nextForm = parentPath.length === 0 ? finalValue : setAtPathWithSchemaFill(form.value, schema, parentPath, finalValue);
2613
3096
  let appliedSync = false;
2614
- if (fieldValidationMode === "change") {
3097
+ const reshapeMode = meta?.instance?.validateOn ?? fieldValidationMode;
3098
+ if (reshapeMode === "change") {
2615
3099
  const syncOrPromise = schema.validateAtPath(finalValue, parentPath, { sync: true });
2616
3100
  if (!(syncOrPromise instanceof Promise)) {
2617
3101
  const reStamped = syncOrPromise.success ? [] : syncOrPromise.errors.map((err) => ({
@@ -2631,17 +3115,18 @@ function createFormStore(options) {
2631
3115
  }
2632
3116
  applyFormReplacement(nextForm, meta);
2633
3117
  for (const k of newBlankPaths) blankPaths.add(k);
2634
- if (fieldValidationMode === "change" && !appliedSync) {
2635
- scheduleFieldValidation(
2636
- parentPath,
2637
- false
2638
- /* debounced */
2639
- );
3118
+ if (reshapeMode === "change" && !appliedSync) {
3119
+ scheduleFieldValidation(parentPath, false, {
3120
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3121
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3122
+ });
2640
3123
  }
2641
3124
  return true;
2642
3125
  }
2643
- function scheduleFieldValidation(path, immediate) {
2644
- if (fieldValidationMode === "submit") return;
3126
+ function scheduleFieldValidation(path, immediate, override) {
3127
+ const effectiveMode = override?.mode ?? fieldValidationMode;
3128
+ if (effectiveMode === "submit") return;
3129
+ const effectiveDebounce = override?.debounceMs ?? fieldValidationDebounceMs;
2645
3130
  const { key } = paths.canonicalizePath(path);
2646
3131
  const prev = fieldValidationState.get(key);
2647
3132
  if (prev !== void 0) {
@@ -2655,8 +3140,17 @@ function createFormStore(options) {
2655
3140
  fresh.timer = null;
2656
3141
  if (controller.signal.aborted) return;
2657
3142
  const data = getAtPath(form.value, path);
2658
- activeValidations.value += 1;
2659
- incFieldValidation(key);
3143
+ let activeIncremented = false;
3144
+ try {
3145
+ activeValidations.value += 1;
3146
+ activeIncremented = true;
3147
+ incFieldValidation(key);
3148
+ } catch (err) {
3149
+ if (activeIncremented) {
3150
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
3151
+ }
3152
+ throw err;
3153
+ }
2660
3154
  void Promise.resolve().then(() => schema.validateAtPath(data, path)).then((response) => {
2661
3155
  if (controller.signal.aborted) return;
2662
3156
  const reStamped = response.success ? [] : response.errors.map((err) => ({
@@ -2670,10 +3164,10 @@ function createFormStore(options) {
2670
3164
  decFieldValidation(key);
2671
3165
  });
2672
3166
  };
2673
- if (immediate || fieldValidationDebounceMs === 0) {
3167
+ if (immediate || effectiveDebounce === 0) {
2674
3168
  run();
2675
3169
  } else {
2676
- fresh.timer = setTimeout(run, fieldValidationDebounceMs);
3170
+ fresh.timer = setTimeout(run, effectiveDebounce);
2677
3171
  }
2678
3172
  }
2679
3173
  function cancelFieldValidation() {
@@ -2737,13 +3231,26 @@ function createFormStore(options) {
2737
3231
  submitSuccessListeners.clear();
2738
3232
  resetListeners.clear();
2739
3233
  persistOptIns.clear();
3234
+ noSyncPaths.clear();
3235
+ noSyncPathCounts.clear();
2740
3236
  }
2741
3237
  function getValueAtPath(path) {
2742
3238
  return getAtPath(form.value, path);
2743
3239
  }
3240
+ function rerouteFormLevelEntry(err) {
3241
+ if (err.path.length === 0) {
3242
+ return { ...err, path: [...paths.FORM_ERRORS_PATH] };
3243
+ }
3244
+ return err;
3245
+ }
3246
+ function pathKeyForEntry(err) {
3247
+ if (err.path.length === 0) return paths.FORM_ERRORS_PATH_KEY;
3248
+ return paths.canonicalizePath(err.path).key;
3249
+ }
2744
3250
  function appendErrorsTo(map, entries) {
2745
- for (const err of entries) {
2746
- const { key } = paths.canonicalizePath(err.path);
3251
+ for (const raw of entries) {
3252
+ const err = rerouteFormLevelEntry(raw);
3253
+ const key = pathKeyForEntry(err);
2747
3254
  const current = map.get(key);
2748
3255
  if (current === void 0) {
2749
3256
  map.set(key, [err]);
@@ -2773,16 +3280,18 @@ function createFormStore(options) {
2773
3280
  schemaErrors.set(key, [...entries]);
2774
3281
  }
2775
3282
  function applySchemaErrorsForSubtree(path, entries) {
2776
- const { key: parentKey } = paths.canonicalizePath(path);
3283
+ const parentKey = path.length === 0 ? paths.FORM_ERRORS_PATH_KEY : paths.canonicalizePath(path).key;
2777
3284
  const grouped = /* @__PURE__ */ new Map();
2778
- for (const err of entries) {
2779
- const { key } = paths.canonicalizePath(err.path);
3285
+ for (const raw of entries) {
3286
+ const err = rerouteFormLevelEntry(raw);
3287
+ const key = pathKeyForEntry(err);
2780
3288
  const list = grouped.get(key);
2781
3289
  if (list === void 0) grouped.set(key, [err]);
2782
3290
  else list.push(err);
2783
3291
  }
2784
3292
  if (!grouped.has(parentKey)) schemaErrors.delete(parentKey);
2785
3293
  for (const existingKey of [...schemaErrors.keys()]) {
3294
+ if (existingKey === parentKey) continue;
2786
3295
  if (isPathKeyUnder(existingKey, path) && !grouped.has(existingKey)) {
2787
3296
  schemaErrors.delete(existingKey);
2788
3297
  }
@@ -2858,7 +3367,7 @@ function createFormStore(options) {
2858
3367
  if (current?.connected === true) return;
2859
3368
  touchFieldRecord(key, path, { connected: true });
2860
3369
  }
2861
- function markFocused(path, focused) {
3370
+ function markFocused(path, focused, meta) {
2862
3371
  const { key } = paths.canonicalizePath(path);
2863
3372
  touchFieldRecord(key, path, {
2864
3373
  focused,
@@ -2867,24 +3376,45 @@ function createFormStore(options) {
2867
3376
  // a field is currently focused we keep whatever value it held.
2868
3377
  touched: focused ? fields.get(key)?.touched ?? null : true
2869
3378
  });
2870
- if (!focused && fieldValidationMode === "blur") {
2871
- scheduleFieldValidation(
2872
- path,
2873
- true
2874
- /* immediate */
2875
- );
3379
+ const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
3380
+ if (!focused && focusMode === "blur") {
3381
+ scheduleFieldValidation(path, true, {
3382
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3383
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3384
+ });
2876
3385
  }
2877
3386
  }
2878
3387
  function markTouched(path) {
2879
3388
  const { key } = paths.canonicalizePath(path);
2880
3389
  touchFieldRecord(key, path, { touched: true });
2881
3390
  }
3391
+ function touchAtPath(segments) {
3392
+ const formValue = form.value;
3393
+ let touchedAny = false;
3394
+ for (const [, entry] of originals) {
3395
+ if (!isPathPrefix(segments, entry.segments)) continue;
3396
+ if (!hasAtPath(formValue, entry.segments)) continue;
3397
+ touchedAny = true;
3398
+ const leafKey = paths.canonicalizePath(entry.segments).key;
3399
+ const current = fields.get(leafKey);
3400
+ if (current?.touched === true) continue;
3401
+ touchFieldRecord(leafKey, entry.segments, { touched: true });
3402
+ }
3403
+ if (!touchedAny && plugin.__DEV__) {
3404
+ console.warn(
3405
+ `[attaform] form.touch(): no fields resolved at path ${JSON.stringify(segments)}. Check the path matches an existing field or container.`
3406
+ );
3407
+ }
3408
+ }
2882
3409
  function reset(nextDefaultValues) {
2883
- const next = schema.getDefaultValues({
3410
+ const resetSource = nextDefaultValues ?? defaultValues;
3411
+ const completedResetConstraints = resetSource === void 0 ? void 0 : mergeStructural(schema, [], resetSource);
3412
+ const resetResponse = schema.getDefaultValues({
2884
3413
  useDefaultSchemaValues: true,
2885
- constraints: nextDefaultValues ?? defaultValues,
3414
+ constraints: completedResetConstraints,
2886
3415
  strict
2887
- }).data;
3416
+ });
3417
+ const next = resetResponse.data;
2888
3418
  applyFormReplacement(next);
2889
3419
  originals.clear();
2890
3420
  diffAndApply({}, next, [], (patch) => {
@@ -2903,6 +3433,24 @@ function createFormStore(options) {
2903
3433
  }
2904
3434
  schemaErrors.clear();
2905
3435
  userErrors.clear();
3436
+ if (strict && !resetResponse.success) {
3437
+ setAllSchemaErrors(resetResponse.errors);
3438
+ }
3439
+ if (strict) {
3440
+ const syncResult = schema.validateAtPath(form.value, void 0, { sync: true });
3441
+ if (!(syncResult instanceof Promise) && !syncResult.success) {
3442
+ applySchemaErrorsForSubtree([], syncResult.errors);
3443
+ }
3444
+ }
3445
+ firstValidationDone.value = !strict || schema.needsAsyncValidation?.() !== true;
3446
+ const needsAsync = !ssr && strict && schema.needsAsyncValidation?.() === true;
3447
+ if (needsAsync) {
3448
+ queueMicrotask(() => scheduleFieldValidation(
3449
+ [],
3450
+ true
3451
+ /* immediate */
3452
+ ));
3453
+ }
2906
3454
  const now = (/* @__PURE__ */ new Date()).toISOString();
2907
3455
  for (const [pathKey, record] of fields) {
2908
3456
  fields.set(pathKey, {
@@ -2946,30 +3494,28 @@ function createFormStore(options) {
2946
3494
  `[attaform] resetField: leaf write rejected for path '${targetKey}' \u2014 originals contain a value that doesn't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
2947
3495
  );
2948
3496
  }
2949
- schemaErrors.delete(targetKey);
2950
- userErrors.delete(targetKey);
2951
- clearFieldRecordFlags(targetKey);
2952
- return;
2953
- }
2954
- let subtree = void 0;
2955
- let anyMatch = false;
2956
- for (const [, entry] of originals) {
2957
- const leafSegments = entry.segments;
2958
- if (!isPathPrefix(targetSegments, leafSegments)) continue;
2959
- if (leafSegments.length === targetSegments.length) continue;
2960
- anyMatch = true;
2961
- const relative = leafSegments.slice(targetSegments.length);
2962
- if (subtree === void 0) {
2963
- subtree = typeof relative[0] === "number" ? [] : {};
2964
- }
2965
- subtree = setAtPath(subtree, relative, entry.value);
2966
- }
2967
- if (!anyMatch) return;
2968
- const wroteSubtree = setValueAtPath(targetSegments, subtree);
2969
- if (!wroteSubtree) {
2970
- console.error(
2971
- `[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.`
2972
- );
3497
+ } else {
3498
+ let subtree = void 0;
3499
+ let anyMatch = false;
3500
+ for (const [, entry] of originals) {
3501
+ const leafSegments = entry.segments;
3502
+ if (!isPathPrefix(targetSegments, leafSegments)) continue;
3503
+ if (leafSegments.length === targetSegments.length) continue;
3504
+ anyMatch = true;
3505
+ const relative = leafSegments.slice(targetSegments.length);
3506
+ if (subtree === void 0) {
3507
+ subtree = typeof relative[0] === "number" ? [] : {};
3508
+ }
3509
+ subtree = setAtPath(subtree, relative, entry.value);
3510
+ }
3511
+ if (anyMatch) {
3512
+ const wroteSubtree = setValueAtPath(targetSegments, subtree);
3513
+ if (!wroteSubtree) {
3514
+ console.error(
3515
+ `[attaform] resetField: subtree write rejected at path '${targetKey}' \u2014 originals contain values that don't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
3516
+ );
3517
+ }
3518
+ }
2973
3519
  }
2974
3520
  deleteErrorsUnderPrefix(schemaErrors, targetSegments);
2975
3521
  deleteErrorsUnderPrefix(userErrors, targetSegments);
@@ -3055,6 +3601,7 @@ function createFormStore(options) {
3055
3601
  originals,
3056
3602
  schema,
3057
3603
  ssr,
3604
+ shouldShowErrors: resolvedShouldShowErrors,
3058
3605
  submitting,
3059
3606
  activeSubmissions,
3060
3607
  submitCount,
@@ -3081,6 +3628,7 @@ function createFormStore(options) {
3081
3628
  deregisterElement,
3082
3629
  markFocused,
3083
3630
  markTouched,
3631
+ touchAtPath,
3084
3632
  markConnectedOptimistically,
3085
3633
  isPristineAtPath,
3086
3634
  getFieldRecord,
@@ -3097,6 +3645,11 @@ function createFormStore(options) {
3097
3645
  awaitPendingWrites,
3098
3646
  modules,
3099
3647
  persistOptIns,
3648
+ isSensitivePath: resolvedIsSensitivePath,
3649
+ segmentMatchesSensitive: resolvedSegmentMatchesSensitive,
3650
+ noSyncPaths,
3651
+ incrementNoSyncOptOut,
3652
+ decrementNoSyncOptOut,
3100
3653
  coerceIndex,
3101
3654
  blankPaths,
3102
3655
  originalBlankPaths,
@@ -3104,43 +3657,140 @@ function createFormStore(options) {
3104
3657
  };
3105
3658
  }
3106
3659
 
3107
- function getComputedSchema(formKey, schemaOrCallback) {
3108
- if (typeof schemaOrCallback === "function") return schemaOrCallback(formKey);
3660
+ function getComputedSchema(formKey, schemaOrCallback, options) {
3661
+ if (typeof schemaOrCallback === "function") return schemaOrCallback(formKey, options);
3109
3662
  return schemaOrCallback;
3110
3663
  }
3111
3664
 
3665
+ function captureErrorEntries(map) {
3666
+ const out = [];
3667
+ for (const [k, v] of map) out.push([k, [...v]]);
3668
+ return out;
3669
+ }
3670
+ function errorsEqual(a, b) {
3671
+ if (a.length !== b.length) return false;
3672
+ const bMap = /* @__PURE__ */ new Map();
3673
+ for (const [k, v] of b) bMap.set(k, v);
3674
+ for (const [k, v] of a) {
3675
+ const bv = bMap.get(k);
3676
+ if (bv === void 0) return false;
3677
+ if (v.length !== bv.length) return false;
3678
+ for (let i = 0; i < v.length; i++) {
3679
+ const av = v[i];
3680
+ const bvi = bv[i];
3681
+ if (av === bvi) continue;
3682
+ if (av.message !== bvi.message) return false;
3683
+ if (av.code !== bvi.code) return false;
3684
+ if (av.formKey !== bvi.formKey) return false;
3685
+ if (av.path !== bvi.path) {
3686
+ if (av.path.length !== bvi.path.length) return false;
3687
+ for (let j = 0; j < av.path.length; j++) {
3688
+ if (av.path[j] !== bvi.path[j]) return false;
3689
+ }
3690
+ }
3691
+ }
3692
+ }
3693
+ return true;
3694
+ }
3695
+ function diffBlankPaths$1(prev, curr) {
3696
+ const added = [];
3697
+ const removed = [];
3698
+ for (const k of curr) if (!prev.has(k)) added.push(k);
3699
+ for (const k of prev) if (!curr.has(k)) removed.push(k);
3700
+ return { added, removed };
3701
+ }
3702
+ function applyDeltaForward(snap, d) {
3703
+ const nextForm = applyPatchesForward(snap.form, d.formPatches);
3704
+ const nextBlank = new Set(snap.blankPaths);
3705
+ for (const k of d.blankPathsRemoved) nextBlank.delete(k);
3706
+ for (const k of d.blankPathsAdded) nextBlank.add(k);
3707
+ return {
3708
+ form: nextForm,
3709
+ blankPaths: [...nextBlank],
3710
+ schemaErrors: d.schemaErrors !== void 0 ? d.schemaErrors.after : snap.schemaErrors,
3711
+ userErrors: d.userErrors !== void 0 ? d.userErrors.after : snap.userErrors
3712
+ };
3713
+ }
3714
+ function applyDeltaInverse(snap, d) {
3715
+ const prevForm = applyPatchesInverse(snap.form, d.formPatches);
3716
+ const prevBlank = new Set(snap.blankPaths);
3717
+ for (const k of d.blankPathsAdded) prevBlank.delete(k);
3718
+ for (const k of d.blankPathsRemoved) prevBlank.add(k);
3719
+ return {
3720
+ form: prevForm,
3721
+ blankPaths: [...prevBlank],
3722
+ schemaErrors: d.schemaErrors !== void 0 ? d.schemaErrors.before : snap.schemaErrors,
3723
+ userErrors: d.userErrors !== void 0 ? d.userErrors.before : snap.userErrors
3724
+ };
3725
+ }
3112
3726
  function createHistoryModule(state, config) {
3113
- const max = typeof config === "object" ? config.max ?? DEFAULT_HISTORY_MAX_SNAPSHOTS : DEFAULT_HISTORY_MAX_SNAPSHOTS;
3114
- const undoStack = vue.shallowRef([]);
3115
- const redoStack = vue.shallowRef([]);
3116
- let suppressNext = false;
3727
+ const max = normalizeNumericOption({
3728
+ value: typeof config === "object" ? config.max ?? DEFAULT_HISTORY_MAX_SNAPSHOTS : DEFAULT_HISTORY_MAX_SNAPSHOTS,
3729
+ source: "useForm.history.max",
3730
+ allowInfinity: false,
3731
+ min: 0,
3732
+ defaultValue: DEFAULT_HISTORY_MAX_SNAPSHOTS
3733
+ });
3117
3734
  function captureSnapshot() {
3118
3735
  return {
3119
3736
  form: structuralSnapshot(state.form.value),
3120
3737
  blankPaths: [...state.blankPaths],
3121
- schemaErrors: [...state.schemaErrors.entries()].map(([k, v]) => [k, [...v]]),
3122
- userErrors: [...state.userErrors.entries()].map(([k, v]) => [k, [...v]])
3738
+ schemaErrors: captureErrorEntries(state.schemaErrors),
3739
+ userErrors: captureErrorEntries(state.userErrors)
3123
3740
  };
3124
3741
  }
3125
- function pushSnapshot(snap) {
3126
- const next = [...undoStack.value, snap];
3127
- undoStack.value = next.length > max ? next.slice(-max) : next;
3128
- redoStack.value = [];
3742
+ const initial = captureSnapshot();
3743
+ const base = vue.shallowRef(initial);
3744
+ const currentSnapshot = vue.shallowRef(initial);
3745
+ const undoDeltas = vue.shallowRef([]);
3746
+ const redoDeltas = vue.shallowRef([]);
3747
+ let suppressNext = false;
3748
+ function appendDelta(delta, newCurrent) {
3749
+ if (max === 0) {
3750
+ base.value = newCurrent;
3751
+ currentSnapshot.value = newCurrent;
3752
+ redoDeltas.value = [];
3753
+ return;
3754
+ }
3755
+ undoDeltas.value = [...undoDeltas.value, delta];
3756
+ redoDeltas.value = [];
3757
+ currentSnapshot.value = newCurrent;
3758
+ while (1 + undoDeltas.value.length > max && undoDeltas.value.length > 0) {
3759
+ const oldest = undoDeltas.value[0];
3760
+ base.value = applyDeltaForward(base.value, oldest);
3761
+ undoDeltas.value = undoDeltas.value.slice(1);
3762
+ }
3129
3763
  }
3130
- pushSnapshot(captureSnapshot());
3131
- const unsubscribeChange = state.onFormChange(() => {
3764
+ const unsubscribeChange = state.onFormChange((_next, meta) => {
3132
3765
  if (suppressNext) {
3133
3766
  suppressNext = false;
3134
3767
  return;
3135
3768
  }
3136
- pushSnapshot(captureSnapshot());
3137
- });
3138
- const unsubscribeReset = state.onReset(() => {
3139
- undoStack.value = [];
3140
- redoStack.value = [];
3141
- pushSnapshot(captureSnapshot());
3769
+ if (meta?.hydration === true) {
3770
+ clear();
3771
+ return;
3772
+ }
3773
+ if (meta?.crossTab === true) {
3774
+ currentSnapshot.value = captureSnapshot();
3775
+ return;
3776
+ }
3777
+ const newSnap = captureSnapshot();
3778
+ const prevSnap = currentSnapshot.value;
3779
+ const formPatches = [];
3780
+ diffAndApply(prevSnap.form, newSnap.form, [], (p) => formPatches.push(p));
3781
+ const prevBlankSet = new Set(prevSnap.blankPaths);
3782
+ const currBlankSet = new Set(newSnap.blankPaths);
3783
+ const blankDiff = diffBlankPaths$1(prevBlankSet, currBlankSet);
3784
+ const delta = {
3785
+ formPatches,
3786
+ blankPathsAdded: blankDiff.added,
3787
+ blankPathsRemoved: blankDiff.removed,
3788
+ ...errorsEqual(prevSnap.schemaErrors, newSnap.schemaErrors) ? {} : { schemaErrors: { before: prevSnap.schemaErrors, after: newSnap.schemaErrors } },
3789
+ ...errorsEqual(prevSnap.userErrors, newSnap.userErrors) ? {} : { userErrors: { before: prevSnap.userErrors, after: newSnap.userErrors } }
3790
+ };
3791
+ appendDelta(delta, newSnap);
3142
3792
  });
3143
- function restore(snap) {
3793
+ function restoreCurrent(snap) {
3144
3794
  suppressNext = true;
3145
3795
  state.blankPaths.clear();
3146
3796
  for (const key of snap.blankPaths) state.blankPaths.add(key);
@@ -3153,38 +3803,46 @@ function createHistoryModule(state, config) {
3153
3803
  state.setAllUserErrors(userFlat);
3154
3804
  }
3155
3805
  function undo() {
3156
- if (undoStack.value.length <= 1) return false;
3157
- const current = undoStack.value[undoStack.value.length - 1];
3158
- const prev = undoStack.value[undoStack.value.length - 2];
3159
- if (current === void 0 || prev === void 0) return false;
3160
- redoStack.value = [...redoStack.value, current];
3161
- undoStack.value = undoStack.value.slice(0, -1);
3162
- restore(prev);
3806
+ if (undoDeltas.value.length === 0) return false;
3807
+ const d = undoDeltas.value[undoDeltas.value.length - 1];
3808
+ const restored = applyDeltaInverse(currentSnapshot.value, d);
3809
+ redoDeltas.value = [...redoDeltas.value, d];
3810
+ undoDeltas.value = undoDeltas.value.slice(0, -1);
3811
+ currentSnapshot.value = restored;
3812
+ restoreCurrent(restored);
3163
3813
  return true;
3164
3814
  }
3165
3815
  function redo() {
3166
- if (redoStack.value.length === 0) return false;
3167
- const next = redoStack.value[redoStack.value.length - 1];
3168
- if (next === void 0) return false;
3169
- redoStack.value = redoStack.value.slice(0, -1);
3170
- undoStack.value = [...undoStack.value, next];
3171
- restore(next);
3816
+ if (redoDeltas.value.length === 0) return false;
3817
+ const d = redoDeltas.value[redoDeltas.value.length - 1];
3818
+ const next = applyDeltaForward(currentSnapshot.value, d);
3819
+ undoDeltas.value = [...undoDeltas.value, d];
3820
+ redoDeltas.value = redoDeltas.value.slice(0, -1);
3821
+ currentSnapshot.value = next;
3822
+ restoreCurrent(next);
3172
3823
  return true;
3173
3824
  }
3174
- const canUndo = vue.computed(() => undoStack.value.length > 1);
3175
- const canRedo = vue.computed(() => redoStack.value.length > 0);
3176
- const historySize = vue.computed(() => undoStack.value.length + redoStack.value.length);
3825
+ function clear() {
3826
+ const fresh = captureSnapshot();
3827
+ base.value = fresh;
3828
+ currentSnapshot.value = fresh;
3829
+ undoDeltas.value = [];
3830
+ redoDeltas.value = [];
3831
+ }
3832
+ const canUndo = vue.computed(() => undoDeltas.value.length > 0);
3833
+ const canRedo = vue.computed(() => redoDeltas.value.length > 0);
3834
+ const historySize = vue.computed(() => 1 + undoDeltas.value.length + redoDeltas.value.length);
3177
3835
  return {
3178
3836
  undo,
3179
3837
  redo,
3838
+ clear,
3180
3839
  canUndo,
3181
3840
  canRedo,
3182
3841
  historySize,
3183
3842
  dispose() {
3184
3843
  unsubscribeChange();
3185
- unsubscribeReset();
3186
- undoStack.value = [];
3187
- redoStack.value = [];
3844
+ undoDeltas.value = [];
3845
+ redoDeltas.value = [];
3188
3846
  }
3189
3847
  };
3190
3848
  }
@@ -3202,12 +3860,368 @@ function hashStableString(input, seed = 0) {
3202
3860
  return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
3203
3861
  }
3204
3862
 
3863
+ const PROTOCOL_VERSION = 1;
3864
+ const JOIN_COLLECTION_WINDOW_MS = 50;
3865
+ const SNAPSHOT_TIMEOUT_MS = 200;
3866
+ const MAX_LEADER_ATTEMPTS = 3;
3867
+ function isDangerousSegment(s) {
3868
+ return s === "__proto__" || s === "constructor" || s === "prototype";
3869
+ }
3870
+ function pathContainsDangerousSegment(path) {
3871
+ for (let i = 0; i < path.length; i++) {
3872
+ if (isDangerousSegment(path[i])) return true;
3873
+ }
3874
+ return false;
3875
+ }
3876
+ function diffBlankPaths(prev, curr) {
3877
+ const added = [];
3878
+ const removed = [];
3879
+ const prevSet = new Set(prev);
3880
+ for (const k of curr) if (!prevSet.has(k)) added.push(k);
3881
+ for (const k of prev) if (!curr.has(k)) removed.push(k);
3882
+ return { added, removed };
3883
+ }
3884
+ function snapshotForm(form) {
3885
+ return structuralSnapshot(form);
3886
+ }
3887
+ function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
3888
+ if (value === null || typeof value !== "object") return value;
3889
+ if (Array.isArray(value)) {
3890
+ return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
3891
+ }
3892
+ const proto = Object.getPrototypeOf(value);
3893
+ if (proto !== Object.prototype && proto !== null) return value;
3894
+ const out = {};
3895
+ const src = value;
3896
+ for (const key of Object.keys(src)) {
3897
+ const childPath = [...pathSoFar, key];
3898
+ if (isSensitivePath(childPath)) {
3899
+ out[key] = void 0;
3900
+ continue;
3901
+ }
3902
+ out[key] = stripSensitivePathsDeep(src[key], childPath, isSensitivePath);
3903
+ }
3904
+ return out;
3905
+ }
3906
+ function isValidSyncMessage(data) {
3907
+ if (data === null || typeof data !== "object") return false;
3908
+ const m = data;
3909
+ if (m["v"] !== PROTOCOL_VERSION) return false;
3910
+ if (typeof m["senderId"] !== "string") return false;
3911
+ if (typeof m["kind"] !== "string") return false;
3912
+ switch (m["kind"]) {
3913
+ case "hello":
3914
+ case "announce":
3915
+ return true;
3916
+ case "requestSnapshot":
3917
+ return typeof m["targetId"] === "string";
3918
+ case "snapshot":
3919
+ return Array.isArray(m["blankPaths"]) && "form" in m;
3920
+ case "patches":
3921
+ return Array.isArray(m["formPatches"]) && Array.isArray(m["blankPathsAdded"]) && Array.isArray(m["blankPathsRemoved"]);
3922
+ default:
3923
+ return false;
3924
+ }
3925
+ }
3926
+ function generateSenderId() {
3927
+ try {
3928
+ return globalThis.crypto.randomUUID();
3929
+ } catch {
3930
+ return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
3931
+ }
3932
+ }
3933
+ function createMultiTabSyncModule(state, channelName, options) {
3934
+ if (typeof BroadcastChannel === "undefined") {
3935
+ return {
3936
+ dispose: () => void 0,
3937
+ lifecycle: () => "established",
3938
+ senderId: "",
3939
+ channelName
3940
+ };
3941
+ }
3942
+ let channel;
3943
+ try {
3944
+ channel = new BroadcastChannel(channelName);
3945
+ } catch {
3946
+ return {
3947
+ dispose: () => void 0,
3948
+ lifecycle: () => "established",
3949
+ senderId: "",
3950
+ channelName
3951
+ };
3952
+ }
3953
+ const senderId = generateSenderId();
3954
+ let lifecycle = "joining";
3955
+ let disposed = false;
3956
+ const peerIds = /* @__PURE__ */ new Set();
3957
+ let joinCollectionTimer = null;
3958
+ let snapshotTimeoutTimer = null;
3959
+ let leaderAttempts = 0;
3960
+ let prior = {
3961
+ form: snapshotForm(state.form.value),
3962
+ blankPathsSnapshot: [...state.blankPaths]
3963
+ };
3964
+ function safePost(msg) {
3965
+ if (disposed) return;
3966
+ try {
3967
+ channel.postMessage(msg);
3968
+ } catch {
3969
+ }
3970
+ }
3971
+ function refreshPrior() {
3972
+ prior = {
3973
+ form: snapshotForm(state.form.value),
3974
+ blankPathsSnapshot: [...state.blankPaths]
3975
+ };
3976
+ }
3977
+ function isPathLocallySuppressed(path) {
3978
+ if (pathContainsDangerousSegment(path)) return true;
3979
+ if (options.isSensitivePath(path)) return true;
3980
+ const { key } = paths.canonicalizePath([...path]);
3981
+ if (options.noSyncPaths.has(key)) return true;
3982
+ return false;
3983
+ }
3984
+ function postPatches() {
3985
+ if (lifecycle !== "established") return;
3986
+ const next = snapshotForm(state.form.value);
3987
+ const rawPatches = [];
3988
+ diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
3989
+ const safePatches = [];
3990
+ for (const p of rawPatches) {
3991
+ if (isPathLocallySuppressed(p.path)) continue;
3992
+ safePatches.push(p);
3993
+ }
3994
+ const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
3995
+ if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
3996
+ prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
3997
+ return;
3998
+ }
3999
+ safePost({
4000
+ v: PROTOCOL_VERSION,
4001
+ kind: "patches",
4002
+ senderId,
4003
+ formPatches: safePatches,
4004
+ blankPathsAdded: added,
4005
+ blankPathsRemoved: removed
4006
+ });
4007
+ prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
4008
+ }
4009
+ const unsubscribeChange = state.onFormChange((_next, meta) => {
4010
+ if (disposed) return;
4011
+ if (lifecycle !== "established") return;
4012
+ if (meta?.crossTab === true) return;
4013
+ if (meta?.hydration === true) {
4014
+ refreshPrior();
4015
+ return;
4016
+ }
4017
+ postPatches();
4018
+ });
4019
+ function applyIncomingForm(form, blankPaths) {
4020
+ state.blankPaths.clear();
4021
+ for (const k of blankPaths) state.blankPaths.add(k);
4022
+ state.applyFormReplacement(form, { crossTab: true, persist: false });
4023
+ refreshPrior();
4024
+ }
4025
+ function handlePatches(msg) {
4026
+ if (lifecycle !== "established") return;
4027
+ const safePatches = [];
4028
+ for (const p of msg.formPatches) {
4029
+ if (!Array.isArray(p.path)) continue;
4030
+ if (isPathLocallySuppressed(p.path)) continue;
4031
+ safePatches.push(p);
4032
+ }
4033
+ const safeBlankAdded = [];
4034
+ for (const k of msg.blankPathsAdded) {
4035
+ const segs = paths.canonicalizePath(k).segments;
4036
+ if (isPathLocallySuppressed(segs)) continue;
4037
+ safeBlankAdded.push(k);
4038
+ }
4039
+ const safeBlankRemoved = [];
4040
+ for (const k of msg.blankPathsRemoved) {
4041
+ const segs = paths.canonicalizePath(k).segments;
4042
+ if (isPathLocallySuppressed(segs)) continue;
4043
+ safeBlankRemoved.push(k);
4044
+ }
4045
+ if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
4046
+ return;
4047
+ }
4048
+ const candidate = applyPatchesForward(state.form.value, safePatches);
4049
+ try {
4050
+ options.validateForm(state.form.value);
4051
+ try {
4052
+ options.validateForm(candidate);
4053
+ } catch {
4054
+ return;
4055
+ }
4056
+ } catch {
4057
+ }
4058
+ const nextBlankPaths = new Set(state.blankPaths);
4059
+ for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
4060
+ for (const k of safeBlankAdded) nextBlankPaths.add(k);
4061
+ applyIncomingForm(candidate, [...nextBlankPaths]);
4062
+ }
4063
+ function handleSnapshot(msg) {
4064
+ if (lifecycle !== "joining") return;
4065
+ try {
4066
+ options.validateForm(msg.form);
4067
+ } catch {
4068
+ return;
4069
+ }
4070
+ if (snapshotTimeoutTimer !== null) {
4071
+ clearTimeout(snapshotTimeoutTimer);
4072
+ snapshotTimeoutTimer = null;
4073
+ }
4074
+ if (joinCollectionTimer !== null) {
4075
+ clearTimeout(joinCollectionTimer);
4076
+ joinCollectionTimer = null;
4077
+ }
4078
+ applyIncomingForm(msg.form, msg.blankPaths);
4079
+ lifecycle = "established";
4080
+ peerIds.clear();
4081
+ }
4082
+ function respondToHello() {
4083
+ safePost({ v: PROTOCOL_VERSION, kind: "announce", senderId });
4084
+ }
4085
+ function respondToSnapshotRequest() {
4086
+ const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath);
4087
+ safePost({
4088
+ v: PROTOCOL_VERSION,
4089
+ kind: "snapshot",
4090
+ senderId,
4091
+ form: scrubbedForm,
4092
+ blankPaths: [...state.blankPaths]
4093
+ });
4094
+ }
4095
+ channel.onmessage = (event) => {
4096
+ if (disposed) return;
4097
+ const data = event.data;
4098
+ if (!isValidSyncMessage(data)) return;
4099
+ const msg = data;
4100
+ if (msg.senderId === senderId) return;
4101
+ switch (msg.kind) {
4102
+ case "hello":
4103
+ if (lifecycle !== "established") return;
4104
+ respondToHello();
4105
+ break;
4106
+ case "announce":
4107
+ if (lifecycle === "joining") peerIds.add(msg.senderId);
4108
+ break;
4109
+ case "requestSnapshot":
4110
+ if (lifecycle !== "established") return;
4111
+ if (msg.targetId !== senderId) return;
4112
+ respondToSnapshotRequest();
4113
+ break;
4114
+ case "snapshot":
4115
+ handleSnapshot(msg);
4116
+ break;
4117
+ case "patches":
4118
+ handlePatches(msg);
4119
+ break;
4120
+ }
4121
+ };
4122
+ function electLeaderAndRequest() {
4123
+ if (disposed) return;
4124
+ if (peerIds.size === 0) {
4125
+ lifecycle = "established";
4126
+ refreshPrior();
4127
+ return;
4128
+ }
4129
+ const sorted = [...peerIds].sort();
4130
+ const leaderId = sorted[0];
4131
+ peerIds.delete(leaderId);
4132
+ leaderAttempts++;
4133
+ safePost({
4134
+ v: PROTOCOL_VERSION,
4135
+ kind: "requestSnapshot",
4136
+ senderId,
4137
+ targetId: leaderId
4138
+ });
4139
+ snapshotTimeoutTimer = setTimeout(() => {
4140
+ snapshotTimeoutTimer = null;
4141
+ if (disposed) return;
4142
+ if (lifecycle === "established") return;
4143
+ if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
4144
+ lifecycle = "established";
4145
+ refreshPrior();
4146
+ return;
4147
+ }
4148
+ electLeaderAndRequest();
4149
+ }, SNAPSHOT_TIMEOUT_MS);
4150
+ }
4151
+ function joinFlow() {
4152
+ safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
4153
+ joinCollectionTimer = setTimeout(() => {
4154
+ joinCollectionTimer = null;
4155
+ if (disposed) return;
4156
+ if (lifecycle === "established") return;
4157
+ electLeaderAndRequest();
4158
+ }, JOIN_COLLECTION_WINDOW_MS);
4159
+ }
4160
+ joinFlow();
4161
+ return {
4162
+ dispose: () => {
4163
+ if (disposed) return;
4164
+ disposed = true;
4165
+ if (joinCollectionTimer !== null) {
4166
+ clearTimeout(joinCollectionTimer);
4167
+ joinCollectionTimer = null;
4168
+ }
4169
+ if (snapshotTimeoutTimer !== null) {
4170
+ clearTimeout(snapshotTimeoutTimer);
4171
+ snapshotTimeoutTimer = null;
4172
+ }
4173
+ unsubscribeChange();
4174
+ try {
4175
+ channel.close();
4176
+ } catch {
4177
+ }
4178
+ },
4179
+ lifecycle: () => lifecycle,
4180
+ senderId,
4181
+ channelName
4182
+ };
4183
+ }
4184
+ const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
4185
+
4186
+ const warned = /* @__PURE__ */ new Set();
4187
+ function warnOnceInsecureContext(feature) {
4188
+ if (!plugin.__DEV__) return;
4189
+ if (warned.has(feature)) return;
4190
+ warned.add(feature);
4191
+ const message = featureMessage(feature);
4192
+ console.warn(`[attaform] ${message}`);
4193
+ }
4194
+ function featureMessage(feature) {
4195
+ switch (feature) {
4196
+ case "multiTab":
4197
+ return "Multi-tab sync requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is interceptable by network observers, so the sync module is disabled. Serve over HTTPS in production (or develop on `localhost`) to enable cross-tab synchronisation. Use `multiTab: false` on `useForm` to silence this warning.";
4198
+ case "persist:local":
4199
+ return "Built-in `persist: 'local'` storage requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is MITM-interceptable, so the persistence layer is disabled. Serve over HTTPS to enable localStorage persistence, or pass a custom storage adapter to opt out of the secure-context gate.";
4200
+ case "persist:session":
4201
+ return "Built-in `persist: 'session'` storage requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is MITM-interceptable, so the persistence layer is disabled. Serve over HTTPS to enable sessionStorage persistence, or pass a custom storage adapter to opt out of the secure-context gate.";
4202
+ }
4203
+ }
4204
+ function isSecureContext() {
4205
+ return typeof window !== "undefined" && window.isSecureContext === true;
4206
+ }
4207
+
3205
4208
  function useAbstractForm(configuration) {
3206
4209
  if (configuration === void 0 || configuration === null || configuration.schema === void 0) {
3207
4210
  throw new plugin.InvalidUseFormConfigError();
3208
4211
  }
3209
4212
  const key = resolveFormKey(configuration.key);
3210
- const resolvedSchema = getComputedSchema(key, configuration.schema);
4213
+ const instance = vue.getCurrentInstance();
4214
+ if (instance !== null) plugin.ensureAttaformInstalled(instance.appContext.app);
4215
+ const registry = plugin.useRegistry();
4216
+ const merged = mergeWithDefaults(registry.defaults, configuration);
4217
+ const maxRecursionDepth = normalizeNumericOption({
4218
+ value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
4219
+ source: "useForm.maxRecursionDepth",
4220
+ allowInfinity: true,
4221
+ min: 0,
4222
+ defaultValue: DEFAULT_MAX_RECURSION_DEPTH
4223
+ });
4224
+ const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
3211
4225
  if (configuration.persist !== void 0 && configuration.key === void 0) {
3212
4226
  throw new plugin.AnonPersistError({
3213
4227
  cause: "no-key",
@@ -3215,13 +4229,10 @@ function useAbstractForm(configuration) {
3215
4229
  callSite: plugin.captureUserCallSite()
3216
4230
  });
3217
4231
  }
3218
- const instance = vue.getCurrentInstance();
3219
- if (instance !== null) plugin.ensureAttaformInstalled(instance.appContext.app);
3220
- const registry = plugin.useRegistry();
3221
- const merged = mergeWithDefaults(registry.defaults, configuration);
3222
4232
  const existing = registry.forms.get(key);
3223
4233
  if (plugin.__DEV__ && existing !== void 0) {
3224
4234
  warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
4235
+ warnOnPersistDivergence(key, existing, configuration.persist);
3225
4236
  }
3226
4237
  const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
3227
4238
  if (vue.getCurrentScope() !== void 0) {
@@ -3232,16 +4243,54 @@ function useAbstractForm(configuration) {
3232
4243
  if (existing === void 0 && !registry.ssr) {
3233
4244
  if (merged.persist !== void 0 && !persistDisabledByAnonRule) {
3234
4245
  const resolvedPersist = normalizePersistConfig(merged.persist);
3235
- const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
3236
- void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
3237
- const persistenceModule = wirePersistence(state, resolvedPersist);
3238
- state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
3239
- state.registerDrain(() => persistenceModule.awaitPendingWrites());
3240
- state.registerCleanup(() => persistenceModule.dispose());
4246
+ const storageKind = resolvedPersist.storage;
4247
+ const isBuiltinStorage = typeof storageKind === "string";
4248
+ const secureContextOk = !isBuiltinStorage || isSecureContext();
4249
+ if (!secureContextOk) {
4250
+ const feature = storageKind === "session" ? "persist:session" : "persist:local";
4251
+ warnOnceInsecureContext(feature);
4252
+ void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
4253
+ } else {
4254
+ const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
4255
+ void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
4256
+ const persistenceModule = wirePersistence(state, resolvedPersist);
4257
+ state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
4258
+ state.registerDrain(() => persistenceModule.awaitPendingWrites());
4259
+ state.registerCleanup(() => persistenceModule.dispose());
4260
+ }
3241
4261
  } else {
3242
4262
  void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
3243
4263
  }
3244
4264
  }
4265
+ if (existing === void 0 && merged.multiTab !== false && configuration.key !== void 0 && !registry.ssr) {
4266
+ const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
4267
+ const secureContext = isSecureContext();
4268
+ if (hasBroadcastChannel && secureContext) {
4269
+ let channelName;
4270
+ try {
4271
+ channelName = `attaform:sync:${state.formKey}:${hashStableString(state.schema.fingerprint())}`;
4272
+ } catch {
4273
+ channelName = null;
4274
+ }
4275
+ if (channelName !== null) {
4276
+ const syncModule = createMultiTabSyncModule(state, channelName, {
4277
+ isSensitivePath: state.isSensitivePath,
4278
+ noSyncPaths: state.noSyncPaths,
4279
+ validateForm: (form) => {
4280
+ const result = state.schema.validateAtPath(form, void 0, { sync: true });
4281
+ if (result instanceof Promise) return;
4282
+ if (!result.success) {
4283
+ throw new Error("attaform multi-tab sync: post-apply schema validation failed");
4284
+ }
4285
+ }
4286
+ });
4287
+ state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
4288
+ state.registerCleanup(() => syncModule.dispose());
4289
+ }
4290
+ } else if (hasBroadcastChannel && !secureContext) {
4291
+ warnOnceInsecureContext("multiTab");
4292
+ }
4293
+ }
3245
4294
  if (existing === void 0 && merged.history !== void 0) {
3246
4295
  const historyModule = createHistoryModule(state, merged.history);
3247
4296
  state.modules.set(HISTORY_MODULE_KEY, historyModule);
@@ -3263,6 +4312,22 @@ function useAbstractForm(configuration) {
3263
4312
  if (history !== void 0) {
3264
4313
  apiOptions.history = history;
3265
4314
  }
4315
+ if (merged.validateOn !== void 0) {
4316
+ apiOptions.validateOn = merged.validateOn;
4317
+ }
4318
+ const mergedDebounceMs = merged.debounceMs;
4319
+ if (mergedDebounceMs !== void 0) {
4320
+ apiOptions.debounceMs = mergedDebounceMs;
4321
+ }
4322
+ if (merged.shouldShowErrors !== void 0) {
4323
+ apiOptions.shouldShowErrors = resolveShouldShowErrors(merged.shouldShowErrors);
4324
+ }
4325
+ if (merged.coerce !== void 0) {
4326
+ apiOptions.coerce = merged.coerce;
4327
+ }
4328
+ if (merged.rememberVariants !== void 0) {
4329
+ apiOptions.rememberVariants = merged.rememberVariants;
4330
+ }
3266
4331
  return buildFormApi(state, formInstanceId, apiOptions);
3267
4332
  }
3268
4333
  function mergeWithDefaults(defaults, configuration) {
@@ -3273,6 +4338,10 @@ function mergeWithDefaults(defaults, configuration) {
3273
4338
  const coerce = configuration.coerce ?? defaults.coerce;
3274
4339
  const validateOn = configuration.validateOn ?? defaults.validateOn;
3275
4340
  const debounceMs = configuration.debounceMs ?? defaults.debounceMs;
4341
+ const shouldShowErrors = configuration.shouldShowErrors ?? defaults.shouldShowErrors;
4342
+ const maxRecursionDepth = configuration.maxRecursionDepth ?? defaults.maxRecursionDepth;
4343
+ const sensitiveNames = configuration.sensitiveNames ?? defaults.sensitiveNames;
4344
+ const multiTab = configuration.multiTab ?? defaults.multiTab;
3276
4345
  return {
3277
4346
  ...configuration,
3278
4347
  ...strict === void 0 ? {} : { strict },
@@ -3281,7 +4350,11 @@ function mergeWithDefaults(defaults, configuration) {
3281
4350
  ...rememberVariants === void 0 ? {} : { rememberVariants },
3282
4351
  ...coerce === void 0 ? {} : { coerce },
3283
4352
  ...validateOn === void 0 ? {} : { validateOn },
3284
- ...debounceMs === void 0 ? {} : { debounceMs }
4353
+ ...debounceMs === void 0 ? {} : { debounceMs },
4354
+ ...shouldShowErrors === void 0 ? {} : { shouldShowErrors },
4355
+ ...maxRecursionDepth === void 0 ? {} : { maxRecursionDepth },
4356
+ ...sensitiveNames === void 0 ? {} : { sensitiveNames },
4357
+ ...multiTab === void 0 ? {} : { multiTab }
3285
4358
  };
3286
4359
  }
3287
4360
  const HISTORY_MODULE_KEY = "history";
@@ -3293,6 +4366,9 @@ function buildFreshState(key, schema, configuration, registry) {
3293
4366
  schema
3294
4367
  );
3295
4368
  const initialBlankPaths = pending === void 0 ? walked.paths : void 0;
4369
+ const resolvedSensitiveNames = configuration.sensitiveNames;
4370
+ const resolvedIsSensitivePath = resolvedSensitiveNames === void 0 ? void 0 : plugin.createIsSensitivePath(resolvedSensitiveNames);
4371
+ const resolvedSegmentMatchesSensitive = resolvedSensitiveNames === void 0 ? void 0 : plugin.createSegmentMatchesSensitive(resolvedSensitiveNames);
3296
4372
  const createOptions = {
3297
4373
  formKey: key,
3298
4374
  schema,
@@ -3304,7 +4380,10 @@ function buildFreshState(key, schema, configuration, registry) {
3304
4380
  ssr: registry.ssr,
3305
4381
  ...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
3306
4382
  ...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
3307
- ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {}
4383
+ ...configuration.shouldShowErrors !== void 0 ? { shouldShowErrors: configuration.shouldShowErrors } : {},
4384
+ ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {},
4385
+ ...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {},
4386
+ ...resolvedSegmentMatchesSensitive !== void 0 ? { segmentMatchesSensitive: resolvedSegmentMatchesSensitive } : {}
3308
4387
  };
3309
4388
  const state = createFormStore(createOptions);
3310
4389
  registry.forms.set(
@@ -3363,11 +4442,47 @@ function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
3363
4442
  incoming: ${incomingFp}`
3364
4443
  );
3365
4444
  }
4445
+ function warnOnPersistDivergence(key, existing, incomingPersist) {
4446
+ if (incomingPersist === void 0) return;
4447
+ const wired = existing.modules.get(PERSISTENCE_MODULE_KEY);
4448
+ const incomingNormalized = normalizePersistConfig(incomingPersist);
4449
+ if (wired === void 0) {
4450
+ console.warn(
4451
+ `[attaform] useForm({ key: "${key}" }) passed a persist config but the first useForm({ key }) call didn't wire persistence; the new config is silently dropped. Pass persist on the first call, or remove persist here to make the inheritance explicit.`
4452
+ );
4453
+ return;
4454
+ }
4455
+ if (persistConfigsEquivalent(wired.wiredConfig, incomingNormalized)) return;
4456
+ console.warn(
4457
+ `[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
4458
+ wired: ${describePersist(wired.wiredConfig)}
4459
+ incoming: ${describePersist(incomingNormalized)}`
4460
+ );
4461
+ }
4462
+ function persistConfigsEquivalent(a, b) {
4463
+ if (a.storage !== b.storage) return false;
4464
+ if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
4465
+ if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
4466
+ return true;
4467
+ }
4468
+ function describePersist(config) {
4469
+ const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
4470
+ const parts = [`storage=${storage}`];
4471
+ if (config.key !== void 0) parts.push(`key=${config.key}`);
4472
+ if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
4473
+ return `{ ${parts.join(", ")} }`;
4474
+ }
3366
4475
  function wirePersistence(state, config) {
3367
4476
  const fingerprint = hashStableString(state.schema.fingerprint());
3368
4477
  const base = resolveStorageKeyBase(config, state.formKey);
3369
4478
  const key = `${base}:${fingerprint}`;
3370
- const debounceMs = config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS;
4479
+ const debounceMs = normalizeNumericOption({
4480
+ value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
4481
+ source: "useForm.persist.debounceMs",
4482
+ allowInfinity: false,
4483
+ min: 0,
4484
+ defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
4485
+ });
3371
4486
  const include = config.include ?? "form";
3372
4487
  const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
3373
4488
  const adapterPromise = getStorageAdapter(config.storage);
@@ -3402,6 +4517,7 @@ function wirePersistence(state, config) {
3402
4517
  }, debounceMs);
3403
4518
  const unsubscribeChange = state.onFormChange((_next, meta) => {
3404
4519
  if (disposed || inFlightFinalFlush !== null) return;
4520
+ if (meta?.crossTab === true) return;
3405
4521
  if (meta?.persist !== true) return;
3406
4522
  pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
3407
4523
  writer.schedule();
@@ -3435,7 +4551,7 @@ function wirePersistence(state, config) {
3435
4551
  payload.data.form,
3436
4552
  state.schema
3437
4553
  );
3438
- state.applyFormReplacement(merged);
4554
+ state.applyFormReplacement(merged, { hydration: true });
3439
4555
  const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
3440
4556
  for (const k of persistedLeafPaths) {
3441
4557
  state.blankPaths.delete(k);
@@ -3566,6 +4682,7 @@ function wirePersistence(state, config) {
3566
4682
  });
3567
4683
  }
3568
4684
  return {
4685
+ wiredConfig: config,
3569
4686
  writePathImmediately,
3570
4687
  clearPersistedDraft,
3571
4688
  awaitPendingWrites,
@@ -3640,7 +4757,11 @@ function injectForm(key) {
3640
4757
  }
3641
4758
  const ambientInstanceId = vue.getCurrentInstance() !== null ? vue.inject(plugin.kFormInstanceId, null) : null;
3642
4759
  const formInstanceId = ambientInstanceId ?? (vue.getCurrentInstance() !== null ? vue.useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`);
3643
- return buildFormApi(state, formInstanceId, apiOptions);
4760
+ return buildFormApi(
4761
+ state,
4762
+ formInstanceId,
4763
+ apiOptions
4764
+ );
3644
4765
  }
3645
4766
  function resolveState(key, registry) {
3646
4767
  if (key !== void 0) {
@@ -3686,14 +4807,16 @@ function warnIfAmbientProviderHadDuplicates() {
3686
4807
 
3687
4808
  exports.AttaformErrorCode = AttaformErrorCode;
3688
4809
  exports.defaultCoercionRules = defaultCoercionRules;
4810
+ exports.defaultShouldShowErrors = defaultShouldShowErrors;
3689
4811
  exports.defineCoercion = defineCoercion;
3690
4812
  exports.getAtPath = getAtPath;
3691
4813
  exports.humanize = humanize;
3692
4814
  exports.injectForm = injectForm;
3693
4815
  exports.isPlainRecord = isPlainRecord;
3694
4816
  exports.isUnset = isUnset;
4817
+ exports.normalizeNumericOption = normalizeNumericOption;
3695
4818
  exports.setAtPath = setAtPath;
3696
4819
  exports.slimKindOf = slimKindOf;
3697
4820
  exports.unset = unset;
3698
4821
  exports.useAbstractForm = useAbstractForm;
3699
- //# sourceMappingURL=attaform.C9Ph2SMx.cjs.map
4822
+ //# sourceMappingURL=attaform.BFumZXY2.cjs.map