attaform 0.19.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/chunks/devtools.cjs +1 -1
- package/dist/chunks/devtools.mjs +1 -1
- package/dist/chunks/indexeddb.cjs +1 -1
- package/dist/chunks/indexeddb.mjs +1 -1
- package/dist/chunks/local-storage.cjs +1 -1
- package/dist/chunks/local-storage.mjs +1 -1
- package/dist/chunks/session-storage.cjs +1 -1
- package/dist/chunks/session-storage.mjs +1 -1
- package/dist/index.cjs +3 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -40
- package/dist/index.d.mts +14 -40
- package/dist/index.d.ts +14 -40
- package/dist/index.mjs +5 -5
- package/dist/nuxt.d.cts +1 -1
- package/dist/nuxt.d.mts +1 -1
- package/dist/nuxt.d.ts +1 -1
- package/dist/runtime/components/AttaformDevtoolsPanel.vue +2 -2
- package/dist/runtime/components/DevtoolsValueTree.d.vue.ts +1 -3
- package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +1 -3
- package/dist/runtime/plugins/attaform.cjs +2 -2
- package/dist/runtime/plugins/attaform.mjs +2 -2
- package/dist/shared/attaform.BCBxTyMC.cjs +1882 -0
- package/dist/shared/attaform.BCBxTyMC.cjs.map +1 -0
- package/dist/shared/{attaform.CrpjyXdO.mjs → attaform.BKozEdTr.mjs} +275 -266
- package/dist/shared/attaform.BKozEdTr.mjs.map +1 -0
- package/dist/shared/{attaform.Bubm_slq.cjs → attaform.BM6YD9kZ.cjs} +212 -269
- package/dist/shared/attaform.BM6YD9kZ.cjs.map +1 -0
- package/dist/shared/{attaform.CoxJ8Qm8.cjs → attaform.BPxsYtTe.cjs} +2 -26
- package/dist/shared/attaform.BPxsYtTe.cjs.map +1 -0
- package/dist/shared/{attaform.BqEfHpVB.cjs → attaform.BPy-4qRx.cjs} +275 -268
- package/dist/shared/attaform.BPy-4qRx.cjs.map +1 -0
- package/dist/shared/{attaform.BTpuvGec.d.ts → attaform.Bh3ACtts.d.ts} +109 -101
- package/dist/shared/{attaform.BTi-PsHr.mjs → attaform.BqZuwLTK.mjs} +1868 -1477
- package/dist/shared/attaform.BqZuwLTK.mjs.map +1 -0
- package/dist/shared/{attaform.JBx8cfMA.cjs → attaform.BrrXNmfK.cjs} +263 -799
- package/dist/shared/attaform.BrrXNmfK.cjs.map +1 -0
- package/dist/shared/{attaform.CXpzmj38.mjs → attaform.BupwXkj_.mjs} +213 -270
- package/dist/shared/attaform.BupwXkj_.mjs.map +1 -0
- package/dist/shared/{attaform.ePUcKxId.d.cts → attaform.D5-1XGQU.d.cts} +109 -101
- package/dist/shared/attaform.D6CwqkPx.mjs +1876 -0
- package/dist/shared/attaform.D6CwqkPx.mjs.map +1 -0
- package/dist/shared/attaform.DHRWn-cu.cjs +785 -0
- package/dist/shared/attaform.DHRWn-cu.cjs.map +1 -0
- package/dist/shared/{attaform.C1msmO2v.cjs → attaform.DLnE5bZa.cjs} +1798 -1405
- package/dist/shared/attaform.DLnE5bZa.cjs.map +1 -0
- package/dist/shared/{attaform.D4I63aBV.d.ts → attaform.DSD85fHb.d.cts} +1 -19
- package/dist/shared/{attaform.CBjmobqk.d.cts → attaform.DSD85fHb.d.mts} +1 -19
- package/dist/shared/{attaform.DXYHL99q.d.mts → attaform.DSD85fHb.d.ts} +1 -19
- package/dist/shared/{attaform.B7rzpK1U.d.cts → attaform.DkA5J8NW.d.cts} +1 -17
- package/dist/shared/{attaform.B7rzpK1U.d.mts → attaform.DkA5J8NW.d.mts} +1 -17
- package/dist/shared/{attaform.B7rzpK1U.d.ts → attaform.DkA5J8NW.d.ts} +1 -17
- package/dist/shared/{attaform.CJ-e9gYI.d.ts → attaform.Dl5kDY-A.d.ts} +1 -1
- package/dist/shared/{attaform.CRNA0vrd.d.mts → attaform.DoKXru-a.d.mts} +1 -1
- package/dist/shared/{attaform.BtBmfLQN.d.mts → attaform.EMzJcQci.d.mts} +109 -101
- package/dist/shared/attaform.EZG6fOFb.mjs +35 -0
- package/dist/shared/attaform.EZG6fOFb.mjs.map +1 -0
- package/dist/shared/{attaform.QvygsFGh.d.cts → attaform.GbDo_lJi.d.cts} +1 -1
- package/dist/shared/{attaform.C0uGZQ4M.d.ts → attaform.SfhU0OEY.d.cts} +134 -30
- package/dist/shared/{attaform.C0uGZQ4M.d.cts → attaform.SfhU0OEY.d.mts} +134 -30
- package/dist/shared/{attaform.C0uGZQ4M.d.mts → attaform.SfhU0OEY.d.ts} +134 -30
- package/dist/shared/{attaform.a3uBo-gw.mjs → attaform.iWo9soNX.mjs} +257 -793
- package/dist/shared/attaform.iWo9soNX.mjs.map +1 -0
- package/dist/shared/attaform.tVkmQh5w.mjs +774 -0
- package/dist/shared/attaform.tVkmQh5w.mjs.map +1 -0
- package/dist/transforms.cjs +2 -2
- package/dist/transforms.d.cts +22 -13
- package/dist/transforms.d.mts +22 -13
- package/dist/transforms.d.ts +22 -13
- package/dist/transforms.mjs +1 -1
- package/dist/vite.cjs +8 -7
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.mjs +8 -7
- package/dist/vite.mjs.map +1 -1
- package/dist/zod-v3.cjs +3 -3
- package/dist/zod-v3.d.cts +32 -6
- package/dist/zod-v3.d.mts +32 -6
- package/dist/zod-v3.d.ts +32 -6
- package/dist/zod-v3.mjs +3 -3
- package/dist/zod-v4.cjs +3 -3
- package/dist/zod-v4.d.cts +12 -8
- package/dist/zod-v4.d.mts +12 -8
- package/dist/zod-v4.d.ts +12 -8
- package/dist/zod-v4.mjs +3 -3
- package/dist/zod.cjs +8 -8
- package/dist/zod.cjs.map +1 -1
- package/dist/zod.d.cts +6 -6
- package/dist/zod.d.mts +6 -6
- package/dist/zod.d.ts +6 -6
- package/dist/zod.mjs +6 -6
- package/package.json +2 -1
- package/dist/shared/attaform.BTi-PsHr.mjs.map +0 -1
- package/dist/shared/attaform.BqEfHpVB.cjs.map +0 -1
- package/dist/shared/attaform.Bubm_slq.cjs.map +0 -1
- package/dist/shared/attaform.C1msmO2v.cjs.map +0 -1
- package/dist/shared/attaform.C8CyvYa_.cjs +0 -36
- package/dist/shared/attaform.C8CyvYa_.cjs.map +0 -1
- package/dist/shared/attaform.CXpzmj38.mjs.map +0 -1
- package/dist/shared/attaform.Cghpuav8.mjs +0 -57
- package/dist/shared/attaform.Cghpuav8.mjs.map +0 -1
- package/dist/shared/attaform.CiMqJHDm.mjs +0 -1594
- package/dist/shared/attaform.CiMqJHDm.mjs.map +0 -1
- package/dist/shared/attaform.CoxJ8Qm8.cjs.map +0 -1
- package/dist/shared/attaform.CrpjyXdO.mjs.map +0 -1
- package/dist/shared/attaform.D13GMFgK.mjs +0 -32
- package/dist/shared/attaform.D13GMFgK.mjs.map +0 -1
- package/dist/shared/attaform.JBx8cfMA.cjs.map +0 -1
- package/dist/shared/attaform.OznWyOPy.cjs +0 -1600
- package/dist/shared/attaform.OznWyOPy.cjs.map +0 -1
- package/dist/shared/attaform.a3uBo-gw.mjs.map +0 -1
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const vue = require('vue');
|
|
4
|
-
const paths = require('./attaform.
|
|
4
|
+
const paths = require('./attaform.BPy-4qRx.cjs');
|
|
5
|
+
|
|
6
|
+
function safeAssign(target, key, value) {
|
|
7
|
+
if (key === "__proto__") {
|
|
8
|
+
Object.defineProperty(target, key, {
|
|
9
|
+
value,
|
|
10
|
+
writable: true,
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true
|
|
13
|
+
});
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
target[key] = value;
|
|
17
|
+
}
|
|
18
|
+
function safeOwnRead(target, key) {
|
|
19
|
+
if (key === "__proto__") {
|
|
20
|
+
const desc = Object.getOwnPropertyDescriptor(target, "__proto__");
|
|
21
|
+
return desc?.value;
|
|
22
|
+
}
|
|
23
|
+
return target[key];
|
|
24
|
+
}
|
|
25
|
+
function safeOwnHas(target, key) {
|
|
26
|
+
return Object.prototype.hasOwnProperty.call(target, key);
|
|
27
|
+
}
|
|
5
28
|
|
|
6
29
|
const NOT_FOUND = Symbol("NOT_FOUND");
|
|
7
30
|
function descendStep(value, segment) {
|
|
@@ -65,7 +88,7 @@ function setAtPathOffset(root, path, value, offset) {
|
|
|
65
88
|
return arr;
|
|
66
89
|
}
|
|
67
90
|
const rec = isPlainRecord(root) ? { ...root } : {};
|
|
68
|
-
rec
|
|
91
|
+
safeAssign(rec, head, setAtPathOffset(safeOwnRead(rec, head), path, value, nextOffset));
|
|
69
92
|
return rec;
|
|
70
93
|
}
|
|
71
94
|
function deleteAtPath(root, path) {
|
|
@@ -94,9 +117,9 @@ function deleteAtPathOffset(root, path, offset) {
|
|
|
94
117
|
delete rec2[head];
|
|
95
118
|
return rec2;
|
|
96
119
|
}
|
|
97
|
-
if (!(head
|
|
120
|
+
if (!safeOwnHas(root, head)) return root;
|
|
98
121
|
const rec = { ...root };
|
|
99
|
-
rec
|
|
122
|
+
safeAssign(rec, head, deleteAtPathOffset(safeOwnRead(rec, head), path, nextOffset));
|
|
100
123
|
return rec;
|
|
101
124
|
}
|
|
102
125
|
function resolveArrayShape(schema, scratch) {
|
|
@@ -167,25 +190,25 @@ function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
|
|
|
167
190
|
let mutated = false;
|
|
168
191
|
const out = { ...consumer };
|
|
169
192
|
for (const key of Object.keys(defaultValue)) {
|
|
170
|
-
if (!(key
|
|
171
|
-
const defAtKey = defaultValue
|
|
193
|
+
if (!safeOwnHas(consumer, key)) {
|
|
194
|
+
const defAtKey = safeOwnRead(defaultValue, key);
|
|
172
195
|
scratch.push(key);
|
|
173
196
|
const filled = mergeStructuralImpl(schema, scratch, void 0, defAtKey);
|
|
174
197
|
scratch.pop();
|
|
175
198
|
if (filled !== void 0) {
|
|
176
|
-
out
|
|
199
|
+
safeAssign(out, key, filled);
|
|
177
200
|
mutated = true;
|
|
178
201
|
}
|
|
179
202
|
}
|
|
180
203
|
}
|
|
181
204
|
for (const key of Object.keys(consumer)) {
|
|
182
|
-
const cVal = consumer
|
|
205
|
+
const cVal = safeOwnRead(consumer, key);
|
|
183
206
|
if (cVal === void 0) continue;
|
|
184
207
|
scratch.push(key);
|
|
185
|
-
const merged = mergeStructuralImpl(schema, scratch, cVal, defaultValue
|
|
208
|
+
const merged = mergeStructuralImpl(schema, scratch, cVal, safeOwnRead(defaultValue, key));
|
|
186
209
|
scratch.pop();
|
|
187
210
|
if (merged !== cVal) {
|
|
188
|
-
out
|
|
211
|
+
safeAssign(out, key, merged);
|
|
189
212
|
mutated = true;
|
|
190
213
|
}
|
|
191
214
|
}
|
|
@@ -379,7 +402,8 @@ function applyChangedKeys(target, source) {
|
|
|
379
402
|
}
|
|
380
403
|
for (const k of changedFirstSegments) {
|
|
381
404
|
if (typeof k === "symbol") continue;
|
|
382
|
-
|
|
405
|
+
const key = String(k);
|
|
406
|
+
safeAssign(t, key, safeOwnRead(s, key));
|
|
383
407
|
}
|
|
384
408
|
}
|
|
385
409
|
return true;
|
|
@@ -431,7 +455,7 @@ function structuralSnapshot(value) {
|
|
|
431
455
|
const src = value;
|
|
432
456
|
const out = {};
|
|
433
457
|
for (const k of Object.keys(src)) {
|
|
434
|
-
out
|
|
458
|
+
safeAssign(out, k, structuralSnapshot(safeOwnRead(src, k)));
|
|
435
459
|
}
|
|
436
460
|
return out;
|
|
437
461
|
}
|
|
@@ -490,7 +514,7 @@ function hashStableString(input, seed = 0) {
|
|
|
490
514
|
const ANON_STEM = "atta";
|
|
491
515
|
function readableFormKeyStem(formKey) {
|
|
492
516
|
if (formKey === "" || formKey.startsWith(ANONYMOUS_FORM_KEY_PREFIX)) return ANON_STEM;
|
|
493
|
-
const sanitized = formKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(
|
|
517
|
+
const sanitized = formKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|(?<!-)-+$/g, "");
|
|
494
518
|
return sanitized === "" ? ANON_STEM : sanitized;
|
|
495
519
|
}
|
|
496
520
|
function fieldIdToken(formInstanceId, pathKey) {
|
|
@@ -524,6 +548,7 @@ function humanize(segment) {
|
|
|
524
548
|
}).join(" ");
|
|
525
549
|
}
|
|
526
550
|
|
|
551
|
+
const warnedDisplayStatePredicates = /* @__PURE__ */ new WeakSet();
|
|
527
552
|
function isUnderStubAncestor(state, segments) {
|
|
528
553
|
for (let i = 0; i < segments.length; i++) {
|
|
529
554
|
const ancestorPath = segments.slice(0, i);
|
|
@@ -607,7 +632,7 @@ function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBa
|
|
|
607
632
|
function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
|
|
608
633
|
const formValue = state.form.value;
|
|
609
634
|
const value = state.getValueAtPath(segments);
|
|
610
|
-
const original = state.originals.get(
|
|
635
|
+
const original = state.originals.get(key)?.value;
|
|
611
636
|
let pristine = true;
|
|
612
637
|
let blank = true;
|
|
613
638
|
let dirty = false;
|
|
@@ -620,13 +645,12 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
|
|
|
620
645
|
let validating = false;
|
|
621
646
|
let updatedAt = null;
|
|
622
647
|
let asyncPending = false;
|
|
623
|
-
for (const [, entry] of state.originals) {
|
|
648
|
+
for (const [leafKey, entry] of state.originals) {
|
|
624
649
|
if (!paths.isPathPrefix(segments, entry.segments)) continue;
|
|
625
650
|
if (segments.length === entry.segments.length) continue;
|
|
626
651
|
if (!hasAtPath(formValue, entry.segments)) continue;
|
|
627
|
-
const leafKey = paths.canonicalizePath(entry.segments).key;
|
|
628
652
|
const leafRecord = state.fields.get(leafKey);
|
|
629
|
-
if (!state.
|
|
653
|
+
if (!state.isPristineAtPathByKey(leafKey, entry.segments)) {
|
|
630
654
|
pristine = false;
|
|
631
655
|
dirty = true;
|
|
632
656
|
}
|
|
@@ -638,7 +662,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
|
|
|
638
662
|
if (leafRecord?.blurredAfterInteraction === true) blurredAfterInteraction = true;
|
|
639
663
|
if (leafRecord?.connected === true) connected = true;
|
|
640
664
|
if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) validating = true;
|
|
641
|
-
if (state.
|
|
665
|
+
if (state.pathHasAsyncValidationByKey(leafKey, entry.segments)) asyncPending = true;
|
|
642
666
|
const ts = leafRecord?.updatedAt;
|
|
643
667
|
if (ts !== void 0 && ts !== null) {
|
|
644
668
|
if (updatedAt === null || ts > updatedAt) updatedAt = ts;
|
|
@@ -693,7 +717,14 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState)
|
|
|
693
717
|
let displayState;
|
|
694
718
|
try {
|
|
695
719
|
displayState = predicate(base, formMeta);
|
|
696
|
-
} catch {
|
|
720
|
+
} catch (err) {
|
|
721
|
+
if (paths.__DEV__ && !warnedDisplayStatePredicates.has(predicate)) {
|
|
722
|
+
warnedDisplayStatePredicates.add(predicate);
|
|
723
|
+
console.warn(
|
|
724
|
+
"[attaform] custom getDisplayState threw \u2014 falling back to defaultDisplayState. Subsequent throws from the same predicate will not warn again.",
|
|
725
|
+
err
|
|
726
|
+
);
|
|
727
|
+
}
|
|
697
728
|
displayState = defaultDisplayState(base, formMeta);
|
|
698
729
|
}
|
|
699
730
|
return {
|
|
@@ -730,6 +761,41 @@ function aggregateErrorsAt(state, prefix) {
|
|
|
730
761
|
}
|
|
731
762
|
const EMPTY_ELEMENTS = Object.freeze([]);
|
|
732
763
|
|
|
764
|
+
function liveKeysAtPath(state, segments) {
|
|
765
|
+
const value = getAtPath(state.form.value, segments);
|
|
766
|
+
if (value === null || value === void 0) return [];
|
|
767
|
+
if (Array.isArray(value)) {
|
|
768
|
+
const keys = new Array(value.length);
|
|
769
|
+
for (let i = 0; i < value.length; i += 1) keys[i] = String(i);
|
|
770
|
+
return keys;
|
|
771
|
+
}
|
|
772
|
+
if (typeof value === "object") return Object.keys(value);
|
|
773
|
+
return [];
|
|
774
|
+
}
|
|
775
|
+
function isArrayPath(state, segments) {
|
|
776
|
+
if (segments.length === 0) return false;
|
|
777
|
+
return Array.isArray(getAtPath(state.form.value, segments));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function warnReadOnly(surface, action, key) {
|
|
781
|
+
if (!paths.__DEV__) return;
|
|
782
|
+
const phrase = action === "write" ? `write to "${String(key)}"` : `${action} of "${String(key)}"`;
|
|
783
|
+
console.warn(
|
|
784
|
+
`[attaform] ${surface} is read-only \u2014 ${phrase} was ignored. Mutate the form via setValue / the directive / field-array helpers instead.`
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
function makeReadonlyCoercion(snapshot) {
|
|
788
|
+
const toString = () => JSON.stringify(snapshot());
|
|
789
|
+
return {
|
|
790
|
+
toString,
|
|
791
|
+
valueOf() {
|
|
792
|
+
return this;
|
|
793
|
+
},
|
|
794
|
+
toJSON: snapshot,
|
|
795
|
+
toPrimitive: (hint) => hint === "number" ? NaN : toString()
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
733
799
|
const INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
|
|
734
800
|
function keyToSegment(key) {
|
|
735
801
|
return INTEGER_SEGMENT.test(key) ? Number(key) : key;
|
|
@@ -761,12 +827,12 @@ function buildSurfaceProxy(opts) {
|
|
|
761
827
|
const existing = containerCache.get(cacheKey);
|
|
762
828
|
if (existing !== void 0) return existing;
|
|
763
829
|
const snapshotContainer = () => opts.materializeContainer === void 0 ? {} : opts.materializeContainer(segments);
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
830
|
+
const {
|
|
831
|
+
toString: containerToString,
|
|
832
|
+
valueOf: containerValueOf,
|
|
833
|
+
toJSON: containerToJSON,
|
|
834
|
+
toPrimitive: containerToPrimitive
|
|
835
|
+
} = makeReadonlyCoercion(snapshotContainer);
|
|
770
836
|
const target = isArrayLike ? [] : (() => {
|
|
771
837
|
});
|
|
772
838
|
const proxy = new Proxy(target, {
|
|
@@ -791,6 +857,9 @@ function buildSurfaceProxy(opts) {
|
|
|
791
857
|
return opts.containerOwnKeys === void 0 ? 0 : opts.containerOwnKeys(segments).length;
|
|
792
858
|
}
|
|
793
859
|
const childSegs = [...segments, keyToSegment(key)];
|
|
860
|
+
if ((isArrayLike || opts.isArrayContainer?.(segments) === true) && typeof keyToSegment(key) === "string" && key in Array.prototype) {
|
|
861
|
+
return Reflect.get(Array.prototype, key);
|
|
862
|
+
}
|
|
794
863
|
if (key === "toString" || key === "valueOf") {
|
|
795
864
|
if (!schemaHasPath(childSegs)) {
|
|
796
865
|
return key === "toString" ? containerToString : containerValueOf;
|
|
@@ -835,10 +904,25 @@ function buildSurfaceProxy(opts) {
|
|
|
835
904
|
};
|
|
836
905
|
},
|
|
837
906
|
// Block writes at the proxy boundary. Mutations go through
|
|
838
|
-
// `setValue`, the directive, or the field-array helpers.
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
907
|
+
// `setValue`, the directive, or the field-array helpers. Each
|
|
908
|
+
// trap returns `true` (warn-and-noop) — returning `false` from a
|
|
909
|
+
// `set`/`delete`/`defineProperty` trap throws `TypeError` under
|
|
910
|
+
// strict mode (every ESM / `<script setup>`), which would surface
|
|
911
|
+
// a host-level exception in consumer code that the library
|
|
912
|
+
// documents as "writes are ignored." Aligns with the contract on
|
|
913
|
+
// `form.values` / `wizard.statuses`.
|
|
914
|
+
set: (_, key) => {
|
|
915
|
+
warnReadOnly("form.fields / form.errors", "write", key);
|
|
916
|
+
return true;
|
|
917
|
+
},
|
|
918
|
+
deleteProperty: (_, key) => {
|
|
919
|
+
warnReadOnly("form.fields / form.errors", "delete", key);
|
|
920
|
+
return true;
|
|
921
|
+
},
|
|
922
|
+
defineProperty: (_, key) => {
|
|
923
|
+
warnReadOnly("form.fields / form.errors", "define", key);
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
842
926
|
});
|
|
843
927
|
containerCache.set(cacheKey, proxy);
|
|
844
928
|
return proxy;
|
|
@@ -860,11 +944,12 @@ function buildSurfaceProxy(opts) {
|
|
|
860
944
|
}
|
|
861
945
|
return snapshot;
|
|
862
946
|
};
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
947
|
+
const {
|
|
948
|
+
toString: leafToString,
|
|
949
|
+
valueOf: leafValueOf,
|
|
950
|
+
toJSON: leafToJSONHandler,
|
|
951
|
+
toPrimitive: leafToPrimitive
|
|
952
|
+
} = makeReadonlyCoercion(snapshotLeaf);
|
|
868
953
|
const target = (() => {
|
|
869
954
|
});
|
|
870
955
|
const proxy = new Proxy(target, {
|
|
@@ -882,7 +967,7 @@ function buildSurfaceProxy(opts) {
|
|
|
882
967
|
if (typeof key !== "string") return void 0;
|
|
883
968
|
if (key === "toString") return leafToString;
|
|
884
969
|
if (key === "valueOf") return leafValueOf;
|
|
885
|
-
if (key === "toJSON") return
|
|
970
|
+
if (key === "toJSON") return leafToJSONHandler;
|
|
886
971
|
if (leafKeys.has(key)) {
|
|
887
972
|
const leaf = opts.resolveLeaf(segments);
|
|
888
973
|
return readLeafKey(leaf, key);
|
|
@@ -909,9 +994,22 @@ function buildSurfaceProxy(opts) {
|
|
|
909
994
|
writable: false
|
|
910
995
|
};
|
|
911
996
|
},
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
997
|
+
// Same warn-and-noop contract as the container traps above.
|
|
998
|
+
// Returning `true` keeps strict-mode callers from throwing on
|
|
999
|
+
// `form.fields.email.value = …`; the actual readonly guarantee
|
|
1000
|
+
// is the absence of any mutation, not the host-level reject.
|
|
1001
|
+
set: (_, key) => {
|
|
1002
|
+
warnReadOnly("form.fields.<leaf>", "write", key);
|
|
1003
|
+
return true;
|
|
1004
|
+
},
|
|
1005
|
+
deleteProperty: (_, key) => {
|
|
1006
|
+
warnReadOnly("form.fields.<leaf>", "delete", key);
|
|
1007
|
+
return true;
|
|
1008
|
+
},
|
|
1009
|
+
defineProperty: (_, key) => {
|
|
1010
|
+
warnReadOnly("form.fields.<leaf>", "define", key);
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
915
1013
|
});
|
|
916
1014
|
leafViewCache.set(cacheKey, proxy);
|
|
917
1015
|
return proxy;
|
|
@@ -975,29 +1073,49 @@ function buildErrorsProxy(state) {
|
|
|
975
1073
|
// working — the call-form just extends that semantic to
|
|
976
1074
|
// containers and dynamic paths.
|
|
977
1075
|
resolveCallTarget: (path) => aggregateErrorsAt(state, path),
|
|
978
|
-
//
|
|
979
|
-
//
|
|
980
|
-
//
|
|
981
|
-
//
|
|
982
|
-
//
|
|
983
|
-
|
|
984
|
-
|
|
1076
|
+
// Enumeration unions the live form-data keys at this path with the
|
|
1077
|
+
// first-child segments drawn from every error store. Without the
|
|
1078
|
+
// union, `Object.keys(form.errors)` / `{...form.errors}` /
|
|
1079
|
+
// `v-for="(errs, k) in form.errors"` silently dropped two
|
|
1080
|
+
// important error classes that the dot / call / JSON.stringify
|
|
1081
|
+
// surfaces already exposed:
|
|
1082
|
+
//
|
|
1083
|
+
// - **Form-level** errors at the synthetic `['']` path (set via
|
|
1084
|
+
// `setFormErrors` or root cross-field refines).
|
|
1085
|
+
// - **Server-only** errors at a key the schema doesn't know
|
|
1086
|
+
// about (`['ghost']`, `['address', 'ghost']`).
|
|
1087
|
+
//
|
|
1088
|
+
// The union closes that gap so `ownKeys` agrees with the rest of
|
|
1089
|
+
// the surface. Active-path filter mirrors `resolveLeaf`:
|
|
1090
|
+
// library-produced verdicts (schema + derived-blank) at unreachable
|
|
1091
|
+
// paths stay hidden; user-supplied errors are unconditional.
|
|
1092
|
+
containerOwnKeys: (segments) => errorAwareContainerKeys(state, segments),
|
|
1093
|
+
isArrayContainer: (segments) => isArrayPath(state, segments)
|
|
985
1094
|
});
|
|
986
1095
|
}
|
|
987
|
-
function
|
|
988
|
-
const
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1096
|
+
function errorAwareContainerKeys(state, segments) {
|
|
1097
|
+
const keys = new Set(liveKeysAtPath(state, segments));
|
|
1098
|
+
const formValue = state.form.value;
|
|
1099
|
+
const walk = (store, applyActivePathFilter) => {
|
|
1100
|
+
for (const [pathKey, errors] of store) {
|
|
1101
|
+
if (errors.length === 0) continue;
|
|
1102
|
+
if (pathKey === paths.FORM_ERRORS_PATH_KEY) {
|
|
1103
|
+
if (segments.length === 0) keys.add("");
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
const decoded = paths.segmentsForPathKey(pathKey);
|
|
1107
|
+
if (decoded === null) continue;
|
|
1108
|
+
if (decoded.length <= segments.length) continue;
|
|
1109
|
+
if (!paths.isPathPrefix(segments, decoded)) continue;
|
|
1110
|
+
if (applyActivePathFilter && !hasAtPath(formValue, decoded)) continue;
|
|
1111
|
+
const nextSeg = decoded[segments.length];
|
|
1112
|
+
keys.add(typeof nextSeg === "number" ? String(nextSeg) : nextSeg);
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
walk(state.schemaErrors, true);
|
|
1116
|
+
walk(state.derivedBlankErrors.value, true);
|
|
1117
|
+
walk(state.userErrors, false);
|
|
1118
|
+
return [...keys];
|
|
1001
1119
|
}
|
|
1002
1120
|
function materializeErrors(state, containerSegments) {
|
|
1003
1121
|
const liveContainer = getAtPath(state.form.value, containerSegments);
|
|
@@ -1049,18 +1167,18 @@ function placeAt(tree, path, errors) {
|
|
|
1049
1167
|
const nextSeg = path[i + 1];
|
|
1050
1168
|
const key = typeof seg === "number" ? String(seg) : seg;
|
|
1051
1169
|
const cursorRecord2 = cursor;
|
|
1052
|
-
let child = cursorRecord2
|
|
1170
|
+
let child = safeOwnRead(cursorRecord2, key);
|
|
1053
1171
|
if (child === null || child === void 0 || typeof child !== "object") {
|
|
1054
1172
|
child = typeof nextSeg === "number" ? [] : {};
|
|
1055
|
-
cursorRecord2
|
|
1173
|
+
safeAssign(cursorRecord2, key, child);
|
|
1056
1174
|
}
|
|
1057
1175
|
cursor = child;
|
|
1058
1176
|
}
|
|
1059
1177
|
const lastSeg = path[path.length - 1];
|
|
1060
1178
|
const lastKey = typeof lastSeg === "number" ? String(lastSeg) : lastSeg;
|
|
1061
1179
|
const cursorRecord = cursor;
|
|
1062
|
-
const existing = cursorRecord
|
|
1063
|
-
cursorRecord
|
|
1180
|
+
const existing = safeOwnRead(cursorRecord, lastKey);
|
|
1181
|
+
safeAssign(cursorRecord, lastKey, Array.isArray(existing) ? [...existing, ...errors] : errors);
|
|
1064
1182
|
}
|
|
1065
1183
|
|
|
1066
1184
|
function buildFieldArrayApi(state) {
|
|
@@ -1090,9 +1208,10 @@ function buildFieldArrayApi(state) {
|
|
|
1090
1208
|
},
|
|
1091
1209
|
insert(path, index, value) {
|
|
1092
1210
|
const next = readArray(path);
|
|
1093
|
-
next.
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1211
|
+
const preLen = next.length;
|
|
1212
|
+
const insertIndex = index < 0 ? Math.max(0, preLen + index) : Math.min(index, preLen);
|
|
1213
|
+
next.splice(insertIndex, 0, value);
|
|
1214
|
+
return writeArray(path, next, { kind: "insert", index: insertIndex });
|
|
1096
1215
|
},
|
|
1097
1216
|
remove(path, index) {
|
|
1098
1217
|
const next = readArray(path);
|
|
@@ -1214,9 +1333,34 @@ function buildFieldStateProxy(state, formInstanceId, getFormMetaBase, options) {
|
|
|
1214
1333
|
writable: false
|
|
1215
1334
|
};
|
|
1216
1335
|
},
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1336
|
+
// Warn-and-noop: returning `false` here throws `TypeError` under
|
|
1337
|
+
// strict mode (`form.fields('email').value = 1` from any ESM
|
|
1338
|
+
// module), which would violate the documented "writes are
|
|
1339
|
+
// ignored" contract. Mirrors `values-proxy` / `wizard-statuses-proxy`.
|
|
1340
|
+
set: (_, key) => {
|
|
1341
|
+
if (paths.__DEV__) {
|
|
1342
|
+
console.warn(
|
|
1343
|
+
`[attaform] form.fields(path) is read-only \u2014 write to "${String(key)}" was ignored. Use form.setValue / the directive / field-array helpers instead.`
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
return true;
|
|
1347
|
+
},
|
|
1348
|
+
deleteProperty: (_, key) => {
|
|
1349
|
+
if (paths.__DEV__) {
|
|
1350
|
+
console.warn(
|
|
1351
|
+
`[attaform] form.fields(path) is read-only \u2014 delete of "${String(key)}" was ignored.`
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
return true;
|
|
1355
|
+
},
|
|
1356
|
+
defineProperty: (_, key) => {
|
|
1357
|
+
if (paths.__DEV__) {
|
|
1358
|
+
console.warn(
|
|
1359
|
+
`[attaform] form.fields(path) is read-only \u2014 define of "${String(key)}" was ignored.`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
return true;
|
|
1363
|
+
}
|
|
1220
1364
|
});
|
|
1221
1365
|
terminalCache.set(cacheKey, proxy);
|
|
1222
1366
|
return proxy;
|
|
@@ -1232,21 +1376,6 @@ function buildFieldStateProxy(state, formInstanceId, getFormMetaBase, options) {
|
|
|
1232
1376
|
isArrayContainer: (segments) => isArrayPath(state, segments)
|
|
1233
1377
|
});
|
|
1234
1378
|
}
|
|
1235
|
-
function liveKeysAtPath(state, segments) {
|
|
1236
|
-
const value = getAtPath(state.form.value, segments);
|
|
1237
|
-
if (value === null || value === void 0) return [];
|
|
1238
|
-
if (Array.isArray(value)) {
|
|
1239
|
-
const keys = new Array(value.length);
|
|
1240
|
-
for (let i = 0; i < value.length; i += 1) keys[i] = String(i);
|
|
1241
|
-
return keys;
|
|
1242
|
-
}
|
|
1243
|
-
if (typeof value === "object") return Object.keys(value);
|
|
1244
|
-
return [];
|
|
1245
|
-
}
|
|
1246
|
-
function isArrayPath(state, segments) {
|
|
1247
|
-
if (segments.length === 0) return false;
|
|
1248
|
-
return Array.isArray(getAtPath(state.form.value, segments));
|
|
1249
|
-
}
|
|
1250
1379
|
function materializeFields(state, containerSegments, snapshotFieldStateAt) {
|
|
1251
1380
|
const liveValue = getAtPath(state.form.value, containerSegments);
|
|
1252
1381
|
return walk$2(liveValue, containerSegments, state.schema, snapshotFieldStateAt);
|
|
@@ -1290,7 +1419,7 @@ async function getStorageAdapter(storage) {
|
|
|
1290
1419
|
}
|
|
1291
1420
|
}
|
|
1292
1421
|
}
|
|
1293
|
-
const PERSISTED_ENVELOPE_VERSION =
|
|
1422
|
+
const PERSISTED_ENVELOPE_VERSION = 6;
|
|
1294
1423
|
function readPersistedPayload(value) {
|
|
1295
1424
|
if (value === null || value === void 0 || typeof value !== "object") return null;
|
|
1296
1425
|
const envelope = value;
|
|
@@ -1314,12 +1443,7 @@ function warnVersionMismatch(observedVersion) {
|
|
|
1314
1443
|
function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPaths) {
|
|
1315
1444
|
let transientList;
|
|
1316
1445
|
if (blankPaths !== void 0 && blankPaths.size > 0) {
|
|
1317
|
-
|
|
1318
|
-
for (const key of blankPaths) {
|
|
1319
|
-
const d = paths.pathKeyToDotted(key);
|
|
1320
|
-
if (d !== null) dotted.push(d);
|
|
1321
|
-
}
|
|
1322
|
-
transientList = dotted.length > 0 ? dotted : void 0;
|
|
1446
|
+
transientList = [...blankPaths];
|
|
1323
1447
|
}
|
|
1324
1448
|
if (include === "form") {
|
|
1325
1449
|
if (transientList === void 0) return { v: PERSISTED_ENVELOPE_VERSION, data: { form } };
|
|
@@ -1341,30 +1465,35 @@ function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPat
|
|
|
1341
1465
|
function createDebouncedWriter(write, debounceMs) {
|
|
1342
1466
|
let timer = null;
|
|
1343
1467
|
let pending = null;
|
|
1468
|
+
let writeGeneration = 0;
|
|
1469
|
+
function runWrite() {
|
|
1470
|
+
const gen = ++writeGeneration;
|
|
1471
|
+
pending = write().finally(() => {
|
|
1472
|
+
if (writeGeneration === gen) pending = null;
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1344
1475
|
function schedule() {
|
|
1345
1476
|
if (timer !== null) clearTimeout(timer);
|
|
1346
1477
|
if (debounceMs === 0) {
|
|
1347
|
-
|
|
1348
|
-
pending = null;
|
|
1349
|
-
});
|
|
1478
|
+
runWrite();
|
|
1350
1479
|
return;
|
|
1351
1480
|
}
|
|
1352
1481
|
timer = setTimeout(() => {
|
|
1353
1482
|
timer = null;
|
|
1354
|
-
|
|
1355
|
-
pending = null;
|
|
1356
|
-
});
|
|
1483
|
+
runWrite();
|
|
1357
1484
|
}, debounceMs);
|
|
1358
1485
|
}
|
|
1359
1486
|
async function flush() {
|
|
1360
1487
|
if (timer !== null) {
|
|
1361
1488
|
clearTimeout(timer);
|
|
1362
1489
|
timer = null;
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1490
|
+
runWrite();
|
|
1491
|
+
}
|
|
1492
|
+
while (pending !== null) {
|
|
1493
|
+
const awaited = pending;
|
|
1494
|
+
await awaited;
|
|
1495
|
+
if (pending === awaited) break;
|
|
1366
1496
|
}
|
|
1367
|
-
if (pending !== null) await pending;
|
|
1368
1497
|
}
|
|
1369
1498
|
function cancel() {
|
|
1370
1499
|
if (timer !== null) {
|
|
@@ -1377,7 +1506,7 @@ function createDebouncedWriter(write, debounceMs) {
|
|
|
1377
1506
|
function resolveStorageKeyBase(config, formKey) {
|
|
1378
1507
|
return config.key ?? `${PERSISTENCE_KEY_PREFIX}${formKey}`;
|
|
1379
1508
|
}
|
|
1380
|
-
async function
|
|
1509
|
+
async function removeMatchingKeys(adapter, base, keepKey) {
|
|
1381
1510
|
let keys;
|
|
1382
1511
|
try {
|
|
1383
1512
|
keys = await adapter.listKeys(base);
|
|
@@ -1385,12 +1514,15 @@ async function cleanupOrphanKeys(adapter, base, currentKey) {
|
|
|
1385
1514
|
return;
|
|
1386
1515
|
}
|
|
1387
1516
|
for (const key of keys) {
|
|
1388
|
-
if (key ===
|
|
1517
|
+
if (key === keepKey) continue;
|
|
1389
1518
|
if (key === base || key.startsWith(`${base}:`)) {
|
|
1390
1519
|
void adapter.removeItem(key).catch(() => void 0);
|
|
1391
1520
|
}
|
|
1392
1521
|
}
|
|
1393
1522
|
}
|
|
1523
|
+
async function cleanupOrphanKeys(adapter, base, currentKey) {
|
|
1524
|
+
await removeMatchingKeys(adapter, base, currentKey);
|
|
1525
|
+
}
|
|
1394
1526
|
const STANDARD_STORAGE_KINDS = ["local", "session", "indexeddb"];
|
|
1395
1527
|
function normalizePersistConfig(input) {
|
|
1396
1528
|
if (typeof input === "string") return { storage: input };
|
|
@@ -1401,12 +1533,7 @@ async function sweepAllOrphansAcrossStandardStores(base) {
|
|
|
1401
1533
|
for (const kind of STANDARD_STORAGE_KINDS) {
|
|
1402
1534
|
try {
|
|
1403
1535
|
const adapter = await getStorageAdapter(kind);
|
|
1404
|
-
|
|
1405
|
-
for (const key of keys) {
|
|
1406
|
-
if (key === base || key.startsWith(`${base}:`)) {
|
|
1407
|
-
void adapter.removeItem(key).catch(() => void 0);
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1536
|
+
await removeMatchingKeys(adapter, base);
|
|
1410
1537
|
} catch {
|
|
1411
1538
|
}
|
|
1412
1539
|
}
|
|
@@ -1417,12 +1544,7 @@ async function sweepNonConfiguredStandardStoresForOrphans(configured, base) {
|
|
|
1417
1544
|
if (kind === configuredKind) continue;
|
|
1418
1545
|
try {
|
|
1419
1546
|
const adapter = await getStorageAdapter(kind);
|
|
1420
|
-
|
|
1421
|
-
for (const key of keys) {
|
|
1422
|
-
if (key === base || key.startsWith(`${base}:`)) {
|
|
1423
|
-
void adapter.removeItem(key).catch(() => void 0);
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1547
|
+
await removeMatchingKeys(adapter, base);
|
|
1426
1548
|
} catch {
|
|
1427
1549
|
}
|
|
1428
1550
|
}
|
|
@@ -1438,6 +1560,29 @@ function pluckPaths(form, pathKeys) {
|
|
|
1438
1560
|
}
|
|
1439
1561
|
return sparse ?? {};
|
|
1440
1562
|
}
|
|
1563
|
+
function stripUnacknowledgedSensitiveLeaves(form, optedInPaths, isSensitivePath) {
|
|
1564
|
+
const acknowledgedSensitive = [];
|
|
1565
|
+
for (const key of optedInPaths) {
|
|
1566
|
+
const segs = paths.segmentsForPathKey(key);
|
|
1567
|
+
if (segs !== null && isSensitivePath(segs)) acknowledgedSensitive.push(segs);
|
|
1568
|
+
}
|
|
1569
|
+
const coveredByAcknowledged = (path) => acknowledgedSensitive.some((prefix) => paths.isPathPrefix(prefix, path));
|
|
1570
|
+
const walk = (path, value) => {
|
|
1571
|
+
if (path.length > 0 && isSensitivePath(path) && !coveredByAcknowledged(path)) {
|
|
1572
|
+
return void 0;
|
|
1573
|
+
}
|
|
1574
|
+
if (value === null || typeof value !== "object") return value;
|
|
1575
|
+
if (Array.isArray(value)) return value.map((item, i) => walk([...path, i], item));
|
|
1576
|
+
if (!isPlainRecord(value)) return value;
|
|
1577
|
+
const out = {};
|
|
1578
|
+
for (const key of Object.keys(value)) {
|
|
1579
|
+
const walked = walk([...path, key], value[key]);
|
|
1580
|
+
if (walked !== void 0) safeAssign(out, key, walked);
|
|
1581
|
+
}
|
|
1582
|
+
return out;
|
|
1583
|
+
};
|
|
1584
|
+
return walk([], form);
|
|
1585
|
+
}
|
|
1441
1586
|
function filterErrorsByPaths(errors, pathKeys) {
|
|
1442
1587
|
const out = /* @__PURE__ */ new Map();
|
|
1443
1588
|
for (const [key, value] of errors) {
|
|
@@ -1466,8 +1611,17 @@ function mergeDeep(target, source, path, schema) {
|
|
|
1466
1611
|
if (isPlainRecord(variantDefault)) {
|
|
1467
1612
|
const out2 = { ...variantDefault };
|
|
1468
1613
|
for (const key of Object.keys(sourceRecord)) {
|
|
1469
|
-
if (!(key
|
|
1470
|
-
|
|
1614
|
+
if (!safeOwnHas(variantDefault, key) && key !== du.discriminatorKey) continue;
|
|
1615
|
+
safeAssign(
|
|
1616
|
+
out2,
|
|
1617
|
+
key,
|
|
1618
|
+
mergeDeep(
|
|
1619
|
+
safeOwnRead(out2, key),
|
|
1620
|
+
safeOwnRead(sourceRecord, key),
|
|
1621
|
+
[...path, key],
|
|
1622
|
+
schema
|
|
1623
|
+
)
|
|
1624
|
+
);
|
|
1471
1625
|
}
|
|
1472
1626
|
return out2;
|
|
1473
1627
|
}
|
|
@@ -1478,7 +1632,16 @@ function mergeDeep(target, source, path, schema) {
|
|
|
1478
1632
|
const mergeTarget = target;
|
|
1479
1633
|
const out = isPlainRecord(mergeTarget) ? { ...mergeTarget } : {};
|
|
1480
1634
|
for (const key of Object.keys(source)) {
|
|
1481
|
-
|
|
1635
|
+
safeAssign(
|
|
1636
|
+
out,
|
|
1637
|
+
key,
|
|
1638
|
+
mergeDeep(
|
|
1639
|
+
safeOwnRead(out, key),
|
|
1640
|
+
safeOwnRead(source, key),
|
|
1641
|
+
[...path, key],
|
|
1642
|
+
schema
|
|
1643
|
+
)
|
|
1644
|
+
);
|
|
1482
1645
|
}
|
|
1483
1646
|
return out;
|
|
1484
1647
|
}
|
|
@@ -1579,39 +1742,44 @@ function buildProcessForm(state, formInstanceId, options = {}) {
|
|
|
1579
1742
|
}
|
|
1580
1743
|
return result;
|
|
1581
1744
|
}
|
|
1582
|
-
async function
|
|
1745
|
+
async function runImperativeValidation(pathInput, config) {
|
|
1583
1746
|
const segments = pathInput === void 0 ? void 0 : toSegments(pathInput);
|
|
1584
1747
|
const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
|
|
1585
1748
|
try {
|
|
1586
1749
|
state.activeValidations.value += 1;
|
|
1587
|
-
state.cancelFieldValidation();
|
|
1750
|
+
if (config.cancelInFlight) state.cancelFieldValidation();
|
|
1588
1751
|
const refinement = await runRefinementValidation(dataAtPath, segments);
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1752
|
+
if (config.commitToSchemaErrors) {
|
|
1753
|
+
const scopePath = segments ?? [];
|
|
1754
|
+
const errors = refinement.success ? [] : refinement.errors;
|
|
1755
|
+
const reStamped = segments === void 0 ? errors : errors.map((err) => ({
|
|
1756
|
+
...err,
|
|
1757
|
+
path: [...segments, ...err.path]
|
|
1758
|
+
}));
|
|
1759
|
+
state.applySchemaErrorsForSubtree(scopePath, reStamped);
|
|
1760
|
+
}
|
|
1761
|
+
return { ok: true, refinement, segments };
|
|
1597
1762
|
} catch (err) {
|
|
1598
|
-
return adapterThrowResponse(err);
|
|
1763
|
+
return { ok: false, error: adapterThrowResponse(err) };
|
|
1599
1764
|
} finally {
|
|
1600
1765
|
state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
|
|
1601
1766
|
}
|
|
1602
1767
|
}
|
|
1768
|
+
async function validateAsync(pathInput) {
|
|
1769
|
+
const result = await runImperativeValidation(pathInput, {
|
|
1770
|
+
cancelInFlight: true,
|
|
1771
|
+
commitToSchemaErrors: true
|
|
1772
|
+
});
|
|
1773
|
+
if (!result.ok) return result.error;
|
|
1774
|
+
return stripData(composeWithDerivedBlank(result.refinement, result.segments));
|
|
1775
|
+
}
|
|
1603
1776
|
async function process(pathInput) {
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
} catch (err) {
|
|
1611
|
-
return adapterThrowResponse(err);
|
|
1612
|
-
} finally {
|
|
1613
|
-
state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
|
|
1614
|
-
}
|
|
1777
|
+
const result = await runImperativeValidation(pathInput, {
|
|
1778
|
+
cancelInFlight: false,
|
|
1779
|
+
commitToSchemaErrors: false
|
|
1780
|
+
});
|
|
1781
|
+
if (!result.ok) return result.error;
|
|
1782
|
+
return composeWithDerivedBlank(result.refinement, result.segments);
|
|
1615
1783
|
}
|
|
1616
1784
|
function adapterThrowResponse(err) {
|
|
1617
1785
|
return {
|
|
@@ -2049,7 +2217,6 @@ function coerceValue(value, accepted, elementAccepted, index) {
|
|
|
2049
2217
|
}
|
|
2050
2218
|
|
|
2051
2219
|
const EMPTY_TRANSFORMS = Object.freeze([]);
|
|
2052
|
-
const INTERACTIVE_TAG_NAMES = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
|
|
2053
2220
|
const attaformListenersSymbol = Symbol.for("attaform:focus-listeners");
|
|
2054
2221
|
function attachFocusListeners(state, segments, element, instanceMeta) {
|
|
2055
2222
|
const target = element;
|
|
@@ -2100,7 +2267,11 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
2100
2267
|
if (typed !== null && typeof raw === "number" && parseFloat(typed) === raw) {
|
|
2101
2268
|
return typed;
|
|
2102
2269
|
}
|
|
2103
|
-
|
|
2270
|
+
try {
|
|
2271
|
+
return String(raw);
|
|
2272
|
+
} catch {
|
|
2273
|
+
return Object.prototype.toString.call(raw);
|
|
2274
|
+
}
|
|
2104
2275
|
});
|
|
2105
2276
|
const slimDefault = state.schema.getDefaultAtPath(segments);
|
|
2106
2277
|
const slimTypes = state.schema.getSlimPrimitiveTypesAtPath(segments);
|
|
@@ -2155,7 +2326,7 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
2155
2326
|
state.markInteracted(segments);
|
|
2156
2327
|
},
|
|
2157
2328
|
registerElement: (element) => {
|
|
2158
|
-
if (!INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
|
|
2329
|
+
if (!paths.INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
|
|
2159
2330
|
const added = state.registerElement(segments, element, formInstanceId);
|
|
2160
2331
|
if (added) attachFocusListeners(state, segments, element, instanceMeta);
|
|
2161
2332
|
},
|
|
@@ -2259,12 +2430,12 @@ function walk(input, segments, schema, paths) {
|
|
|
2259
2430
|
for (const key of allKeys) {
|
|
2260
2431
|
const orig = input[key];
|
|
2261
2432
|
if (orig === void 0 && inputKeysSet.has(key)) {
|
|
2262
|
-
out
|
|
2433
|
+
safeAssign(out, key, void 0);
|
|
2263
2434
|
mutated = true;
|
|
2264
2435
|
continue;
|
|
2265
2436
|
}
|
|
2266
2437
|
const walked = walk(orig, [...segments, key], schema, paths);
|
|
2267
|
-
out
|
|
2438
|
+
safeAssign(out, key, walked);
|
|
2268
2439
|
if (walked !== orig) mutated = true;
|
|
2269
2440
|
}
|
|
2270
2441
|
return mutated ? out : input;
|
|
@@ -2285,7 +2456,11 @@ function walkUnspecified(slim, segments, paths$1) {
|
|
|
2285
2456
|
if (slim !== null && typeof slim === "object") {
|
|
2286
2457
|
const out = {};
|
|
2287
2458
|
for (const key of Object.keys(slim)) {
|
|
2288
|
-
|
|
2459
|
+
safeAssign(
|
|
2460
|
+
out,
|
|
2461
|
+
key,
|
|
2462
|
+
walkUnspecified(slim[key], [...segments, key], paths$1)
|
|
2463
|
+
);
|
|
2289
2464
|
}
|
|
2290
2465
|
return out;
|
|
2291
2466
|
}
|
|
@@ -2320,7 +2495,7 @@ function substitute(input, segments, schema, paths) {
|
|
|
2320
2495
|
for (const key of Object.keys(input)) {
|
|
2321
2496
|
const orig = input[key];
|
|
2322
2497
|
const walked = substitute(orig, [...segments, key], schema, paths);
|
|
2323
|
-
out
|
|
2498
|
+
safeAssign(out, key, walked);
|
|
2324
2499
|
if (walked !== orig) mutated = true;
|
|
2325
2500
|
}
|
|
2326
2501
|
return mutated ? out : input;
|
|
@@ -2368,70 +2543,86 @@ function expandUnsetAt(segments, schema, paths$1) {
|
|
|
2368
2543
|
return result;
|
|
2369
2544
|
}
|
|
2370
2545
|
|
|
2371
|
-
function
|
|
2372
|
-
const inner = vue.computed(() => vue.readonly(form.value));
|
|
2546
|
+
function buildCallableReadonlySnapshotProxy(opts) {
|
|
2373
2547
|
const target = (() => {
|
|
2374
2548
|
});
|
|
2375
|
-
const
|
|
2376
|
-
const
|
|
2549
|
+
const { toString, valueOf, toJSON, toPrimitive } = makeReadonlyCoercion(opts.snapshot);
|
|
2550
|
+
const callResolve = opts.resolveCall ?? ((arg) => opts.resolveKey(String(arg)));
|
|
2377
2551
|
return new Proxy(target, {
|
|
2378
2552
|
apply(_, __, args) {
|
|
2379
2553
|
const arg = args[0];
|
|
2380
|
-
if (arg === void 0) return
|
|
2381
|
-
|
|
2382
|
-
let cursor = inner.value;
|
|
2383
|
-
for (const seg of segments) {
|
|
2384
|
-
if (cursor === null || cursor === void 0) return void 0;
|
|
2385
|
-
cursor = cursor[seg];
|
|
2386
|
-
}
|
|
2387
|
-
return cursor;
|
|
2554
|
+
if (arg === void 0) return opts.snapshot();
|
|
2555
|
+
return callResolve(arg);
|
|
2388
2556
|
},
|
|
2389
2557
|
get(_, key) {
|
|
2390
2558
|
if (typeof key === "symbol") {
|
|
2391
|
-
if (key === Symbol.toPrimitive) return
|
|
2559
|
+
if (key === Symbol.toPrimitive) return toPrimitive;
|
|
2392
2560
|
return Reflect.get(target, key);
|
|
2393
2561
|
}
|
|
2394
|
-
if (key === "toJSON") return
|
|
2395
|
-
if (key === "toString") return
|
|
2396
|
-
if (key === "valueOf")
|
|
2397
|
-
|
|
2398
|
-
return this;
|
|
2399
|
-
};
|
|
2400
|
-
return inner.value[key];
|
|
2562
|
+
if (key === "toJSON") return toJSON;
|
|
2563
|
+
if (key === "toString") return toString;
|
|
2564
|
+
if (key === "valueOf") return valueOf;
|
|
2565
|
+
return opts.resolveKey(key);
|
|
2401
2566
|
},
|
|
2402
2567
|
has(_, key) {
|
|
2403
2568
|
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
2404
|
-
return
|
|
2405
|
-
},
|
|
2406
|
-
ownKeys() {
|
|
2407
|
-
return Reflect.ownKeys(inner.value);
|
|
2569
|
+
return opts.hasKey(key);
|
|
2408
2570
|
},
|
|
2571
|
+
ownKeys: () => opts.ownKeys(),
|
|
2409
2572
|
getOwnPropertyDescriptor(_, key) {
|
|
2410
|
-
|
|
2411
|
-
if (
|
|
2412
|
-
return
|
|
2573
|
+
if (typeof key !== "string") return void 0;
|
|
2574
|
+
if (opts.describeKey !== void 0) return opts.describeKey(key);
|
|
2575
|
+
if (!opts.hasKey(key)) return void 0;
|
|
2576
|
+
return {
|
|
2577
|
+
configurable: true,
|
|
2578
|
+
enumerable: true,
|
|
2579
|
+
writable: false,
|
|
2580
|
+
value: opts.resolveKey(key)
|
|
2581
|
+
};
|
|
2413
2582
|
},
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
// TypeError in strict-mode consumers, surprising users who
|
|
2417
|
-
// assigned through the proxy and expected it to be ignored.
|
|
2418
|
-
set(_, key) {
|
|
2419
|
-
if (paths.__DEV__) {
|
|
2420
|
-
console.warn(
|
|
2421
|
-
`[attaform] form.values is read-only \u2014 write to "${String(key)}" was ignored. Use form.setValue / the directive / field-array helpers instead.`
|
|
2422
|
-
);
|
|
2423
|
-
}
|
|
2583
|
+
set: (_, key) => {
|
|
2584
|
+
warnReadOnly(opts.surface, "write", key);
|
|
2424
2585
|
return true;
|
|
2425
2586
|
},
|
|
2426
|
-
deleteProperty(_, key) {
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2587
|
+
deleteProperty: (_, key) => {
|
|
2588
|
+
warnReadOnly(opts.surface, "delete", key);
|
|
2589
|
+
return true;
|
|
2590
|
+
},
|
|
2591
|
+
defineProperty: (_, key) => {
|
|
2592
|
+
warnReadOnly(opts.surface, "define", key);
|
|
2432
2593
|
return true;
|
|
2594
|
+
}
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
function buildValuesProxy(form) {
|
|
2599
|
+
const inner = vue.computed(() => vue.readonly(form.value));
|
|
2600
|
+
return buildCallableReadonlySnapshotProxy({
|
|
2601
|
+
surface: "form.values",
|
|
2602
|
+
snapshot: () => inner.value,
|
|
2603
|
+
// Read through the readonly proxy at access time so Vue's
|
|
2604
|
+
// dependency tracking lands inside the consumer's active effect
|
|
2605
|
+
// — `inner.value[key]` is what triggers per-key tracking.
|
|
2606
|
+
resolveKey: (key) => inner.value[key],
|
|
2607
|
+
// Dynamic path: walk segments through the readonly proxy. Each
|
|
2608
|
+
// step reads through the proxy's own get traps so dependency
|
|
2609
|
+
// tracking propagates at every level.
|
|
2610
|
+
resolveCall: (arg) => {
|
|
2611
|
+
const { segments } = paths.canonicalizePath(arg);
|
|
2612
|
+
let cursor = inner.value;
|
|
2613
|
+
for (const seg of segments) {
|
|
2614
|
+
if (cursor === null || cursor === void 0) return void 0;
|
|
2615
|
+
cursor = cursor[seg];
|
|
2616
|
+
}
|
|
2617
|
+
return cursor;
|
|
2433
2618
|
},
|
|
2434
|
-
|
|
2619
|
+
ownKeys: () => Reflect.ownKeys(inner.value),
|
|
2620
|
+
hasKey: (key) => Reflect.has(inner.value, key),
|
|
2621
|
+
describeKey: (key) => {
|
|
2622
|
+
const desc = Reflect.getOwnPropertyDescriptor(inner.value, key);
|
|
2623
|
+
if (desc !== void 0) desc.configurable = true;
|
|
2624
|
+
return desc;
|
|
2625
|
+
}
|
|
2435
2626
|
});
|
|
2436
2627
|
}
|
|
2437
2628
|
|
|
@@ -2497,10 +2688,18 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2497
2688
|
next,
|
|
2498
2689
|
state.schema
|
|
2499
2690
|
);
|
|
2691
|
+
const ok2 = state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
|
|
2692
|
+
if (!ok2) return false;
|
|
2500
2693
|
for (const pathKey of walked2.paths) {
|
|
2501
|
-
|
|
2694
|
+
const blankSegments = paths.segmentsForPathKey(pathKey);
|
|
2695
|
+
if (blankSegments === null) continue;
|
|
2696
|
+
state.setValueAtPath(
|
|
2697
|
+
blankSegments,
|
|
2698
|
+
state.getValueAtPath(blankSegments),
|
|
2699
|
+
withInstanceMeta({ blank: true })
|
|
2700
|
+
);
|
|
2502
2701
|
}
|
|
2503
|
-
return
|
|
2702
|
+
return true;
|
|
2504
2703
|
}
|
|
2505
2704
|
const segments = paths.canonicalizePath(pathOrValue).segments;
|
|
2506
2705
|
const writeUnsetAt = () => {
|
|
@@ -2649,23 +2848,45 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2649
2848
|
const rootFieldState = getRootFieldStateAt([]);
|
|
2650
2849
|
const formMeta = vue.readonly(
|
|
2651
2850
|
vue.reactive({
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
pristine
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2851
|
+
get value() {
|
|
2852
|
+
return rootFieldState.value.value;
|
|
2853
|
+
},
|
|
2854
|
+
get original() {
|
|
2855
|
+
return rootFieldState.value.original;
|
|
2856
|
+
},
|
|
2857
|
+
get pristine() {
|
|
2858
|
+
return rootFieldState.value.pristine;
|
|
2859
|
+
},
|
|
2860
|
+
get dirty() {
|
|
2861
|
+
return rootFieldState.value.dirty;
|
|
2862
|
+
},
|
|
2863
|
+
get focused() {
|
|
2864
|
+
return rootFieldState.value.focused;
|
|
2865
|
+
},
|
|
2866
|
+
get blurred() {
|
|
2867
|
+
return rootFieldState.value.blurred;
|
|
2868
|
+
},
|
|
2869
|
+
get touched() {
|
|
2870
|
+
return rootFieldState.value.touched;
|
|
2871
|
+
},
|
|
2872
|
+
get interacted() {
|
|
2873
|
+
return rootFieldState.value.interacted;
|
|
2874
|
+
},
|
|
2875
|
+
get blurredAfterInteraction() {
|
|
2876
|
+
return rootFieldState.value.blurredAfterInteraction;
|
|
2877
|
+
},
|
|
2878
|
+
get connected() {
|
|
2879
|
+
return rootFieldState.value.connected;
|
|
2880
|
+
},
|
|
2881
|
+
get element() {
|
|
2882
|
+
return rootFieldState.value.element;
|
|
2883
|
+
},
|
|
2884
|
+
get elements() {
|
|
2885
|
+
return rootFieldState.value.elements;
|
|
2886
|
+
},
|
|
2887
|
+
get updatedAt() {
|
|
2888
|
+
return rootFieldState.value.updatedAt;
|
|
2889
|
+
},
|
|
2669
2890
|
// Whole-form validating mirrors the LIFECYCLE counter
|
|
2670
2891
|
// (`state.activeValidations`) ORed with any per-leaf validation
|
|
2671
2892
|
// in flight (via `rootFieldState.validating`). A submit-time
|
|
@@ -2688,21 +2909,51 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2688
2909
|
// FieldState surface, so `form.meta.displayState` matches
|
|
2689
2910
|
// `form.fields().displayState` exactly — the predicate runs once
|
|
2690
2911
|
// at the root and the result is shared.
|
|
2691
|
-
displayState
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2912
|
+
get displayState() {
|
|
2913
|
+
return rootFieldState.value.displayState;
|
|
2914
|
+
},
|
|
2915
|
+
get showErrors() {
|
|
2916
|
+
return rootFieldState.value.showErrors;
|
|
2917
|
+
},
|
|
2918
|
+
get showPending() {
|
|
2919
|
+
return rootFieldState.value.showPending;
|
|
2920
|
+
},
|
|
2921
|
+
get showSuccess() {
|
|
2922
|
+
return rootFieldState.value.showSuccess;
|
|
2923
|
+
},
|
|
2924
|
+
get showIdle() {
|
|
2925
|
+
return rootFieldState.value.showIdle;
|
|
2926
|
+
},
|
|
2927
|
+
get firstError() {
|
|
2928
|
+
return rootFieldState.value.firstError;
|
|
2929
|
+
},
|
|
2930
|
+
get path() {
|
|
2931
|
+
return rootFieldState.value.path;
|
|
2932
|
+
},
|
|
2933
|
+
get id() {
|
|
2934
|
+
return rootFieldState.value.id;
|
|
2935
|
+
},
|
|
2936
|
+
get aria() {
|
|
2937
|
+
return rootFieldState.value.aria;
|
|
2938
|
+
},
|
|
2939
|
+
get key() {
|
|
2940
|
+
return rootFieldState.value.key;
|
|
2941
|
+
},
|
|
2942
|
+
get blank() {
|
|
2943
|
+
return rootFieldState.value.blank;
|
|
2944
|
+
},
|
|
2945
|
+
get label() {
|
|
2946
|
+
return rootFieldState.value.label;
|
|
2947
|
+
},
|
|
2948
|
+
get description() {
|
|
2949
|
+
return rootFieldState.value.description;
|
|
2950
|
+
},
|
|
2951
|
+
get placeholder() {
|
|
2952
|
+
return rootFieldState.value.placeholder;
|
|
2953
|
+
},
|
|
2954
|
+
get meta() {
|
|
2955
|
+
return rootFieldState.value.meta;
|
|
2956
|
+
},
|
|
2706
2957
|
// Lifecycle (form-level only — not on FieldState).
|
|
2707
2958
|
submitting,
|
|
2708
2959
|
submissionAttempts,
|
|
@@ -2711,7 +2962,9 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2711
2962
|
// Scalar mirror over the array — meta is a single sticky surface
|
|
2712
2963
|
// for both templates and `useWizard`'s `FormStatus`, so the
|
|
2713
2964
|
// projection lives here.
|
|
2714
|
-
errorCount
|
|
2965
|
+
get errorCount() {
|
|
2966
|
+
return metaErrors.value.length;
|
|
2967
|
+
},
|
|
2715
2968
|
submitted,
|
|
2716
2969
|
// Per-`useForm()`-call identity. Stable for one mount; new on
|
|
2717
2970
|
// re-mount; orthogonal to `form.key` (which is the user-supplied
|
|
@@ -2748,12 +3001,20 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2748
3001
|
}
|
|
2749
3002
|
};
|
|
2750
3003
|
function clear(pathInput) {
|
|
2751
|
-
|
|
2752
|
-
|
|
3004
|
+
if (pathInput === void 0) {
|
|
3005
|
+
return setValueImpl(unset);
|
|
3006
|
+
}
|
|
3007
|
+
return setValueImpl(pathInput, unset);
|
|
2753
3008
|
}
|
|
2754
3009
|
const persist = async (pathInput, options2) => {
|
|
2755
3010
|
const segments = paths.canonicalizePath(pathInput).segments;
|
|
2756
|
-
paths.
|
|
3011
|
+
if (!paths.allowSensitivePersist(
|
|
3012
|
+
segments,
|
|
3013
|
+
options2?.acknowledgeSensitive === true,
|
|
3014
|
+
state.isSensitivePath
|
|
3015
|
+
)) {
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
2757
3018
|
if (persistence === void 0) return;
|
|
2758
3019
|
await persistence.writePathImmediately(segments);
|
|
2759
3020
|
};
|
|
@@ -2819,7 +3080,9 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2819
3080
|
getFormMetaBase,
|
|
2820
3081
|
fieldStateAccessorOptions
|
|
2821
3082
|
);
|
|
3083
|
+
const needsLazyGate = state.defaultValuesFactory.value !== void 0 || state.hasSsrPrefetch;
|
|
2822
3084
|
function gated(fn) {
|
|
3085
|
+
if (!needsLazyGate) return fn;
|
|
2823
3086
|
return ((...args) => {
|
|
2824
3087
|
void state.activate();
|
|
2825
3088
|
return fn(...args);
|
|
@@ -2844,7 +3107,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2844
3107
|
}
|
|
2845
3108
|
const out = {};
|
|
2846
3109
|
for (const key of Object.keys(value)) {
|
|
2847
|
-
out
|
|
3110
|
+
safeAssign(out, key, callTerminal(`${path}.${key}`));
|
|
2848
3111
|
}
|
|
2849
3112
|
return Object.freeze(out);
|
|
2850
3113
|
}
|
|
@@ -2938,9 +3201,132 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2938
3201
|
};
|
|
2939
3202
|
}
|
|
2940
3203
|
|
|
2941
|
-
function
|
|
2942
|
-
const
|
|
2943
|
-
|
|
3204
|
+
function applyDuStubs(schema, data, options = {}) {
|
|
3205
|
+
const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
|
|
3206
|
+
return walkDuStubs(schema, data, options.basePath ?? [], warned);
|
|
3207
|
+
}
|
|
3208
|
+
function walkDuStubs(schema, value, path, warned) {
|
|
3209
|
+
if (value === null || value === void 0 || typeof value !== "object") return value;
|
|
3210
|
+
if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || typeof value === "function") {
|
|
3211
|
+
return value;
|
|
3212
|
+
}
|
|
3213
|
+
if (Array.isArray(value)) {
|
|
3214
|
+
return value.map((item, i) => walkDuStubs(schema, item, [...path, i], warned));
|
|
3215
|
+
}
|
|
3216
|
+
const rec = value;
|
|
3217
|
+
const du = schema.getUnionDiscriminatorAtPath(path);
|
|
3218
|
+
if (du !== void 0) {
|
|
3219
|
+
const discValue = rec[du.discriminatorKey];
|
|
3220
|
+
if (discValue !== void 0 && !du.isVariantSelected(discValue)) {
|
|
3221
|
+
const isKindBlank = discValue === "" || discValue === 0 || discValue === 0n || discValue === false || discValue === null;
|
|
3222
|
+
if (!isKindBlank && warned !== void 0 && paths.__DEV__) {
|
|
3223
|
+
const dotted = path.map((s) => String(s)).join(".") || "(root)";
|
|
3224
|
+
const key = `${dotted}::${String(discValue)}`;
|
|
3225
|
+
if (!warned.has(key)) {
|
|
3226
|
+
warned.add(key);
|
|
3227
|
+
console.warn(
|
|
3228
|
+
`[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.`
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
const stub = {};
|
|
3233
|
+
safeAssign(stub, du.discriminatorKey, discValue);
|
|
3234
|
+
return stub;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
const out = {};
|
|
3238
|
+
for (const k of Object.keys(rec)) {
|
|
3239
|
+
safeAssign(out, k, walkDuStubs(schema, rec[k], [...path, k], warned));
|
|
3240
|
+
}
|
|
3241
|
+
return out;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
function createVariantMemory() {
|
|
3245
|
+
const memory = /* @__PURE__ */ new Map();
|
|
3246
|
+
function clearAtArrayIndices(arrayPath, indexFilter) {
|
|
3247
|
+
for (const memKey of [...memory.keys()]) {
|
|
3248
|
+
const segs = paths.segmentsForPathKey(memKey);
|
|
3249
|
+
if (segs === null) continue;
|
|
3250
|
+
if (!paths.isPathPrefix(arrayPath, segs)) continue;
|
|
3251
|
+
if (segs.length <= arrayPath.length) continue;
|
|
3252
|
+
const idxSeg = segs[arrayPath.length];
|
|
3253
|
+
if (typeof idxSeg !== "number") continue;
|
|
3254
|
+
if (indexFilter(idxSeg)) memory.delete(memKey);
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
return {
|
|
3258
|
+
clear() {
|
|
3259
|
+
memory.clear();
|
|
3260
|
+
},
|
|
3261
|
+
clearUnderPath(parentPath) {
|
|
3262
|
+
for (const memKey of [...memory.keys()]) {
|
|
3263
|
+
const segs = paths.segmentsForPathKey(memKey);
|
|
3264
|
+
if (segs === null) continue;
|
|
3265
|
+
if (paths.isPathPrefix(parentPath, segs)) memory.delete(memKey);
|
|
3266
|
+
}
|
|
3267
|
+
},
|
|
3268
|
+
applyArrayOp(arrayPath, op) {
|
|
3269
|
+
switch (op.kind) {
|
|
3270
|
+
case "insert":
|
|
3271
|
+
case "remove":
|
|
3272
|
+
clearAtArrayIndices(arrayPath, (i) => i >= op.index);
|
|
3273
|
+
return;
|
|
3274
|
+
case "move": {
|
|
3275
|
+
const lo = Math.min(op.from, op.to);
|
|
3276
|
+
const hi = Math.max(op.from, op.to);
|
|
3277
|
+
clearAtArrayIndices(arrayPath, (i) => i >= lo && i <= hi);
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
case "swap":
|
|
3281
|
+
clearAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
|
|
3282
|
+
return;
|
|
3283
|
+
case "replace-at":
|
|
3284
|
+
clearAtArrayIndices(arrayPath, (i) => i === op.index);
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
},
|
|
3288
|
+
recordOutgoing(unionKey, discValue, snapshot) {
|
|
3289
|
+
let perUnion = memory.get(unionKey);
|
|
3290
|
+
if (perUnion === void 0) {
|
|
3291
|
+
perUnion = /* @__PURE__ */ new Map();
|
|
3292
|
+
memory.set(unionKey, perUnion);
|
|
3293
|
+
}
|
|
3294
|
+
perUnion.set(discValue, snapshot);
|
|
3295
|
+
},
|
|
3296
|
+
lookupIncoming(unionKey, discValue) {
|
|
3297
|
+
return memory.get(unionKey)?.get(discValue);
|
|
3298
|
+
}
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
function cloneVariantSnapshot(value) {
|
|
3302
|
+
if (value === null || typeof value !== "object") return value;
|
|
3303
|
+
const raw = vue.toRaw(value);
|
|
3304
|
+
if (raw instanceof Date) return new Date(raw.getTime());
|
|
3305
|
+
if (raw instanceof Map) {
|
|
3306
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
3307
|
+
for (const [k, v] of raw.entries()) out2.set(cloneVariantSnapshot(k), cloneVariantSnapshot(v));
|
|
3308
|
+
return out2;
|
|
3309
|
+
}
|
|
3310
|
+
if (raw instanceof Set) {
|
|
3311
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
3312
|
+
for (const v of raw) out2.add(cloneVariantSnapshot(v));
|
|
3313
|
+
return out2;
|
|
3314
|
+
}
|
|
3315
|
+
if (raw instanceof RegExp) return new RegExp(raw.source, raw.flags);
|
|
3316
|
+
if (Array.isArray(raw)) {
|
|
3317
|
+
const out2 = new Array(raw.length);
|
|
3318
|
+
for (let i = 0; i < raw.length; i++) out2[i] = cloneVariantSnapshot(raw[i]);
|
|
3319
|
+
return out2;
|
|
3320
|
+
}
|
|
3321
|
+
const src = raw;
|
|
3322
|
+
const out = {};
|
|
3323
|
+
for (const k of Object.keys(src)) safeAssign(out, k, cloneVariantSnapshot(src[k]));
|
|
3324
|
+
return out;
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
function createArrayIdentity(getArrayLength) {
|
|
3328
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
3329
|
+
const baselines = /* @__PURE__ */ new Map();
|
|
2944
3330
|
let counter = 0;
|
|
2945
3331
|
const allocate = () => `k${(counter++).toString(36)}`;
|
|
2946
3332
|
function ensure(arrayKey, expectedLen) {
|
|
@@ -3000,6 +3386,36 @@ function createArrayIdentity(getArrayLength) {
|
|
|
3000
3386
|
return;
|
|
3001
3387
|
}
|
|
3002
3388
|
},
|
|
3389
|
+
applyRemap(arrayPath, remap) {
|
|
3390
|
+
if (remap.moved.size === 0 && remap.vacated.size === 0 && remap.fresh.size === 0) return;
|
|
3391
|
+
const relocate = (store) => {
|
|
3392
|
+
const idxPos = arrayPath.length;
|
|
3393
|
+
const snapshots = [];
|
|
3394
|
+
for (const [key, value] of store) {
|
|
3395
|
+
const segments = paths.segmentsForPathKey(key);
|
|
3396
|
+
if (segments === null) continue;
|
|
3397
|
+
if (!paths.isPathPrefix(arrayPath, segments)) continue;
|
|
3398
|
+
if (segments.length <= idxPos) continue;
|
|
3399
|
+
const idxSeg = segments[idxPos];
|
|
3400
|
+
if (typeof idxSeg !== "number") continue;
|
|
3401
|
+
if (!remap.moved.has(idxSeg) && !remap.vacated.has(idxSeg) && !remap.fresh.has(idxSeg)) {
|
|
3402
|
+
continue;
|
|
3403
|
+
}
|
|
3404
|
+
snapshots.push({ segments: [...segments], index: idxSeg, value });
|
|
3405
|
+
}
|
|
3406
|
+
if (snapshots.length === 0) return;
|
|
3407
|
+
for (const snap of snapshots) store.delete(paths.canonicalizePath(snap.segments).key);
|
|
3408
|
+
for (const snap of snapshots) {
|
|
3409
|
+
const target = remap.moved.get(snap.index);
|
|
3410
|
+
if (target === void 0) continue;
|
|
3411
|
+
const relocated = snap.segments.slice();
|
|
3412
|
+
relocated[idxPos] = target;
|
|
3413
|
+
store.set(paths.canonicalizePath(relocated).key, snap.value);
|
|
3414
|
+
}
|
|
3415
|
+
};
|
|
3416
|
+
relocate(tokens);
|
|
3417
|
+
relocate(baselines);
|
|
3418
|
+
},
|
|
3003
3419
|
realign(arraySegs) {
|
|
3004
3420
|
ensure(paths.canonicalizePath(arraySegs).key, getArrayLength(arraySegs));
|
|
3005
3421
|
},
|
|
@@ -3114,6 +3530,94 @@ function migrateSetSubtree(set, arrayPath, remap) {
|
|
|
3114
3530
|
}
|
|
3115
3531
|
}
|
|
3116
3532
|
|
|
3533
|
+
function createArrayBookkeeping(deps) {
|
|
3534
|
+
const {
|
|
3535
|
+
form,
|
|
3536
|
+
fields,
|
|
3537
|
+
userErrors,
|
|
3538
|
+
originals,
|
|
3539
|
+
blankPaths,
|
|
3540
|
+
originalBlankPaths,
|
|
3541
|
+
fieldValidationCounts,
|
|
3542
|
+
fieldValidationState,
|
|
3543
|
+
schemaErrors,
|
|
3544
|
+
activeValidations,
|
|
3545
|
+
arrayIdentity,
|
|
3546
|
+
touchFieldRecord,
|
|
3547
|
+
decFieldValidation
|
|
3548
|
+
} = deps;
|
|
3549
|
+
function migrateElementState(arrayPath, remap) {
|
|
3550
|
+
if (remap.moved.size === 0 && remap.vacated.size === 0) return;
|
|
3551
|
+
migrateMapSubtree(fields, arrayPath, remap, (record, segments) => ({
|
|
3552
|
+
...record,
|
|
3553
|
+
path: segments
|
|
3554
|
+
}));
|
|
3555
|
+
migrateMapSubtree(
|
|
3556
|
+
userErrors,
|
|
3557
|
+
arrayPath,
|
|
3558
|
+
remap,
|
|
3559
|
+
(errors, segments) => errors.map((error) => ({ ...error, path: [...segments] }))
|
|
3560
|
+
);
|
|
3561
|
+
migrateMapSubtree(originals, arrayPath, remap, (record, segments) => ({
|
|
3562
|
+
segments,
|
|
3563
|
+
value: record.value
|
|
3564
|
+
}));
|
|
3565
|
+
migrateSetSubtree(blankPaths, arrayPath, remap);
|
|
3566
|
+
migrateSetSubtree(originalBlankPaths, arrayPath, remap);
|
|
3567
|
+
migrateMapSubtree(fieldValidationCounts, arrayPath, remap, (count) => count);
|
|
3568
|
+
arrayIdentity.applyRemap(arrayPath, remap);
|
|
3569
|
+
}
|
|
3570
|
+
function seedFreshElement(arrayPath, freshIndex) {
|
|
3571
|
+
const elementPath = [...arrayPath, freshIndex];
|
|
3572
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3573
|
+
diffAndApply(void 0, getAtPath(form.value, elementPath), elementPath, (patch) => {
|
|
3574
|
+
if (patch.kind !== "added") return;
|
|
3575
|
+
const { key } = paths.canonicalizePath(patch.path);
|
|
3576
|
+
if (!originals.has(key)) originals.set(key, { segments: patch.path, value: void 0 });
|
|
3577
|
+
touchFieldRecord(key, patch.path, { updatedAt: now });
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
3580
|
+
function dropSchemaErrorsAtChangedIndices(arrayPath, remap) {
|
|
3581
|
+
const changed = changedIndices(remap);
|
|
3582
|
+
if (changed.size === 0) return;
|
|
3583
|
+
const idxPos = arrayPath.length;
|
|
3584
|
+
for (const key of [...schemaErrors.keys()]) {
|
|
3585
|
+
const segs = paths.segmentsForPathKey(key);
|
|
3586
|
+
if (segs === null) continue;
|
|
3587
|
+
if (!paths.isPathPrefix(arrayPath, segs)) continue;
|
|
3588
|
+
if (segs.length <= idxPos) continue;
|
|
3589
|
+
const idx = segs[idxPos];
|
|
3590
|
+
if (typeof idx === "number" && changed.has(idx)) schemaErrors.delete(key);
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
function abortValidationAtVacatedIndices(arrayPath, remap) {
|
|
3594
|
+
if (remap.vacated.size === 0) return;
|
|
3595
|
+
const idxPos = arrayPath.length;
|
|
3596
|
+
for (const [key, entry] of [...fieldValidationState]) {
|
|
3597
|
+
const segs = paths.segmentsForPathKey(key);
|
|
3598
|
+
if (segs === null) continue;
|
|
3599
|
+
if (!paths.isPathPrefix(arrayPath, segs)) continue;
|
|
3600
|
+
if (segs.length <= idxPos) continue;
|
|
3601
|
+
const idx = segs[idxPos];
|
|
3602
|
+
if (typeof idx !== "number" || !remap.vacated.has(idx)) continue;
|
|
3603
|
+
if (entry.timer !== null) {
|
|
3604
|
+
clearTimeout(entry.timer);
|
|
3605
|
+
} else if (!entry.settled) {
|
|
3606
|
+
activeValidations.value = Math.max(0, activeValidations.value - 1);
|
|
3607
|
+
decFieldValidation(key);
|
|
3608
|
+
}
|
|
3609
|
+
entry.controller.abort();
|
|
3610
|
+
fieldValidationState.delete(key);
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
return {
|
|
3614
|
+
migrateElementState,
|
|
3615
|
+
seedFreshElement,
|
|
3616
|
+
dropSchemaErrorsAtChangedIndices,
|
|
3617
|
+
abortValidationAtVacatedIndices
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3117
3621
|
function isHydratedFieldRecord(value) {
|
|
3118
3622
|
if (typeof value !== "object" || value === null) return false;
|
|
3119
3623
|
const r = value;
|
|
@@ -3137,43 +3641,6 @@ function warnMalformedHydration(formKey, kind, rawKey) {
|
|
|
3137
3641
|
`[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).`
|
|
3138
3642
|
);
|
|
3139
3643
|
}
|
|
3140
|
-
function applyDuStubs(schema, data, options = {}) {
|
|
3141
|
-
const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
|
|
3142
|
-
return walkDuStubs(schema, data, options.basePath ?? [], warned);
|
|
3143
|
-
}
|
|
3144
|
-
function walkDuStubs(schema, value, path, warned) {
|
|
3145
|
-
if (value === null || value === void 0 || typeof value !== "object") return value;
|
|
3146
|
-
if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || typeof value === "function") {
|
|
3147
|
-
return value;
|
|
3148
|
-
}
|
|
3149
|
-
if (Array.isArray(value)) {
|
|
3150
|
-
return value.map((item, i) => walkDuStubs(schema, item, [...path, i], warned));
|
|
3151
|
-
}
|
|
3152
|
-
const rec = value;
|
|
3153
|
-
const du = schema.getUnionDiscriminatorAtPath(path);
|
|
3154
|
-
if (du !== void 0) {
|
|
3155
|
-
const discValue = rec[du.discriminatorKey];
|
|
3156
|
-
if (discValue !== void 0 && !du.isVariantSelected(discValue)) {
|
|
3157
|
-
const isKindBlank = discValue === "" || discValue === 0 || discValue === 0n || discValue === false || discValue === null;
|
|
3158
|
-
if (!isKindBlank && warned !== void 0 && paths.__DEV__) {
|
|
3159
|
-
const dotted = path.map((s) => String(s)).join(".") || "(root)";
|
|
3160
|
-
const key = `${dotted}::${String(discValue)}`;
|
|
3161
|
-
if (!warned.has(key)) {
|
|
3162
|
-
warned.add(key);
|
|
3163
|
-
console.warn(
|
|
3164
|
-
`[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.`
|
|
3165
|
-
);
|
|
3166
|
-
}
|
|
3167
|
-
}
|
|
3168
|
-
return { [du.discriminatorKey]: discValue };
|
|
3169
|
-
}
|
|
3170
|
-
}
|
|
3171
|
-
const out = {};
|
|
3172
|
-
for (const k of Object.keys(rec)) {
|
|
3173
|
-
out[k] = walkDuStubs(schema, rec[k], [...path, k], warned);
|
|
3174
|
-
}
|
|
3175
|
-
return out;
|
|
3176
|
-
}
|
|
3177
3644
|
function isPathKeyUnder(existingKey, parentPath) {
|
|
3178
3645
|
const parsed = paths.segmentsForPathKey(existingKey);
|
|
3179
3646
|
if (parsed === null) return false;
|
|
@@ -3209,31 +3676,6 @@ function stripSymbolsDeep(value) {
|
|
|
3209
3676
|
}
|
|
3210
3677
|
return mutated ? out : value;
|
|
3211
3678
|
}
|
|
3212
|
-
function cloneVariantSnapshot(value) {
|
|
3213
|
-
if (value === null || typeof value !== "object") return value;
|
|
3214
|
-
const raw = vue.toRaw(value);
|
|
3215
|
-
if (raw instanceof Date) return new Date(raw.getTime());
|
|
3216
|
-
if (raw instanceof Map) {
|
|
3217
|
-
const out2 = /* @__PURE__ */ new Map();
|
|
3218
|
-
for (const [k, v] of raw.entries()) out2.set(cloneVariantSnapshot(k), cloneVariantSnapshot(v));
|
|
3219
|
-
return out2;
|
|
3220
|
-
}
|
|
3221
|
-
if (raw instanceof Set) {
|
|
3222
|
-
const out2 = /* @__PURE__ */ new Set();
|
|
3223
|
-
for (const v of raw) out2.add(cloneVariantSnapshot(v));
|
|
3224
|
-
return out2;
|
|
3225
|
-
}
|
|
3226
|
-
if (raw instanceof RegExp) return new RegExp(raw.source, raw.flags);
|
|
3227
|
-
if (Array.isArray(raw)) {
|
|
3228
|
-
const out2 = new Array(raw.length);
|
|
3229
|
-
for (let i = 0; i < raw.length; i++) out2[i] = cloneVariantSnapshot(raw[i]);
|
|
3230
|
-
return out2;
|
|
3231
|
-
}
|
|
3232
|
-
const src = raw;
|
|
3233
|
-
const out = {};
|
|
3234
|
-
for (const k of Object.keys(src)) out[k] = cloneVariantSnapshot(src[k]);
|
|
3235
|
-
return out;
|
|
3236
|
-
}
|
|
3237
3679
|
function walkAuthoredFromConstraints(value, prefix, out) {
|
|
3238
3680
|
if (prefix.length > 0) out.add(paths.canonicalizePath(prefix).key);
|
|
3239
3681
|
if (isPlainRecord(value)) {
|
|
@@ -3306,7 +3748,6 @@ function createFormStore(options) {
|
|
|
3306
3748
|
const coerceIndex = resolveCoercionIndex(options.coerce);
|
|
3307
3749
|
const resolvedGetDisplayState = resolveGetDisplayState(options.getDisplayState);
|
|
3308
3750
|
const resolvedIsSensitivePath = options.isSensitivePath ?? paths.isSensitivePath;
|
|
3309
|
-
const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ?? paths.segmentMatchesSensitive;
|
|
3310
3751
|
const cleanupHooks = [];
|
|
3311
3752
|
const modules = /* @__PURE__ */ new Map();
|
|
3312
3753
|
const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
|
|
@@ -3369,117 +3810,17 @@ function createFormStore(options) {
|
|
|
3369
3810
|
blankPaths.add(key);
|
|
3370
3811
|
originalBlankPaths.add(key);
|
|
3371
3812
|
}
|
|
3372
|
-
const variantMemory =
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
for (const memKey of [...variantMemory.keys()]) {
|
|
3382
|
-
const segs = paths.segmentsForPathKey(memKey);
|
|
3383
|
-
if (segs === null) continue;
|
|
3384
|
-
if (!isPathPrefix(arrayPath, segs)) continue;
|
|
3385
|
-
if (segs.length <= arrayPath.length) continue;
|
|
3386
|
-
const idxSeg = segs[arrayPath.length];
|
|
3387
|
-
if (typeof idxSeg !== "number") continue;
|
|
3388
|
-
if (indexFilter(idxSeg)) variantMemory.delete(memKey);
|
|
3389
|
-
}
|
|
3390
|
-
}
|
|
3391
|
-
function applyArrayOpToMemory(arrayPath, op) {
|
|
3392
|
-
switch (op.kind) {
|
|
3393
|
-
case "insert":
|
|
3394
|
-
case "remove":
|
|
3395
|
-
clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.index);
|
|
3396
|
-
return;
|
|
3397
|
-
case "move": {
|
|
3398
|
-
const lo = Math.min(op.from, op.to);
|
|
3399
|
-
const hi = Math.max(op.from, op.to);
|
|
3400
|
-
clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= lo && i <= hi);
|
|
3401
|
-
return;
|
|
3402
|
-
}
|
|
3403
|
-
case "swap":
|
|
3404
|
-
clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
|
|
3405
|
-
return;
|
|
3406
|
-
case "replace-at":
|
|
3407
|
-
clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.index);
|
|
3408
|
-
return;
|
|
3813
|
+
const variantMemory = createVariantMemory();
|
|
3814
|
+
const pathOrdinals = /* @__PURE__ */ new Map();
|
|
3815
|
+
let nextOrdinal = 0;
|
|
3816
|
+
function ensurePathOrdinal(key) {
|
|
3817
|
+
let ordinal = pathOrdinals.get(key);
|
|
3818
|
+
if (ordinal === void 0) {
|
|
3819
|
+
ordinal = nextOrdinal;
|
|
3820
|
+
pathOrdinals.set(key, ordinal);
|
|
3821
|
+
nextOrdinal += 1;
|
|
3409
3822
|
}
|
|
3410
|
-
|
|
3411
|
-
function migrateArrayElementState(arrayPath, remap) {
|
|
3412
|
-
if (remap.moved.size === 0 && remap.vacated.size === 0) return;
|
|
3413
|
-
migrateMapSubtree(fields, arrayPath, remap, (record, segments) => ({
|
|
3414
|
-
...record,
|
|
3415
|
-
path: segments
|
|
3416
|
-
}));
|
|
3417
|
-
migrateMapSubtree(
|
|
3418
|
-
userErrors,
|
|
3419
|
-
arrayPath,
|
|
3420
|
-
remap,
|
|
3421
|
-
(errors, segments) => errors.map((error) => ({ ...error, path: [...segments] }))
|
|
3422
|
-
);
|
|
3423
|
-
migrateMapSubtree(originals, arrayPath, remap, (record, segments) => ({
|
|
3424
|
-
segments,
|
|
3425
|
-
value: record.value
|
|
3426
|
-
}));
|
|
3427
|
-
migrateSetSubtree(blankPaths, arrayPath, remap);
|
|
3428
|
-
migrateSetSubtree(originalBlankPaths, arrayPath, remap);
|
|
3429
|
-
}
|
|
3430
|
-
function seedFreshElement(arrayPath, freshIndex) {
|
|
3431
|
-
const elementPath = [...arrayPath, freshIndex];
|
|
3432
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3433
|
-
diffAndApply(void 0, getAtPath(form.value, elementPath), elementPath, (patch) => {
|
|
3434
|
-
if (patch.kind !== "added") return;
|
|
3435
|
-
const { key } = paths.canonicalizePath(patch.path);
|
|
3436
|
-
if (!originals.has(key)) originals.set(key, { segments: patch.path, value: void 0 });
|
|
3437
|
-
touchFieldRecord(key, patch.path, { updatedAt: now });
|
|
3438
|
-
});
|
|
3439
|
-
}
|
|
3440
|
-
function dropSchemaErrorsAtChangedIndices(arrayPath, remap) {
|
|
3441
|
-
const changed = changedIndices(remap);
|
|
3442
|
-
if (changed.size === 0) return;
|
|
3443
|
-
const idxPos = arrayPath.length;
|
|
3444
|
-
for (const key of [...schemaErrors.keys()]) {
|
|
3445
|
-
const segs = paths.segmentsForPathKey(key);
|
|
3446
|
-
if (segs === null) continue;
|
|
3447
|
-
if (!isPathPrefix(arrayPath, segs)) continue;
|
|
3448
|
-
if (segs.length <= idxPos) continue;
|
|
3449
|
-
const idx = segs[idxPos];
|
|
3450
|
-
if (typeof idx === "number" && changed.has(idx)) schemaErrors.delete(key);
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
|
-
function abortValidationAtVacatedIndices(arrayPath, remap) {
|
|
3454
|
-
if (remap.vacated.size === 0) return;
|
|
3455
|
-
const idxPos = arrayPath.length;
|
|
3456
|
-
for (const [key, entry] of [...fieldValidationState]) {
|
|
3457
|
-
const segs = paths.segmentsForPathKey(key);
|
|
3458
|
-
if (segs === null) continue;
|
|
3459
|
-
if (!isPathPrefix(arrayPath, segs)) continue;
|
|
3460
|
-
if (segs.length <= idxPos) continue;
|
|
3461
|
-
const idx = segs[idxPos];
|
|
3462
|
-
if (typeof idx !== "number" || !remap.vacated.has(idx)) continue;
|
|
3463
|
-
if (entry.timer !== null) {
|
|
3464
|
-
clearTimeout(entry.timer);
|
|
3465
|
-
} else if (!entry.settled) {
|
|
3466
|
-
activeValidations.value = Math.max(0, activeValidations.value - 1);
|
|
3467
|
-
decFieldValidation(key);
|
|
3468
|
-
}
|
|
3469
|
-
entry.controller.abort();
|
|
3470
|
-
fieldValidationState.delete(key);
|
|
3471
|
-
}
|
|
3472
|
-
}
|
|
3473
|
-
const pathOrdinals = /* @__PURE__ */ new Map();
|
|
3474
|
-
let nextOrdinal = 0;
|
|
3475
|
-
function ensurePathOrdinal(key) {
|
|
3476
|
-
let ordinal = pathOrdinals.get(key);
|
|
3477
|
-
if (ordinal === void 0) {
|
|
3478
|
-
ordinal = nextOrdinal;
|
|
3479
|
-
pathOrdinals.set(key, ordinal);
|
|
3480
|
-
nextOrdinal += 1;
|
|
3481
|
-
}
|
|
3482
|
-
return ordinal;
|
|
3823
|
+
return ordinal;
|
|
3483
3824
|
}
|
|
3484
3825
|
const derivedBlankErrors = vue.computed(() => {
|
|
3485
3826
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -3507,7 +3848,9 @@ function createFormStore(options) {
|
|
|
3507
3848
|
const departAttempts = vue.ref(0);
|
|
3508
3849
|
const submissionGeneration = vue.ref(0);
|
|
3509
3850
|
const activeValidations = vue.ref(0);
|
|
3510
|
-
|
|
3851
|
+
const pathSnapshots = /* @__PURE__ */ new Map();
|
|
3852
|
+
let scheduleEpoch = 0;
|
|
3853
|
+
let lastCommittedEpoch = 0;
|
|
3511
3854
|
const hydrating = vue.ref(false);
|
|
3512
3855
|
const hydrateError = vue.ref(null);
|
|
3513
3856
|
const defaultValuesFactory = vue.ref(void 0);
|
|
@@ -3523,9 +3866,12 @@ function createFormStore(options) {
|
|
|
3523
3866
|
const pathAsyncCache = /* @__PURE__ */ new Map();
|
|
3524
3867
|
function pathHasAsyncValidation(path) {
|
|
3525
3868
|
const { key } = paths.canonicalizePath(path);
|
|
3869
|
+
return pathHasAsyncValidationByKey(key, path);
|
|
3870
|
+
}
|
|
3871
|
+
function pathHasAsyncValidationByKey(key, segments) {
|
|
3526
3872
|
const cached = pathAsyncCache.get(key);
|
|
3527
3873
|
if (cached !== void 0) return cached;
|
|
3528
|
-
const candidates = schema.getSchemasAtPath(
|
|
3874
|
+
const candidates = schema.getSchemasAtPath(segments);
|
|
3529
3875
|
const hasAsync = candidates.some((sub) => sub.needsAsyncValidation?.() === true);
|
|
3530
3876
|
pathAsyncCache.set(key, hasAsync);
|
|
3531
3877
|
return hasAsync;
|
|
@@ -3617,6 +3963,21 @@ function createFormStore(options) {
|
|
|
3617
3963
|
blurredAfterInteraction: patch.blurredAfterInteraction ?? current?.blurredAfterInteraction ?? false
|
|
3618
3964
|
});
|
|
3619
3965
|
}
|
|
3966
|
+
const arrayBookkeeping = createArrayBookkeeping({
|
|
3967
|
+
form,
|
|
3968
|
+
fields,
|
|
3969
|
+
userErrors,
|
|
3970
|
+
originals,
|
|
3971
|
+
blankPaths,
|
|
3972
|
+
originalBlankPaths,
|
|
3973
|
+
fieldValidationCounts,
|
|
3974
|
+
fieldValidationState,
|
|
3975
|
+
schemaErrors,
|
|
3976
|
+
activeValidations,
|
|
3977
|
+
arrayIdentity,
|
|
3978
|
+
touchFieldRecord,
|
|
3979
|
+
decFieldValidation
|
|
3980
|
+
});
|
|
3620
3981
|
function applyFormReplacement(next, meta) {
|
|
3621
3982
|
const prev = form.value;
|
|
3622
3983
|
if (Object.is(prev, next)) return;
|
|
@@ -3740,8 +4101,13 @@ function createFormStore(options) {
|
|
|
3740
4101
|
const pathKey = paths.canonicalizePath(path).key;
|
|
3741
4102
|
if (meta?.blank === true) {
|
|
3742
4103
|
blankPaths.add(pathKey);
|
|
3743
|
-
} else
|
|
3744
|
-
blankPaths.delete(pathKey);
|
|
4104
|
+
} else {
|
|
4105
|
+
if (blankPaths.has(pathKey)) blankPaths.delete(pathKey);
|
|
4106
|
+
if (meta?.arrayOp === void 0) {
|
|
4107
|
+
for (const existingKey of [...blankPaths]) {
|
|
4108
|
+
if (isPathKeyUnder(existingKey, path)) blankPaths.delete(existingKey);
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
3745
4111
|
}
|
|
3746
4112
|
const wasAuthoredBefore = authoredPaths.has(pathKey);
|
|
3747
4113
|
walkAuthoredFromConstraints(value, path, authoredPaths);
|
|
@@ -3765,14 +4131,14 @@ function createFormStore(options) {
|
|
|
3765
4131
|
applyFormReplacement(nextForm, meta);
|
|
3766
4132
|
if (meta?.arrayOp !== void 0) {
|
|
3767
4133
|
const remap = remapForOp(meta.arrayOp, oldArrayLength);
|
|
3768
|
-
|
|
3769
|
-
for (const freshIndex of remap.fresh) seedFreshElement(path, freshIndex);
|
|
3770
|
-
dropSchemaErrorsAtChangedIndices(path, remap);
|
|
3771
|
-
abortValidationAtVacatedIndices(path, remap);
|
|
3772
|
-
|
|
4134
|
+
arrayBookkeeping.migrateElementState(path, remap);
|
|
4135
|
+
for (const freshIndex of remap.fresh) arrayBookkeeping.seedFreshElement(path, freshIndex);
|
|
4136
|
+
arrayBookkeeping.dropSchemaErrorsAtChangedIndices(path, remap);
|
|
4137
|
+
arrayBookkeeping.abortValidationAtVacatedIndices(path, remap);
|
|
4138
|
+
variantMemory.applyArrayOp(path, meta.arrayOp);
|
|
3773
4139
|
arrayIdentity.applyOp(path, meta.arrayOp);
|
|
3774
4140
|
} else if (Array.isArray(value) && Array.isArray(currentValue)) {
|
|
3775
|
-
|
|
4141
|
+
variantMemory.clearUnderPath(path);
|
|
3776
4142
|
arrayIdentity.realign(path);
|
|
3777
4143
|
}
|
|
3778
4144
|
const effectiveModeAfterWrite = meta?.instance?.validateOn ?? fieldValidationMode;
|
|
@@ -3797,18 +4163,12 @@ function createFormStore(options) {
|
|
|
3797
4163
|
for (const k of blankPaths) {
|
|
3798
4164
|
if (isPathKeyUnder(k, parentPath)) outgoingBlanks.push(k);
|
|
3799
4165
|
}
|
|
3800
|
-
|
|
3801
|
-
if (memoryForUnion2 === void 0) {
|
|
3802
|
-
memoryForUnion2 = /* @__PURE__ */ new Map();
|
|
3803
|
-
variantMemory.set(parentKey, memoryForUnion2);
|
|
3804
|
-
}
|
|
3805
|
-
memoryForUnion2.set(oldDiscValue, {
|
|
4166
|
+
variantMemory.recordOutgoing(parentKey, oldDiscValue, {
|
|
3806
4167
|
value: currentValue2,
|
|
3807
4168
|
blankPaths: outgoingBlanks
|
|
3808
4169
|
});
|
|
3809
4170
|
}
|
|
3810
|
-
const
|
|
3811
|
-
const restored = memoryForUnion?.get(newDiscValue);
|
|
4171
|
+
const restored = variantMemory.lookupIncoming(parentKey, newDiscValue);
|
|
3812
4172
|
if (restored !== void 0) {
|
|
3813
4173
|
baseline = restored.value;
|
|
3814
4174
|
restoredBlanks = [...restored.blankPaths];
|
|
@@ -3880,12 +4240,10 @@ function createFormStore(options) {
|
|
|
3880
4240
|
const controller = new AbortController();
|
|
3881
4241
|
const fresh = { controller, timer: null, settled: false };
|
|
3882
4242
|
fieldValidationState.set(key, fresh);
|
|
4243
|
+
const myEpoch = ++scheduleEpoch;
|
|
3883
4244
|
const run = () => {
|
|
3884
4245
|
fresh.timer = null;
|
|
3885
4246
|
if (controller.signal.aborted) return;
|
|
3886
|
-
if (effectiveMode === "blur") {
|
|
3887
|
-
lastValidatedSnapshot = { value: structuralSnapshot(form.value) };
|
|
3888
|
-
}
|
|
3889
4247
|
let activeIncremented = false;
|
|
3890
4248
|
try {
|
|
3891
4249
|
activeValidations.value += 1;
|
|
@@ -3897,11 +4255,25 @@ function createFormStore(options) {
|
|
|
3897
4255
|
}
|
|
3898
4256
|
throw err;
|
|
3899
4257
|
}
|
|
3900
|
-
|
|
4258
|
+
const subtreeScope = path.length > 0 && schema.hasContainerOrRootRefine?.() === false;
|
|
4259
|
+
const scopePath = subtreeScope ? path : void 0;
|
|
4260
|
+
const dataAtScope = subtreeScope ? getAtPath(form.value, path) : form.value;
|
|
4261
|
+
const scopeKey = subtreeScope ? paths.canonicalizePath(path).key : paths.ROOT_PATH_KEY;
|
|
4262
|
+
void Promise.resolve().then(() => schema.validateAtPath(dataAtScope, scopePath)).then((response) => {
|
|
3901
4263
|
if (controller.signal.aborted) return;
|
|
4264
|
+
if (myEpoch <= lastCommittedEpoch) return;
|
|
4265
|
+
lastCommittedEpoch = myEpoch;
|
|
4266
|
+
if (effectiveMode === "blur") {
|
|
4267
|
+
const snapshotSource = scopePath !== void 0 ? getAtPath(form.value, scopePath) : form.value;
|
|
4268
|
+
pathSnapshots.set(scopeKey, structuralSnapshot(snapshotSource));
|
|
4269
|
+
}
|
|
3902
4270
|
const errors = response.success ? [] : response.errors;
|
|
3903
4271
|
const filtered = filterAuthoredErrors(errors);
|
|
3904
|
-
|
|
4272
|
+
const restamped = subtreeScope ? filtered.map((err) => ({
|
|
4273
|
+
...err,
|
|
4274
|
+
path: [...path, ...err.path]
|
|
4275
|
+
})) : filtered;
|
|
4276
|
+
applySchemaErrorsForSubtree(scopePath ?? [], restamped);
|
|
3905
4277
|
}).catch(() => {
|
|
3906
4278
|
}).finally(() => {
|
|
3907
4279
|
activeValidations.value = Math.max(0, activeValidations.value - 1);
|
|
@@ -4144,11 +4516,24 @@ function createFormStore(options) {
|
|
|
4144
4516
|
const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
|
|
4145
4517
|
if (!focused && focusMode === "blur") {
|
|
4146
4518
|
const firstInteractiveBlur = current?.interacted === true && current.blurredAfterInteraction !== true;
|
|
4147
|
-
|
|
4519
|
+
let snapshot = void 0;
|
|
4520
|
+
let snapshotScopeLength = 0;
|
|
4521
|
+
for (let i = path.length; i >= 0; i--) {
|
|
4522
|
+
const ancestorKey = paths.canonicalizePath(path.slice(0, i)).key;
|
|
4523
|
+
const entry = pathSnapshots.get(ancestorKey);
|
|
4524
|
+
if (entry !== void 0) {
|
|
4525
|
+
snapshot = entry;
|
|
4526
|
+
snapshotScopeLength = i;
|
|
4527
|
+
break;
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4148
4530
|
let changed = true;
|
|
4149
|
-
if (!firstInteractiveBlur && snapshot !==
|
|
4531
|
+
if (!firstInteractiveBlur && snapshot !== void 0) {
|
|
4532
|
+
const relPath = path.slice(snapshotScopeLength);
|
|
4533
|
+
const snapshotSubtree = getAtPath(snapshot, relPath);
|
|
4534
|
+
const liveSubtree = getAtPath(form.value, path);
|
|
4150
4535
|
changed = false;
|
|
4151
|
-
diffAndApply(
|
|
4536
|
+
diffAndApply(snapshotSubtree, liveSubtree, path, () => {
|
|
4152
4537
|
changed = true;
|
|
4153
4538
|
});
|
|
4154
4539
|
}
|
|
@@ -4160,10 +4545,6 @@ function createFormStore(options) {
|
|
|
4160
4545
|
}
|
|
4161
4546
|
}
|
|
4162
4547
|
}
|
|
4163
|
-
function markTouched(path) {
|
|
4164
|
-
const { key } = paths.canonicalizePath(path);
|
|
4165
|
-
touchFieldRecord(key, path, { touched: true });
|
|
4166
|
-
}
|
|
4167
4548
|
function markInteracted(path) {
|
|
4168
4549
|
const { key } = paths.canonicalizePath(path);
|
|
4169
4550
|
if (fields.get(key)?.interacted === true) return;
|
|
@@ -4173,7 +4554,7 @@ function createFormStore(options) {
|
|
|
4173
4554
|
const formValue = form.value;
|
|
4174
4555
|
let touchedAny = false;
|
|
4175
4556
|
for (const [, entry] of originals) {
|
|
4176
|
-
if (!isPathPrefix(segments, entry.segments)) continue;
|
|
4557
|
+
if (!paths.isPathPrefix(segments, entry.segments)) continue;
|
|
4177
4558
|
if (!hasAtPath(formValue, entry.segments)) continue;
|
|
4178
4559
|
touchedAny = true;
|
|
4179
4560
|
const leafKey = paths.canonicalizePath(entry.segments).key;
|
|
@@ -4187,9 +4568,6 @@ function createFormStore(options) {
|
|
|
4187
4568
|
);
|
|
4188
4569
|
}
|
|
4189
4570
|
}
|
|
4190
|
-
function clear(path) {
|
|
4191
|
-
return setValueAtPath(path, schema.getEmptyValueAtPath(path));
|
|
4192
|
-
}
|
|
4193
4571
|
function rehydrate() {
|
|
4194
4572
|
const factory = defaultValuesFactory.value;
|
|
4195
4573
|
if (factory === void 0) {
|
|
@@ -4336,6 +4714,9 @@ function createFormStore(options) {
|
|
|
4336
4714
|
submitError.value = null;
|
|
4337
4715
|
departAttempts.value = 0;
|
|
4338
4716
|
cancelFieldValidation();
|
|
4717
|
+
pathSnapshots.clear();
|
|
4718
|
+
scheduleEpoch = 0;
|
|
4719
|
+
lastCommittedEpoch = 0;
|
|
4339
4720
|
variantMemory.clear();
|
|
4340
4721
|
for (const listener of resetListeners) {
|
|
4341
4722
|
try {
|
|
@@ -4347,13 +4728,7 @@ function createFormStore(options) {
|
|
|
4347
4728
|
}
|
|
4348
4729
|
function resetField(path) {
|
|
4349
4730
|
const { key: targetKey, segments: targetSegments } = paths.canonicalizePath(path);
|
|
4350
|
-
|
|
4351
|
-
const memSegments = paths.segmentsForPathKey(memKey);
|
|
4352
|
-
if (memSegments === null) continue;
|
|
4353
|
-
if (isPathPrefix(targetSegments, memSegments)) {
|
|
4354
|
-
variantMemory.delete(memKey);
|
|
4355
|
-
}
|
|
4356
|
-
}
|
|
4731
|
+
variantMemory.clearUnderPath(targetSegments);
|
|
4357
4732
|
const leafEntry = originals.get(targetKey);
|
|
4358
4733
|
if (leafEntry !== void 0) {
|
|
4359
4734
|
const wrote = setValueAtPath(targetSegments, leafEntry.value);
|
|
@@ -4367,7 +4742,7 @@ function createFormStore(options) {
|
|
|
4367
4742
|
let anyMatch = false;
|
|
4368
4743
|
for (const [, entry] of originals) {
|
|
4369
4744
|
const leafSegments = entry.segments;
|
|
4370
|
-
if (!isPathPrefix(targetSegments, leafSegments)) continue;
|
|
4745
|
+
if (!paths.isPathPrefix(targetSegments, leafSegments)) continue;
|
|
4371
4746
|
if (leafSegments.length === targetSegments.length) continue;
|
|
4372
4747
|
anyMatch = true;
|
|
4373
4748
|
const relative = leafSegments.slice(targetSegments.length);
|
|
@@ -4388,14 +4763,14 @@ function createFormStore(options) {
|
|
|
4388
4763
|
deleteErrorsUnderPrefix(schemaErrors, targetSegments);
|
|
4389
4764
|
deleteErrorsUnderPrefix(userErrors, targetSegments);
|
|
4390
4765
|
for (const [fieldKey, record] of Array.from(fields.entries())) {
|
|
4391
|
-
if (isPathPrefix(targetSegments, record.path)) clearFieldRecordFlags(fieldKey);
|
|
4766
|
+
if (paths.isPathPrefix(targetSegments, record.path)) clearFieldRecordFlags(fieldKey);
|
|
4392
4767
|
}
|
|
4393
4768
|
}
|
|
4394
4769
|
function deleteErrorsUnderPrefix(map, prefix) {
|
|
4395
4770
|
for (const [errorKey, errs] of Array.from(map.entries())) {
|
|
4396
4771
|
const first = errs[0];
|
|
4397
4772
|
if (first === void 0) continue;
|
|
4398
|
-
if (isPathPrefix(prefix, first.path)) {
|
|
4773
|
+
if (paths.isPathPrefix(prefix, first.path)) {
|
|
4399
4774
|
map.delete(errorKey);
|
|
4400
4775
|
}
|
|
4401
4776
|
}
|
|
@@ -4414,15 +4789,11 @@ function createFormStore(options) {
|
|
|
4414
4789
|
blurredAfterInteraction: false
|
|
4415
4790
|
});
|
|
4416
4791
|
}
|
|
4417
|
-
function isPathPrefix(prefix, candidate) {
|
|
4418
|
-
if (prefix.length > candidate.length) return false;
|
|
4419
|
-
for (let i = 0; i < prefix.length; i++) {
|
|
4420
|
-
if (prefix[i] !== candidate[i]) return false;
|
|
4421
|
-
}
|
|
4422
|
-
return true;
|
|
4423
|
-
}
|
|
4424
4792
|
function isPristineAtPath(path) {
|
|
4425
4793
|
const { key, segments } = paths.canonicalizePath(path);
|
|
4794
|
+
return isPristineAtPathByKey(key, segments);
|
|
4795
|
+
}
|
|
4796
|
+
function isPristineAtPathByKey(key, segments) {
|
|
4426
4797
|
if (blankPaths.has(key) !== originalBlankPaths.has(key)) return false;
|
|
4427
4798
|
const entry = originals.get(key);
|
|
4428
4799
|
if (entry === void 0) return true;
|
|
@@ -4484,6 +4855,7 @@ function createFormStore(options) {
|
|
|
4484
4855
|
hydrating,
|
|
4485
4856
|
hydrateError,
|
|
4486
4857
|
defaultValuesFactory,
|
|
4858
|
+
hasSsrPrefetch: ssrPrefetch !== void 0,
|
|
4487
4859
|
defaultsResolved,
|
|
4488
4860
|
activated,
|
|
4489
4861
|
activationPromise,
|
|
@@ -4493,6 +4865,7 @@ function createFormStore(options) {
|
|
|
4493
4865
|
activeValidations,
|
|
4494
4866
|
firstValidationDone,
|
|
4495
4867
|
pathHasAsyncValidation,
|
|
4868
|
+
pathHasAsyncValidationByKey,
|
|
4496
4869
|
fieldValidationCounts,
|
|
4497
4870
|
applyFormReplacement,
|
|
4498
4871
|
setValueAtPath,
|
|
@@ -4500,7 +4873,6 @@ function createFormStore(options) {
|
|
|
4500
4873
|
arrayElementKey,
|
|
4501
4874
|
reset,
|
|
4502
4875
|
resetField,
|
|
4503
|
-
clear,
|
|
4504
4876
|
setSchemaErrorsForPath,
|
|
4505
4877
|
setAllSchemaErrors,
|
|
4506
4878
|
clearSchemaErrors,
|
|
@@ -4513,11 +4885,11 @@ function createFormStore(options) {
|
|
|
4513
4885
|
registerElement,
|
|
4514
4886
|
deregisterElement,
|
|
4515
4887
|
markFocused,
|
|
4516
|
-
markTouched,
|
|
4517
4888
|
markInteracted,
|
|
4518
4889
|
touchAtPath,
|
|
4519
4890
|
markConnectedOptimistically,
|
|
4520
4891
|
isPristineAtPath,
|
|
4892
|
+
isPristineAtPathByKey,
|
|
4521
4893
|
hasStructuralChangeUnder,
|
|
4522
4894
|
getFieldRecord,
|
|
4523
4895
|
getOriginalAtPath,
|
|
@@ -4534,7 +4906,6 @@ function createFormStore(options) {
|
|
|
4534
4906
|
modules,
|
|
4535
4907
|
persistOptIns,
|
|
4536
4908
|
isSensitivePath: resolvedIsSensitivePath,
|
|
4537
|
-
segmentMatchesSensitive: resolvedSegmentMatchesSensitive,
|
|
4538
4909
|
noSyncPaths,
|
|
4539
4910
|
incrementNoSyncOptOut,
|
|
4540
4911
|
decrementNoSyncOptOut,
|
|
@@ -4735,532 +5106,814 @@ function createHistoryModule(state, config) {
|
|
|
4735
5106
|
};
|
|
4736
5107
|
}
|
|
4737
5108
|
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
function isDangerousSegment(s) {
|
|
4748
|
-
return s === "__proto__" || s === "constructor" || s === "prototype";
|
|
4749
|
-
}
|
|
4750
|
-
function pathContainsDangerousSegment(path) {
|
|
4751
|
-
for (let i = 0; i < path.length; i++) {
|
|
4752
|
-
if (isDangerousSegment(path[i])) return true;
|
|
4753
|
-
}
|
|
4754
|
-
return false;
|
|
4755
|
-
}
|
|
4756
|
-
function isInboundShapeAcceptable(schema, path, value) {
|
|
4757
|
-
if (isFileLikeValue(value)) return false;
|
|
4758
|
-
let kind;
|
|
4759
|
-
if (Array.isArray(value)) {
|
|
4760
|
-
kind = "array";
|
|
4761
|
-
} else if (value !== null && typeof value === "object" && isPlainRecord(value)) {
|
|
4762
|
-
kind = "object";
|
|
4763
|
-
} else {
|
|
4764
|
-
kind = slimKindOf(value);
|
|
4765
|
-
}
|
|
4766
|
-
const accepted = schema.getSlimPrimitiveTypesAtPath(path);
|
|
4767
|
-
if (!accepted.has(kind)) return false;
|
|
4768
|
-
if (Array.isArray(value)) {
|
|
4769
|
-
for (let i = 0; i < value.length; i++) {
|
|
4770
|
-
if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
|
|
5109
|
+
function wirePersistence(state, config) {
|
|
5110
|
+
let fingerprint;
|
|
5111
|
+
try {
|
|
5112
|
+
fingerprint = hashStableString(state.schema.fingerprint());
|
|
5113
|
+
} catch (err) {
|
|
5114
|
+
if (paths.__DEV__) {
|
|
5115
|
+
console.warn(
|
|
5116
|
+
`[attaform] Could not fingerprint the schema for form '${state.formKey}': ${err instanceof Error ? err.message : String(err)}. Persistence falls back to a fingerprint-free key, so a schema change won't auto-invalidate a saved draft.`
|
|
5117
|
+
);
|
|
4771
5118
|
}
|
|
4772
|
-
|
|
5119
|
+
fingerprint = "unfingerprinted";
|
|
4773
5120
|
}
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
5121
|
+
const base = resolveStorageKeyBase(config, state.formKey);
|
|
5122
|
+
const key = `${base}:${fingerprint}`;
|
|
5123
|
+
const debounceMs = normalizeNumericOption({
|
|
5124
|
+
value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
|
|
5125
|
+
source: "useForm.persist.debounceMs",
|
|
5126
|
+
allowInfinity: false,
|
|
5127
|
+
min: 0,
|
|
5128
|
+
defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
|
|
5129
|
+
});
|
|
5130
|
+
const include = config.include ?? "form";
|
|
5131
|
+
const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
|
|
5132
|
+
const adapterPromise = getStorageAdapter(config.storage);
|
|
5133
|
+
let disposed = false;
|
|
5134
|
+
const isDisposed = () => disposed;
|
|
5135
|
+
let inFlightFinalFlush = null;
|
|
5136
|
+
let pendingOptedInPaths = null;
|
|
5137
|
+
const writer = createDebouncedWriter(async () => {
|
|
5138
|
+
const optedInPaths = pendingOptedInPaths ?? new Set(state.persistOptIns.optedInPaths());
|
|
5139
|
+
pendingOptedInPaths = null;
|
|
5140
|
+
const adapter = await adapterPromise;
|
|
5141
|
+
if (isDisposed()) return;
|
|
5142
|
+
if (optedInPaths.size === 0) {
|
|
5143
|
+
await adapter.removeItem(key);
|
|
5144
|
+
return;
|
|
4777
5145
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
}
|
|
4790
|
-
function snapshotForm(form) {
|
|
4791
|
-
return structuralSnapshot(form);
|
|
4792
|
-
}
|
|
4793
|
-
function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
|
|
4794
|
-
if (isFileLikeValue(value)) return void 0;
|
|
4795
|
-
if (value === null || typeof value !== "object") return value;
|
|
4796
|
-
if (Array.isArray(value)) {
|
|
4797
|
-
return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
|
|
4798
|
-
}
|
|
4799
|
-
const proto = Object.getPrototypeOf(value);
|
|
4800
|
-
if (proto !== Object.prototype && proto !== null) return value;
|
|
4801
|
-
const out = {};
|
|
4802
|
-
const src = value;
|
|
4803
|
-
for (const key of Object.keys(src)) {
|
|
4804
|
-
const childPath = [...pathSoFar, key];
|
|
4805
|
-
if (isSensitivePath(childPath)) {
|
|
4806
|
-
out[key] = void 0;
|
|
4807
|
-
continue;
|
|
5146
|
+
const rawForm = vue.toRaw(state.form.value);
|
|
5147
|
+
const filteredForm = stripUnacknowledgedSensitiveLeaves(
|
|
5148
|
+
pluckPaths(rawForm, optedInPaths),
|
|
5149
|
+
optedInPaths,
|
|
5150
|
+
state.isSensitivePath
|
|
5151
|
+
);
|
|
5152
|
+
const filteredSchemaErrors = filterErrorsByPaths(state.schemaErrors, optedInPaths);
|
|
5153
|
+
const filteredUserErrors = filterErrorsByPaths(state.userErrors, optedInPaths);
|
|
5154
|
+
const filteredTransientEmpty = /* @__PURE__ */ new Set();
|
|
5155
|
+
for (const tk of state.blankPaths) {
|
|
5156
|
+
if (optedInPaths.has(tk)) filteredTransientEmpty.add(tk);
|
|
4808
5157
|
}
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
}
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
if (typeof BroadcastChannel === "undefined") {
|
|
4842
|
-
return {
|
|
4843
|
-
dispose: () => void 0,
|
|
4844
|
-
lifecycle: () => "established",
|
|
4845
|
-
senderId: "",
|
|
4846
|
-
channelName
|
|
4847
|
-
};
|
|
5158
|
+
const payload = buildPersistedPayload(
|
|
5159
|
+
filteredForm,
|
|
5160
|
+
include,
|
|
5161
|
+
filteredSchemaErrors,
|
|
5162
|
+
filteredUserErrors,
|
|
5163
|
+
filteredTransientEmpty
|
|
5164
|
+
);
|
|
5165
|
+
await adapter.setItem(key, payload);
|
|
5166
|
+
}, debounceMs);
|
|
5167
|
+
const unsubscribeChange = state.onFormChange((_next, meta) => {
|
|
5168
|
+
if (isDisposed() || inFlightFinalFlush !== null) return;
|
|
5169
|
+
if (meta?.crossTab === true) return;
|
|
5170
|
+
if (meta?.persist !== true) return;
|
|
5171
|
+
pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
|
|
5172
|
+
writer.schedule();
|
|
5173
|
+
});
|
|
5174
|
+
const unsubscribeSuccess = clearOnSubmitSuccess ? state.onSubmitSuccess(() => {
|
|
5175
|
+
if (isDisposed()) return;
|
|
5176
|
+
void (async () => {
|
|
5177
|
+
await writer.flush();
|
|
5178
|
+
if (isDisposed()) return;
|
|
5179
|
+
const adapter = await adapterPromise;
|
|
5180
|
+
if (isDisposed()) return;
|
|
5181
|
+
await adapter.removeItem(key);
|
|
5182
|
+
})();
|
|
5183
|
+
}) : () => void 0;
|
|
5184
|
+
const handlePageHide = () => {
|
|
5185
|
+
if (isDisposed()) return;
|
|
5186
|
+
void writer.flush();
|
|
5187
|
+
};
|
|
5188
|
+
if (typeof window !== "undefined") {
|
|
5189
|
+
window.addEventListener("pagehide", handlePageHide);
|
|
4848
5190
|
}
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
return {
|
|
4854
|
-
dispose: () => void 0,
|
|
4855
|
-
lifecycle: () => "established",
|
|
4856
|
-
senderId: "",
|
|
4857
|
-
channelName
|
|
4858
|
-
};
|
|
4859
|
-
}
|
|
4860
|
-
const senderId = generateSenderId();
|
|
4861
|
-
let lifecycle = "joining";
|
|
4862
|
-
let disposed = false;
|
|
4863
|
-
const peerIds = /* @__PURE__ */ new Set();
|
|
4864
|
-
let joinCollectionTimer = null;
|
|
4865
|
-
let snapshotTimeoutTimer = null;
|
|
4866
|
-
let leaderAttempts = 0;
|
|
4867
|
-
let prior = {
|
|
4868
|
-
form: snapshotForm(state.form.value),
|
|
4869
|
-
blankPathsSnapshot: [...state.blankPaths]
|
|
4870
|
-
};
|
|
4871
|
-
function safePost(msg) {
|
|
4872
|
-
if (disposed) return;
|
|
5191
|
+
void (async () => {
|
|
5192
|
+
const adapter = await adapterPromise;
|
|
5193
|
+
if (isDisposed()) return;
|
|
5194
|
+
void cleanupOrphanKeys(adapter, base, key);
|
|
4873
5195
|
try {
|
|
4874
|
-
|
|
5196
|
+
const raw = await adapter.getItem(key);
|
|
5197
|
+
const payload = readPersistedPayload(raw);
|
|
5198
|
+
if (payload === null) {
|
|
5199
|
+
if (raw !== null && raw !== void 0) {
|
|
5200
|
+
await adapter.removeItem(key);
|
|
5201
|
+
}
|
|
5202
|
+
return;
|
|
5203
|
+
}
|
|
5204
|
+
if (isDisposed()) return;
|
|
5205
|
+
const merged = mergeSparseHydration(
|
|
5206
|
+
vue.toRaw(state.form.value),
|
|
5207
|
+
payload.data.form,
|
|
5208
|
+
state.schema
|
|
5209
|
+
);
|
|
5210
|
+
state.applyFormReplacement(merged, { hydration: true });
|
|
5211
|
+
const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
|
|
5212
|
+
for (const k of persistedLeafPaths) {
|
|
5213
|
+
state.blankPaths.delete(k);
|
|
5214
|
+
state.originalBlankPaths.delete(k);
|
|
5215
|
+
}
|
|
5216
|
+
for (const k of payload.data.blankPaths ?? []) {
|
|
5217
|
+
state.blankPaths.add(k);
|
|
5218
|
+
state.originalBlankPaths.add(k);
|
|
5219
|
+
}
|
|
5220
|
+
if (include === "form+errors") {
|
|
5221
|
+
if (payload.data.schemaErrors !== void 0) {
|
|
5222
|
+
const flat = payload.data.schemaErrors.flatMap(([, errs]) => errs);
|
|
5223
|
+
state.setAllSchemaErrors(flat);
|
|
5224
|
+
}
|
|
5225
|
+
if (payload.data.userErrors !== void 0) {
|
|
5226
|
+
const flat = payload.data.userErrors.flatMap(([, errs]) => errs);
|
|
5227
|
+
state.setAllUserErrors(flat);
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
state.scheduleFieldValidation(
|
|
5231
|
+
[],
|
|
5232
|
+
true
|
|
5233
|
+
/* immediate */
|
|
5234
|
+
);
|
|
4875
5235
|
} catch {
|
|
4876
5236
|
}
|
|
4877
|
-
}
|
|
4878
|
-
function
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
const
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
const
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
if (isPathLocallySuppressed(p.path)) continue;
|
|
4899
|
-
if ("value" in p && isFileLikeValue(p.value)) continue;
|
|
4900
|
-
safePatches.push(p);
|
|
4901
|
-
}
|
|
4902
|
-
const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
|
|
4903
|
-
if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
|
|
4904
|
-
prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
|
|
4905
|
-
return;
|
|
5237
|
+
})();
|
|
5238
|
+
async function writePathImmediately(path) {
|
|
5239
|
+
if (isDisposed()) return;
|
|
5240
|
+
await writer.flush();
|
|
5241
|
+
if (isDisposed()) return;
|
|
5242
|
+
const adapter = await adapterPromise;
|
|
5243
|
+
if (isDisposed()) return;
|
|
5244
|
+
const raw = await adapter.getItem(key);
|
|
5245
|
+
const existing = readPersistedPayload(raw);
|
|
5246
|
+
const value = getAtPath(vue.toRaw(state.form.value), path);
|
|
5247
|
+
const nextForm = setAtPath(existing?.data.form ?? {}, path, value);
|
|
5248
|
+
const { key: pathKey } = paths.canonicalizePath(path);
|
|
5249
|
+
const transientSet = new Set(
|
|
5250
|
+
(existing?.data.blankPaths ?? []).filter(
|
|
5251
|
+
(k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
|
|
5252
|
+
)
|
|
5253
|
+
);
|
|
5254
|
+
for (const liveKey of state.blankPaths) {
|
|
5255
|
+
if (liveKey === pathKey || isDescendantPathKey(liveKey, pathKey)) {
|
|
5256
|
+
transientSet.add(liveKey);
|
|
5257
|
+
}
|
|
4906
5258
|
}
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
blankPathsAdded: added,
|
|
4913
|
-
blankPathsRemoved: removed
|
|
4914
|
-
});
|
|
4915
|
-
prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
|
|
4916
|
-
}
|
|
4917
|
-
const unsubscribeChange = state.onFormChange((_next, meta) => {
|
|
4918
|
-
if (disposed) return;
|
|
4919
|
-
if (lifecycle !== "established") return;
|
|
4920
|
-
if (meta?.crossTab === true) return;
|
|
4921
|
-
if (meta?.hydration === true) {
|
|
4922
|
-
refreshPrior();
|
|
5259
|
+
if (include === "form") {
|
|
5260
|
+
await adapter.setItem(
|
|
5261
|
+
key,
|
|
5262
|
+
buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
|
|
5263
|
+
);
|
|
4923
5264
|
return;
|
|
4924
5265
|
}
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
state.
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
function handlePatches(msg) {
|
|
4934
|
-
if (lifecycle !== "established") return;
|
|
4935
|
-
const safePatches = [];
|
|
4936
|
-
for (const p of msg.formPatches) {
|
|
4937
|
-
if (!Array.isArray(p.path)) continue;
|
|
4938
|
-
if (isPathLocallySuppressed(p.path)) continue;
|
|
4939
|
-
if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
|
|
4940
|
-
continue;
|
|
4941
|
-
}
|
|
4942
|
-
safePatches.push(p);
|
|
4943
|
-
}
|
|
4944
|
-
const safeBlankAdded = [];
|
|
4945
|
-
for (const k of msg.blankPathsAdded) {
|
|
4946
|
-
const segs = paths.canonicalizePath(k).segments;
|
|
4947
|
-
if (isPathLocallySuppressed(segs)) continue;
|
|
4948
|
-
safeBlankAdded.push(k);
|
|
5266
|
+
const schemaMap = new Map(existing?.data.schemaErrors ?? []);
|
|
5267
|
+
const userMap = new Map(existing?.data.userErrors ?? []);
|
|
5268
|
+
const currentSchema = state.schemaErrors.get(pathKey);
|
|
5269
|
+
const currentUser = state.userErrors.get(pathKey);
|
|
5270
|
+
if (currentSchema !== void 0 && currentSchema.length > 0) {
|
|
5271
|
+
schemaMap.set(pathKey, [...currentSchema]);
|
|
5272
|
+
} else {
|
|
5273
|
+
schemaMap.delete(pathKey);
|
|
4949
5274
|
}
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
safeBlankRemoved.push(k);
|
|
5275
|
+
if (currentUser !== void 0 && currentUser.length > 0) {
|
|
5276
|
+
userMap.set(pathKey, [...currentUser]);
|
|
5277
|
+
} else {
|
|
5278
|
+
userMap.delete(pathKey);
|
|
4955
5279
|
}
|
|
4956
|
-
|
|
5280
|
+
await adapter.setItem(
|
|
5281
|
+
key,
|
|
5282
|
+
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
5283
|
+
);
|
|
5284
|
+
}
|
|
5285
|
+
async function clearPersistedDraft(path) {
|
|
5286
|
+
if (isDisposed()) return;
|
|
5287
|
+
await writer.flush();
|
|
5288
|
+
if (isDisposed()) return;
|
|
5289
|
+
const adapter = await adapterPromise;
|
|
5290
|
+
if (isDisposed()) return;
|
|
5291
|
+
if (path === void 0) {
|
|
5292
|
+
await adapter.removeItem(key);
|
|
4957
5293
|
return;
|
|
4958
5294
|
}
|
|
4959
|
-
const
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
}
|
|
4967
|
-
} catch {
|
|
4968
|
-
}
|
|
4969
|
-
const nextBlankPaths = new Set(state.blankPaths);
|
|
4970
|
-
for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
|
|
4971
|
-
for (const k of safeBlankAdded) nextBlankPaths.add(k);
|
|
4972
|
-
applyIncomingForm(candidate, [...nextBlankPaths]);
|
|
4973
|
-
}
|
|
4974
|
-
function handleSnapshot(msg) {
|
|
4975
|
-
if (lifecycle !== "joining") return;
|
|
4976
|
-
if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return;
|
|
4977
|
-
if (snapshotTimeoutTimer !== null) {
|
|
4978
|
-
clearTimeout(snapshotTimeoutTimer);
|
|
4979
|
-
snapshotTimeoutTimer = null;
|
|
5295
|
+
const raw = await adapter.getItem(key);
|
|
5296
|
+
const existing = readPersistedPayload(raw);
|
|
5297
|
+
if (existing === null) return;
|
|
5298
|
+
const nextForm = deleteAtPath(existing.data.form, path);
|
|
5299
|
+
if (isEmptyContainer(nextForm)) {
|
|
5300
|
+
await adapter.removeItem(key);
|
|
5301
|
+
return;
|
|
4980
5302
|
}
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
5303
|
+
const { key: pathKey } = paths.canonicalizePath(path);
|
|
5304
|
+
const transientSet = new Set(
|
|
5305
|
+
(existing.data.blankPaths ?? []).filter(
|
|
5306
|
+
(k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
|
|
5307
|
+
)
|
|
5308
|
+
);
|
|
5309
|
+
if (include === "form") {
|
|
5310
|
+
await adapter.setItem(
|
|
5311
|
+
key,
|
|
5312
|
+
buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
|
|
5313
|
+
);
|
|
5314
|
+
return;
|
|
4984
5315
|
}
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
5316
|
+
const schemaErrors = (existing.data.schemaErrors ?? []).filter(([k]) => k !== pathKey);
|
|
5317
|
+
const userErrors = (existing.data.userErrors ?? []).filter(([k]) => k !== pathKey);
|
|
5318
|
+
const schemaMap = new Map(schemaErrors.map(([k, v]) => [k, [...v]]));
|
|
5319
|
+
const userMap = new Map(userErrors.map(([k, v]) => [k, [...v]]));
|
|
5320
|
+
await adapter.setItem(
|
|
5321
|
+
key,
|
|
5322
|
+
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
5323
|
+
);
|
|
4988
5324
|
}
|
|
4989
|
-
function
|
|
4990
|
-
|
|
5325
|
+
function awaitPendingWrites() {
|
|
5326
|
+
if (inFlightFinalFlush !== null) return inFlightFinalFlush;
|
|
5327
|
+
if (isDisposed()) return Promise.resolve();
|
|
5328
|
+
return writer.flush().catch(() => void 0);
|
|
4991
5329
|
}
|
|
4992
|
-
function
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5330
|
+
function dispose() {
|
|
5331
|
+
if (isDisposed() || inFlightFinalFlush !== null) return;
|
|
5332
|
+
unsubscribeChange();
|
|
5333
|
+
unsubscribeSuccess();
|
|
5334
|
+
if (typeof window !== "undefined") {
|
|
5335
|
+
window.removeEventListener("pagehide", handlePageHide);
|
|
5336
|
+
}
|
|
5337
|
+
inFlightFinalFlush = writer.flush().catch(() => void 0).finally(() => {
|
|
5338
|
+
disposed = true;
|
|
5339
|
+
inFlightFinalFlush = null;
|
|
5000
5340
|
});
|
|
5001
5341
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
switch (msg.kind) {
|
|
5009
|
-
case "hello":
|
|
5010
|
-
if (lifecycle !== "established") return;
|
|
5011
|
-
respondToHello();
|
|
5012
|
-
break;
|
|
5013
|
-
case "announce":
|
|
5014
|
-
if (lifecycle === "joining") peerIds.add(msg.senderId);
|
|
5015
|
-
break;
|
|
5016
|
-
case "requestSnapshot":
|
|
5017
|
-
if (lifecycle !== "established") return;
|
|
5018
|
-
if (msg.targetId !== senderId) return;
|
|
5019
|
-
respondToSnapshotRequest();
|
|
5020
|
-
break;
|
|
5021
|
-
case "snapshot":
|
|
5022
|
-
handleSnapshot(msg);
|
|
5023
|
-
break;
|
|
5024
|
-
case "patches":
|
|
5025
|
-
handlePatches(msg);
|
|
5026
|
-
break;
|
|
5027
|
-
}
|
|
5342
|
+
return {
|
|
5343
|
+
wiredConfig: config,
|
|
5344
|
+
writePathImmediately,
|
|
5345
|
+
clearPersistedDraft,
|
|
5346
|
+
awaitPendingWrites,
|
|
5347
|
+
dispose
|
|
5028
5348
|
};
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5349
|
+
}
|
|
5350
|
+
function isEmptyContainer(value) {
|
|
5351
|
+
if (value === void 0 || value === null) return true;
|
|
5352
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
5353
|
+
if (isPlainRecord(value)) return Object.keys(value).length === 0;
|
|
5354
|
+
return false;
|
|
5355
|
+
}
|
|
5356
|
+
function collectPersistedLeafPaths(form) {
|
|
5357
|
+
const out = [];
|
|
5358
|
+
walk(form, []);
|
|
5359
|
+
return out;
|
|
5360
|
+
function walk(node, prefix) {
|
|
5361
|
+
if (Array.isArray(node)) {
|
|
5362
|
+
for (let i = 0; i < node.length; i++) {
|
|
5363
|
+
walk(node[i], [...prefix, i]);
|
|
5364
|
+
}
|
|
5034
5365
|
return;
|
|
5035
5366
|
}
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
leaderAttempts++;
|
|
5040
|
-
safePost({
|
|
5041
|
-
v: PROTOCOL_VERSION,
|
|
5042
|
-
kind: "requestSnapshot",
|
|
5043
|
-
senderId,
|
|
5044
|
-
targetId: leaderId
|
|
5045
|
-
});
|
|
5046
|
-
snapshotTimeoutTimer = setTimeout(() => {
|
|
5047
|
-
snapshotTimeoutTimer = null;
|
|
5048
|
-
if (disposed) return;
|
|
5049
|
-
if (lifecycle === "established") return;
|
|
5050
|
-
if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
|
|
5051
|
-
lifecycle = "established";
|
|
5052
|
-
refreshPrior();
|
|
5053
|
-
return;
|
|
5367
|
+
if (isPlainRecord(node)) {
|
|
5368
|
+
for (const key of Object.keys(node)) {
|
|
5369
|
+
walk(node[key], [...prefix, key]);
|
|
5054
5370
|
}
|
|
5055
|
-
|
|
5056
|
-
}
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
|
|
5060
|
-
joinCollectionTimer = setTimeout(() => {
|
|
5061
|
-
joinCollectionTimer = null;
|
|
5062
|
-
if (disposed) return;
|
|
5063
|
-
if (lifecycle === "established") return;
|
|
5064
|
-
electLeaderAndRequest();
|
|
5065
|
-
}, JOIN_COLLECTION_WINDOW_MS);
|
|
5371
|
+
return;
|
|
5372
|
+
}
|
|
5373
|
+
if (prefix.length === 0) return;
|
|
5374
|
+
out.push(paths.canonicalizePath(prefix).key);
|
|
5066
5375
|
}
|
|
5067
|
-
joinFlow();
|
|
5068
|
-
return {
|
|
5069
|
-
dispose: () => {
|
|
5070
|
-
if (disposed) return;
|
|
5071
|
-
disposed = true;
|
|
5072
|
-
if (joinCollectionTimer !== null) {
|
|
5073
|
-
clearTimeout(joinCollectionTimer);
|
|
5074
|
-
joinCollectionTimer = null;
|
|
5075
|
-
}
|
|
5076
|
-
if (snapshotTimeoutTimer !== null) {
|
|
5077
|
-
clearTimeout(snapshotTimeoutTimer);
|
|
5078
|
-
snapshotTimeoutTimer = null;
|
|
5079
|
-
}
|
|
5080
|
-
unsubscribeChange();
|
|
5081
|
-
try {
|
|
5082
|
-
channel.close();
|
|
5083
|
-
} catch {
|
|
5084
|
-
}
|
|
5085
|
-
},
|
|
5086
|
-
lifecycle: () => lifecycle,
|
|
5087
|
-
senderId,
|
|
5088
|
-
channelName
|
|
5089
|
-
};
|
|
5090
5376
|
}
|
|
5091
|
-
|
|
5377
|
+
function isDescendantPathKey(candidate, ancestor) {
|
|
5378
|
+
if (candidate.length <= ancestor.length) return false;
|
|
5379
|
+
if (!ancestor.endsWith("]")) return false;
|
|
5380
|
+
const childPrefix = `${ancestor.slice(0, -1)},`;
|
|
5381
|
+
return candidate.startsWith(childPrefix);
|
|
5382
|
+
}
|
|
5092
5383
|
|
|
5093
|
-
const
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5384
|
+
const PROTOCOL_VERSION = 1;
|
|
5385
|
+
const JOIN_COLLECTION_WINDOW_MS = 50;
|
|
5386
|
+
const SNAPSHOT_TIMEOUT_MS = 200;
|
|
5387
|
+
const MAX_LEADER_ATTEMPTS = 3;
|
|
5388
|
+
const SNAPSHOT_RESPONSE_MIN_INTERVAL_MS = 500;
|
|
5389
|
+
function isFileLikeValue(value) {
|
|
5390
|
+
if (typeof File !== "undefined" && value instanceof File) return true;
|
|
5391
|
+
if (typeof Blob !== "undefined" && value instanceof Blob) return true;
|
|
5392
|
+
return false;
|
|
5100
5393
|
}
|
|
5101
|
-
function
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5394
|
+
function isInboundShapeAcceptable(schema, path, value) {
|
|
5395
|
+
if (isFileLikeValue(value)) return false;
|
|
5396
|
+
let kind;
|
|
5397
|
+
if (Array.isArray(value)) {
|
|
5398
|
+
kind = "array";
|
|
5399
|
+
} else if (value !== null && typeof value === "object" && isPlainRecord(value)) {
|
|
5400
|
+
kind = "object";
|
|
5401
|
+
} else {
|
|
5402
|
+
kind = slimKindOf(value);
|
|
5403
|
+
}
|
|
5404
|
+
const accepted = schema.getSlimPrimitiveTypesAtPath(path);
|
|
5405
|
+
if (!accepted.has(kind)) return false;
|
|
5406
|
+
if (Array.isArray(value)) {
|
|
5407
|
+
for (let i = 0; i < value.length; i++) {
|
|
5408
|
+
if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
|
|
5409
|
+
}
|
|
5410
|
+
return true;
|
|
5411
|
+
}
|
|
5412
|
+
if (isPlainRecord(value)) {
|
|
5413
|
+
for (const key of Object.keys(value)) {
|
|
5414
|
+
if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false;
|
|
5415
|
+
}
|
|
5416
|
+
return true;
|
|
5109
5417
|
}
|
|
5418
|
+
return true;
|
|
5110
5419
|
}
|
|
5111
|
-
function
|
|
5112
|
-
|
|
5420
|
+
function diffBlankPaths(prev, curr) {
|
|
5421
|
+
const added = [];
|
|
5422
|
+
const removed = [];
|
|
5423
|
+
const prevSet = new Set(prev);
|
|
5424
|
+
for (const k of curr) if (!prevSet.has(k)) added.push(k);
|
|
5425
|
+
for (const k of prev) if (!curr.has(k)) removed.push(k);
|
|
5426
|
+
return { added, removed };
|
|
5113
5427
|
}
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5428
|
+
function snapshotForm(form) {
|
|
5429
|
+
return structuralSnapshot(form);
|
|
5430
|
+
}
|
|
5431
|
+
function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
|
|
5432
|
+
if (isFileLikeValue(value)) return void 0;
|
|
5433
|
+
if (value === null || typeof value !== "object") return value;
|
|
5434
|
+
if (Array.isArray(value)) {
|
|
5435
|
+
return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
|
|
5118
5436
|
}
|
|
5119
|
-
|
|
5437
|
+
const proto = Object.getPrototypeOf(value);
|
|
5438
|
+
if (proto !== Object.prototype && proto !== null) return value;
|
|
5439
|
+
const out = {};
|
|
5440
|
+
const src = value;
|
|
5441
|
+
for (const key of Object.keys(src)) {
|
|
5442
|
+
const childPath = [...pathSoFar, key];
|
|
5443
|
+
if (isSensitivePath(childPath)) {
|
|
5444
|
+
safeAssign(out, key, void 0);
|
|
5445
|
+
continue;
|
|
5446
|
+
}
|
|
5447
|
+
safeAssign(out, key, stripSensitivePathsDeep(src[key], childPath, isSensitivePath));
|
|
5448
|
+
}
|
|
5449
|
+
return out;
|
|
5120
5450
|
}
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5451
|
+
function isStringArray(value) {
|
|
5452
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
5453
|
+
}
|
|
5454
|
+
function isPatchArray(value) {
|
|
5455
|
+
return Array.isArray(value) && value.every(
|
|
5456
|
+
(p) => p !== null && typeof p === "object" && Array.isArray(p.path)
|
|
5457
|
+
);
|
|
5458
|
+
}
|
|
5459
|
+
function isValidSyncMessage(data) {
|
|
5460
|
+
if (data === null || typeof data !== "object") return false;
|
|
5461
|
+
const m = data;
|
|
5462
|
+
if (m["v"] !== PROTOCOL_VERSION) return false;
|
|
5463
|
+
if (typeof m["senderId"] !== "string") return false;
|
|
5464
|
+
if (typeof m["kind"] !== "string") return false;
|
|
5465
|
+
switch (m["kind"]) {
|
|
5466
|
+
case "hello":
|
|
5467
|
+
case "announce":
|
|
5468
|
+
return true;
|
|
5469
|
+
case "requestSnapshot":
|
|
5470
|
+
return typeof m["targetId"] === "string";
|
|
5471
|
+
case "snapshot":
|
|
5472
|
+
return isStringArray(m["blankPaths"]) && "form" in m;
|
|
5473
|
+
case "patches":
|
|
5474
|
+
return isPatchArray(m["formPatches"]) && isStringArray(m["blankPathsAdded"]) && isStringArray(m["blankPathsRemoved"]);
|
|
5475
|
+
default:
|
|
5476
|
+
return false;
|
|
5125
5477
|
}
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
const { defaultValues: _droppedDefaults, ...configWithoutDefaults } = configuration;
|
|
5133
|
-
const trichotomyOverride = materialisedDefaults === void 0 ? configWithoutDefaults : { ...configWithoutDefaults, defaultValues: materialisedDefaults };
|
|
5134
|
-
const merged = mergeWithDefaults(registry.defaults, trichotomyOverride);
|
|
5135
|
-
const maxRecursionDepth = normalizeNumericOption({
|
|
5136
|
-
value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
|
|
5137
|
-
source: "useForm.maxRecursionDepth",
|
|
5138
|
-
allowInfinity: true,
|
|
5139
|
-
min: 0,
|
|
5140
|
-
defaultValue: DEFAULT_MAX_RECURSION_DEPTH
|
|
5141
|
-
});
|
|
5142
|
-
const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
|
|
5143
|
-
if (configuration.persist !== void 0 && configuration.key === void 0) {
|
|
5144
|
-
throw new paths.AnonPersistError({
|
|
5145
|
-
cause: "no-key",
|
|
5146
|
-
schemaFields: extractSchemaFields(resolvedSchema),
|
|
5147
|
-
callSite: paths.captureUserCallSite()
|
|
5148
|
-
});
|
|
5478
|
+
}
|
|
5479
|
+
function generateSenderId() {
|
|
5480
|
+
try {
|
|
5481
|
+
return globalThis.crypto.randomUUID();
|
|
5482
|
+
} catch {
|
|
5483
|
+
return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
|
|
5149
5484
|
}
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5485
|
+
}
|
|
5486
|
+
function createMultiTabSyncModule(state, channelName, options) {
|
|
5487
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
5488
|
+
return {
|
|
5489
|
+
dispose: () => void 0,
|
|
5490
|
+
lifecycle: () => "established",
|
|
5491
|
+
senderId: "",
|
|
5492
|
+
channelName
|
|
5493
|
+
};
|
|
5154
5494
|
}
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5495
|
+
let channel;
|
|
5496
|
+
try {
|
|
5497
|
+
channel = new BroadcastChannel(channelName);
|
|
5498
|
+
} catch {
|
|
5499
|
+
return {
|
|
5500
|
+
dispose: () => void 0,
|
|
5501
|
+
lifecycle: () => "established",
|
|
5502
|
+
senderId: "",
|
|
5503
|
+
channelName
|
|
5504
|
+
};
|
|
5159
5505
|
}
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5506
|
+
const senderId = generateSenderId();
|
|
5507
|
+
let lifecycle = "joining";
|
|
5508
|
+
let disposed = false;
|
|
5509
|
+
const peerIds = /* @__PURE__ */ new Set();
|
|
5510
|
+
const lastSnapshotResponseAt = /* @__PURE__ */ new Map();
|
|
5511
|
+
let joinCollectionTimer = null;
|
|
5512
|
+
let snapshotTimeoutTimer = null;
|
|
5513
|
+
let leaderAttempts = 0;
|
|
5514
|
+
let prior = {
|
|
5515
|
+
form: snapshotForm(state.form.value),
|
|
5516
|
+
blankPathsSnapshot: [...state.blankPaths]
|
|
5517
|
+
};
|
|
5518
|
+
function safePost(msg) {
|
|
5519
|
+
if (disposed) return;
|
|
5520
|
+
try {
|
|
5521
|
+
channel.postMessage(msg);
|
|
5522
|
+
} catch {
|
|
5174
5523
|
}
|
|
5175
5524
|
}
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5525
|
+
function refreshPrior() {
|
|
5526
|
+
prior = {
|
|
5527
|
+
form: snapshotForm(state.form.value),
|
|
5528
|
+
blankPathsSnapshot: [...state.blankPaths]
|
|
5529
|
+
};
|
|
5179
5530
|
}
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
state.registerDrain(() => persistenceModule.awaitPendingWrites());
|
|
5197
|
-
state.registerCleanup(() => persistenceModule.dispose());
|
|
5198
|
-
}
|
|
5199
|
-
} else {
|
|
5200
|
-
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
5531
|
+
function isPathLocallySuppressed(path) {
|
|
5532
|
+
if (options.isSensitivePath(path)) return true;
|
|
5533
|
+
const { key } = paths.canonicalizePath([...path]);
|
|
5534
|
+
if (options.noSyncPaths.has(key)) return true;
|
|
5535
|
+
return false;
|
|
5536
|
+
}
|
|
5537
|
+
function postPatches() {
|
|
5538
|
+
if (lifecycle !== "established") return;
|
|
5539
|
+
const next = snapshotForm(state.form.value);
|
|
5540
|
+
const rawPatches = [];
|
|
5541
|
+
diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
|
|
5542
|
+
const safePatches = [];
|
|
5543
|
+
for (const p of rawPatches) {
|
|
5544
|
+
if (isPathLocallySuppressed(p.path)) continue;
|
|
5545
|
+
if ("value" in p && isFileLikeValue(p.value)) continue;
|
|
5546
|
+
safePatches.push(p);
|
|
5201
5547
|
}
|
|
5548
|
+
const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
|
|
5549
|
+
if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
|
|
5550
|
+
prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
|
|
5551
|
+
return;
|
|
5552
|
+
}
|
|
5553
|
+
safePost({
|
|
5554
|
+
v: PROTOCOL_VERSION,
|
|
5555
|
+
kind: "patches",
|
|
5556
|
+
senderId,
|
|
5557
|
+
formPatches: safePatches,
|
|
5558
|
+
blankPathsAdded: added,
|
|
5559
|
+
blankPathsRemoved: removed
|
|
5560
|
+
});
|
|
5561
|
+
prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
|
|
5202
5562
|
}
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
if (
|
|
5207
|
-
|
|
5563
|
+
const unsubscribeChange = state.onFormChange((_next, meta) => {
|
|
5564
|
+
if (disposed) return;
|
|
5565
|
+
if (lifecycle !== "established") return;
|
|
5566
|
+
if (meta?.crossTab === true) return;
|
|
5567
|
+
if (meta?.hydration === true) {
|
|
5568
|
+
refreshPrior();
|
|
5569
|
+
return;
|
|
5570
|
+
}
|
|
5571
|
+
postPatches();
|
|
5572
|
+
});
|
|
5573
|
+
function applyIncomingForm(form, blankPaths) {
|
|
5574
|
+
state.blankPaths.clear();
|
|
5575
|
+
for (const k of blankPaths) state.blankPaths.add(k);
|
|
5576
|
+
state.applyFormReplacement(form, { crossTab: true, persist: false });
|
|
5577
|
+
refreshPrior();
|
|
5578
|
+
}
|
|
5579
|
+
function handlePatches(msg) {
|
|
5580
|
+
if (lifecycle !== "established") return;
|
|
5581
|
+
const safePatches = [];
|
|
5582
|
+
for (const p of msg.formPatches) {
|
|
5583
|
+
if (!Array.isArray(p.path)) continue;
|
|
5584
|
+
if (isPathLocallySuppressed(p.path)) continue;
|
|
5585
|
+
if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
|
|
5586
|
+
continue;
|
|
5587
|
+
}
|
|
5588
|
+
safePatches.push(p);
|
|
5589
|
+
}
|
|
5590
|
+
const safeBlankAdded = [];
|
|
5591
|
+
for (const k of msg.blankPathsAdded) {
|
|
5592
|
+
const segs = paths.canonicalizePath(k).segments;
|
|
5593
|
+
if (isPathLocallySuppressed(segs)) continue;
|
|
5594
|
+
safeBlankAdded.push(k);
|
|
5595
|
+
}
|
|
5596
|
+
const safeBlankRemoved = [];
|
|
5597
|
+
for (const k of msg.blankPathsRemoved) {
|
|
5598
|
+
const segs = paths.canonicalizePath(k).segments;
|
|
5599
|
+
if (isPathLocallySuppressed(segs)) continue;
|
|
5600
|
+
safeBlankRemoved.push(k);
|
|
5601
|
+
}
|
|
5602
|
+
if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
|
|
5603
|
+
return;
|
|
5604
|
+
}
|
|
5605
|
+
const candidate = applyPatchesForward(state.form.value, safePatches);
|
|
5606
|
+
try {
|
|
5607
|
+
options.validateForm(state.form.value);
|
|
5208
5608
|
try {
|
|
5209
|
-
|
|
5609
|
+
options.validateForm(candidate);
|
|
5210
5610
|
} catch {
|
|
5211
|
-
|
|
5212
|
-
}
|
|
5213
|
-
if (channelName !== null) {
|
|
5214
|
-
const syncModule = createMultiTabSyncModule(state, channelName, {
|
|
5215
|
-
isSensitivePath: state.isSensitivePath,
|
|
5216
|
-
noSyncPaths: state.noSyncPaths,
|
|
5217
|
-
validateForm: (form) => {
|
|
5218
|
-
const result = state.schema.validateAtPath(form, void 0, { sync: true });
|
|
5219
|
-
if (result instanceof Promise) return;
|
|
5220
|
-
if (!result.success) {
|
|
5221
|
-
throw new Error("attaform multi-tab sync: post-apply schema validation failed");
|
|
5222
|
-
}
|
|
5223
|
-
}
|
|
5224
|
-
});
|
|
5225
|
-
state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
|
|
5226
|
-
state.registerCleanup(() => syncModule.dispose());
|
|
5611
|
+
return;
|
|
5227
5612
|
}
|
|
5228
|
-
}
|
|
5229
|
-
warnOnceInsecureContext("multiTab");
|
|
5613
|
+
} catch {
|
|
5230
5614
|
}
|
|
5615
|
+
const nextBlankPaths = new Set(state.blankPaths);
|
|
5616
|
+
for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
|
|
5617
|
+
for (const k of safeBlankAdded) nextBlankPaths.add(k);
|
|
5618
|
+
applyIncomingForm(candidate, [...nextBlankPaths]);
|
|
5231
5619
|
}
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
state.
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
if (merged.onInvalidSubmit !== void 0) {
|
|
5247
|
-
apiOptions.onInvalidSubmit = merged.onInvalidSubmit;
|
|
5620
|
+
function handleSnapshot(msg) {
|
|
5621
|
+
if (lifecycle !== "joining") return;
|
|
5622
|
+
if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return;
|
|
5623
|
+
if (snapshotTimeoutTimer !== null) {
|
|
5624
|
+
clearTimeout(snapshotTimeoutTimer);
|
|
5625
|
+
snapshotTimeoutTimer = null;
|
|
5626
|
+
}
|
|
5627
|
+
if (joinCollectionTimer !== null) {
|
|
5628
|
+
clearTimeout(joinCollectionTimer);
|
|
5629
|
+
joinCollectionTimer = null;
|
|
5630
|
+
}
|
|
5631
|
+
applyIncomingForm(msg.form, msg.blankPaths);
|
|
5632
|
+
lifecycle = "established";
|
|
5633
|
+
peerIds.clear();
|
|
5248
5634
|
}
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
apiOptions.history = history;
|
|
5635
|
+
function respondToHello() {
|
|
5636
|
+
safePost({ v: PROTOCOL_VERSION, kind: "announce", senderId });
|
|
5252
5637
|
}
|
|
5253
|
-
|
|
5254
|
-
|
|
5638
|
+
function respondToSnapshotRequest(requesterId) {
|
|
5639
|
+
const now = Date.now();
|
|
5640
|
+
const last = lastSnapshotResponseAt.get(requesterId);
|
|
5641
|
+
if (last !== void 0 && now - last < SNAPSHOT_RESPONSE_MIN_INTERVAL_MS) return;
|
|
5642
|
+
lastSnapshotResponseAt.set(requesterId, now);
|
|
5643
|
+
const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath);
|
|
5644
|
+
safePost({
|
|
5645
|
+
v: PROTOCOL_VERSION,
|
|
5646
|
+
kind: "snapshot",
|
|
5647
|
+
senderId,
|
|
5648
|
+
form: scrubbedForm,
|
|
5649
|
+
blankPaths: [...state.blankPaths]
|
|
5650
|
+
});
|
|
5255
5651
|
}
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5652
|
+
channel.onmessage = (event) => {
|
|
5653
|
+
if (disposed) return;
|
|
5654
|
+
try {
|
|
5655
|
+
const data = event.data;
|
|
5656
|
+
if (!isValidSyncMessage(data)) return;
|
|
5657
|
+
const msg = data;
|
|
5658
|
+
if (msg.senderId === senderId) return;
|
|
5659
|
+
switch (msg.kind) {
|
|
5660
|
+
case "hello":
|
|
5661
|
+
if (lifecycle !== "established") return;
|
|
5662
|
+
respondToHello();
|
|
5663
|
+
break;
|
|
5664
|
+
case "announce":
|
|
5665
|
+
if (lifecycle === "joining") peerIds.add(msg.senderId);
|
|
5666
|
+
break;
|
|
5667
|
+
case "requestSnapshot":
|
|
5668
|
+
if (lifecycle !== "established") return;
|
|
5669
|
+
if (msg.targetId !== senderId) return;
|
|
5670
|
+
respondToSnapshotRequest(msg.senderId);
|
|
5671
|
+
break;
|
|
5672
|
+
case "snapshot":
|
|
5673
|
+
handleSnapshot(msg);
|
|
5674
|
+
break;
|
|
5675
|
+
case "patches":
|
|
5676
|
+
handlePatches(msg);
|
|
5677
|
+
break;
|
|
5678
|
+
}
|
|
5679
|
+
} catch {
|
|
5680
|
+
}
|
|
5681
|
+
};
|
|
5682
|
+
function electLeaderAndRequest() {
|
|
5683
|
+
if (disposed) return;
|
|
5684
|
+
if (peerIds.size === 0) {
|
|
5685
|
+
lifecycle = "established";
|
|
5686
|
+
refreshPrior();
|
|
5687
|
+
return;
|
|
5688
|
+
}
|
|
5689
|
+
const sorted = [...peerIds].sort();
|
|
5690
|
+
const leaderId = sorted[0];
|
|
5691
|
+
peerIds.delete(leaderId);
|
|
5692
|
+
leaderAttempts++;
|
|
5693
|
+
safePost({
|
|
5694
|
+
v: PROTOCOL_VERSION,
|
|
5695
|
+
kind: "requestSnapshot",
|
|
5696
|
+
senderId,
|
|
5697
|
+
targetId: leaderId
|
|
5698
|
+
});
|
|
5699
|
+
snapshotTimeoutTimer = setTimeout(() => {
|
|
5700
|
+
snapshotTimeoutTimer = null;
|
|
5701
|
+
if (disposed) return;
|
|
5702
|
+
if (lifecycle === "established") return;
|
|
5703
|
+
if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
|
|
5704
|
+
lifecycle = "established";
|
|
5705
|
+
refreshPrior();
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
electLeaderAndRequest();
|
|
5709
|
+
}, SNAPSHOT_TIMEOUT_MS);
|
|
5259
5710
|
}
|
|
5260
|
-
|
|
5261
|
-
|
|
5711
|
+
function joinFlow() {
|
|
5712
|
+
safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
|
|
5713
|
+
joinCollectionTimer = setTimeout(() => {
|
|
5714
|
+
joinCollectionTimer = null;
|
|
5715
|
+
if (disposed) return;
|
|
5716
|
+
if (lifecycle === "established") return;
|
|
5717
|
+
electLeaderAndRequest();
|
|
5718
|
+
}, JOIN_COLLECTION_WINDOW_MS);
|
|
5262
5719
|
}
|
|
5263
|
-
|
|
5720
|
+
joinFlow();
|
|
5721
|
+
return {
|
|
5722
|
+
dispose: () => {
|
|
5723
|
+
if (disposed) return;
|
|
5724
|
+
disposed = true;
|
|
5725
|
+
if (joinCollectionTimer !== null) {
|
|
5726
|
+
clearTimeout(joinCollectionTimer);
|
|
5727
|
+
joinCollectionTimer = null;
|
|
5728
|
+
}
|
|
5729
|
+
if (snapshotTimeoutTimer !== null) {
|
|
5730
|
+
clearTimeout(snapshotTimeoutTimer);
|
|
5731
|
+
snapshotTimeoutTimer = null;
|
|
5732
|
+
}
|
|
5733
|
+
unsubscribeChange();
|
|
5734
|
+
try {
|
|
5735
|
+
channel.close();
|
|
5736
|
+
} catch {
|
|
5737
|
+
}
|
|
5738
|
+
},
|
|
5739
|
+
lifecycle: () => lifecycle,
|
|
5740
|
+
senderId,
|
|
5741
|
+
channelName
|
|
5742
|
+
};
|
|
5743
|
+
}
|
|
5744
|
+
const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
|
|
5745
|
+
|
|
5746
|
+
const warned = /* @__PURE__ */ new Set();
|
|
5747
|
+
function warnOnceInsecureContext(feature) {
|
|
5748
|
+
if (!paths.__DEV__) return;
|
|
5749
|
+
if (warned.has(feature)) return;
|
|
5750
|
+
warned.add(feature);
|
|
5751
|
+
const message = featureMessage(feature);
|
|
5752
|
+
console.warn(`[attaform] ${message}`);
|
|
5753
|
+
}
|
|
5754
|
+
function featureMessage(feature) {
|
|
5755
|
+
switch (feature) {
|
|
5756
|
+
case "multiTab":
|
|
5757
|
+
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.";
|
|
5758
|
+
case "persist:local":
|
|
5759
|
+
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.";
|
|
5760
|
+
case "persist:session":
|
|
5761
|
+
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.";
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
function isSecureContext() {
|
|
5765
|
+
return typeof window !== "undefined" && window.isSecureContext === true;
|
|
5766
|
+
}
|
|
5767
|
+
|
|
5768
|
+
function resolveTrichotomy(input) {
|
|
5769
|
+
if (typeof input === "function") {
|
|
5770
|
+
return { kind: "async", factory: input };
|
|
5771
|
+
}
|
|
5772
|
+
return { kind: "sync", value: input };
|
|
5773
|
+
}
|
|
5774
|
+
|
|
5775
|
+
function useAbstractForm(configuration, options) {
|
|
5776
|
+
if (configuration === void 0 || configuration === null || configuration.schema === void 0) {
|
|
5777
|
+
throw new paths.InvalidUseFormConfigError();
|
|
5778
|
+
}
|
|
5779
|
+
const key = resolveFormKey(configuration.key);
|
|
5780
|
+
const instance = vue.getCurrentInstance();
|
|
5781
|
+
if (instance !== null) paths.ensureAttaformInstalled(instance.appContext.app);
|
|
5782
|
+
const registry = options?.registry ?? paths.useRegistry();
|
|
5783
|
+
const resolvedDefaults = resolveTrichotomy(configuration.defaultValues);
|
|
5784
|
+
const materialisedDefaults = resolvedDefaults.kind === "sync" ? resolvedDefaults.value : void 0;
|
|
5785
|
+
const { defaultValues: _droppedDefaults, ...configWithoutDefaults } = configuration;
|
|
5786
|
+
const trichotomyOverride = materialisedDefaults === void 0 ? configWithoutDefaults : { ...configWithoutDefaults, defaultValues: materialisedDefaults };
|
|
5787
|
+
const merged = mergeWithDefaults(registry.defaults, trichotomyOverride);
|
|
5788
|
+
const maxRecursionDepth = normalizeNumericOption({
|
|
5789
|
+
value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
|
|
5790
|
+
source: "useForm.maxRecursionDepth",
|
|
5791
|
+
allowInfinity: true,
|
|
5792
|
+
min: 0,
|
|
5793
|
+
defaultValue: DEFAULT_MAX_RECURSION_DEPTH
|
|
5794
|
+
});
|
|
5795
|
+
const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
|
|
5796
|
+
if (configuration.persist !== void 0 && configuration.key === void 0) {
|
|
5797
|
+
throw new paths.AnonPersistError({
|
|
5798
|
+
cause: "no-key",
|
|
5799
|
+
schemaFields: extractSchemaFields(resolvedSchema),
|
|
5800
|
+
callSite: paths.captureUserCallSite()
|
|
5801
|
+
});
|
|
5802
|
+
}
|
|
5803
|
+
const existing = registry.forms.get(key);
|
|
5804
|
+
if (paths.__DEV__ && existing !== void 0) {
|
|
5805
|
+
warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
|
|
5806
|
+
warnOnPersistDivergence(key, existing, configuration.persist);
|
|
5807
|
+
}
|
|
5808
|
+
const hadPendingHydration = registry.pendingHydration.has(key);
|
|
5809
|
+
const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
|
|
5810
|
+
if (existing !== void 0) ; else if (resolvedDefaults.kind === "sync") {
|
|
5811
|
+
state.defaultsResolved.value = true;
|
|
5812
|
+
}
|
|
5813
|
+
if (existing === void 0 && resolvedDefaults.kind === "async") {
|
|
5814
|
+
const factory = resolvedDefaults.factory;
|
|
5815
|
+
state.defaultValuesFactory.value = factory;
|
|
5816
|
+
if (hadPendingHydration) {
|
|
5817
|
+
state.hydrating.value = false;
|
|
5818
|
+
state.defaultsResolved.value = true;
|
|
5819
|
+
} else if (registry.ssr) {
|
|
5820
|
+
if (configuration.__ssrAccessed === true) {
|
|
5821
|
+
registry.enqueuePrefetch(key);
|
|
5822
|
+
}
|
|
5823
|
+
vue.onServerPrefetch(() => {
|
|
5824
|
+
if (!registry.shouldPrefetch(key)) return;
|
|
5825
|
+
return state.activate();
|
|
5826
|
+
});
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
if (vue.getCurrentScope() !== void 0) {
|
|
5830
|
+
const releaseConsumer = registry.trackConsumer(key);
|
|
5831
|
+
vue.onScopeDispose(releaseConsumer);
|
|
5832
|
+
}
|
|
5833
|
+
const persistDisabledByAnonRule = merged.persist !== void 0 && enforceAnonPersistRule(state.formKey, registry.ssr);
|
|
5834
|
+
if (existing === void 0 && !registry.ssr) {
|
|
5835
|
+
if (merged.persist !== void 0 && !persistDisabledByAnonRule) {
|
|
5836
|
+
const resolvedPersist = normalizePersistConfig(merged.persist);
|
|
5837
|
+
const storageKind = resolvedPersist.storage;
|
|
5838
|
+
const isBuiltinStorage = typeof storageKind === "string";
|
|
5839
|
+
const secureContextOk = !isBuiltinStorage || isSecureContext();
|
|
5840
|
+
if (!secureContextOk) {
|
|
5841
|
+
const feature = storageKind === "session" ? "persist:session" : "persist:local";
|
|
5842
|
+
warnOnceInsecureContext(feature);
|
|
5843
|
+
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
5844
|
+
} else {
|
|
5845
|
+
const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
|
|
5846
|
+
void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
|
|
5847
|
+
const persistenceModule = wirePersistence(state, resolvedPersist);
|
|
5848
|
+
state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
|
|
5849
|
+
state.registerDrain(() => persistenceModule.awaitPendingWrites());
|
|
5850
|
+
state.registerCleanup(() => persistenceModule.dispose());
|
|
5851
|
+
}
|
|
5852
|
+
} else {
|
|
5853
|
+
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
if (existing === void 0 && merged.multiTab === true && configuration.key !== void 0 && !registry.ssr) {
|
|
5857
|
+
const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
|
|
5858
|
+
const secureContext = isSecureContext();
|
|
5859
|
+
if (hasBroadcastChannel && secureContext) {
|
|
5860
|
+
let channelName;
|
|
5861
|
+
try {
|
|
5862
|
+
channelName = `attaform:sync:${state.formKey}:${hashStableString(state.schema.fingerprint())}`;
|
|
5863
|
+
} catch {
|
|
5864
|
+
channelName = null;
|
|
5865
|
+
}
|
|
5866
|
+
if (channelName !== null) {
|
|
5867
|
+
const syncModule = createMultiTabSyncModule(state, channelName, {
|
|
5868
|
+
isSensitivePath: state.isSensitivePath,
|
|
5869
|
+
noSyncPaths: state.noSyncPaths,
|
|
5870
|
+
validateForm: (form) => {
|
|
5871
|
+
const result = state.schema.validateAtPath(form, void 0, { sync: true });
|
|
5872
|
+
if (result instanceof Promise) return;
|
|
5873
|
+
if (!result.success) {
|
|
5874
|
+
throw new Error("attaform multi-tab sync: post-apply schema validation failed");
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
});
|
|
5878
|
+
state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
|
|
5879
|
+
state.registerCleanup(() => syncModule.dispose());
|
|
5880
|
+
}
|
|
5881
|
+
} else if (hasBroadcastChannel && !secureContext) {
|
|
5882
|
+
warnOnceInsecureContext("multiTab");
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
if (existing === void 0 && merged.history !== void 0) {
|
|
5886
|
+
const historyModule = createHistoryModule(state, merged.history);
|
|
5887
|
+
state.modules.set(HISTORY_MODULE_KEY, historyModule);
|
|
5888
|
+
state.registerCleanup(() => historyModule.dispose());
|
|
5889
|
+
}
|
|
5890
|
+
if (configuration.key === void 0) {
|
|
5891
|
+
recordAmbientProvide(registry.ssr);
|
|
5892
|
+
vue.provide(paths.kFormContext, state);
|
|
5893
|
+
}
|
|
5894
|
+
const formInstanceId = vue.getCurrentInstance() !== null ? vue.useId() : `atta:form-instance:${formInstanceCounter++}`;
|
|
5895
|
+
if (vue.getCurrentInstance() !== null) {
|
|
5896
|
+
vue.provide(paths.kFormInstanceId, formInstanceId);
|
|
5897
|
+
}
|
|
5898
|
+
const apiOptions = {};
|
|
5899
|
+
if (merged.onInvalidSubmit !== void 0) {
|
|
5900
|
+
apiOptions.onInvalidSubmit = merged.onInvalidSubmit;
|
|
5901
|
+
}
|
|
5902
|
+
const history = state.modules.get(HISTORY_MODULE_KEY);
|
|
5903
|
+
if (history !== void 0) {
|
|
5904
|
+
apiOptions.history = history;
|
|
5905
|
+
}
|
|
5906
|
+
if (merged.validateOn !== void 0) {
|
|
5907
|
+
apiOptions.validateOn = merged.validateOn;
|
|
5908
|
+
}
|
|
5909
|
+
const mergedDebounceMs = merged.debounceMs;
|
|
5910
|
+
if (mergedDebounceMs !== void 0) {
|
|
5911
|
+
apiOptions.debounceMs = mergedDebounceMs;
|
|
5912
|
+
}
|
|
5913
|
+
if (merged.getDisplayState !== void 0) {
|
|
5914
|
+
apiOptions.getDisplayState = merged.getDisplayState;
|
|
5915
|
+
}
|
|
5916
|
+
if (merged.coerce !== void 0) {
|
|
5264
5917
|
apiOptions.coerce = merged.coerce;
|
|
5265
5918
|
}
|
|
5266
5919
|
if (merged.rememberVariants !== void 0) {
|
|
@@ -5318,7 +5971,6 @@ function buildFreshState(key, schema, configuration, registry) {
|
|
|
5318
5971
|
}
|
|
5319
5972
|
const resolvedSensitiveNames = configuration.sensitiveNames;
|
|
5320
5973
|
const resolvedIsSensitivePath = resolvedSensitiveNames === void 0 ? void 0 : paths.createIsSensitivePath(resolvedSensitiveNames);
|
|
5321
|
-
const resolvedSegmentMatchesSensitive = resolvedSensitiveNames === void 0 ? void 0 : paths.createSegmentMatchesSensitive(resolvedSensitiveNames);
|
|
5322
5974
|
const createOptions = {
|
|
5323
5975
|
formKey: key,
|
|
5324
5976
|
schema,
|
|
@@ -5347,319 +5999,94 @@ function buildFreshState(key, schema, configuration, registry) {
|
|
|
5347
5999
|
...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
|
|
5348
6000
|
...configuration.getDisplayState !== void 0 ? { getDisplayState: configuration.getDisplayState } : {},
|
|
5349
6001
|
...initialBlankPaths !== void 0 ? { initialBlankPaths } : {},
|
|
5350
|
-
...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {}
|
|
5351
|
-
...resolvedSegmentMatchesSensitive !== void 0 ? { segmentMatchesSensitive: resolvedSegmentMatchesSensitive } : {}
|
|
6002
|
+
...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {}
|
|
5352
6003
|
};
|
|
5353
6004
|
const state = createFormStore(createOptions);
|
|
5354
6005
|
registry.forms.set(
|
|
5355
6006
|
key,
|
|
5356
6007
|
state
|
|
5357
6008
|
);
|
|
5358
|
-
return state;
|
|
5359
|
-
}
|
|
5360
|
-
let anonCounter = 0;
|
|
5361
|
-
let formInstanceCounter = 0;
|
|
5362
|
-
const ambientProvideHistory = paths.__DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
5363
|
-
function recordAmbientProvide(ssr) {
|
|
5364
|
-
if (!paths.__DEV__ || ssr || ambientProvideHistory === null) return;
|
|
5365
|
-
const instance = vue.getCurrentInstance();
|
|
5366
|
-
if (instance === null) return;
|
|
5367
|
-
const instanceKey = instance;
|
|
5368
|
-
const entry = {
|
|
5369
|
-
source: paths.captureUserCallSite()
|
|
5370
|
-
};
|
|
5371
|
-
const existing = ambientProvideHistory.get(instanceKey);
|
|
5372
|
-
if (existing === void 0) {
|
|
5373
|
-
ambientProvideHistory.set(instanceKey, [entry]);
|
|
5374
|
-
return;
|
|
5375
|
-
}
|
|
5376
|
-
existing.push(entry);
|
|
5377
|
-
}
|
|
5378
|
-
function resolveFormKey(key) {
|
|
5379
|
-
if (key !== void 0 && key !== null && key !== "") {
|
|
5380
|
-
if (key.startsWith(RESERVED_KEY_PREFIX)) {
|
|
5381
|
-
throw new paths.ReservedFormKeyError(key);
|
|
5382
|
-
}
|
|
5383
|
-
return key;
|
|
5384
|
-
}
|
|
5385
|
-
if (vue.getCurrentInstance() !== null) {
|
|
5386
|
-
return `${ANONYMOUS_FORM_KEY_PREFIX}${vue.useId()}`;
|
|
5387
|
-
}
|
|
5388
|
-
return `${ANONYMOUS_FORM_KEY_PREFIX}${anonCounter++}`;
|
|
5389
|
-
}
|
|
5390
|
-
function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
|
|
5391
|
-
let existingFp;
|
|
5392
|
-
let incomingFp;
|
|
5393
|
-
try {
|
|
5394
|
-
existingFp = existing.fingerprint();
|
|
5395
|
-
incomingFp = incoming.fingerprint();
|
|
5396
|
-
} catch (error) {
|
|
5397
|
-
console.error(
|
|
5398
|
-
`[attaform] fingerprint() threw for key "${key}"; skipping mismatch check.`,
|
|
5399
|
-
error
|
|
5400
|
-
);
|
|
5401
|
-
return;
|
|
5402
|
-
}
|
|
5403
|
-
if (existingFp === incomingFp) return;
|
|
5404
|
-
console.warn(
|
|
5405
|
-
`[attaform] useForm() calls with key "${key}" use different schemas; first wins, second is ignored. Use identical schemas or unique keys.
|
|
5406
|
-
existing: ${existingFp}
|
|
5407
|
-
incoming: ${incomingFp}`
|
|
5408
|
-
);
|
|
5409
|
-
}
|
|
5410
|
-
function warnOnPersistDivergence(key, existing, incomingPersist) {
|
|
5411
|
-
if (incomingPersist === void 0) return;
|
|
5412
|
-
const wired = existing.modules.get(PERSISTENCE_MODULE_KEY);
|
|
5413
|
-
const incomingNormalized = normalizePersistConfig(incomingPersist);
|
|
5414
|
-
if (wired === void 0) {
|
|
5415
|
-
console.warn(
|
|
5416
|
-
`[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.`
|
|
5417
|
-
);
|
|
5418
|
-
return;
|
|
5419
|
-
}
|
|
5420
|
-
if (persistConfigsEquivalent(wired.wiredConfig, incomingNormalized)) return;
|
|
5421
|
-
console.warn(
|
|
5422
|
-
`[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
|
|
5423
|
-
wired: ${describePersist(wired.wiredConfig)}
|
|
5424
|
-
incoming: ${describePersist(incomingNormalized)}`
|
|
5425
|
-
);
|
|
5426
|
-
}
|
|
5427
|
-
function persistConfigsEquivalent(a, b) {
|
|
5428
|
-
if (a.storage !== b.storage) return false;
|
|
5429
|
-
if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
|
|
5430
|
-
if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
|
|
5431
|
-
return true;
|
|
5432
|
-
}
|
|
5433
|
-
function describePersist(config) {
|
|
5434
|
-
const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
|
|
5435
|
-
const parts = [`storage=${storage}`];
|
|
5436
|
-
if (config.key !== void 0) parts.push(`key=${config.key}`);
|
|
5437
|
-
if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
|
|
5438
|
-
return `{ ${parts.join(", ")} }`;
|
|
5439
|
-
}
|
|
5440
|
-
function wirePersistence(state, config) {
|
|
5441
|
-
const fingerprint = hashStableString(state.schema.fingerprint());
|
|
5442
|
-
const base = resolveStorageKeyBase(config, state.formKey);
|
|
5443
|
-
const key = `${base}:${fingerprint}`;
|
|
5444
|
-
const debounceMs = normalizeNumericOption({
|
|
5445
|
-
value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
|
|
5446
|
-
source: "useForm.persist.debounceMs",
|
|
5447
|
-
allowInfinity: false,
|
|
5448
|
-
min: 0,
|
|
5449
|
-
defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
|
|
5450
|
-
});
|
|
5451
|
-
const include = config.include ?? "form";
|
|
5452
|
-
const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
|
|
5453
|
-
const adapterPromise = getStorageAdapter(config.storage);
|
|
5454
|
-
let disposed = false;
|
|
5455
|
-
let inFlightFinalFlush = null;
|
|
5456
|
-
let pendingOptedInPaths = null;
|
|
5457
|
-
const writer = createDebouncedWriter(async () => {
|
|
5458
|
-
const optedInPaths = pendingOptedInPaths ?? new Set(state.persistOptIns.optedInPaths());
|
|
5459
|
-
pendingOptedInPaths = null;
|
|
5460
|
-
const adapter = await adapterPromise;
|
|
5461
|
-
if (disposed) return;
|
|
5462
|
-
if (optedInPaths.size === 0) {
|
|
5463
|
-
await adapter.removeItem(key);
|
|
5464
|
-
return;
|
|
5465
|
-
}
|
|
5466
|
-
const rawForm = vue.toRaw(state.form.value);
|
|
5467
|
-
const filteredForm = pluckPaths(rawForm, optedInPaths);
|
|
5468
|
-
const filteredSchemaErrors = filterErrorsByPaths(state.schemaErrors, optedInPaths);
|
|
5469
|
-
const filteredUserErrors = filterErrorsByPaths(state.userErrors, optedInPaths);
|
|
5470
|
-
const filteredTransientEmpty = /* @__PURE__ */ new Set();
|
|
5471
|
-
for (const tk of state.blankPaths) {
|
|
5472
|
-
if (optedInPaths.has(tk)) filteredTransientEmpty.add(tk);
|
|
5473
|
-
}
|
|
5474
|
-
const payload = buildPersistedPayload(
|
|
5475
|
-
filteredForm,
|
|
5476
|
-
include,
|
|
5477
|
-
filteredSchemaErrors,
|
|
5478
|
-
filteredUserErrors,
|
|
5479
|
-
filteredTransientEmpty
|
|
5480
|
-
);
|
|
5481
|
-
await adapter.setItem(key, payload);
|
|
5482
|
-
}, debounceMs);
|
|
5483
|
-
const unsubscribeChange = state.onFormChange((_next, meta) => {
|
|
5484
|
-
if (disposed || inFlightFinalFlush !== null) return;
|
|
5485
|
-
if (meta?.crossTab === true) return;
|
|
5486
|
-
if (meta?.persist !== true) return;
|
|
5487
|
-
pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
|
|
5488
|
-
writer.schedule();
|
|
5489
|
-
});
|
|
5490
|
-
const unsubscribeSuccess = clearOnSubmitSuccess ? state.onSubmitSuccess(() => {
|
|
5491
|
-
if (disposed) return;
|
|
5492
|
-
void (async () => {
|
|
5493
|
-
await writer.flush();
|
|
5494
|
-
if (disposed) return;
|
|
5495
|
-
const adapter = await adapterPromise;
|
|
5496
|
-
if (disposed) return;
|
|
5497
|
-
await adapter.removeItem(key);
|
|
5498
|
-
})();
|
|
5499
|
-
}) : () => void 0;
|
|
5500
|
-
void (async () => {
|
|
5501
|
-
const adapter = await adapterPromise;
|
|
5502
|
-
if (disposed) return;
|
|
5503
|
-
void cleanupOrphanKeys(adapter, base, key);
|
|
5504
|
-
try {
|
|
5505
|
-
const raw = await adapter.getItem(key);
|
|
5506
|
-
const payload = readPersistedPayload(raw);
|
|
5507
|
-
if (payload === null) {
|
|
5508
|
-
if (raw !== null && raw !== void 0) {
|
|
5509
|
-
await adapter.removeItem(key);
|
|
5510
|
-
}
|
|
5511
|
-
return;
|
|
5512
|
-
}
|
|
5513
|
-
if (disposed) return;
|
|
5514
|
-
const merged = mergeSparseHydration(
|
|
5515
|
-
vue.toRaw(state.form.value),
|
|
5516
|
-
payload.data.form,
|
|
5517
|
-
state.schema
|
|
5518
|
-
);
|
|
5519
|
-
state.applyFormReplacement(merged, { hydration: true });
|
|
5520
|
-
const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
|
|
5521
|
-
for (const k of persistedLeafPaths) {
|
|
5522
|
-
state.blankPaths.delete(k);
|
|
5523
|
-
state.originalBlankPaths.delete(k);
|
|
5524
|
-
}
|
|
5525
|
-
for (const k of payload.data.blankPaths ?? []) {
|
|
5526
|
-
const key2 = paths.coerceToPathKey(k);
|
|
5527
|
-
state.blankPaths.add(key2);
|
|
5528
|
-
state.originalBlankPaths.add(key2);
|
|
5529
|
-
}
|
|
5530
|
-
if (include === "form+errors") {
|
|
5531
|
-
if (payload.data.schemaErrors !== void 0) {
|
|
5532
|
-
const flat = payload.data.schemaErrors.flatMap(([, errs]) => errs);
|
|
5533
|
-
state.setAllSchemaErrors(flat);
|
|
5534
|
-
}
|
|
5535
|
-
if (payload.data.userErrors !== void 0) {
|
|
5536
|
-
const flat = payload.data.userErrors.flatMap(([, errs]) => errs);
|
|
5537
|
-
state.setAllUserErrors(flat);
|
|
5538
|
-
}
|
|
5539
|
-
}
|
|
5540
|
-
state.scheduleFieldValidation(
|
|
5541
|
-
[],
|
|
5542
|
-
true
|
|
5543
|
-
/* immediate */
|
|
5544
|
-
);
|
|
5545
|
-
} catch {
|
|
5546
|
-
}
|
|
5547
|
-
})();
|
|
5548
|
-
async function writePathImmediately(path) {
|
|
5549
|
-
if (disposed) return;
|
|
5550
|
-
await writer.flush();
|
|
5551
|
-
if (disposed) return;
|
|
5552
|
-
const adapter = await adapterPromise;
|
|
5553
|
-
if (disposed) return;
|
|
5554
|
-
const raw = await adapter.getItem(key);
|
|
5555
|
-
const existing = readPersistedPayload(raw);
|
|
5556
|
-
const baseForm = existing?.data.form ?? {};
|
|
5557
|
-
const value = getAtPath(vue.toRaw(state.form.value), path);
|
|
5558
|
-
const nextForm = setAtPath(baseForm, path, value);
|
|
5559
|
-
const { key: pathKey } = paths.canonicalizePath(path);
|
|
5560
|
-
const transientSet = new Set(
|
|
5561
|
-
(existing?.data.blankPaths ?? []).filter(
|
|
5562
|
-
(k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
|
|
5563
|
-
)
|
|
5564
|
-
);
|
|
5565
|
-
for (const liveKey of state.blankPaths) {
|
|
5566
|
-
if (liveKey === pathKey || isDescendantPathKey(liveKey, pathKey)) {
|
|
5567
|
-
transientSet.add(liveKey);
|
|
5568
|
-
}
|
|
5569
|
-
}
|
|
5570
|
-
if (include === "form") {
|
|
5571
|
-
await adapter.setItem(
|
|
5572
|
-
key,
|
|
5573
|
-
buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
|
|
5574
|
-
);
|
|
5575
|
-
return;
|
|
5576
|
-
}
|
|
5577
|
-
const schemaMap = new Map(existing?.data.schemaErrors ?? []);
|
|
5578
|
-
const userMap = new Map(existing?.data.userErrors ?? []);
|
|
5579
|
-
const currentSchema = state.schemaErrors.get(pathKey);
|
|
5580
|
-
const currentUser = state.userErrors.get(pathKey);
|
|
5581
|
-
if (currentSchema !== void 0 && currentSchema.length > 0) {
|
|
5582
|
-
schemaMap.set(pathKey, [...currentSchema]);
|
|
5583
|
-
} else {
|
|
5584
|
-
schemaMap.delete(pathKey);
|
|
5585
|
-
}
|
|
5586
|
-
if (currentUser !== void 0 && currentUser.length > 0) {
|
|
5587
|
-
userMap.set(pathKey, [...currentUser]);
|
|
5588
|
-
} else {
|
|
5589
|
-
userMap.delete(pathKey);
|
|
5590
|
-
}
|
|
5591
|
-
await adapter.setItem(
|
|
5592
|
-
key,
|
|
5593
|
-
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
5594
|
-
);
|
|
5595
|
-
}
|
|
5596
|
-
async function clearPersistedDraft(path) {
|
|
5597
|
-
if (disposed) return;
|
|
5598
|
-
await writer.flush();
|
|
5599
|
-
if (disposed) return;
|
|
5600
|
-
const adapter = await adapterPromise;
|
|
5601
|
-
if (disposed) return;
|
|
5602
|
-
if (path === void 0) {
|
|
5603
|
-
await adapter.removeItem(key);
|
|
5604
|
-
return;
|
|
5605
|
-
}
|
|
5606
|
-
const raw = await adapter.getItem(key);
|
|
5607
|
-
const existing = readPersistedPayload(raw);
|
|
5608
|
-
if (existing === null) return;
|
|
5609
|
-
const nextForm = deleteAtPath(existing.data.form, path);
|
|
5610
|
-
if (isEmptyContainer(nextForm)) {
|
|
5611
|
-
await adapter.removeItem(key);
|
|
5612
|
-
return;
|
|
5613
|
-
}
|
|
5614
|
-
const { key: pathKey } = paths.canonicalizePath(path);
|
|
5615
|
-
const transientSet = new Set(
|
|
5616
|
-
(existing.data.blankPaths ?? []).filter(
|
|
5617
|
-
(k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
|
|
5618
|
-
)
|
|
5619
|
-
);
|
|
5620
|
-
if (include === "form") {
|
|
5621
|
-
await adapter.setItem(
|
|
5622
|
-
key,
|
|
5623
|
-
buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
|
|
5624
|
-
);
|
|
5625
|
-
return;
|
|
6009
|
+
return state;
|
|
6010
|
+
}
|
|
6011
|
+
let anonCounter = 0;
|
|
6012
|
+
let formInstanceCounter = 0;
|
|
6013
|
+
const ambientProvideHistory = paths.__DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
6014
|
+
function recordAmbientProvide(ssr) {
|
|
6015
|
+
if (!paths.__DEV__ || ssr || ambientProvideHistory === null) return;
|
|
6016
|
+
const instance = vue.getCurrentInstance();
|
|
6017
|
+
if (instance === null) return;
|
|
6018
|
+
const instanceKey = instance;
|
|
6019
|
+
const entry = {
|
|
6020
|
+
source: paths.captureUserCallSite()
|
|
6021
|
+
};
|
|
6022
|
+
const existing = ambientProvideHistory.get(instanceKey);
|
|
6023
|
+
if (existing === void 0) {
|
|
6024
|
+
ambientProvideHistory.set(instanceKey, [entry]);
|
|
6025
|
+
return;
|
|
6026
|
+
}
|
|
6027
|
+
existing.push(entry);
|
|
6028
|
+
}
|
|
6029
|
+
function resolveFormKey(key) {
|
|
6030
|
+
if (key !== void 0 && key !== null && key !== "") {
|
|
6031
|
+
if (key.startsWith(RESERVED_KEY_PREFIX)) {
|
|
6032
|
+
throw new paths.ReservedFormKeyError(key);
|
|
5626
6033
|
}
|
|
5627
|
-
|
|
5628
|
-
const userErrors = (existing.data.userErrors ?? []).filter(([k]) => k !== pathKey);
|
|
5629
|
-
const schemaMap = new Map(schemaErrors.map(([k, v]) => [k, [...v]]));
|
|
5630
|
-
const userMap = new Map(userErrors.map(([k, v]) => [k, [...v]]));
|
|
5631
|
-
await adapter.setItem(
|
|
5632
|
-
key,
|
|
5633
|
-
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
5634
|
-
);
|
|
6034
|
+
return key;
|
|
5635
6035
|
}
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
if (disposed) return Promise.resolve();
|
|
5639
|
-
return writer.flush().catch(() => void 0);
|
|
6036
|
+
if (vue.getCurrentInstance() !== null) {
|
|
6037
|
+
return `${ANONYMOUS_FORM_KEY_PREFIX}${vue.useId()}`;
|
|
5640
6038
|
}
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
6039
|
+
return `${ANONYMOUS_FORM_KEY_PREFIX}${anonCounter++}`;
|
|
6040
|
+
}
|
|
6041
|
+
function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
|
|
6042
|
+
let existingFp;
|
|
6043
|
+
let incomingFp;
|
|
6044
|
+
try {
|
|
6045
|
+
existingFp = existing.fingerprint();
|
|
6046
|
+
incomingFp = incoming.fingerprint();
|
|
6047
|
+
} catch (error) {
|
|
6048
|
+
console.error(
|
|
6049
|
+
`[attaform] fingerprint() threw for key "${key}"; skipping mismatch check.`,
|
|
6050
|
+
error
|
|
6051
|
+
);
|
|
6052
|
+
return;
|
|
5649
6053
|
}
|
|
5650
|
-
return
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
};
|
|
6054
|
+
if (existingFp === incomingFp) return;
|
|
6055
|
+
console.warn(
|
|
6056
|
+
`[attaform] useForm() calls with key "${key}" use different schemas; first wins, second is ignored. Use identical schemas or unique keys.
|
|
6057
|
+
existing: ${existingFp}
|
|
6058
|
+
incoming: ${incomingFp}`
|
|
6059
|
+
);
|
|
5657
6060
|
}
|
|
5658
|
-
function
|
|
5659
|
-
if (
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
6061
|
+
function warnOnPersistDivergence(key, existing, incomingPersist) {
|
|
6062
|
+
if (incomingPersist === void 0) return;
|
|
6063
|
+
const wired = existing.modules.get(PERSISTENCE_MODULE_KEY);
|
|
6064
|
+
const incomingNormalized = normalizePersistConfig(incomingPersist);
|
|
6065
|
+
if (wired === void 0) {
|
|
6066
|
+
console.warn(
|
|
6067
|
+
`[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.`
|
|
6068
|
+
);
|
|
6069
|
+
return;
|
|
6070
|
+
}
|
|
6071
|
+
if (persistConfigsEquivalent(wired.wiredConfig, incomingNormalized)) return;
|
|
6072
|
+
console.warn(
|
|
6073
|
+
`[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
|
|
6074
|
+
wired: ${describePersist(wired.wiredConfig)}
|
|
6075
|
+
incoming: ${describePersist(incomingNormalized)}`
|
|
6076
|
+
);
|
|
6077
|
+
}
|
|
6078
|
+
function persistConfigsEquivalent(a, b) {
|
|
6079
|
+
if (a.storage !== b.storage) return false;
|
|
6080
|
+
if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
|
|
6081
|
+
if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
|
|
6082
|
+
return true;
|
|
6083
|
+
}
|
|
6084
|
+
function describePersist(config) {
|
|
6085
|
+
const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
|
|
6086
|
+
const parts = [`storage=${storage}`];
|
|
6087
|
+
if (config.key !== void 0) parts.push(`key=${config.key}`);
|
|
6088
|
+
if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
|
|
6089
|
+
return `{ ${parts.join(", ")} }`;
|
|
5663
6090
|
}
|
|
5664
6091
|
const warnedAnonPersistKeys = /* @__PURE__ */ new Set();
|
|
5665
6092
|
function enforceAnonPersistRule(formKey, ssr) {
|
|
@@ -5677,33 +6104,6 @@ function enforceAnonPersistRule(formKey, ssr) {
|
|
|
5677
6104
|
}
|
|
5678
6105
|
return true;
|
|
5679
6106
|
}
|
|
5680
|
-
function collectPersistedLeafPaths(form) {
|
|
5681
|
-
const out = [];
|
|
5682
|
-
walk(form, []);
|
|
5683
|
-
return out;
|
|
5684
|
-
function walk(node, prefix) {
|
|
5685
|
-
if (Array.isArray(node)) {
|
|
5686
|
-
for (let i = 0; i < node.length; i++) {
|
|
5687
|
-
walk(node[i], [...prefix, i]);
|
|
5688
|
-
}
|
|
5689
|
-
return;
|
|
5690
|
-
}
|
|
5691
|
-
if (isPlainRecord(node)) {
|
|
5692
|
-
for (const key of Object.keys(node)) {
|
|
5693
|
-
walk(node[key], [...prefix, key]);
|
|
5694
|
-
}
|
|
5695
|
-
return;
|
|
5696
|
-
}
|
|
5697
|
-
if (prefix.length === 0) return;
|
|
5698
|
-
out.push(paths.canonicalizePath(prefix).key);
|
|
5699
|
-
}
|
|
5700
|
-
}
|
|
5701
|
-
function isDescendantPathKey(candidate, ancestor) {
|
|
5702
|
-
if (candidate.length <= ancestor.length) return false;
|
|
5703
|
-
if (!ancestor.endsWith("]")) return false;
|
|
5704
|
-
const childPrefix = `${ancestor.slice(0, -1)},`;
|
|
5705
|
-
return candidate.startsWith(childPrefix);
|
|
5706
|
-
}
|
|
5707
6107
|
|
|
5708
6108
|
let injectedInstanceCounter = 0;
|
|
5709
6109
|
function injectForm(input) {
|
|
@@ -5783,8 +6183,6 @@ function isLazyMarker(value) {
|
|
|
5783
6183
|
}
|
|
5784
6184
|
|
|
5785
6185
|
const NOOP_WIZARD_HISTORY = {
|
|
5786
|
-
push() {
|
|
5787
|
-
},
|
|
5788
6186
|
replace() {
|
|
5789
6187
|
},
|
|
5790
6188
|
read() {
|
|
@@ -5811,21 +6209,16 @@ function createWizardHistory(param) {
|
|
|
5811
6209
|
for (const subscriber of subscribers) subscriber(value);
|
|
5812
6210
|
}
|
|
5813
6211
|
window.addEventListener("popstate", handlePopstate);
|
|
5814
|
-
function
|
|
6212
|
+
function safeReplaceState(key) {
|
|
5815
6213
|
try {
|
|
5816
|
-
|
|
5817
|
-
fn.call(window.history, {}, "", buildUrl(key));
|
|
6214
|
+
window.history.replaceState({}, "", buildUrl(key));
|
|
5818
6215
|
} catch {
|
|
5819
6216
|
}
|
|
5820
6217
|
}
|
|
5821
6218
|
return {
|
|
5822
|
-
push(key) {
|
|
5823
|
-
if (disposed) return;
|
|
5824
|
-
safeWriteState(key, "push");
|
|
5825
|
-
},
|
|
5826
6219
|
replace(key) {
|
|
5827
6220
|
if (disposed) return;
|
|
5828
|
-
|
|
6221
|
+
safeReplaceState(key);
|
|
5829
6222
|
},
|
|
5830
6223
|
read() {
|
|
5831
6224
|
const url = new URL(window.location.href);
|
|
@@ -5884,68 +6277,16 @@ function buildWizardStatusesProxy(statuses) {
|
|
|
5884
6277
|
}
|
|
5885
6278
|
return result;
|
|
5886
6279
|
});
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
return computedEntry.value;
|
|
5898
|
-
},
|
|
5899
|
-
get(_, key) {
|
|
5900
|
-
if (typeof key === "symbol") {
|
|
5901
|
-
if (key === Symbol.toPrimitive) return proxyToPrimitive;
|
|
5902
|
-
return Reflect.get(target, key);
|
|
5903
|
-
}
|
|
5904
|
-
if (key === "toJSON") return () => snapshot.value;
|
|
5905
|
-
if (key === "toString") return proxyToString;
|
|
5906
|
-
if (key === "valueOf")
|
|
5907
|
-
return function() {
|
|
5908
|
-
return this;
|
|
5909
|
-
};
|
|
5910
|
-
const computedEntry = statuses[key];
|
|
5911
|
-
if (computedEntry === void 0) return void 0;
|
|
5912
|
-
return computedEntry.value;
|
|
5913
|
-
},
|
|
5914
|
-
has(_, key) {
|
|
5915
|
-
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
5916
|
-
return Object.hasOwn(statuses, key);
|
|
5917
|
-
},
|
|
5918
|
-
ownKeys() {
|
|
5919
|
-
return Object.keys(statuses);
|
|
5920
|
-
},
|
|
5921
|
-
getOwnPropertyDescriptor(_, key) {
|
|
5922
|
-
if (typeof key === "symbol") return void 0;
|
|
5923
|
-
const computedEntry = statuses[key];
|
|
5924
|
-
if (computedEntry === void 0) return void 0;
|
|
5925
|
-
return {
|
|
5926
|
-
configurable: true,
|
|
5927
|
-
enumerable: true,
|
|
5928
|
-
writable: false,
|
|
5929
|
-
value: computedEntry.value
|
|
5930
|
-
};
|
|
5931
|
-
},
|
|
5932
|
-
set(_, key) {
|
|
5933
|
-
if (paths.__DEV__) {
|
|
5934
|
-
console.warn(
|
|
5935
|
-
`[attaform] wizard.statuses is read-only \u2014 write to "${String(key)}" was ignored. Statuses derive from each form's meta; mutate the underlying form instead.`
|
|
5936
|
-
);
|
|
5937
|
-
}
|
|
5938
|
-
return true;
|
|
5939
|
-
},
|
|
5940
|
-
deleteProperty(_, key) {
|
|
5941
|
-
if (paths.__DEV__) {
|
|
5942
|
-
console.warn(
|
|
5943
|
-
`[attaform] wizard.statuses is read-only \u2014 delete of "${String(key)}" was ignored.`
|
|
5944
|
-
);
|
|
5945
|
-
}
|
|
5946
|
-
return true;
|
|
5947
|
-
},
|
|
5948
|
-
defineProperty: () => true
|
|
6280
|
+
return buildCallableReadonlySnapshotProxy({
|
|
6281
|
+
surface: "wizard.statuses",
|
|
6282
|
+
snapshot: () => snapshot.value,
|
|
6283
|
+
resolveKey: (key) => statuses[key]?.value,
|
|
6284
|
+
// Single-key callable form. Strings stringify naturally; non-
|
|
6285
|
+
// string args coerce via `String(arg)` and miss the lookup, which
|
|
6286
|
+
// resolves to `undefined` (consistent with property-access).
|
|
6287
|
+
resolveCall: (arg) => statuses[String(arg)]?.value,
|
|
6288
|
+
ownKeys: () => Object.keys(statuses),
|
|
6289
|
+
hasKey: (key) => Object.hasOwn(statuses, key)
|
|
5949
6290
|
});
|
|
5950
6291
|
}
|
|
5951
6292
|
|
|
@@ -5962,6 +6303,12 @@ const NOOP_VALID_STATUS = {
|
|
|
5962
6303
|
submitted: false,
|
|
5963
6304
|
errorCount: 0
|
|
5964
6305
|
};
|
|
6306
|
+
function asStatusSource(form) {
|
|
6307
|
+
return form;
|
|
6308
|
+
}
|
|
6309
|
+
function asSubmissionSource(form) {
|
|
6310
|
+
return form;
|
|
6311
|
+
}
|
|
5965
6312
|
function useWizard(options) {
|
|
5966
6313
|
const rawSteps = Array.isArray(options.steps) ? options.steps : [];
|
|
5967
6314
|
if (rawSteps.length === 0 && paths.__DEV__) {
|
|
@@ -6049,10 +6396,12 @@ function useWizard(options) {
|
|
|
6049
6396
|
return { configurable: true, enumerable: true, writable: false, value: form };
|
|
6050
6397
|
}
|
|
6051
6398
|
});
|
|
6052
|
-
const slotCtx =
|
|
6399
|
+
const slotCtx = {
|
|
6053
6400
|
forms: slotForms,
|
|
6054
|
-
currentKey
|
|
6055
|
-
|
|
6401
|
+
get currentKey() {
|
|
6402
|
+
return activeKey.value === "" ? void 0 : activeKey.value;
|
|
6403
|
+
}
|
|
6404
|
+
};
|
|
6056
6405
|
function resolveSlot(slot, index, ctx) {
|
|
6057
6406
|
if (typeof slot === "string") {
|
|
6058
6407
|
return getOrBuildNoop(slot);
|
|
@@ -6075,12 +6424,6 @@ function useWizard(options) {
|
|
|
6075
6424
|
}
|
|
6076
6425
|
return result;
|
|
6077
6426
|
}
|
|
6078
|
-
const lazyCtx = {
|
|
6079
|
-
forms: slotForms,
|
|
6080
|
-
get currentKey() {
|
|
6081
|
-
return activeKey.value === "" ? void 0 : activeKey.value;
|
|
6082
|
-
}
|
|
6083
|
-
};
|
|
6084
6427
|
for (let i = 0; i < rawSteps.length; i++) {
|
|
6085
6428
|
const slot = rawSteps[i];
|
|
6086
6429
|
if (isLazyMarker(slot)) {
|
|
@@ -6090,18 +6433,17 @@ function useWizard(options) {
|
|
|
6090
6433
|
idx,
|
|
6091
6434
|
vue.computed(() => {
|
|
6092
6435
|
void lazyEpoch.value;
|
|
6093
|
-
return marker.resolve(
|
|
6436
|
+
return marker.resolve(slotCtx);
|
|
6094
6437
|
})
|
|
6095
6438
|
);
|
|
6096
6439
|
}
|
|
6097
6440
|
}
|
|
6098
6441
|
const compiledSteps = vue.computed(() => {
|
|
6099
|
-
const ctx = slotCtx.value;
|
|
6100
6442
|
const out = [];
|
|
6101
6443
|
const seen = /* @__PURE__ */ new Set();
|
|
6102
6444
|
for (let i = 0; i < rawSteps.length; i++) {
|
|
6103
6445
|
const slot = rawSteps[i];
|
|
6104
|
-
const form = resolveSlot(slot, i,
|
|
6446
|
+
const form = resolveSlot(slot, i, slotCtx);
|
|
6105
6447
|
if (form === void 0) continue;
|
|
6106
6448
|
if (seen.has(form.key)) {
|
|
6107
6449
|
if (paths.__DEV__) {
|
|
@@ -6127,9 +6469,12 @@ function useWizard(options) {
|
|
|
6127
6469
|
return -1;
|
|
6128
6470
|
});
|
|
6129
6471
|
const currentStep = vue.computed(() => {
|
|
6130
|
-
const
|
|
6131
|
-
|
|
6132
|
-
|
|
6472
|
+
const list = compiledSteps.value;
|
|
6473
|
+
const idx = activeIndex.value;
|
|
6474
|
+
if (idx >= 0 && idx < list.length) {
|
|
6475
|
+
return list[idx].key;
|
|
6476
|
+
}
|
|
6477
|
+
const first = list[0];
|
|
6133
6478
|
return first === void 0 ? void 0 : first.key;
|
|
6134
6479
|
});
|
|
6135
6480
|
const activeForm = vue.computed(() => {
|
|
@@ -6152,36 +6497,94 @@ function useWizard(options) {
|
|
|
6152
6497
|
for (const step of compiledSteps.value) out[step.key] = step.form;
|
|
6153
6498
|
return out;
|
|
6154
6499
|
});
|
|
6155
|
-
|
|
6156
|
-
const
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6500
|
+
function isFormReady(key) {
|
|
6501
|
+
const store = registry.forms.get(key);
|
|
6502
|
+
return store?.defaultsResolved.value === true;
|
|
6503
|
+
}
|
|
6504
|
+
function toWizardAggregateError(err, fallbackKey) {
|
|
6505
|
+
const entry = {
|
|
6506
|
+
formKey: err.formKey ?? fallbackKey,
|
|
6507
|
+
path: err.path,
|
|
6508
|
+
message: err.message
|
|
6509
|
+
};
|
|
6510
|
+
if (err.code !== void 0) entry.code = err.code;
|
|
6511
|
+
return entry;
|
|
6512
|
+
}
|
|
6513
|
+
const valuesCache = /* @__PURE__ */ new Map();
|
|
6514
|
+
function valuesFor(form) {
|
|
6515
|
+
const cached = valuesCache.get(form.key);
|
|
6516
|
+
if (cached !== void 0) return cached;
|
|
6517
|
+
const source = asStatusSource(form);
|
|
6518
|
+
const computedValues = vue.computed(() => source.values);
|
|
6519
|
+
valuesCache.set(form.key, computedValues);
|
|
6520
|
+
return computedValues;
|
|
6521
|
+
}
|
|
6522
|
+
const errorsCache = /* @__PURE__ */ new Map();
|
|
6523
|
+
function errorsFor(form) {
|
|
6524
|
+
const cached = errorsCache.get(form.key);
|
|
6525
|
+
if (cached !== void 0) return cached;
|
|
6526
|
+
const source = asStatusSource(form);
|
|
6527
|
+
const computedErrors = vue.computed(() => {
|
|
6528
|
+
if (!isFormReady(form.key)) return [];
|
|
6529
|
+
const errors = source.meta?.errors ?? [];
|
|
6530
|
+
const list = [];
|
|
6531
|
+
for (const err of errors) list.push(toWizardAggregateError(err, form.key));
|
|
6532
|
+
return list;
|
|
6533
|
+
});
|
|
6534
|
+
errorsCache.set(form.key, computedErrors);
|
|
6535
|
+
return computedErrors;
|
|
6536
|
+
}
|
|
6537
|
+
const allValues = new Proxy({}, {
|
|
6538
|
+
get(_, key) {
|
|
6539
|
+
if (typeof key !== "string") return void 0;
|
|
6540
|
+
const form = formsRecord.value[key];
|
|
6541
|
+
if (form === void 0) return void 0;
|
|
6542
|
+
return valuesFor(form).value;
|
|
6543
|
+
},
|
|
6544
|
+
has(_, key) {
|
|
6545
|
+
if (typeof key !== "string") return false;
|
|
6546
|
+
return formsRecord.value[key] !== void 0;
|
|
6547
|
+
},
|
|
6548
|
+
ownKeys() {
|
|
6549
|
+
return Object.keys(formsRecord.value);
|
|
6550
|
+
},
|
|
6551
|
+
getOwnPropertyDescriptor(_, key) {
|
|
6552
|
+
if (typeof key !== "string") return void 0;
|
|
6553
|
+
const form = formsRecord.value[key];
|
|
6554
|
+
if (form === void 0) return void 0;
|
|
6555
|
+
return {
|
|
6556
|
+
configurable: true,
|
|
6557
|
+
enumerable: true,
|
|
6558
|
+
writable: false,
|
|
6559
|
+
value: valuesFor(form).value
|
|
6560
|
+
};
|
|
6160
6561
|
}
|
|
6161
|
-
return out;
|
|
6162
6562
|
});
|
|
6163
|
-
const allErrors =
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
const
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6563
|
+
const allErrors = new Proxy({}, {
|
|
6564
|
+
get(_, key) {
|
|
6565
|
+
if (typeof key !== "string") return void 0;
|
|
6566
|
+
const form = formsRecord.value[key];
|
|
6567
|
+
if (form === void 0) return void 0;
|
|
6568
|
+
return errorsFor(form).value;
|
|
6569
|
+
},
|
|
6570
|
+
has(_, key) {
|
|
6571
|
+
if (typeof key !== "string") return false;
|
|
6572
|
+
return formsRecord.value[key] !== void 0;
|
|
6573
|
+
},
|
|
6574
|
+
ownKeys() {
|
|
6575
|
+
return Object.keys(formsRecord.value);
|
|
6576
|
+
},
|
|
6577
|
+
getOwnPropertyDescriptor(_, key) {
|
|
6578
|
+
if (typeof key !== "string") return void 0;
|
|
6579
|
+
const form = formsRecord.value[key];
|
|
6580
|
+
if (form === void 0) return void 0;
|
|
6581
|
+
return {
|
|
6582
|
+
configurable: true,
|
|
6583
|
+
enumerable: true,
|
|
6584
|
+
writable: false,
|
|
6585
|
+
value: errorsFor(form).value
|
|
6586
|
+
};
|
|
6183
6587
|
}
|
|
6184
|
-
return out;
|
|
6185
6588
|
});
|
|
6186
6589
|
const seedRef = vue.ref(void 0);
|
|
6187
6590
|
const seedInput = options.defaultStatuses;
|
|
@@ -6204,11 +6607,9 @@ function useWizard(options) {
|
|
|
6204
6607
|
function statusFor(form) {
|
|
6205
6608
|
const cached = statusCache.get(form.key);
|
|
6206
6609
|
if (cached !== void 0) return cached;
|
|
6207
|
-
const source = form;
|
|
6610
|
+
const source = asStatusSource(form);
|
|
6208
6611
|
const computedStatus = vue.computed(() => {
|
|
6209
|
-
|
|
6210
|
-
const resolved = store?.defaultsResolved.value === true;
|
|
6211
|
-
if (resolved) {
|
|
6612
|
+
if (isFormReady(form.key)) {
|
|
6212
6613
|
const meta = source.meta;
|
|
6213
6614
|
if (meta !== void 0 && meta !== null) {
|
|
6214
6615
|
return {
|
|
@@ -6351,7 +6752,7 @@ function useWizard(options) {
|
|
|
6351
6752
|
}
|
|
6352
6753
|
if (!registry.ssr) {
|
|
6353
6754
|
for (const step of compiledSteps.value) {
|
|
6354
|
-
const source = step.form;
|
|
6755
|
+
const source = asSubmissionSource(step.form);
|
|
6355
6756
|
if (typeof source.activate === "function") void source.activate();
|
|
6356
6757
|
}
|
|
6357
6758
|
}
|
|
@@ -6395,7 +6796,7 @@ function useWizard(options) {
|
|
|
6395
6796
|
const submissionAttempts = vue.ref(0);
|
|
6396
6797
|
const done = vue.ref(false);
|
|
6397
6798
|
function activateForm(form) {
|
|
6398
|
-
const source = form;
|
|
6799
|
+
const source = asSubmissionSource(form);
|
|
6399
6800
|
if (typeof source.activate === "function") {
|
|
6400
6801
|
void source.activate();
|
|
6401
6802
|
}
|
|
@@ -6495,7 +6896,7 @@ function useWizard(options) {
|
|
|
6495
6896
|
};
|
|
6496
6897
|
}
|
|
6497
6898
|
async function processOne(form) {
|
|
6498
|
-
const full = form;
|
|
6899
|
+
const full = asSubmissionSource(form);
|
|
6499
6900
|
let activationFailure;
|
|
6500
6901
|
try {
|
|
6501
6902
|
if (typeof full.activate === "function") await full.activate();
|
|
@@ -6527,15 +6928,7 @@ function useWizard(options) {
|
|
|
6527
6928
|
for (const step of compiledSteps.value) {
|
|
6528
6929
|
const processed = results.get(step.key);
|
|
6529
6930
|
if (processed === void 0 || processed.success === true) continue;
|
|
6530
|
-
for (const err of processed.errors)
|
|
6531
|
-
const entry = {
|
|
6532
|
-
formKey: err.formKey,
|
|
6533
|
-
path: err.path,
|
|
6534
|
-
message: err.message
|
|
6535
|
-
};
|
|
6536
|
-
if (err.code !== void 0) entry.code = err.code;
|
|
6537
|
-
out.push(entry);
|
|
6538
|
-
}
|
|
6931
|
+
for (const err of processed.errors) out.push(toWizardAggregateError(err, step.key));
|
|
6539
6932
|
}
|
|
6540
6933
|
return out;
|
|
6541
6934
|
}
|
|
@@ -6589,8 +6982,7 @@ function useWizard(options) {
|
|
|
6589
6982
|
if (processed !== void 0 && processed.success === true) {
|
|
6590
6983
|
valuesMap[step.key] = processed.data;
|
|
6591
6984
|
} else {
|
|
6592
|
-
|
|
6593
|
-
valuesMap[step.key] = source.values;
|
|
6985
|
+
valuesMap[step.key] = asStatusSource(step.form).values;
|
|
6594
6986
|
}
|
|
6595
6987
|
}
|
|
6596
6988
|
const ctx = buildSubmitContext(valuesMap, currentKey, final);
|
|
@@ -6611,8 +7003,11 @@ function useWizard(options) {
|
|
|
6611
7003
|
moveTo(firstFailedKey);
|
|
6612
7004
|
await vue.nextTick();
|
|
6613
7005
|
const failedForm = formsRecord.value[firstFailedKey];
|
|
6614
|
-
if (failedForm !== void 0
|
|
6615
|
-
failedForm
|
|
7006
|
+
if (failedForm !== void 0) {
|
|
7007
|
+
const failedSource = asSubmissionSource(failedForm);
|
|
7008
|
+
if (typeof failedSource.applyInvalidSubmitPolicy === "function") {
|
|
7009
|
+
failedSource.applyInvalidSubmitPolicy();
|
|
7010
|
+
}
|
|
6616
7011
|
}
|
|
6617
7012
|
}
|
|
6618
7013
|
}
|
|
@@ -6627,7 +7022,7 @@ function useWizard(options) {
|
|
|
6627
7022
|
done.value = false;
|
|
6628
7023
|
lazyEpoch.value += 1;
|
|
6629
7024
|
for (const step of compiledSteps.value) {
|
|
6630
|
-
const full = step.form;
|
|
7025
|
+
const full = asSubmissionSource(step.form);
|
|
6631
7026
|
if (typeof full.reset === "function") full.reset();
|
|
6632
7027
|
}
|
|
6633
7028
|
const firstStep = compiledSteps.value[0];
|
|
@@ -6677,12 +7072,8 @@ function useWizard(options) {
|
|
|
6677
7072
|
return count.value;
|
|
6678
7073
|
},
|
|
6679
7074
|
statuses,
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
},
|
|
6683
|
-
get allErrors() {
|
|
6684
|
-
return allErrors.value;
|
|
6685
|
-
},
|
|
7075
|
+
allValues,
|
|
7076
|
+
allErrors,
|
|
6686
7077
|
get progress() {
|
|
6687
7078
|
return progress.value;
|
|
6688
7079
|
},
|
|
@@ -6825,9 +7216,11 @@ exports.isPlainRecord = isPlainRecord;
|
|
|
6825
7216
|
exports.isUnset = isUnset;
|
|
6826
7217
|
exports.lazy = lazy;
|
|
6827
7218
|
exports.normalizeNumericOption = normalizeNumericOption;
|
|
7219
|
+
exports.safeAssign = safeAssign;
|
|
7220
|
+
exports.safeOwnRead = safeOwnRead;
|
|
6828
7221
|
exports.setAtPath = setAtPath;
|
|
6829
7222
|
exports.slimKindOf = slimKindOf;
|
|
6830
7223
|
exports.unset = unset;
|
|
6831
7224
|
exports.useAbstractForm = useAbstractForm;
|
|
6832
7225
|
exports.useWizard = useWizard;
|
|
6833
|
-
//# sourceMappingURL=attaform.
|
|
7226
|
+
//# sourceMappingURL=attaform.DLnE5bZa.cjs.map
|