attaform 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -36
- package/dist/chunks/devtools.cjs +10 -37
- package/dist/chunks/devtools.cjs.map +1 -1
- package/dist/chunks/devtools.mjs +10 -37
- package/dist/chunks/devtools.mjs.map +1 -1
- package/dist/chunks/indexeddb.cjs +4 -4
- package/dist/chunks/indexeddb.cjs.map +1 -1
- package/dist/chunks/indexeddb.mjs +1 -1
- package/dist/chunks/local-storage.cjs +2 -2
- package/dist/chunks/local-storage.cjs.map +1 -1
- package/dist/chunks/local-storage.mjs +1 -1
- package/dist/chunks/session-storage.cjs +2 -2
- package/dist/chunks/session-storage.cjs.map +1 -1
- package/dist/chunks/session-storage.mjs +1 -1
- package/dist/index.cjs +42 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +159 -196
- package/dist/index.d.mts +159 -196
- package/dist/index.d.ts +159 -196
- package/dist/index.mjs +5 -7
- package/dist/index.mjs.map +1 -1
- package/dist/nuxt.cjs +31 -40
- package/dist/nuxt.cjs.map +1 -1
- package/dist/nuxt.d.cts +8 -1
- package/dist/nuxt.d.mts +8 -1
- package/dist/nuxt.d.ts +8 -1
- package/dist/nuxt.mjs +32 -41
- package/dist/nuxt.mjs.map +1 -1
- package/dist/runtime/components/AttaformDevtoolsPanel.d.vue.ts +7 -0
- package/dist/runtime/components/AttaformDevtoolsPanel.vue +453 -0
- package/dist/runtime/components/AttaformDevtoolsPanel.vue.d.ts +7 -0
- package/dist/runtime/components/DevtoolsValueTree.d.vue.ts +37 -0
- package/dist/runtime/components/DevtoolsValueTree.vue +192 -0
- package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +37 -0
- package/dist/runtime/plugins/attaform.cjs +17 -6
- package/dist/runtime/plugins/attaform.cjs.map +1 -1
- package/dist/runtime/plugins/attaform.mjs +15 -4
- package/dist/runtime/plugins/attaform.mjs.map +1 -1
- package/dist/shared/attaform.5UhpSVFI.cjs +63 -0
- package/dist/shared/attaform.5UhpSVFI.cjs.map +1 -0
- package/dist/shared/attaform.BDdFdjeX.mjs +57 -0
- package/dist/shared/attaform.BDdFdjeX.mjs.map +1 -0
- package/dist/shared/attaform.Bgu9l6OG.d.cts +1651 -0
- package/dist/shared/attaform.BmDBu4ql.d.ts +1651 -0
- package/dist/shared/{attaform.Dee2rU1P.cjs → attaform.BqK_L4gK.cjs} +310 -24
- package/dist/shared/attaform.BqK_L4gK.cjs.map +1 -0
- package/dist/shared/{attaform.C_5aB6EQ.d.mts → attaform.BsMdl-35.d.cts} +754 -146
- package/dist/shared/{attaform.C_5aB6EQ.d.ts → attaform.BsMdl-35.d.mts} +754 -146
- package/dist/shared/{attaform.C_5aB6EQ.d.cts → attaform.BsMdl-35.d.ts} +754 -146
- package/dist/shared/attaform.Bubm_slq.cjs.map +1 -1
- package/dist/shared/attaform.C3x1hKJC.d.mts +53 -0
- package/dist/shared/{attaform.CuE-bS1C.d.mts → attaform.CWs1Z3p7.d.ts} +57 -23
- package/dist/shared/attaform.CXpzmj38.mjs.map +1 -1
- package/dist/shared/attaform.CjmJpfLH.d.ts +53 -0
- package/dist/shared/{attaform.CVCmBKZX.mjs → attaform.CtNUB9nf.mjs} +74 -72
- package/dist/shared/attaform.CtNUB9nf.mjs.map +1 -0
- package/dist/shared/{attaform.C0iFnTN0.d.ts → attaform.D-hDvb98.d.cts} +57 -23
- package/dist/shared/attaform.DAH3kvav.d.mts +1651 -0
- package/dist/shared/{attaform.B5qiXQwN.cjs → attaform.DUHru0OF.cjs} +83 -81
- package/dist/shared/attaform.DUHru0OF.cjs.map +1 -0
- package/dist/shared/{attaform.BV40t5y2.cjs → attaform.Dlk1jMuv.cjs} +245 -108
- package/dist/shared/attaform.Dlk1jMuv.cjs.map +1 -0
- package/dist/shared/{attaform.B3ZaPIzS.mjs → attaform.DsC3rZHG.mjs} +1804 -219
- package/dist/shared/attaform.DsC3rZHG.mjs.map +1 -0
- package/dist/shared/attaform.Dzi89x8N.d.cts +53 -0
- package/dist/shared/{attaform.Cer8JO_P.cjs → attaform.II89Pcf4.cjs} +1860 -272
- package/dist/shared/attaform.II89Pcf4.cjs.map +1 -0
- package/dist/shared/{attaform.CIEQgJnM.mjs → attaform.Xhg0AYNa.mjs} +300 -26
- package/dist/shared/attaform.Xhg0AYNa.mjs.map +1 -0
- package/dist/shared/{attaform.CpERWz3u.mjs → attaform.Xt0A3QUd.mjs} +232 -95
- package/dist/shared/attaform.Xt0A3QUd.mjs.map +1 -0
- package/dist/shared/{attaform.CHorcsIU.d.cts → attaform.bH7WvNad.d.mts} +57 -23
- package/dist/vite.cjs +270 -2
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.mjs +266 -2
- package/dist/vite.mjs.map +1 -1
- package/dist/zod-v3.cjs +11 -8
- package/dist/zod-v3.cjs.map +1 -1
- package/dist/zod-v3.d.cts +36 -84
- package/dist/zod-v3.d.mts +36 -84
- package/dist/zod-v3.d.ts +36 -84
- package/dist/zod-v3.mjs +3 -3
- package/dist/zod-v4.cjs +11 -8
- package/dist/zod-v4.cjs.map +1 -1
- package/dist/zod-v4.d.cts +5 -5
- package/dist/zod-v4.d.mts +5 -5
- package/dist/zod-v4.d.ts +5 -5
- package/dist/zod-v4.mjs +3 -3
- package/dist/zod.cjs +13 -10
- package/dist/zod.cjs.map +1 -1
- package/dist/zod.d.cts +127 -15
- package/dist/zod.d.mts +127 -15
- package/dist/zod.d.ts +127 -15
- package/dist/zod.mjs +5 -5
- package/dist/zod.mjs.map +1 -1
- package/package.json +19 -5
- package/dist/shared/attaform.B1jvxsOF.d.mts +0 -156
- package/dist/shared/attaform.B3ZaPIzS.mjs.map +0 -1
- package/dist/shared/attaform.B5qiXQwN.cjs.map +0 -1
- package/dist/shared/attaform.BBM2muQ9.cjs +0 -101
- package/dist/shared/attaform.BBM2muQ9.cjs.map +0 -1
- package/dist/shared/attaform.BV40t5y2.cjs.map +0 -1
- package/dist/shared/attaform.C6qzEdIM.d.cts +0 -156
- package/dist/shared/attaform.C8LVFVVe.cjs +0 -32
- package/dist/shared/attaform.C8LVFVVe.cjs.map +0 -1
- package/dist/shared/attaform.CIEQgJnM.mjs.map +0 -1
- package/dist/shared/attaform.CTwNcpLE.d.ts +0 -156
- package/dist/shared/attaform.CVCmBKZX.mjs.map +0 -1
- package/dist/shared/attaform.Cer8JO_P.cjs.map +0 -1
- package/dist/shared/attaform.CpERWz3u.mjs.map +0 -1
- package/dist/shared/attaform.Dee2rU1P.cjs.map +0 -1
- package/dist/shared/attaform.Vo-Kft0t.mjs +0 -29
- package/dist/shared/attaform.Vo-Kft0t.mjs.map +0 -1
- package/dist/shared/attaform.h1sq3BFu.mjs +0 -92
- package/dist/shared/attaform.h1sq3BFu.mjs.map +0 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const vue = require('vue');
|
|
4
|
-
const
|
|
5
|
-
const paths = require('./attaform.BBM2muQ9.cjs');
|
|
4
|
+
const paths = require('./attaform.BqK_L4gK.cjs');
|
|
6
5
|
|
|
7
6
|
const NOT_FOUND = Symbol("NOT_FOUND");
|
|
8
7
|
function descendStep(value, segment) {
|
|
@@ -123,7 +122,12 @@ function mergeStructural(schema, path, consumer, defaultValue = schema.getDefaul
|
|
|
123
122
|
return mergeStructuralImpl(schema, scratch, consumer, defaultValue);
|
|
124
123
|
}
|
|
125
124
|
function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
|
|
126
|
-
if (consumer === void 0)
|
|
125
|
+
if (consumer === void 0) {
|
|
126
|
+
if (schema.getSlimPrimitiveTypesAtPath?.(scratch).has("undefined") === true) {
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
return defaultValue;
|
|
130
|
+
}
|
|
127
131
|
if (consumer === null) return null;
|
|
128
132
|
if (Array.isArray(consumer)) {
|
|
129
133
|
const shape = resolveArrayShape(schema, scratch);
|
|
@@ -163,7 +167,7 @@ function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
|
|
|
163
167
|
let mutated = false;
|
|
164
168
|
const out = { ...consumer };
|
|
165
169
|
for (const key of Object.keys(defaultValue)) {
|
|
166
|
-
if (!(key in consumer)
|
|
170
|
+
if (!(key in consumer)) {
|
|
167
171
|
const defAtKey = defaultValue[key];
|
|
168
172
|
scratch.push(key);
|
|
169
173
|
const filled = mergeStructuralImpl(schema, scratch, void 0, defAtKey);
|
|
@@ -175,10 +179,12 @@ function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
|
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
181
|
for (const key of Object.keys(consumer)) {
|
|
182
|
+
const cVal = consumer[key];
|
|
183
|
+
if (cVal === void 0) continue;
|
|
178
184
|
scratch.push(key);
|
|
179
|
-
const merged = mergeStructuralImpl(schema, scratch,
|
|
185
|
+
const merged = mergeStructuralImpl(schema, scratch, cVal, defaultValue[key]);
|
|
180
186
|
scratch.pop();
|
|
181
|
-
if (merged !==
|
|
187
|
+
if (merged !== cVal) {
|
|
182
188
|
out[key] = merged;
|
|
183
189
|
mutated = true;
|
|
184
190
|
}
|
|
@@ -506,7 +512,7 @@ function buildLeafFieldStateBase(state, segments, key) {
|
|
|
506
512
|
dirty: !pristine,
|
|
507
513
|
focused: record?.focused ?? null,
|
|
508
514
|
blurred: record?.blurred ?? null,
|
|
509
|
-
touched: record?.touched ??
|
|
515
|
+
touched: record?.touched ?? false,
|
|
510
516
|
connected: record?.connected ?? false,
|
|
511
517
|
element: firstElement,
|
|
512
518
|
elements: elementsArr,
|
|
@@ -648,10 +654,12 @@ function buildSurfaceProxy(opts) {
|
|
|
648
654
|
if (opts.leafKeys !== void 0) return leafViewProxyAt(segs);
|
|
649
655
|
return opts.resolveLeaf(segs);
|
|
650
656
|
}
|
|
657
|
+
if (opts.isTerminalAt?.(segs) === true) return opts.resolveLeaf(segs);
|
|
651
658
|
return containerProxyAt(segs);
|
|
652
659
|
}
|
|
653
660
|
function containerProxyAt(segments) {
|
|
654
|
-
const
|
|
661
|
+
const isArrayLike = opts.isArrayContainer?.(segments) === true;
|
|
662
|
+
const cacheKey = `${JSON.stringify(segments)}+${isArrayLike ? "A" : "O"}`;
|
|
655
663
|
const existing = containerCache.get(cacheKey);
|
|
656
664
|
if (existing !== void 0) return existing;
|
|
657
665
|
const snapshotContainer = () => opts.materializeContainer === void 0 ? {} : opts.materializeContainer(segments);
|
|
@@ -661,7 +669,7 @@ function buildSurfaceProxy(opts) {
|
|
|
661
669
|
return this;
|
|
662
670
|
}
|
|
663
671
|
const containerToPrimitive = (hint) => hint === "number" ? NaN : containerToString();
|
|
664
|
-
const target = (() => {
|
|
672
|
+
const target = isArrayLike ? [] : (() => {
|
|
665
673
|
});
|
|
666
674
|
const proxy = new Proxy(target, {
|
|
667
675
|
apply(_, __, args) {
|
|
@@ -676,7 +684,14 @@ function buildSurfaceProxy(opts) {
|
|
|
676
684
|
return Reflect.get(target, key);
|
|
677
685
|
}
|
|
678
686
|
if (typeof key !== "string") return void 0;
|
|
687
|
+
if (key === "__v_skip") return true;
|
|
688
|
+
if (key === "__v_isReactive" || key === "__v_isReadonly" || key === "__v_isShallow" || key === "__v_isRef" || key === "__v_raw") {
|
|
689
|
+
return void 0;
|
|
690
|
+
}
|
|
679
691
|
if (key === "toJSON") return containerToJSON;
|
|
692
|
+
if (key === "length" && (isArrayLike || opts.isArrayContainer?.(segments) === true)) {
|
|
693
|
+
return opts.containerOwnKeys === void 0 ? 0 : opts.containerOwnKeys(segments).length;
|
|
694
|
+
}
|
|
680
695
|
const childSegs = [...segments, keyToSegment(key)];
|
|
681
696
|
if (key === "toString" || key === "valueOf") {
|
|
682
697
|
if (!schemaHasPath(childSegs)) {
|
|
@@ -689,11 +704,38 @@ function buildSurfaceProxy(opts) {
|
|
|
689
704
|
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
690
705
|
return true;
|
|
691
706
|
},
|
|
692
|
-
//
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
|
|
696
|
-
|
|
707
|
+
// Live enumeration. When the surface provides `containerOwnKeys`,
|
|
708
|
+
// `Object.keys(proxy)` / `Object.entries(proxy)` /
|
|
709
|
+
// `v-for="(child, key) in proxy"` reflect whichever keys the
|
|
710
|
+
// underlying form data currently holds at this path: array
|
|
711
|
+
// indices (`'0'`, `'1'`, …) when the live value is an array,
|
|
712
|
+
// object keys when it's a record. `getOwnPropertyDescriptor`
|
|
713
|
+
// descends to the per-key sub-proxy so iteration yields the
|
|
714
|
+
// same objects dot-access would. Without `containerOwnKeys`,
|
|
715
|
+
// the container stays non-enumerable for callers that don't
|
|
716
|
+
// need iteration — `JSON.stringify` still serialises through
|
|
717
|
+
// the `toJSON` trap above either way.
|
|
718
|
+
ownKeys: () => {
|
|
719
|
+
const liveKeys = opts.containerOwnKeys === void 0 ? [] : opts.containerOwnKeys(segments);
|
|
720
|
+
if (isArrayLike) return ["length", ...liveKeys];
|
|
721
|
+
return [...liveKeys];
|
|
722
|
+
},
|
|
723
|
+
getOwnPropertyDescriptor(_, key) {
|
|
724
|
+
if (typeof key !== "string") return void 0;
|
|
725
|
+
if (isArrayLike && key === "length") {
|
|
726
|
+
const length = opts.containerOwnKeys === void 0 ? 0 : opts.containerOwnKeys(segments).length;
|
|
727
|
+
return { configurable: false, enumerable: false, value: length, writable: true };
|
|
728
|
+
}
|
|
729
|
+
if (opts.containerOwnKeys === void 0) return void 0;
|
|
730
|
+
const liveKeys = opts.containerOwnKeys(segments);
|
|
731
|
+
if (!liveKeys.includes(key)) return void 0;
|
|
732
|
+
return {
|
|
733
|
+
configurable: true,
|
|
734
|
+
enumerable: true,
|
|
735
|
+
value: descendOrTerminate([...segments, keyToSegment(key)]),
|
|
736
|
+
writable: false
|
|
737
|
+
};
|
|
738
|
+
},
|
|
697
739
|
// Block writes at the proxy boundary. Mutations go through
|
|
698
740
|
// `setValue`, the directive, or the field-array helpers.
|
|
699
741
|
set: () => false,
|
|
@@ -784,22 +826,47 @@ function buildErrorsProxy(state) {
|
|
|
784
826
|
return buildSurfaceProxy({
|
|
785
827
|
schema: state.schema,
|
|
786
828
|
resolveLeaf: (path) => {
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
|
|
829
|
+
const isContainerSelfAccess = path.length > 1 && path[path.length - 1] === "";
|
|
830
|
+
const collectAtKey = (key2, active2, into) => {
|
|
831
|
+
if (active2) {
|
|
832
|
+
const s = state.schemaErrors.get(key2);
|
|
833
|
+
const b = state.derivedBlankErrors.value.get(key2);
|
|
834
|
+
if (s !== void 0) into.push(...s);
|
|
835
|
+
if (b !== void 0) into.push(...b);
|
|
836
|
+
}
|
|
837
|
+
const u = state.userErrors.get(key2);
|
|
838
|
+
if (u !== void 0) into.push(...u);
|
|
839
|
+
};
|
|
790
840
|
const merged = [];
|
|
791
|
-
if (
|
|
792
|
-
const
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
841
|
+
if (isContainerSelfAccess) {
|
|
842
|
+
const containerPath = path.slice(0, -1);
|
|
843
|
+
const containerKey = paths.canonicalizePath(containerPath).key;
|
|
844
|
+
const literalKey = paths.canonicalizePath(path).key;
|
|
845
|
+
const active2 = hasAtPath(state.form.value, containerPath);
|
|
846
|
+
collectAtKey(containerKey, active2, merged);
|
|
847
|
+
if (literalKey !== containerKey) collectAtKey(literalKey, active2, merged);
|
|
848
|
+
return merged;
|
|
796
849
|
}
|
|
797
|
-
|
|
798
|
-
|
|
850
|
+
const { key } = paths.canonicalizePath(path);
|
|
851
|
+
const isFormLevel = key === paths.FORM_ERRORS_PATH_KEY;
|
|
852
|
+
const active = isFormLevel || hasAtPath(state.form.value, path);
|
|
853
|
+
collectAtKey(key, active, merged);
|
|
854
|
+
return merged;
|
|
799
855
|
},
|
|
800
856
|
// No leafKeys — at a leaf, the resolved value (the merged array or
|
|
801
857
|
// undefined) IS the terminal.
|
|
802
858
|
materializeContainer: (segments) => materializeErrors(state, segments),
|
|
859
|
+
// Any path ending in `''` is a meaningful terminal at the proxy
|
|
860
|
+
// layer: at root it's the form-level bucket; at depth >= 1 it's
|
|
861
|
+
// the container-self sentinel that surfaces cross-field refines
|
|
862
|
+
// and container-targeted marks. `resolveLeaf` translates `[...,
|
|
863
|
+
// '']` lookups to the parent container path before querying the
|
|
864
|
+
// stores. When a schema legitimately owns a `''` field, the
|
|
865
|
+
// literal leaf and any container-self errors share the slot
|
|
866
|
+
// (errors concatenate) — vanishingly rare, accepted as the
|
|
867
|
+
// ergonomic cost of one unified sentinel convention at every
|
|
868
|
+
// depth.
|
|
869
|
+
isTerminalAt: (segs) => segs.length >= 1 && segs[segs.length - 1] === "",
|
|
803
870
|
// Call-form aggregates: `form.errors(path)` returns a single
|
|
804
871
|
// `ValidationError[]` for any depth (leaf or container) — same
|
|
805
872
|
// shared `aggregateErrorsAt` helper that `form.meta.errors` and
|
|
@@ -809,12 +876,31 @@ function buildErrorsProxy(state) {
|
|
|
809
876
|
// when valid) so consumer code that branches on truthiness keeps
|
|
810
877
|
// working — the call-form just extends that semantic to
|
|
811
878
|
// containers and dynamic paths.
|
|
812
|
-
resolveCallTarget: (path) =>
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
879
|
+
resolveCallTarget: (path) => aggregateErrorsAt(state, path),
|
|
880
|
+
// Mirror `form.fields` enumeration: `Object.keys(form.errors.items)`
|
|
881
|
+
// and `v-for="(errs, idx) in form.errors.items"` walk the live
|
|
882
|
+
// array indices / object keys at the path. Iteration yields the
|
|
883
|
+
// descended sub-proxies (one per live key), so consumers can
|
|
884
|
+
// `form.errors.items[idx]` straight from the entry.
|
|
885
|
+
containerOwnKeys: (segments) => liveKeysAtPath$1(state, segments),
|
|
886
|
+
isArrayContainer: (segments) => isArrayPath$1(state, segments)
|
|
816
887
|
});
|
|
817
888
|
}
|
|
889
|
+
function liveKeysAtPath$1(state, segments) {
|
|
890
|
+
const value = getAtPath(state.form.value, segments);
|
|
891
|
+
if (value === null || value === void 0) return [];
|
|
892
|
+
if (Array.isArray(value)) {
|
|
893
|
+
const keys = new Array(value.length);
|
|
894
|
+
for (let i = 0; i < value.length; i += 1) keys[i] = String(i);
|
|
895
|
+
return keys;
|
|
896
|
+
}
|
|
897
|
+
if (typeof value === "object") return Object.keys(value);
|
|
898
|
+
return [];
|
|
899
|
+
}
|
|
900
|
+
function isArrayPath$1(state, segments) {
|
|
901
|
+
if (segments.length === 0) return false;
|
|
902
|
+
return Array.isArray(getAtPath(state.form.value, segments));
|
|
903
|
+
}
|
|
818
904
|
function materializeErrors(state, containerSegments) {
|
|
819
905
|
const liveContainer = getAtPath(state.form.value, containerSegments);
|
|
820
906
|
const tree = Array.isArray(liveContainer) ? [] : {};
|
|
@@ -823,12 +909,33 @@ function materializeErrors(state, containerSegments) {
|
|
|
823
909
|
if (errors.length === 0) continue;
|
|
824
910
|
const fullPath = paths.segmentsForPathKey(pathKey);
|
|
825
911
|
if (fullPath === null) continue;
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
if (fullPath
|
|
912
|
+
const isSyntheticFormLevel = fullPath.length === 1 && fullPath[0] === "";
|
|
913
|
+
if (!isSyntheticFormLevel) {
|
|
914
|
+
if (fullPath.length < containerSegments.length) continue;
|
|
915
|
+
for (let i = 0; i < containerSegments.length; i++) {
|
|
916
|
+
if (fullPath[i] !== containerSegments[i]) continue entries;
|
|
917
|
+
}
|
|
918
|
+
} else if (containerSegments.length !== 0) {
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
if (applyActivePathFilter && !isSyntheticFormLevel && !hasAtPath(state.form.value, fullPath))
|
|
922
|
+
continue;
|
|
923
|
+
let placePath;
|
|
924
|
+
if (isSyntheticFormLevel) {
|
|
925
|
+
placePath = [""];
|
|
926
|
+
} else {
|
|
927
|
+
const relativePath = fullPath.slice(containerSegments.length);
|
|
928
|
+
if (relativePath.length === 0) {
|
|
929
|
+
placePath = [""];
|
|
930
|
+
} else if (state.schema.isLeafAtPath(fullPath)) {
|
|
931
|
+
placePath = relativePath;
|
|
932
|
+
} else if (state.schema.getSlimPrimitiveTypesAtPath(fullPath).size > 0) {
|
|
933
|
+
placePath = [...relativePath, ""];
|
|
934
|
+
} else {
|
|
935
|
+
placePath = relativePath;
|
|
936
|
+
}
|
|
829
937
|
}
|
|
830
|
-
|
|
831
|
-
placeAt(tree, fullPath.slice(containerSegments.length), errors);
|
|
938
|
+
placeAt(tree, placePath, errors);
|
|
832
939
|
}
|
|
833
940
|
};
|
|
834
941
|
collect(state.schemaErrors, true);
|
|
@@ -1016,9 +1123,26 @@ function buildFieldStateProxy(state, getFormMetaBase, options) {
|
|
|
1016
1123
|
leafKeys: FIELD_STATE_KEYS,
|
|
1017
1124
|
readLeafKey: (computed, key) => computed.value[key],
|
|
1018
1125
|
materializeContainer: (segments) => materializeFields(state, segments, snapshotFieldStateAt),
|
|
1019
|
-
resolveCallTarget: (path) => fieldStateTerminalAt(path)
|
|
1126
|
+
resolveCallTarget: (path) => fieldStateTerminalAt(path),
|
|
1127
|
+
containerOwnKeys: (segments) => liveKeysAtPath(state, segments),
|
|
1128
|
+
isArrayContainer: (segments) => isArrayPath(state, segments)
|
|
1020
1129
|
});
|
|
1021
1130
|
}
|
|
1131
|
+
function liveKeysAtPath(state, segments) {
|
|
1132
|
+
const value = getAtPath(state.form.value, segments);
|
|
1133
|
+
if (value === null || value === void 0) return [];
|
|
1134
|
+
if (Array.isArray(value)) {
|
|
1135
|
+
const keys = new Array(value.length);
|
|
1136
|
+
for (let i = 0; i < value.length; i += 1) keys[i] = String(i);
|
|
1137
|
+
return keys;
|
|
1138
|
+
}
|
|
1139
|
+
if (typeof value === "object") return Object.keys(value);
|
|
1140
|
+
return [];
|
|
1141
|
+
}
|
|
1142
|
+
function isArrayPath(state, segments) {
|
|
1143
|
+
if (segments.length === 0) return false;
|
|
1144
|
+
return Array.isArray(getAtPath(state.form.value, segments));
|
|
1145
|
+
}
|
|
1022
1146
|
function materializeFields(state, containerSegments, snapshotFieldStateAt) {
|
|
1023
1147
|
const liveValue = getAtPath(state.form.value, containerSegments);
|
|
1024
1148
|
return walk$2(liveValue, containerSegments, state.schema, snapshotFieldStateAt);
|
|
@@ -1050,12 +1174,13 @@ const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
|
|
|
1050
1174
|
const PERSISTENCE_KEY_PREFIX = "attaform:";
|
|
1051
1175
|
const RESERVED_KEY_PREFIX = "__atta:";
|
|
1052
1176
|
const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
|
|
1177
|
+
const ANONYMOUS_WIZARD_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon-wizard:`;
|
|
1053
1178
|
const DEFAULT_MAX_RECURSION_DEPTH = 64;
|
|
1054
1179
|
function normalizeNumericOption(config) {
|
|
1055
1180
|
const { value, source, allowInfinity, min, defaultValue } = config;
|
|
1056
1181
|
if (allowInfinity && value === Infinity) return Infinity;
|
|
1057
1182
|
if (typeof value !== "number" || Number.isNaN(value) || value === Infinity || value === -Infinity) {
|
|
1058
|
-
if (
|
|
1183
|
+
if (paths.__DEV__) {
|
|
1059
1184
|
const acceptedDescription = allowInfinity ? "a non-negative integer or Infinity" : "a non-negative finite integer";
|
|
1060
1185
|
console.warn(
|
|
1061
1186
|
`[attaform] ${source} must be ${acceptedDescription}; got ${String(value)}. Falling back to ${String(defaultValue)}.`
|
|
@@ -1084,7 +1209,7 @@ async function getStorageAdapter(storage) {
|
|
|
1084
1209
|
}
|
|
1085
1210
|
}
|
|
1086
1211
|
}
|
|
1087
|
-
const PERSISTED_ENVELOPE_VERSION =
|
|
1212
|
+
const PERSISTED_ENVELOPE_VERSION = 5;
|
|
1088
1213
|
function readPersistedPayload(value) {
|
|
1089
1214
|
if (value === null || value === void 0 || typeof value !== "object") return null;
|
|
1090
1215
|
const envelope = value;
|
|
@@ -1096,7 +1221,7 @@ function readPersistedPayload(value) {
|
|
|
1096
1221
|
if (envelope.data === void 0 || typeof envelope.data !== "object") return null;
|
|
1097
1222
|
return envelope;
|
|
1098
1223
|
}
|
|
1099
|
-
const warnedVersions =
|
|
1224
|
+
const warnedVersions = paths.__DEV__ ? /* @__PURE__ */ new Set() : null;
|
|
1100
1225
|
function warnVersionMismatch(observedVersion) {
|
|
1101
1226
|
if (warnedVersions === null) return;
|
|
1102
1227
|
if (warnedVersions.has(observedVersion)) return;
|
|
@@ -1106,7 +1231,15 @@ function warnVersionMismatch(observedVersion) {
|
|
|
1106
1231
|
);
|
|
1107
1232
|
}
|
|
1108
1233
|
function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPaths) {
|
|
1109
|
-
|
|
1234
|
+
let transientList;
|
|
1235
|
+
if (blankPaths !== void 0 && blankPaths.size > 0) {
|
|
1236
|
+
const dotted = [];
|
|
1237
|
+
for (const key of blankPaths) {
|
|
1238
|
+
const d = paths.pathKeyToDotted(key);
|
|
1239
|
+
if (d !== null) dotted.push(d);
|
|
1240
|
+
}
|
|
1241
|
+
transientList = dotted.length > 0 ? dotted : void 0;
|
|
1242
|
+
}
|
|
1110
1243
|
if (include === "form") {
|
|
1111
1244
|
if (transientList === void 0) return { v: PERSISTED_ENVELOPE_VERSION, data: { form } };
|
|
1112
1245
|
return {
|
|
@@ -1274,13 +1407,40 @@ const AttaformErrorCode = {
|
|
|
1274
1407
|
NoValueSupplied: "atta:no-value-supplied",
|
|
1275
1408
|
/** The schema adapter's `validateAtPath` threw synchronously. */
|
|
1276
1409
|
AdapterThrew: "atta:adapter-threw",
|
|
1410
|
+
/**
|
|
1411
|
+
* User code inside a `z.preprocess`, `.refine`, or `.transform`
|
|
1412
|
+
* threw (sync or async). The adapter caught the throw and surfaced
|
|
1413
|
+
* it as a `ValidationError` at the field path so the form's normal
|
|
1414
|
+
* error pipeline handles it instead of leaking as an unhandled
|
|
1415
|
+
* rejection or routing through `submitError`.
|
|
1416
|
+
*/
|
|
1417
|
+
ValidatorThrew: "atta:validator-threw",
|
|
1418
|
+
/**
|
|
1419
|
+
* A function-form `defaultValues` factory threw or its promise
|
|
1420
|
+
* rejected. The runtime captures the raw error on `form.hydrateError`
|
|
1421
|
+
* and ALSO surfaces a form-level `ValidationError` (path `[]`) so
|
|
1422
|
+
* the standard error pipeline carries the signal. Critical for the
|
|
1423
|
+
* SSR round-trip: `hydrateError` itself does not ride the wire
|
|
1424
|
+
* payload, but `schemaErrors` does, so the client sees the failure
|
|
1425
|
+
* after rehydration without an extra channel.
|
|
1426
|
+
*/
|
|
1427
|
+
HydrationFailed: "atta:hydration-failed",
|
|
1277
1428
|
/** The supplied path didn't resolve to any node in the schema. */
|
|
1278
|
-
PathNotFound: "atta:path-not-found"
|
|
1429
|
+
PathNotFound: "atta:path-not-found",
|
|
1430
|
+
/**
|
|
1431
|
+
* A walked form's `activate()` (async `defaultValues` factory) threw
|
|
1432
|
+
* during `wizard.handleSubmit`'s path walk. Surfaced as a synthetic
|
|
1433
|
+
* `ValidationError` at the form-level path (`[]`) so the wizard's
|
|
1434
|
+
* aggregate error pipeline can carry the failure alongside ordinary
|
|
1435
|
+
* validation errors. The raw factory error remains on
|
|
1436
|
+
* `form.hydrateError` for retry UX.
|
|
1437
|
+
*/
|
|
1438
|
+
ActivationFailed: "atta:activation-failed"
|
|
1279
1439
|
};
|
|
1280
1440
|
|
|
1281
|
-
const warnedNoScopeStores =
|
|
1441
|
+
const warnedNoScopeStores = paths.__DEV__ ? /* @__PURE__ */ new WeakSet() : null;
|
|
1282
1442
|
function buildProcessForm(state, formInstanceId, options = {}) {
|
|
1283
|
-
const invalidPolicy = options.onInvalidSubmit ?? "
|
|
1443
|
+
const invalidPolicy = options.onInvalidSubmit ?? "focus-first-error";
|
|
1284
1444
|
function validate(pathInput) {
|
|
1285
1445
|
const result = vue.ref({
|
|
1286
1446
|
pending: true,
|
|
@@ -1330,7 +1490,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
|
|
|
1330
1490
|
});
|
|
1331
1491
|
if (vue.getCurrentScope() !== void 0) {
|
|
1332
1492
|
vue.onScopeDispose(stop);
|
|
1333
|
-
} else if (
|
|
1493
|
+
} else if (paths.__DEV__ && warnedNoScopeStores !== null && !warnedNoScopeStores.has(state)) {
|
|
1334
1494
|
warnedNoScopeStores.add(state);
|
|
1335
1495
|
console.warn(
|
|
1336
1496
|
"[attaform] validate() called outside a Vue effect scope; its reactive watcher will leak until the form is garbage-collected. Fix: call validate() inside setup() / a child component, or wrap the call in `effectScope().run(...)`."
|
|
@@ -1343,7 +1503,15 @@ function buildProcessForm(state, formInstanceId, options = {}) {
|
|
|
1343
1503
|
const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
|
|
1344
1504
|
try {
|
|
1345
1505
|
state.activeValidations.value += 1;
|
|
1506
|
+
state.cancelFieldValidation();
|
|
1346
1507
|
const refinement = await runRefinementValidation(dataAtPath, segments);
|
|
1508
|
+
const scopePath = segments ?? [];
|
|
1509
|
+
const errors = refinement.success ? [] : refinement.errors;
|
|
1510
|
+
const reStamped = segments === void 0 ? errors : errors.map((err) => ({
|
|
1511
|
+
...err,
|
|
1512
|
+
path: [...segments, ...err.path]
|
|
1513
|
+
}));
|
|
1514
|
+
state.applySchemaErrorsForSubtree(scopePath, reStamped);
|
|
1347
1515
|
return stripData(composeWithDerivedBlank(refinement, segments));
|
|
1348
1516
|
} catch (err) {
|
|
1349
1517
|
return adapterThrowResponse(err);
|
|
@@ -1431,7 +1599,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
|
|
|
1431
1599
|
try {
|
|
1432
1600
|
await onError(merged.errors);
|
|
1433
1601
|
} catch (cause) {
|
|
1434
|
-
throw new
|
|
1602
|
+
throw new paths.SubmitErrorHandlerError("User-provided onError threw", { cause });
|
|
1435
1603
|
}
|
|
1436
1604
|
}
|
|
1437
1605
|
return;
|
|
@@ -1440,6 +1608,9 @@ function buildProcessForm(state, formInstanceId, options = {}) {
|
|
|
1440
1608
|
state.clearSchemaErrors();
|
|
1441
1609
|
}
|
|
1442
1610
|
await onSubmit(merged.data);
|
|
1611
|
+
if (state.submissionGeneration.value === genAtEntry) {
|
|
1612
|
+
state.submitted.value = true;
|
|
1613
|
+
}
|
|
1443
1614
|
state.emitSubmitSuccess();
|
|
1444
1615
|
} catch (err) {
|
|
1445
1616
|
if (state.submissionGeneration.value === genAtEntry) {
|
|
@@ -1453,7 +1624,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
|
|
|
1453
1624
|
state.activeSubmissions.value = Math.max(0, state.activeSubmissions.value - 1);
|
|
1454
1625
|
if (state.submissionGeneration.value === genAtEntry) {
|
|
1455
1626
|
state.submitting.value = state.activeSubmissions.value > 0;
|
|
1456
|
-
state.
|
|
1627
|
+
state.submissionAttempts.value += 1;
|
|
1457
1628
|
}
|
|
1458
1629
|
}
|
|
1459
1630
|
};
|
|
@@ -1528,7 +1699,7 @@ function extractSchemaFields(schema) {
|
|
|
1528
1699
|
return [];
|
|
1529
1700
|
}
|
|
1530
1701
|
|
|
1531
|
-
const warnedRejections =
|
|
1702
|
+
const warnedRejections = paths.__DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
1532
1703
|
function shouldWarnOnce$1(store, key) {
|
|
1533
1704
|
if (warnedRejections === null) return false;
|
|
1534
1705
|
let set = warnedRejections.get(store);
|
|
@@ -1547,6 +1718,7 @@ function slimKindOf(value) {
|
|
|
1547
1718
|
if (value instanceof Date) return "date";
|
|
1548
1719
|
if (value instanceof Map) return "map";
|
|
1549
1720
|
if (value instanceof Set) return "set";
|
|
1721
|
+
if (typeof File !== "undefined" && value instanceof File) return "file";
|
|
1550
1722
|
const t = typeof value;
|
|
1551
1723
|
switch (t) {
|
|
1552
1724
|
case "string":
|
|
@@ -1577,6 +1749,7 @@ function isSlimPrimitiveValid(schema, store, path, value) {
|
|
|
1577
1749
|
return walk$1(schema, store, path, value);
|
|
1578
1750
|
}
|
|
1579
1751
|
function walk$1(schema, store, path, value) {
|
|
1752
|
+
if (schema.isPreprocessOrCoerceLeaf(path)) return true;
|
|
1580
1753
|
const accepted = schema.getSlimPrimitiveTypesAtPath(path);
|
|
1581
1754
|
const kind = isLeafValue(value) ? slimKindOf(value) : Array.isArray(value) ? "array" : "object";
|
|
1582
1755
|
if (!accepted.has(kind)) {
|
|
@@ -1600,7 +1773,7 @@ function walk$1(schema, store, path, value) {
|
|
|
1600
1773
|
return true;
|
|
1601
1774
|
}
|
|
1602
1775
|
function reportRejection(store, path, kind, accepted) {
|
|
1603
|
-
if (!
|
|
1776
|
+
if (!paths.__DEV__) return;
|
|
1604
1777
|
const dotted = path.map((s) => String(s)).join(".") || "(root)";
|
|
1605
1778
|
const key = `${dotted}::${kind}`;
|
|
1606
1779
|
if (!shouldWarnOnce$1(store, key)) return;
|
|
@@ -1674,13 +1847,13 @@ function indexRules(rules) {
|
|
|
1674
1847
|
for (const entry of rules) {
|
|
1675
1848
|
const candidate = entry;
|
|
1676
1849
|
if (candidate === null || typeof candidate !== "object" || typeof candidate.transform !== "function") {
|
|
1677
|
-
if (
|
|
1850
|
+
if (paths.__DEV__) {
|
|
1678
1851
|
console.warn("[attaform] coercion entry missing or invalid `transform` \u2014 skipped.");
|
|
1679
1852
|
}
|
|
1680
1853
|
continue;
|
|
1681
1854
|
}
|
|
1682
1855
|
const key = `${entry.input}->${entry.output}`;
|
|
1683
|
-
if (idx.has(key) &&
|
|
1856
|
+
if (idx.has(key) && paths.__DEV__) {
|
|
1684
1857
|
console.warn(`[attaform] duplicate coercion rule for '${key}' \u2014 last entry wins.`);
|
|
1685
1858
|
}
|
|
1686
1859
|
idx.set(key, entry);
|
|
@@ -1709,7 +1882,7 @@ function pickScalarTarget(accepted) {
|
|
|
1709
1882
|
if (accepted.has("bigint")) return "bigint";
|
|
1710
1883
|
return null;
|
|
1711
1884
|
}
|
|
1712
|
-
const warnedCoerce =
|
|
1885
|
+
const warnedCoerce = paths.__DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
1713
1886
|
const sharedWarnStore = {};
|
|
1714
1887
|
function shouldWarnOnce(key) {
|
|
1715
1888
|
if (warnedCoerce === null) return false;
|
|
@@ -1734,7 +1907,7 @@ function coerceScalar(value, accepted, index) {
|
|
|
1734
1907
|
try {
|
|
1735
1908
|
result = entry.transform(value);
|
|
1736
1909
|
} catch (err) {
|
|
1737
|
-
if (
|
|
1910
|
+
if (paths.__DEV__ && shouldWarnOnce(`${entry.input}->${entry.output}::throw`)) {
|
|
1738
1911
|
console.warn(
|
|
1739
1912
|
`[attaform] coercion '${entry.input}->${entry.output}' threw \u2014 write passes through.`,
|
|
1740
1913
|
err
|
|
@@ -1745,7 +1918,7 @@ function coerceScalar(value, accepted, index) {
|
|
|
1745
1918
|
if (!result.coerced) return value;
|
|
1746
1919
|
const returnedKind = slimKindOf(result.value);
|
|
1747
1920
|
if (returnedKind !== entry.output) {
|
|
1748
|
-
if (
|
|
1921
|
+
if (paths.__DEV__ && shouldWarnOnce(`${entry.input}->${entry.output}::wrong-kind:${returnedKind}`)) {
|
|
1749
1922
|
console.warn(
|
|
1750
1923
|
`[attaform] coercion '${entry.input}->${entry.output}' produced a ${returnedKind} \u2014 write passes through.`
|
|
1751
1924
|
);
|
|
@@ -1753,7 +1926,7 @@ function coerceScalar(value, accepted, index) {
|
|
|
1753
1926
|
return value;
|
|
1754
1927
|
}
|
|
1755
1928
|
if (entry.output === "number" && !Number.isFinite(result.value)) {
|
|
1756
|
-
if (
|
|
1929
|
+
if (paths.__DEV__ && shouldWarnOnce(`${entry.input}->${entry.output}::nan`)) {
|
|
1757
1930
|
console.warn(
|
|
1758
1931
|
`[attaform] coercion '${entry.input}->${entry.output}' produced a non-finite number \u2014 write passes through.`
|
|
1759
1932
|
);
|
|
@@ -1806,6 +1979,11 @@ function attachFocusListeners(state, segments, element, instanceMeta) {
|
|
|
1806
1979
|
element.addEventListener("focus", handleFocus);
|
|
1807
1980
|
element.addEventListener("blur", handleBlur);
|
|
1808
1981
|
target[attaformListenersSymbol] = { handleFocus, handleBlur };
|
|
1982
|
+
const rootNode = element.getRootNode();
|
|
1983
|
+
const activeElement = rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : null;
|
|
1984
|
+
if (activeElement === element) {
|
|
1985
|
+
state.markFocused(segments, true, focusMeta);
|
|
1986
|
+
}
|
|
1809
1987
|
}
|
|
1810
1988
|
function detachFocusListeners(element) {
|
|
1811
1989
|
const target = element;
|
|
@@ -1842,6 +2020,9 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
1842
2020
|
return String(raw);
|
|
1843
2021
|
});
|
|
1844
2022
|
const slimDefault = state.schema.getDefaultAtPath(segments);
|
|
2023
|
+
const slimTypes = state.schema.getSlimPrimitiveTypesAtPath(segments);
|
|
2024
|
+
const acceptsUndefined = slimTypes.has("undefined");
|
|
2025
|
+
const acceptsString = slimTypes.has("string");
|
|
1845
2026
|
const persist = options?.persist === true;
|
|
1846
2027
|
const acknowledgeSensitive = options?.acknowledgeSensitive === true;
|
|
1847
2028
|
const multiTab = options?.multiTab !== false;
|
|
@@ -1863,10 +2044,10 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
1863
2044
|
coerceIndex
|
|
1864
2045
|
);
|
|
1865
2046
|
if (persist && !state.ssr && !state.modules.has(PERSISTENCE_MODULE_KEY)) {
|
|
1866
|
-
throw new
|
|
2047
|
+
throw new paths.AnonPersistError({
|
|
1867
2048
|
cause: "register-without-config",
|
|
1868
2049
|
schemaFields: extractSchemaFields(state.schema),
|
|
1869
|
-
callSite:
|
|
2050
|
+
callSite: paths.captureUserCallSite()
|
|
1870
2051
|
});
|
|
1871
2052
|
}
|
|
1872
2053
|
const internalRv = {
|
|
@@ -1925,7 +2106,9 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
1925
2106
|
...unmarkNoSync !== void 0 ? { unmarkNoSync } : {},
|
|
1926
2107
|
transforms,
|
|
1927
2108
|
coerce,
|
|
1928
|
-
...coerceElement !== void 0 ? { coerceElement } : {}
|
|
2109
|
+
...coerceElement !== void 0 ? { coerceElement } : {},
|
|
2110
|
+
acceptsUndefined,
|
|
2111
|
+
acceptsString
|
|
1929
2112
|
};
|
|
1930
2113
|
return vue.shallowReadonly(internalRv);
|
|
1931
2114
|
};
|
|
@@ -1946,19 +2129,13 @@ function walkUnsetSentinels(values, schema) {
|
|
|
1946
2129
|
const cleaned = walk(values, [], schema, paths);
|
|
1947
2130
|
return { cleanedValues: cleaned, paths };
|
|
1948
2131
|
}
|
|
1949
|
-
function walk(input, segments, schema, paths
|
|
2132
|
+
function walk(input, segments, schema, paths) {
|
|
1950
2133
|
if (isUnset(input)) {
|
|
1951
|
-
|
|
1952
|
-
if (!isPrimitiveOrEmpty(slim)) {
|
|
1953
|
-
warnNonPrimitiveLeaf(segments, slim);
|
|
1954
|
-
return walkUnspecified(slim, segments, paths$1);
|
|
1955
|
-
}
|
|
1956
|
-
paths$1.push(paths.canonicalizePath(segments).key);
|
|
1957
|
-
return slim;
|
|
2134
|
+
return expandUnsetAt(segments, schema, paths);
|
|
1958
2135
|
}
|
|
1959
2136
|
if (input === void 0) {
|
|
1960
2137
|
const slim = schema.getDefaultAtPath(segments);
|
|
1961
|
-
return walkUnspecified(slim, segments, paths
|
|
2138
|
+
return walkUnspecified(slim, segments, paths);
|
|
1962
2139
|
}
|
|
1963
2140
|
if (input === null) return null;
|
|
1964
2141
|
if (input instanceof Date || input instanceof RegExp || input instanceof Map || input instanceof Set || typeof input === "function") {
|
|
@@ -1968,7 +2145,7 @@ function walk(input, segments, schema, paths$1) {
|
|
|
1968
2145
|
const out = new Array(input.length);
|
|
1969
2146
|
let mutated = false;
|
|
1970
2147
|
for (let i = 0; i < input.length; i++) {
|
|
1971
|
-
const walked = walk(input[i], [...segments, i], schema, paths
|
|
2148
|
+
const walked = walk(input[i], [...segments, i], schema, paths);
|
|
1972
2149
|
out[i] = walked;
|
|
1973
2150
|
if (walked !== input[i]) mutated = true;
|
|
1974
2151
|
}
|
|
@@ -1977,6 +2154,7 @@ function walk(input, segments, schema, paths$1) {
|
|
|
1977
2154
|
if (typeof input === "object") {
|
|
1978
2155
|
const slim = schema.getDefaultAtPath(segments);
|
|
1979
2156
|
const inputKeys = Object.keys(input);
|
|
2157
|
+
const inputKeysSet = new Set(inputKeys);
|
|
1980
2158
|
const allKeys = new Set(inputKeys);
|
|
1981
2159
|
if (slim !== null && slim !== void 0 && typeof slim === "object" && !Array.isArray(slim) && !(slim instanceof Date) && !(slim instanceof RegExp) && !(slim instanceof Map) && !(slim instanceof Set)) {
|
|
1982
2160
|
for (const k of Object.keys(slim)) allKeys.add(k);
|
|
@@ -1985,7 +2163,12 @@ function walk(input, segments, schema, paths$1) {
|
|
|
1985
2163
|
let mutated = allKeys.size !== inputKeys.length;
|
|
1986
2164
|
for (const key of allKeys) {
|
|
1987
2165
|
const orig = input[key];
|
|
1988
|
-
|
|
2166
|
+
if (orig === void 0 && inputKeysSet.has(key)) {
|
|
2167
|
+
out[key] = void 0;
|
|
2168
|
+
mutated = true;
|
|
2169
|
+
continue;
|
|
2170
|
+
}
|
|
2171
|
+
const walked = walk(orig, [...segments, key], schema, paths);
|
|
1989
2172
|
out[key] = walked;
|
|
1990
2173
|
if (walked !== orig) mutated = true;
|
|
1991
2174
|
}
|
|
@@ -1995,7 +2178,7 @@ function walk(input, segments, schema, paths$1) {
|
|
|
1995
2178
|
}
|
|
1996
2179
|
function walkUnspecified(slim, segments, paths$1) {
|
|
1997
2180
|
if (isPrimitiveOrEmpty(slim)) {
|
|
1998
|
-
if (
|
|
2181
|
+
if (isSlimNumericPrimitive(slim)) {
|
|
1999
2182
|
paths$1.push(paths.canonicalizePath(segments).key);
|
|
2000
2183
|
}
|
|
2001
2184
|
return slim;
|
|
@@ -2018,15 +2201,9 @@ function substituteUnsetSentinels(value, prefix, schema) {
|
|
|
2018
2201
|
const cleaned = substitute(value, [...prefix], schema, paths);
|
|
2019
2202
|
return { cleanedValues: cleaned, paths };
|
|
2020
2203
|
}
|
|
2021
|
-
function substitute(input, segments, schema, paths
|
|
2204
|
+
function substitute(input, segments, schema, paths) {
|
|
2022
2205
|
if (isUnset(input)) {
|
|
2023
|
-
|
|
2024
|
-
if (!isPrimitiveOrEmpty(slim)) {
|
|
2025
|
-
warnNonPrimitiveLeaf(segments, slim);
|
|
2026
|
-
return slim;
|
|
2027
|
-
}
|
|
2028
|
-
paths$1.push(paths.canonicalizePath(segments).key);
|
|
2029
|
-
return slim;
|
|
2206
|
+
return expandUnsetAt(segments, schema, paths);
|
|
2030
2207
|
}
|
|
2031
2208
|
if (input === void 0 || input === null) return input;
|
|
2032
2209
|
if (input instanceof Date || input instanceof RegExp || input instanceof Map || input instanceof Set || typeof input === "function") {
|
|
@@ -2036,7 +2213,7 @@ function substitute(input, segments, schema, paths$1) {
|
|
|
2036
2213
|
let mutated = false;
|
|
2037
2214
|
const out = new Array(input.length);
|
|
2038
2215
|
for (let i = 0; i < input.length; i++) {
|
|
2039
|
-
const walked = substitute(input[i], [...segments, i], schema, paths
|
|
2216
|
+
const walked = substitute(input[i], [...segments, i], schema, paths);
|
|
2040
2217
|
out[i] = walked;
|
|
2041
2218
|
if (walked !== input[i]) mutated = true;
|
|
2042
2219
|
}
|
|
@@ -2047,7 +2224,7 @@ function substitute(input, segments, schema, paths$1) {
|
|
|
2047
2224
|
const out = {};
|
|
2048
2225
|
for (const key of Object.keys(input)) {
|
|
2049
2226
|
const orig = input[key];
|
|
2050
|
-
const walked = substitute(orig, [...segments, key], schema, paths
|
|
2227
|
+
const walked = substitute(orig, [...segments, key], schema, paths);
|
|
2051
2228
|
out[key] = walked;
|
|
2052
2229
|
if (walked !== orig) mutated = true;
|
|
2053
2230
|
}
|
|
@@ -2060,20 +2237,40 @@ function isPrimitiveOrEmpty(value) {
|
|
|
2060
2237
|
const t = typeof value;
|
|
2061
2238
|
return t === "string" || t === "number" || t === "boolean" || t === "bigint";
|
|
2062
2239
|
}
|
|
2063
|
-
function
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
if (
|
|
2070
|
-
|
|
2071
|
-
if (
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
)
|
|
2240
|
+
function isSlimNumericPrimitive(value) {
|
|
2241
|
+
return value === 0 || value === 0n;
|
|
2242
|
+
}
|
|
2243
|
+
function blankForKind(slimDefault) {
|
|
2244
|
+
if (typeof slimDefault === "string") return "";
|
|
2245
|
+
if (typeof slimDefault === "number") return 0;
|
|
2246
|
+
if (typeof slimDefault === "bigint") return 0n;
|
|
2247
|
+
if (typeof slimDefault === "boolean") return false;
|
|
2248
|
+
if (slimDefault === null) return null;
|
|
2249
|
+
return void 0;
|
|
2250
|
+
}
|
|
2251
|
+
function expandUnsetAt(segments, schema, paths$1) {
|
|
2252
|
+
const du = schema.getUnionDiscriminatorAtPath(segments);
|
|
2253
|
+
if (du !== void 0) {
|
|
2254
|
+
const discPath = [...segments, du.discriminatorKey];
|
|
2255
|
+
const discSlim = schema.getEmptyValueAtPath(discPath);
|
|
2256
|
+
paths$1.push(paths.canonicalizePath(discPath).key);
|
|
2257
|
+
return { [du.discriminatorKey]: blankForKind(discSlim) };
|
|
2258
|
+
}
|
|
2259
|
+
const slim = schema.getEmptyValueAtPath(segments);
|
|
2260
|
+
if (isPrimitiveOrEmpty(slim)) {
|
|
2261
|
+
paths$1.push(paths.canonicalizePath(segments).key);
|
|
2262
|
+
return slim;
|
|
2263
|
+
}
|
|
2264
|
+
if (slim instanceof Date || slim instanceof RegExp || slim instanceof Map || slim instanceof Set || typeof slim === "function") {
|
|
2265
|
+
paths$1.push(paths.canonicalizePath(segments).key);
|
|
2266
|
+
return slim;
|
|
2267
|
+
}
|
|
2268
|
+
if (Array.isArray(slim)) return slim;
|
|
2269
|
+
const result = {};
|
|
2270
|
+
for (const key of Object.keys(slim)) {
|
|
2271
|
+
result[key] = expandUnsetAt([...segments, key], schema, paths$1);
|
|
2272
|
+
}
|
|
2273
|
+
return result;
|
|
2077
2274
|
}
|
|
2078
2275
|
|
|
2079
2276
|
function buildValuesProxy(form) {
|
|
@@ -2124,7 +2321,7 @@ function buildValuesProxy(form) {
|
|
|
2124
2321
|
// TypeError in strict-mode consumers, surprising users who
|
|
2125
2322
|
// assigned through the proxy and expected it to be ignored.
|
|
2126
2323
|
set(_, key) {
|
|
2127
|
-
if (
|
|
2324
|
+
if (paths.__DEV__) {
|
|
2128
2325
|
console.warn(
|
|
2129
2326
|
`[attaform] form.values is read-only \u2014 write to "${String(key)}" was ignored. Use form.setValue / the directive / field-array helpers instead.`
|
|
2130
2327
|
);
|
|
@@ -2132,7 +2329,7 @@ function buildValuesProxy(form) {
|
|
|
2132
2329
|
return true;
|
|
2133
2330
|
},
|
|
2134
2331
|
deleteProperty(_, key) {
|
|
2135
|
-
if (
|
|
2332
|
+
if (paths.__DEV__) {
|
|
2136
2333
|
console.warn(
|
|
2137
2334
|
`[attaform] form.values is read-only \u2014 delete of "${String(key)}" was ignored.`
|
|
2138
2335
|
);
|
|
@@ -2143,28 +2340,6 @@ function buildValuesProxy(form) {
|
|
|
2143
2340
|
});
|
|
2144
2341
|
}
|
|
2145
2342
|
|
|
2146
|
-
function blankForKind(slimDefault) {
|
|
2147
|
-
if (typeof slimDefault === "string") return "";
|
|
2148
|
-
if (typeof slimDefault === "number") return 0;
|
|
2149
|
-
if (typeof slimDefault === "bigint") return 0n;
|
|
2150
|
-
if (typeof slimDefault === "boolean") return false;
|
|
2151
|
-
if (slimDefault === null) return null;
|
|
2152
|
-
return void 0;
|
|
2153
|
-
}
|
|
2154
|
-
function readonlySetSnapshot(source) {
|
|
2155
|
-
const snapshot = new Set(source);
|
|
2156
|
-
return new Proxy(snapshot, {
|
|
2157
|
-
get(target, prop) {
|
|
2158
|
-
if (prop === "add" || prop === "delete" || prop === "clear") {
|
|
2159
|
-
return () => {
|
|
2160
|
-
throw new TypeError(`Cannot mutate readonly Set: '${String(prop)}' is not allowed.`);
|
|
2161
|
-
};
|
|
2162
|
-
}
|
|
2163
|
-
const value = Reflect.get(target, prop, target);
|
|
2164
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
2165
|
-
}
|
|
2166
|
-
});
|
|
2167
|
-
}
|
|
2168
2343
|
function buildFormApi(state, formInstanceId, options = {}) {
|
|
2169
2344
|
const instanceMeta = (() => {
|
|
2170
2345
|
const bag = {};
|
|
@@ -2187,6 +2362,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2187
2362
|
Object.keys(registerConfig).length > 0 ? registerConfig : void 0
|
|
2188
2363
|
);
|
|
2189
2364
|
const processOptions = options.onInvalidSubmit !== void 0 ? { onInvalidSubmit: options.onInvalidSubmit } : {};
|
|
2365
|
+
const defaultInvalidSubmitPolicy = options.onInvalidSubmit ?? "focus-first-error";
|
|
2190
2366
|
const {
|
|
2191
2367
|
validate: validateBuilt,
|
|
2192
2368
|
validateAsync: validateAsyncBuilt,
|
|
@@ -2207,49 +2383,53 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2207
2383
|
next,
|
|
2208
2384
|
state.schema
|
|
2209
2385
|
);
|
|
2210
|
-
const ok2 = state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
|
|
2211
|
-
if (!ok2) return false;
|
|
2212
2386
|
for (const pathKey of walked2.paths) {
|
|
2213
|
-
|
|
2214
|
-
if (segments2 === null) continue;
|
|
2215
|
-
state.setValueAtPath(
|
|
2216
|
-
segments2,
|
|
2217
|
-
state.schema.getDefaultAtPath(segments2),
|
|
2218
|
-
withInstanceMeta({ blank: true })
|
|
2219
|
-
);
|
|
2387
|
+
state.blankPaths.add(pathKey);
|
|
2220
2388
|
}
|
|
2221
|
-
return
|
|
2389
|
+
return state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
|
|
2222
2390
|
}
|
|
2223
2391
|
const segments = paths.canonicalizePath(pathOrValue).segments;
|
|
2224
|
-
|
|
2392
|
+
const writeUnsetAt = () => {
|
|
2225
2393
|
const last = segments.length > 0 ? segments[segments.length - 1] : void 0;
|
|
2226
2394
|
if (typeof last === "string") {
|
|
2227
2395
|
const parent = segments.slice(0, -1);
|
|
2228
2396
|
const parentDU = state.schema.getUnionDiscriminatorAtPath(parent);
|
|
2229
2397
|
if (parentDU?.discriminatorKey === last) {
|
|
2230
|
-
const slimDefault = state.schema.
|
|
2398
|
+
const slimDefault = state.schema.getEmptyValueAtPath(segments);
|
|
2231
2399
|
const blank = blankForKind(slimDefault);
|
|
2232
2400
|
return state.setValueAtPath(segments, blank, withInstanceMeta({ blank: true }));
|
|
2233
2401
|
}
|
|
2234
2402
|
}
|
|
2235
|
-
|
|
2403
|
+
const blankPaths = [];
|
|
2404
|
+
const expanded = expandUnsetAt(
|
|
2236
2405
|
segments,
|
|
2237
|
-
state.schema
|
|
2238
|
-
|
|
2406
|
+
state.schema,
|
|
2407
|
+
blankPaths
|
|
2239
2408
|
);
|
|
2240
|
-
|
|
2409
|
+
const segmentsKey = paths.canonicalizePath(segments).key;
|
|
2410
|
+
if (blankPaths.length === 1 && blankPaths[0] === segmentsKey) {
|
|
2411
|
+
return state.setValueAtPath(segments, expanded, withInstanceMeta({ blank: true }));
|
|
2412
|
+
}
|
|
2413
|
+
const ok2 = state.setValueAtPath(segments, expanded, withInstanceMeta());
|
|
2414
|
+
if (!ok2) return false;
|
|
2415
|
+
for (const pathKey of blankPaths) {
|
|
2416
|
+
const blankSegments = paths.segmentsForPathKey(pathKey);
|
|
2417
|
+
if (blankSegments === null) continue;
|
|
2418
|
+
state.setValueAtPath(
|
|
2419
|
+
blankSegments,
|
|
2420
|
+
state.getValueAtPath(blankSegments),
|
|
2421
|
+
withInstanceMeta({ blank: true })
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
return true;
|
|
2425
|
+
};
|
|
2426
|
+
if (isUnset(maybeValue)) return writeUnsetAt();
|
|
2241
2427
|
let resolvedValue;
|
|
2242
2428
|
if (typeof maybeValue === "function") {
|
|
2243
2429
|
const current = state.getValueAtPath(segments);
|
|
2244
2430
|
const prev = current === void 0 ? state.schema.getDefaultAtPath(segments) : current;
|
|
2245
2431
|
resolvedValue = maybeValue(prev);
|
|
2246
|
-
if (isUnset(resolvedValue))
|
|
2247
|
-
return state.setValueAtPath(
|
|
2248
|
-
segments,
|
|
2249
|
-
state.schema.getDefaultAtPath(segments),
|
|
2250
|
-
withInstanceMeta({ blank: true })
|
|
2251
|
-
);
|
|
2252
|
-
}
|
|
2432
|
+
if (isUnset(resolvedValue)) return writeUnsetAt();
|
|
2253
2433
|
} else {
|
|
2254
2434
|
resolvedValue = maybeValue;
|
|
2255
2435
|
}
|
|
@@ -2265,7 +2445,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2265
2445
|
if (blankSegments === null) continue;
|
|
2266
2446
|
state.setValueAtPath(
|
|
2267
2447
|
blankSegments,
|
|
2268
|
-
state.
|
|
2448
|
+
state.getValueAtPath(blankSegments),
|
|
2269
2449
|
withInstanceMeta({ blank: true })
|
|
2270
2450
|
);
|
|
2271
2451
|
}
|
|
@@ -2279,7 +2459,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2279
2459
|
if (e.formKey === state.formKey) own.push(e);
|
|
2280
2460
|
else dropped++;
|
|
2281
2461
|
}
|
|
2282
|
-
if (
|
|
2462
|
+
if (paths.__DEV__ && dropped > 0) {
|
|
2283
2463
|
console.warn(
|
|
2284
2464
|
`[attaform] ${op}: dropped ${dropped} error(s) with non-matching formKey (this form's key is "${String(state.formKey)}"). Errors are scoped to the form that produced them \u2014 pass them to the matching form instance.`
|
|
2285
2465
|
);
|
|
@@ -2329,8 +2509,10 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2329
2509
|
state.userErrors.delete(paths.FORM_ERRORS_PATH_KEY);
|
|
2330
2510
|
}
|
|
2331
2511
|
const submitting = vue.computed(() => state.submitting.value);
|
|
2332
|
-
const
|
|
2512
|
+
const submissionAttempts = vue.computed(() => state.submissionAttempts.value);
|
|
2513
|
+
const submitted = vue.computed(() => state.submitted.value);
|
|
2333
2514
|
const submitError = vue.computed(() => state.submitError.value);
|
|
2515
|
+
const departAttempts = vue.computed(() => state.departAttempts.value);
|
|
2334
2516
|
const validating = vue.computed(() => state.activeValidations.value > 0);
|
|
2335
2517
|
const valid = vue.computed(
|
|
2336
2518
|
() => state.firstValidationDone.value && state.schemaErrors.size === 0 && state.userErrors.size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
|
|
@@ -2355,8 +2537,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2355
2537
|
return {
|
|
2356
2538
|
...rootBase,
|
|
2357
2539
|
submitting: state.submitting.value,
|
|
2358
|
-
|
|
2540
|
+
submissionAttempts: state.submissionAttempts.value,
|
|
2541
|
+
departAttempts: state.departAttempts.value,
|
|
2359
2542
|
submitError: state.submitError.value,
|
|
2543
|
+
errorCount: rootBase.errors.length,
|
|
2544
|
+
submitted: state.submitted.value,
|
|
2360
2545
|
instanceId: formInstanceId
|
|
2361
2546
|
};
|
|
2362
2547
|
};
|
|
@@ -2416,8 +2601,14 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2416
2601
|
meta: vue.computed(() => rootFieldState.value.meta),
|
|
2417
2602
|
// Lifecycle (form-level only — not on FieldState).
|
|
2418
2603
|
submitting,
|
|
2419
|
-
|
|
2604
|
+
submissionAttempts,
|
|
2605
|
+
departAttempts,
|
|
2420
2606
|
submitError,
|
|
2607
|
+
// Scalar mirror over the array — meta is a single sticky surface
|
|
2608
|
+
// for both templates and `useWizard`'s `FormStatus`, so the
|
|
2609
|
+
// projection lives here.
|
|
2610
|
+
errorCount: vue.computed(() => metaErrors.value.length),
|
|
2611
|
+
submitted,
|
|
2421
2612
|
// Per-`useForm()`-call identity. Stable for one mount; new on
|
|
2422
2613
|
// re-mount; orthogonal to `form.key` (which is the user-supplied
|
|
2423
2614
|
// shared identifier). Useful for devtools panels disambiguating
|
|
@@ -2437,13 +2628,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2437
2628
|
);
|
|
2438
2629
|
state.reset(walked.cleanedValues);
|
|
2439
2630
|
for (const pathKey of walked.paths) {
|
|
2440
|
-
|
|
2441
|
-
if (segments === null) continue;
|
|
2442
|
-
state.setValueAtPath(
|
|
2443
|
-
segments,
|
|
2444
|
-
state.schema.getDefaultAtPath(segments),
|
|
2445
|
-
withInstanceMeta({ blank: true })
|
|
2446
|
-
);
|
|
2631
|
+
state.blankPaths.add(pathKey);
|
|
2447
2632
|
state.originalBlankPaths.add(pathKey);
|
|
2448
2633
|
}
|
|
2449
2634
|
}
|
|
@@ -2464,7 +2649,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2464
2649
|
}
|
|
2465
2650
|
const persist = async (pathInput, options2) => {
|
|
2466
2651
|
const segments = paths.canonicalizePath(pathInput).segments;
|
|
2467
|
-
|
|
2652
|
+
paths.enforceSensitiveCheck(segments, options2?.acknowledgeSensitive === true, state.isSensitivePath);
|
|
2468
2653
|
if (persistence === void 0) return;
|
|
2469
2654
|
await persistence.writePathImmediately(segments);
|
|
2470
2655
|
};
|
|
@@ -2493,56 +2678,141 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2493
2678
|
target.element.scrollIntoView(options2);
|
|
2494
2679
|
return true;
|
|
2495
2680
|
};
|
|
2681
|
+
const applyInvalidSubmitPolicyPublic = (policy) => {
|
|
2682
|
+
applyInvalidSubmitPolicy(state, formInstanceId, policy ?? defaultInvalidSubmitPolicy);
|
|
2683
|
+
};
|
|
2496
2684
|
const fieldArrays = buildFieldArrayApi(state);
|
|
2497
2685
|
const blankPathsView = vue.computed(() => {
|
|
2498
|
-
|
|
2686
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2687
|
+
const paths$1 = [];
|
|
2688
|
+
for (const pk of state.blankPaths) {
|
|
2689
|
+
keys.add(pk);
|
|
2690
|
+
const segs = paths.segmentsForPathKey(pk);
|
|
2691
|
+
if (segs !== null) paths$1.push(segs);
|
|
2692
|
+
}
|
|
2693
|
+
Object.freeze(paths$1);
|
|
2694
|
+
const view = {
|
|
2695
|
+
get size() {
|
|
2696
|
+
return keys.size;
|
|
2697
|
+
},
|
|
2698
|
+
has(input) {
|
|
2699
|
+
const { key } = paths.canonicalizePath(input);
|
|
2700
|
+
return keys.has(key);
|
|
2701
|
+
},
|
|
2702
|
+
values() {
|
|
2703
|
+
return paths$1;
|
|
2704
|
+
},
|
|
2705
|
+
[Symbol.iterator]() {
|
|
2706
|
+
return paths$1[Symbol.iterator]();
|
|
2707
|
+
}
|
|
2708
|
+
};
|
|
2709
|
+
return Object.freeze(view);
|
|
2499
2710
|
});
|
|
2500
2711
|
const valuesProxy = buildValuesProxy(state.form);
|
|
2501
2712
|
const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase, fieldStateAccessorOptions);
|
|
2713
|
+
function gated(fn) {
|
|
2714
|
+
return ((...args) => {
|
|
2715
|
+
void state.activate();
|
|
2716
|
+
return fn(...args);
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2502
2719
|
return {
|
|
2503
|
-
handleSubmit,
|
|
2504
|
-
//
|
|
2505
|
-
//
|
|
2506
|
-
// so
|
|
2507
|
-
//
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2720
|
+
handleSubmit: gated(handleSubmit),
|
|
2721
|
+
// Callable readonly Proxies (`values`, `fields`, `errors`) and the
|
|
2722
|
+
// reactive containers (`meta`, `history`, `blankPaths`) are exposed
|
|
2723
|
+
// through getters so reading them activates the form on first
|
|
2724
|
+
// touch. Each underlying object is identity-stable across reads.
|
|
2725
|
+
get values() {
|
|
2726
|
+
void state.activate();
|
|
2727
|
+
return valuesProxy;
|
|
2728
|
+
},
|
|
2729
|
+
get fields() {
|
|
2730
|
+
void state.activate();
|
|
2731
|
+
return fieldStateProxy;
|
|
2732
|
+
},
|
|
2733
|
+
setValue: gated(setValueImpl),
|
|
2734
|
+
validate: gated(validate),
|
|
2735
|
+
validateAsync: gated(validateAsync),
|
|
2736
|
+
process: gated(process),
|
|
2737
|
+
register: gated(register),
|
|
2516
2738
|
key: state.formKey,
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2739
|
+
// Auto-unwrapping views over the per-store async-defaults lifecycle
|
|
2740
|
+
// refs (see FormStore.hydrating / hydrateError). Reading either
|
|
2741
|
+
// activates the form — observing factory state implies use.
|
|
2742
|
+
get hydrating() {
|
|
2743
|
+
void state.activate();
|
|
2744
|
+
return state.hydrating.value;
|
|
2745
|
+
},
|
|
2746
|
+
get hydrateError() {
|
|
2747
|
+
void state.activate();
|
|
2748
|
+
return state.hydrateError.value;
|
|
2749
|
+
},
|
|
2750
|
+
// Orthogonal to `hydrating` and `hydrateError`: `ready` flips true
|
|
2751
|
+
// once defaults are applied (sync at construction or async factory
|
|
2752
|
+
// resolved successfully). One-way latch — stays true through later
|
|
2753
|
+
// refetches even when those refetches fail, so stale-while-
|
|
2754
|
+
// revalidate UIs keep rendering the prior values while
|
|
2755
|
+
// `hydrateError` surfaces the refresh failure.
|
|
2756
|
+
get ready() {
|
|
2757
|
+
void state.activate();
|
|
2758
|
+
return state.defaultsResolved.value;
|
|
2759
|
+
},
|
|
2760
|
+
// `rehydrate` and `activate` are themselves activation entry points
|
|
2761
|
+
// — they fire the factory by design. Wrapping them with `gated`
|
|
2762
|
+
// would double-fire (`state.activate()` plus the underlying call),
|
|
2763
|
+
// so they call `state` directly.
|
|
2764
|
+
rehydrate: () => state.rehydrate(),
|
|
2765
|
+
activate: () => state.activate(),
|
|
2766
|
+
get errors() {
|
|
2767
|
+
void state.activate();
|
|
2768
|
+
return errorsProxy;
|
|
2769
|
+
},
|
|
2770
|
+
toRef: gated(pathToRef),
|
|
2771
|
+
setFieldErrors: gated(setFieldErrors),
|
|
2772
|
+
addFieldErrors: gated(addFieldErrors),
|
|
2773
|
+
clearFieldErrors: gated(clearFieldErrors),
|
|
2774
|
+
setFormErrors: gated(setFormErrors),
|
|
2775
|
+
clearFormErrors: gated(clearFormErrors),
|
|
2776
|
+
get meta() {
|
|
2777
|
+
void state.activate();
|
|
2778
|
+
return formMeta;
|
|
2779
|
+
},
|
|
2780
|
+
reset: gated(reset),
|
|
2781
|
+
resetField: gated(resetField),
|
|
2782
|
+
clear: gated(clear),
|
|
2783
|
+
persist: gated(persist),
|
|
2784
|
+
clearPersistedDraft: gated(clearPersistedDraft),
|
|
2785
|
+
focusFirstError: gated(focusFirstError),
|
|
2786
|
+
scrollToFirstError: gated(scrollToFirstError),
|
|
2787
|
+
applyInvalidSubmitPolicy: gated(applyInvalidSubmitPolicyPublic),
|
|
2788
|
+
touch: gated(touch),
|
|
2789
|
+
get history() {
|
|
2790
|
+
void state.activate();
|
|
2791
|
+
return formHistory;
|
|
2792
|
+
},
|
|
2793
|
+
append: gated(fieldArrays.append),
|
|
2794
|
+
prepend: gated(fieldArrays.prepend),
|
|
2795
|
+
insert: gated(fieldArrays.insert),
|
|
2796
|
+
remove: gated(fieldArrays.remove),
|
|
2797
|
+
swap: gated(fieldArrays.swap),
|
|
2798
|
+
move: gated(fieldArrays.move),
|
|
2799
|
+
replace: gated(fieldArrays.replace),
|
|
2800
|
+
get blankPaths() {
|
|
2801
|
+
void state.activate();
|
|
2802
|
+
return blankPathsView;
|
|
2803
|
+
}
|
|
2542
2804
|
};
|
|
2543
2805
|
}
|
|
2544
2806
|
|
|
2545
|
-
const defaultShouldShowErrors = (field, formMeta) =>
|
|
2807
|
+
const defaultShouldShowErrors = (field, formMeta) => {
|
|
2808
|
+
const hasOwnError = field.errors.some(
|
|
2809
|
+
(e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
|
|
2810
|
+
);
|
|
2811
|
+
if (!hasOwnError) return false;
|
|
2812
|
+
if (field.validating === true) return false;
|
|
2813
|
+
if (formMeta.submissionAttempts > 0) return true;
|
|
2814
|
+
return field.touched === true && field.focused !== true;
|
|
2815
|
+
};
|
|
2546
2816
|
const SHOW_ALWAYS = () => true;
|
|
2547
2817
|
const SHOW_NEVER = () => false;
|
|
2548
2818
|
function resolveShouldShowErrors(config) {
|
|
@@ -2555,7 +2825,7 @@ function resolveShouldShowErrors(config) {
|
|
|
2555
2825
|
function isHydratedFieldRecord(value) {
|
|
2556
2826
|
if (typeof value !== "object" || value === null) return false;
|
|
2557
2827
|
const r = value;
|
|
2558
|
-
return Array.isArray(r.path) && (typeof r.updatedAt === "string" || r.updatedAt === null) && typeof r.connected === "boolean" && (typeof r.focused === "boolean" || r.focused === null) && (typeof r.blurred === "boolean" || r.blurred === null) &&
|
|
2828
|
+
return Array.isArray(r.path) && (typeof r.updatedAt === "string" || r.updatedAt === null) && typeof r.connected === "boolean" && (typeof r.focused === "boolean" || r.focused === null) && (typeof r.blurred === "boolean" || r.blurred === null) && typeof r.touched === "boolean";
|
|
2559
2829
|
}
|
|
2560
2830
|
function isHydratedValidationErrorArray(value) {
|
|
2561
2831
|
if (!Array.isArray(value)) return false;
|
|
@@ -2570,7 +2840,7 @@ function isHydratedValidationErrorArray(value) {
|
|
|
2570
2840
|
return true;
|
|
2571
2841
|
}
|
|
2572
2842
|
function warnMalformedHydration(formKey, kind, rawKey) {
|
|
2573
|
-
if (!
|
|
2843
|
+
if (!paths.__DEV__) return;
|
|
2574
2844
|
console.warn(
|
|
2575
2845
|
`[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).`
|
|
2576
2846
|
);
|
|
@@ -2592,7 +2862,8 @@ function walkDuStubs(schema, value, path, warned) {
|
|
|
2592
2862
|
if (du !== void 0) {
|
|
2593
2863
|
const discValue = rec[du.discriminatorKey];
|
|
2594
2864
|
if (discValue !== void 0 && !du.isVariantSelected(discValue)) {
|
|
2595
|
-
|
|
2865
|
+
const isKindBlank = discValue === "" || discValue === 0 || discValue === 0n || discValue === false || discValue === null;
|
|
2866
|
+
if (!isKindBlank && warned !== void 0 && paths.__DEV__) {
|
|
2596
2867
|
const dotted = path.map((s) => String(s)).join(".") || "(root)";
|
|
2597
2868
|
const key = `${dotted}::${String(discValue)}`;
|
|
2598
2869
|
if (!warned.has(key)) {
|
|
@@ -2671,9 +2942,45 @@ function cloneVariantSnapshot(value) {
|
|
|
2671
2942
|
for (const k of Object.keys(src)) out[k] = cloneVariantSnapshot(src[k]);
|
|
2672
2943
|
return out;
|
|
2673
2944
|
}
|
|
2945
|
+
function walkAuthoredFromConstraints(value, prefix, out) {
|
|
2946
|
+
if (prefix.length > 0) out.add(paths.canonicalizePath(prefix).key);
|
|
2947
|
+
if (isPlainRecord(value)) {
|
|
2948
|
+
for (const k of Object.keys(value)) {
|
|
2949
|
+
walkAuthoredFromConstraints(value[k], [...prefix, k], out);
|
|
2950
|
+
}
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
if (Array.isArray(value)) {
|
|
2954
|
+
for (let i = 0; i < value.length; i++) {
|
|
2955
|
+
walkAuthoredFromConstraints(value[i], [...prefix, i], out);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
function walkAuthoredFromSchemaDiff(withDefaults, withoutDefaults, prefix, out) {
|
|
2960
|
+
if (isPlainRecord(withDefaults) && isPlainRecord(withoutDefaults)) {
|
|
2961
|
+
const left = withDefaults;
|
|
2962
|
+
const right = withoutDefaults;
|
|
2963
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
2964
|
+
for (const k of keys) {
|
|
2965
|
+
walkAuthoredFromSchemaDiff(left[k], right[k], [...prefix, k], out);
|
|
2966
|
+
}
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
if (Array.isArray(withDefaults) && Array.isArray(withoutDefaults)) {
|
|
2970
|
+
const len = Math.max(withDefaults.length, withoutDefaults.length);
|
|
2971
|
+
for (let i = 0; i < len; i++) {
|
|
2972
|
+
walkAuthoredFromSchemaDiff(withDefaults[i], withoutDefaults[i], [...prefix, i], out);
|
|
2973
|
+
}
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
if (!Object.is(withDefaults, withoutDefaults) && prefix.length > 0) {
|
|
2977
|
+
out.add(paths.canonicalizePath(prefix).key);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2674
2980
|
function createFormStore(options) {
|
|
2675
2981
|
const { formKey, schema, defaultValues, strict = true, hydration } = options;
|
|
2676
2982
|
const ssr = options.ssr === true;
|
|
2983
|
+
const ssrPrefetch = options.ssrPrefetch;
|
|
2677
2984
|
const rememberVariants = options.rememberVariants !== false;
|
|
2678
2985
|
const fieldValidationMode = options.validateOn ?? "change";
|
|
2679
2986
|
const fieldValidationDebounceMs = normalizeNumericOption({
|
|
@@ -2687,7 +2994,7 @@ function createFormStore(options) {
|
|
|
2687
2994
|
const formChangeListeners = /* @__PURE__ */ new Set();
|
|
2688
2995
|
const submitSuccessListeners = /* @__PURE__ */ new Set();
|
|
2689
2996
|
const resetListeners = /* @__PURE__ */ new Set();
|
|
2690
|
-
const persistOptIns =
|
|
2997
|
+
const persistOptIns = paths.createPersistOptInRegistry();
|
|
2691
2998
|
const noSyncPaths = /* @__PURE__ */ new Set();
|
|
2692
2999
|
const noSyncPathCounts = /* @__PURE__ */ new Map();
|
|
2693
3000
|
function incrementNoSyncOptOut(path) {
|
|
@@ -2708,8 +3015,8 @@ function createFormStore(options) {
|
|
|
2708
3015
|
const resolvedShouldShowErrors = resolveShouldShowErrors(
|
|
2709
3016
|
options.shouldShowErrors
|
|
2710
3017
|
);
|
|
2711
|
-
const resolvedIsSensitivePath = options.isSensitivePath ??
|
|
2712
|
-
const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ??
|
|
3018
|
+
const resolvedIsSensitivePath = options.isSensitivePath ?? paths.isSensitivePath;
|
|
3019
|
+
const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ?? paths.segmentMatchesSensitive;
|
|
2713
3020
|
const cleanupHooks = [];
|
|
2714
3021
|
const modules = /* @__PURE__ */ new Map();
|
|
2715
3022
|
const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
|
|
@@ -2719,6 +3026,29 @@ function createFormStore(options) {
|
|
|
2719
3026
|
strict
|
|
2720
3027
|
});
|
|
2721
3028
|
const schemaInitialData = schemaResponse.data;
|
|
3029
|
+
const authoredPaths = /* @__PURE__ */ new Set();
|
|
3030
|
+
function rebuildAuthoredPaths(constraints, schemaWithDefaultsData) {
|
|
3031
|
+
authoredPaths.clear();
|
|
3032
|
+
if (constraints !== void 0) {
|
|
3033
|
+
walkAuthoredFromConstraints(constraints, [], authoredPaths);
|
|
3034
|
+
}
|
|
3035
|
+
const slimResponse = schema.getDefaultValues({
|
|
3036
|
+
useDefaultSchemaValues: false,
|
|
3037
|
+
strict
|
|
3038
|
+
});
|
|
3039
|
+
walkAuthoredFromSchemaDiff(schemaWithDefaultsData, slimResponse.data, [], authoredPaths);
|
|
3040
|
+
}
|
|
3041
|
+
rebuildAuthoredPaths(defaultValues, schemaInitialData);
|
|
3042
|
+
function filterAuthoredErrors(errors) {
|
|
3043
|
+
return errors.filter((err) => {
|
|
3044
|
+
const pathSegments = err.path;
|
|
3045
|
+
if (pathSegments.length === 0) return true;
|
|
3046
|
+
const value = getAtPath(form.value, pathSegments);
|
|
3047
|
+
if (value !== void 0) return true;
|
|
3048
|
+
if (authoredPaths.has(paths.canonicalizePath(pathSegments).key)) return true;
|
|
3049
|
+
return !schema.isPreprocessOrCoerceLeaf(pathSegments);
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
2722
3052
|
const initialData = hydration !== void 0 ? hydration.form : structuralSnapshot(schemaInitialData);
|
|
2723
3053
|
const stubbedInitialData = applyDuStubs(schema, initialData, {
|
|
2724
3054
|
warn: true
|
|
@@ -2735,8 +3065,9 @@ function createFormStore(options) {
|
|
|
2735
3065
|
const blankPaths = vue.reactive(/* @__PURE__ */ new Set());
|
|
2736
3066
|
const originalBlankPaths = /* @__PURE__ */ new Set();
|
|
2737
3067
|
for (const raw of initialTransientList) {
|
|
2738
|
-
|
|
2739
|
-
|
|
3068
|
+
const key = paths.coerceToPathKey(raw);
|
|
3069
|
+
blankPaths.add(key);
|
|
3070
|
+
originalBlankPaths.add(key);
|
|
2740
3071
|
}
|
|
2741
3072
|
const variantMemory = /* @__PURE__ */ new Map();
|
|
2742
3073
|
function clearVariantMemoryUnderPath(arrayPath) {
|
|
@@ -2804,10 +3135,18 @@ function createFormStore(options) {
|
|
|
2804
3135
|
});
|
|
2805
3136
|
const submitting = vue.ref(false);
|
|
2806
3137
|
const activeSubmissions = vue.ref(0);
|
|
2807
|
-
const
|
|
3138
|
+
const submissionAttempts = vue.ref(0);
|
|
3139
|
+
const submitted = vue.ref(false);
|
|
2808
3140
|
const submitError = vue.ref(null);
|
|
3141
|
+
const departAttempts = vue.ref(0);
|
|
2809
3142
|
const submissionGeneration = vue.ref(0);
|
|
2810
3143
|
const activeValidations = vue.ref(0);
|
|
3144
|
+
const hydrating = vue.ref(false);
|
|
3145
|
+
const hydrateError = vue.ref(null);
|
|
3146
|
+
const defaultValuesFactory = vue.ref(void 0);
|
|
3147
|
+
const defaultsResolved = vue.ref(false);
|
|
3148
|
+
const activated = vue.ref(false);
|
|
3149
|
+
const activationPromise = vue.ref(void 0);
|
|
2811
3150
|
const firstValidationDone = vue.ref(!strict || schema.needsAsyncValidation?.() !== true);
|
|
2812
3151
|
vue.watch(activeValidations, (now, prev) => {
|
|
2813
3152
|
if (prev > 0 && now === 0) {
|
|
@@ -2872,7 +3211,7 @@ function createFormStore(options) {
|
|
|
2872
3211
|
connected: false,
|
|
2873
3212
|
focused: null,
|
|
2874
3213
|
blurred: null,
|
|
2875
|
-
touched:
|
|
3214
|
+
touched: false
|
|
2876
3215
|
});
|
|
2877
3216
|
});
|
|
2878
3217
|
if (strict && !schemaResponse.success) {
|
|
@@ -2892,9 +3231,16 @@ function createFormStore(options) {
|
|
|
2892
3231
|
path,
|
|
2893
3232
|
updatedAt: patch.updatedAt ?? current?.updatedAt ?? null,
|
|
2894
3233
|
connected: patch.connected ?? current?.connected ?? false,
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
3234
|
+
// focused/blurred use an explicit-undefined guard because
|
|
3235
|
+
// patches legitimately carry `null` to mark a disconnect — the
|
|
3236
|
+
// `??` operator would short-circuit on null and fall through to
|
|
3237
|
+
// `current`, losing the intent. `!== undefined` honours an
|
|
3238
|
+
// explicit null and preserves current only on absence.
|
|
3239
|
+
focused: patch.focused !== void 0 ? patch.focused : current?.focused ?? null,
|
|
3240
|
+
blurred: patch.blurred !== void 0 ? patch.blurred : current?.blurred ?? null,
|
|
3241
|
+
// touched is plain `boolean`; `??` is equivalent to the explicit
|
|
3242
|
+
// guard here because `false` is not nullish.
|
|
3243
|
+
touched: patch.touched ?? current?.touched ?? false
|
|
2898
3244
|
});
|
|
2899
3245
|
}
|
|
2900
3246
|
function applyFormReplacement(next, meta) {
|
|
@@ -2925,7 +3271,6 @@ function createFormStore(options) {
|
|
|
2925
3271
|
}
|
|
2926
3272
|
function setValueAtPath(path, value, meta) {
|
|
2927
3273
|
value = stripSymbolsDeep(value);
|
|
2928
|
-
value = schema.normalizeWriteValueAtPath(value, path);
|
|
2929
3274
|
if (!isSlimPrimitiveValid(schema, form, path, value)) {
|
|
2930
3275
|
return false;
|
|
2931
3276
|
}
|
|
@@ -3024,9 +3369,21 @@ function createFormStore(options) {
|
|
|
3024
3369
|
} else if (blankPaths.has(pathKey)) {
|
|
3025
3370
|
blankPaths.delete(pathKey);
|
|
3026
3371
|
}
|
|
3372
|
+
const wasAuthoredBefore = authoredPaths.has(pathKey);
|
|
3373
|
+
walkAuthoredFromConstraints(value, path, authoredPaths);
|
|
3374
|
+
const newlyAuthored = !wasAuthoredBefore && authoredPaths.has(pathKey);
|
|
3027
3375
|
const completedValue = mergeStructural(schema, path, value);
|
|
3028
3376
|
const currentValue = getAtPath(form.value, path);
|
|
3029
3377
|
if (Object.is(currentValue, completedValue)) {
|
|
3378
|
+
if (newlyAuthored && schema.isPreprocessOrCoerceLeaf(path)) {
|
|
3379
|
+
const modeForAuthoringTransition = meta?.instance?.validateOn ?? fieldValidationMode;
|
|
3380
|
+
if (modeForAuthoringTransition === "change") {
|
|
3381
|
+
scheduleFieldValidation(path, false, {
|
|
3382
|
+
...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
|
|
3383
|
+
...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3030
3387
|
return true;
|
|
3031
3388
|
}
|
|
3032
3389
|
const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
|
|
@@ -3139,12 +3496,11 @@ function createFormStore(options) {
|
|
|
3139
3496
|
prev.controller.abort();
|
|
3140
3497
|
}
|
|
3141
3498
|
const controller = new AbortController();
|
|
3142
|
-
const fresh = { controller, timer: null };
|
|
3499
|
+
const fresh = { controller, timer: null, settled: false };
|
|
3143
3500
|
fieldValidationState.set(key, fresh);
|
|
3144
3501
|
const run = () => {
|
|
3145
3502
|
fresh.timer = null;
|
|
3146
3503
|
if (controller.signal.aborted) return;
|
|
3147
|
-
const data = getAtPath(form.value, path);
|
|
3148
3504
|
let activeIncremented = false;
|
|
3149
3505
|
try {
|
|
3150
3506
|
activeValidations.value += 1;
|
|
@@ -3156,17 +3512,16 @@ function createFormStore(options) {
|
|
|
3156
3512
|
}
|
|
3157
3513
|
throw err;
|
|
3158
3514
|
}
|
|
3159
|
-
void Promise.resolve().then(() => schema.validateAtPath(
|
|
3515
|
+
void Promise.resolve().then(() => schema.validateAtPath(form.value, void 0)).then((response) => {
|
|
3160
3516
|
if (controller.signal.aborted) return;
|
|
3161
|
-
const
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
}));
|
|
3165
|
-
applySchemaErrorsForSubtree(path, reStamped);
|
|
3517
|
+
const errors = response.success ? [] : response.errors;
|
|
3518
|
+
const filtered = filterAuthoredErrors(errors);
|
|
3519
|
+
applySchemaErrorsForSubtree([], filtered);
|
|
3166
3520
|
}).catch(() => {
|
|
3167
3521
|
}).finally(() => {
|
|
3168
3522
|
activeValidations.value = Math.max(0, activeValidations.value - 1);
|
|
3169
3523
|
decFieldValidation(key);
|
|
3524
|
+
fresh.settled = true;
|
|
3170
3525
|
});
|
|
3171
3526
|
};
|
|
3172
3527
|
if (immediate || effectiveDebounce === 0) {
|
|
@@ -3176,8 +3531,13 @@ function createFormStore(options) {
|
|
|
3176
3531
|
}
|
|
3177
3532
|
}
|
|
3178
3533
|
function cancelFieldValidation() {
|
|
3179
|
-
for (const entry of fieldValidationState
|
|
3180
|
-
if (entry.timer !== null)
|
|
3534
|
+
for (const [pkey, entry] of fieldValidationState) {
|
|
3535
|
+
if (entry.timer !== null) {
|
|
3536
|
+
clearTimeout(entry.timer);
|
|
3537
|
+
} else if (!entry.settled) {
|
|
3538
|
+
activeValidations.value = Math.max(0, activeValidations.value - 1);
|
|
3539
|
+
decFieldValidation(pkey);
|
|
3540
|
+
}
|
|
3181
3541
|
entry.controller.abort();
|
|
3182
3542
|
}
|
|
3183
3543
|
fieldValidationState.clear();
|
|
@@ -3346,7 +3706,12 @@ function createFormStore(options) {
|
|
|
3346
3706
|
}
|
|
3347
3707
|
elementToFormInstance.set(element, formInstanceId);
|
|
3348
3708
|
sortedRegistrationsCache = null;
|
|
3349
|
-
|
|
3709
|
+
const current = fields.get(key);
|
|
3710
|
+
touchFieldRecord(key, path, {
|
|
3711
|
+
connected: true,
|
|
3712
|
+
focused: current?.focused ?? false,
|
|
3713
|
+
blurred: current?.blurred ?? true
|
|
3714
|
+
});
|
|
3350
3715
|
return true;
|
|
3351
3716
|
}
|
|
3352
3717
|
function deregisterElement(path, element) {
|
|
@@ -3361,7 +3726,7 @@ function createFormStore(options) {
|
|
|
3361
3726
|
const remaining = record.elements.size;
|
|
3362
3727
|
if (remaining === 0) {
|
|
3363
3728
|
elements.delete(key);
|
|
3364
|
-
touchFieldRecord(key, path, { connected: false });
|
|
3729
|
+
touchFieldRecord(key, path, { connected: false, focused: null, blurred: null });
|
|
3365
3730
|
}
|
|
3366
3731
|
return remaining;
|
|
3367
3732
|
}
|
|
@@ -3370,7 +3735,11 @@ function createFormStore(options) {
|
|
|
3370
3735
|
const { key } = paths.canonicalizePath(path);
|
|
3371
3736
|
const current = fields.get(key);
|
|
3372
3737
|
if (current?.connected === true) return;
|
|
3373
|
-
touchFieldRecord(key, path, {
|
|
3738
|
+
touchFieldRecord(key, path, {
|
|
3739
|
+
connected: true,
|
|
3740
|
+
focused: current?.focused ?? false,
|
|
3741
|
+
blurred: current?.blurred ?? true
|
|
3742
|
+
});
|
|
3374
3743
|
}
|
|
3375
3744
|
function markFocused(path, focused, meta) {
|
|
3376
3745
|
const { key } = paths.canonicalizePath(path);
|
|
@@ -3379,7 +3748,7 @@ function createFormStore(options) {
|
|
|
3379
3748
|
blurred: !focused,
|
|
3380
3749
|
// `touched` flips to true on blur and stays true thereafter; while
|
|
3381
3750
|
// a field is currently focused we keep whatever value it held.
|
|
3382
|
-
touched: focused ? fields.get(key)?.touched ??
|
|
3751
|
+
touched: focused ? fields.get(key)?.touched ?? false : true
|
|
3383
3752
|
});
|
|
3384
3753
|
const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
|
|
3385
3754
|
if (!focused && focusMode === "blur") {
|
|
@@ -3405,7 +3774,7 @@ function createFormStore(options) {
|
|
|
3405
3774
|
if (current?.touched === true) continue;
|
|
3406
3775
|
touchFieldRecord(leafKey, entry.segments, { touched: true });
|
|
3407
3776
|
}
|
|
3408
|
-
if (!touchedAny &&
|
|
3777
|
+
if (!touchedAny && paths.__DEV__) {
|
|
3409
3778
|
console.warn(
|
|
3410
3779
|
`[attaform] form.touch(): no fields resolved at path ${JSON.stringify(segments)}. Check the path matches an existing field or container.`
|
|
3411
3780
|
);
|
|
@@ -3414,6 +3783,84 @@ function createFormStore(options) {
|
|
|
3414
3783
|
function clear(path) {
|
|
3415
3784
|
return setValueAtPath(path, schema.getEmptyValueAtPath(path));
|
|
3416
3785
|
}
|
|
3786
|
+
function rehydrate() {
|
|
3787
|
+
const factory = defaultValuesFactory.value;
|
|
3788
|
+
if (factory === void 0) {
|
|
3789
|
+
throw new Error(
|
|
3790
|
+
"[attaform] form.rehydrate(): no defaultValues factory was captured. Configure useForm({ defaultValues: () => ... }) to enable rehydrate."
|
|
3791
|
+
);
|
|
3792
|
+
}
|
|
3793
|
+
return fireFactory(factory);
|
|
3794
|
+
}
|
|
3795
|
+
function fireFactory(factory) {
|
|
3796
|
+
activated.value = true;
|
|
3797
|
+
const promise = runFactoryAndApply(factory);
|
|
3798
|
+
activationPromise.value = promise;
|
|
3799
|
+
void promise.finally(() => {
|
|
3800
|
+
if (activationPromise.value === promise) activationPromise.value = void 0;
|
|
3801
|
+
});
|
|
3802
|
+
return promise;
|
|
3803
|
+
}
|
|
3804
|
+
function activate() {
|
|
3805
|
+
if (ssrPrefetch !== void 0) {
|
|
3806
|
+
ssrPrefetch.enqueue();
|
|
3807
|
+
if (!ssrPrefetch.shouldFire()) return Promise.resolve();
|
|
3808
|
+
}
|
|
3809
|
+
if (defaultsResolved.value === true) return Promise.resolve();
|
|
3810
|
+
if (activationPromise.value !== void 0) return activationPromise.value;
|
|
3811
|
+
if (activated.value === true) return Promise.resolve();
|
|
3812
|
+
const factory = defaultValuesFactory.value;
|
|
3813
|
+
if (factory === void 0) return Promise.resolve();
|
|
3814
|
+
return fireFactory(factory);
|
|
3815
|
+
}
|
|
3816
|
+
async function runFactoryAndApply(factory) {
|
|
3817
|
+
hydrating.value = true;
|
|
3818
|
+
try {
|
|
3819
|
+
const value = await factory();
|
|
3820
|
+
walkAuthoredFromConstraints(value, [], authoredPaths);
|
|
3821
|
+
const full = mergeSparseHydration(
|
|
3822
|
+
vue.toRaw(form.value),
|
|
3823
|
+
value,
|
|
3824
|
+
schema
|
|
3825
|
+
);
|
|
3826
|
+
applyFormReplacement(full, { hydration: true });
|
|
3827
|
+
scheduleFieldValidation(
|
|
3828
|
+
[],
|
|
3829
|
+
true
|
|
3830
|
+
/* immediate */
|
|
3831
|
+
);
|
|
3832
|
+
clearHydrationFailedEntry();
|
|
3833
|
+
hydrateError.value = null;
|
|
3834
|
+
defaultsResolved.value = true;
|
|
3835
|
+
} catch (error) {
|
|
3836
|
+
clearHydrationFailedEntry();
|
|
3837
|
+
hydrateError.value = appendHydrationFailedEntry(error);
|
|
3838
|
+
} finally {
|
|
3839
|
+
hydrating.value = false;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
function clearHydrationFailedEntry() {
|
|
3843
|
+
const existing = schemaErrors.get(paths.FORM_ERRORS_PATH_KEY);
|
|
3844
|
+
if (existing === void 0) return;
|
|
3845
|
+
const filtered = existing.filter((e) => e.code !== AttaformErrorCode.HydrationFailed);
|
|
3846
|
+
if (filtered.length === 0) {
|
|
3847
|
+
schemaErrors.delete(paths.FORM_ERRORS_PATH_KEY);
|
|
3848
|
+
} else {
|
|
3849
|
+
schemaErrors.set(paths.FORM_ERRORS_PATH_KEY, filtered);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
function appendHydrationFailedEntry(error) {
|
|
3853
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Hydration failed";
|
|
3854
|
+
const entry = {
|
|
3855
|
+
message,
|
|
3856
|
+
path: [...paths.FORM_ERRORS_PATH],
|
|
3857
|
+
formKey,
|
|
3858
|
+
code: AttaformErrorCode.HydrationFailed
|
|
3859
|
+
};
|
|
3860
|
+
const existing = schemaErrors.get(paths.FORM_ERRORS_PATH_KEY) ?? [];
|
|
3861
|
+
schemaErrors.set(paths.FORM_ERRORS_PATH_KEY, [...existing, entry]);
|
|
3862
|
+
return entry;
|
|
3863
|
+
}
|
|
3417
3864
|
function reset(nextDefaultValues) {
|
|
3418
3865
|
const resetSource = nextDefaultValues ?? defaultValues;
|
|
3419
3866
|
const completedResetConstraints = resetSource === void 0 ? void 0 : mergeStructural(schema, [], resetSource);
|
|
@@ -3423,6 +3870,7 @@ function createFormStore(options) {
|
|
|
3423
3870
|
strict
|
|
3424
3871
|
});
|
|
3425
3872
|
const next = resetResponse.data;
|
|
3873
|
+
rebuildAuthoredPaths(resetSource, next);
|
|
3426
3874
|
applyFormReplacement(next);
|
|
3427
3875
|
originals.clear();
|
|
3428
3876
|
diffAndApply({}, next, [], (patch) => {
|
|
@@ -3465,16 +3913,18 @@ function createFormStore(options) {
|
|
|
3465
3913
|
path: record.path,
|
|
3466
3914
|
updatedAt: now,
|
|
3467
3915
|
connected: record.connected,
|
|
3468
|
-
focused:
|
|
3469
|
-
blurred:
|
|
3470
|
-
touched:
|
|
3916
|
+
focused: record.focused,
|
|
3917
|
+
blurred: record.blurred,
|
|
3918
|
+
touched: false
|
|
3471
3919
|
});
|
|
3472
3920
|
}
|
|
3473
3921
|
submissionGeneration.value += 1;
|
|
3474
3922
|
submitting.value = false;
|
|
3475
3923
|
activeSubmissions.value = 0;
|
|
3476
|
-
|
|
3924
|
+
submissionAttempts.value = 0;
|
|
3925
|
+
submitted.value = false;
|
|
3477
3926
|
submitError.value = null;
|
|
3927
|
+
departAttempts.value = 0;
|
|
3478
3928
|
cancelFieldValidation();
|
|
3479
3929
|
variantMemory.clear();
|
|
3480
3930
|
for (const listener of resetListeners) {
|
|
@@ -3547,9 +3997,9 @@ function createFormStore(options) {
|
|
|
3547
3997
|
path: record.path,
|
|
3548
3998
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3549
3999
|
connected: record.connected,
|
|
3550
|
-
focused:
|
|
3551
|
-
blurred:
|
|
3552
|
-
touched:
|
|
4000
|
+
focused: record.focused,
|
|
4001
|
+
blurred: record.blurred,
|
|
4002
|
+
touched: false
|
|
3553
4003
|
});
|
|
3554
4004
|
}
|
|
3555
4005
|
function isPathPrefix(prefix, candidate) {
|
|
@@ -3612,8 +4062,18 @@ function createFormStore(options) {
|
|
|
3612
4062
|
shouldShowErrors: resolvedShouldShowErrors,
|
|
3613
4063
|
submitting,
|
|
3614
4064
|
activeSubmissions,
|
|
3615
|
-
|
|
4065
|
+
submissionAttempts,
|
|
4066
|
+
submitted,
|
|
3616
4067
|
submitError,
|
|
4068
|
+
departAttempts,
|
|
4069
|
+
hydrating,
|
|
4070
|
+
hydrateError,
|
|
4071
|
+
defaultValuesFactory,
|
|
4072
|
+
defaultsResolved,
|
|
4073
|
+
activated,
|
|
4074
|
+
activationPromise,
|
|
4075
|
+
rehydrate,
|
|
4076
|
+
activate,
|
|
3617
4077
|
submissionGeneration,
|
|
3618
4078
|
activeValidations,
|
|
3619
4079
|
firstValidationDone,
|
|
@@ -3628,6 +4088,7 @@ function createFormStore(options) {
|
|
|
3628
4088
|
setSchemaErrorsForPath,
|
|
3629
4089
|
setAllSchemaErrors,
|
|
3630
4090
|
clearSchemaErrors,
|
|
4091
|
+
applySchemaErrorsForSubtree,
|
|
3631
4092
|
setAllUserErrors,
|
|
3632
4093
|
addUserErrors,
|
|
3633
4094
|
clearUserErrors,
|
|
@@ -3873,6 +4334,11 @@ const PROTOCOL_VERSION = 1;
|
|
|
3873
4334
|
const JOIN_COLLECTION_WINDOW_MS = 50;
|
|
3874
4335
|
const SNAPSHOT_TIMEOUT_MS = 200;
|
|
3875
4336
|
const MAX_LEADER_ATTEMPTS = 3;
|
|
4337
|
+
function isFileLikeValue(value) {
|
|
4338
|
+
if (typeof File !== "undefined" && value instanceof File) return true;
|
|
4339
|
+
if (typeof Blob !== "undefined" && value instanceof Blob) return true;
|
|
4340
|
+
return false;
|
|
4341
|
+
}
|
|
3876
4342
|
function isDangerousSegment(s) {
|
|
3877
4343
|
return s === "__proto__" || s === "constructor" || s === "prototype";
|
|
3878
4344
|
}
|
|
@@ -3882,6 +4348,32 @@ function pathContainsDangerousSegment(path) {
|
|
|
3882
4348
|
}
|
|
3883
4349
|
return false;
|
|
3884
4350
|
}
|
|
4351
|
+
function isInboundShapeAcceptable(schema, path, value) {
|
|
4352
|
+
if (isFileLikeValue(value)) return false;
|
|
4353
|
+
let kind;
|
|
4354
|
+
if (Array.isArray(value)) {
|
|
4355
|
+
kind = "array";
|
|
4356
|
+
} else if (value !== null && typeof value === "object" && isPlainRecord(value)) {
|
|
4357
|
+
kind = "object";
|
|
4358
|
+
} else {
|
|
4359
|
+
kind = slimKindOf(value);
|
|
4360
|
+
}
|
|
4361
|
+
const accepted = schema.getSlimPrimitiveTypesAtPath(path);
|
|
4362
|
+
if (!accepted.has(kind)) return false;
|
|
4363
|
+
if (Array.isArray(value)) {
|
|
4364
|
+
for (let i = 0; i < value.length; i++) {
|
|
4365
|
+
if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
|
|
4366
|
+
}
|
|
4367
|
+
return true;
|
|
4368
|
+
}
|
|
4369
|
+
if (isPlainRecord(value)) {
|
|
4370
|
+
for (const key of Object.keys(value)) {
|
|
4371
|
+
if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false;
|
|
4372
|
+
}
|
|
4373
|
+
return true;
|
|
4374
|
+
}
|
|
4375
|
+
return true;
|
|
4376
|
+
}
|
|
3885
4377
|
function diffBlankPaths(prev, curr) {
|
|
3886
4378
|
const added = [];
|
|
3887
4379
|
const removed = [];
|
|
@@ -3894,6 +4386,7 @@ function snapshotForm(form) {
|
|
|
3894
4386
|
return structuralSnapshot(form);
|
|
3895
4387
|
}
|
|
3896
4388
|
function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
|
|
4389
|
+
if (isFileLikeValue(value)) return void 0;
|
|
3897
4390
|
if (value === null || typeof value !== "object") return value;
|
|
3898
4391
|
if (Array.isArray(value)) {
|
|
3899
4392
|
return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
|
|
@@ -3998,6 +4491,7 @@ function createMultiTabSyncModule(state, channelName, options) {
|
|
|
3998
4491
|
const safePatches = [];
|
|
3999
4492
|
for (const p of rawPatches) {
|
|
4000
4493
|
if (isPathLocallySuppressed(p.path)) continue;
|
|
4494
|
+
if ("value" in p && isFileLikeValue(p.value)) continue;
|
|
4001
4495
|
safePatches.push(p);
|
|
4002
4496
|
}
|
|
4003
4497
|
const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
|
|
@@ -4037,6 +4531,9 @@ function createMultiTabSyncModule(state, channelName, options) {
|
|
|
4037
4531
|
for (const p of msg.formPatches) {
|
|
4038
4532
|
if (!Array.isArray(p.path)) continue;
|
|
4039
4533
|
if (isPathLocallySuppressed(p.path)) continue;
|
|
4534
|
+
if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
|
|
4535
|
+
continue;
|
|
4536
|
+
}
|
|
4040
4537
|
safePatches.push(p);
|
|
4041
4538
|
}
|
|
4042
4539
|
const safeBlankAdded = [];
|
|
@@ -4071,11 +4568,7 @@ function createMultiTabSyncModule(state, channelName, options) {
|
|
|
4071
4568
|
}
|
|
4072
4569
|
function handleSnapshot(msg) {
|
|
4073
4570
|
if (lifecycle !== "joining") return;
|
|
4074
|
-
|
|
4075
|
-
options.validateForm(msg.form);
|
|
4076
|
-
} catch {
|
|
4077
|
-
return;
|
|
4078
|
-
}
|
|
4571
|
+
if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return;
|
|
4079
4572
|
if (snapshotTimeoutTimer !== null) {
|
|
4080
4573
|
clearTimeout(snapshotTimeoutTimer);
|
|
4081
4574
|
snapshotTimeoutTimer = null;
|
|
@@ -4194,7 +4687,7 @@ const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
|
|
|
4194
4687
|
|
|
4195
4688
|
const warned = /* @__PURE__ */ new Set();
|
|
4196
4689
|
function warnOnceInsecureContext(feature) {
|
|
4197
|
-
if (!
|
|
4690
|
+
if (!paths.__DEV__) return;
|
|
4198
4691
|
if (warned.has(feature)) return;
|
|
4199
4692
|
warned.add(feature);
|
|
4200
4693
|
const message = featureMessage(feature);
|
|
@@ -4214,15 +4707,26 @@ function isSecureContext() {
|
|
|
4214
4707
|
return typeof window !== "undefined" && window.isSecureContext === true;
|
|
4215
4708
|
}
|
|
4216
4709
|
|
|
4217
|
-
function
|
|
4710
|
+
function resolveTrichotomy(input) {
|
|
4711
|
+
if (typeof input === "function") {
|
|
4712
|
+
return { kind: "async", factory: input };
|
|
4713
|
+
}
|
|
4714
|
+
return { kind: "sync", value: input };
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4717
|
+
function useAbstractForm(configuration, options) {
|
|
4218
4718
|
if (configuration === void 0 || configuration === null || configuration.schema === void 0) {
|
|
4219
|
-
throw new
|
|
4719
|
+
throw new paths.InvalidUseFormConfigError();
|
|
4220
4720
|
}
|
|
4221
4721
|
const key = resolveFormKey(configuration.key);
|
|
4222
4722
|
const instance = vue.getCurrentInstance();
|
|
4223
|
-
if (instance !== null)
|
|
4224
|
-
const registry =
|
|
4225
|
-
const
|
|
4723
|
+
if (instance !== null) paths.ensureAttaformInstalled(instance.appContext.app);
|
|
4724
|
+
const registry = options?.registry ?? paths.useRegistry();
|
|
4725
|
+
const resolvedDefaults = resolveTrichotomy(configuration.defaultValues);
|
|
4726
|
+
const materialisedDefaults = resolvedDefaults.kind === "sync" ? resolvedDefaults.value : void 0;
|
|
4727
|
+
const { defaultValues: _droppedDefaults, ...configWithoutDefaults } = configuration;
|
|
4728
|
+
const trichotomyOverride = materialisedDefaults === void 0 ? configWithoutDefaults : { ...configWithoutDefaults, defaultValues: materialisedDefaults };
|
|
4729
|
+
const merged = mergeWithDefaults(registry.defaults, trichotomyOverride);
|
|
4226
4730
|
const maxRecursionDepth = normalizeNumericOption({
|
|
4227
4731
|
value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
|
|
4228
4732
|
source: "useForm.maxRecursionDepth",
|
|
@@ -4232,18 +4736,38 @@ function useAbstractForm(configuration) {
|
|
|
4232
4736
|
});
|
|
4233
4737
|
const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
|
|
4234
4738
|
if (configuration.persist !== void 0 && configuration.key === void 0) {
|
|
4235
|
-
throw new
|
|
4739
|
+
throw new paths.AnonPersistError({
|
|
4236
4740
|
cause: "no-key",
|
|
4237
4741
|
schemaFields: extractSchemaFields(resolvedSchema),
|
|
4238
|
-
callSite:
|
|
4742
|
+
callSite: paths.captureUserCallSite()
|
|
4239
4743
|
});
|
|
4240
4744
|
}
|
|
4241
4745
|
const existing = registry.forms.get(key);
|
|
4242
|
-
if (
|
|
4746
|
+
if (paths.__DEV__ && existing !== void 0) {
|
|
4243
4747
|
warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
|
|
4244
4748
|
warnOnPersistDivergence(key, existing, configuration.persist);
|
|
4245
4749
|
}
|
|
4750
|
+
const hadPendingHydration = registry.pendingHydration.has(key);
|
|
4246
4751
|
const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
|
|
4752
|
+
if (existing !== void 0) ; else if (resolvedDefaults.kind === "sync") {
|
|
4753
|
+
state.defaultsResolved.value = true;
|
|
4754
|
+
}
|
|
4755
|
+
if (existing === void 0 && resolvedDefaults.kind === "async") {
|
|
4756
|
+
const factory = resolvedDefaults.factory;
|
|
4757
|
+
state.defaultValuesFactory.value = factory;
|
|
4758
|
+
if (hadPendingHydration) {
|
|
4759
|
+
state.hydrating.value = false;
|
|
4760
|
+
state.defaultsResolved.value = true;
|
|
4761
|
+
} else if (registry.ssr) {
|
|
4762
|
+
if (configuration.__ssrAccessed === true) {
|
|
4763
|
+
registry.enqueuePrefetch(key);
|
|
4764
|
+
}
|
|
4765
|
+
vue.onServerPrefetch(() => {
|
|
4766
|
+
if (!registry.shouldPrefetch(key)) return;
|
|
4767
|
+
return state.activate();
|
|
4768
|
+
});
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4247
4771
|
if (vue.getCurrentScope() !== void 0) {
|
|
4248
4772
|
const releaseConsumer = registry.trackConsumer(key);
|
|
4249
4773
|
vue.onScopeDispose(releaseConsumer);
|
|
@@ -4271,7 +4795,7 @@ function useAbstractForm(configuration) {
|
|
|
4271
4795
|
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
4272
4796
|
}
|
|
4273
4797
|
}
|
|
4274
|
-
if (existing === void 0 && merged.multiTab
|
|
4798
|
+
if (existing === void 0 && merged.multiTab === true && configuration.key !== void 0 && !registry.ssr) {
|
|
4275
4799
|
const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
|
|
4276
4800
|
const secureContext = isSecureContext();
|
|
4277
4801
|
if (hasBroadcastChannel && secureContext) {
|
|
@@ -4307,11 +4831,11 @@ function useAbstractForm(configuration) {
|
|
|
4307
4831
|
}
|
|
4308
4832
|
if (configuration.key === void 0) {
|
|
4309
4833
|
recordAmbientProvide(registry.ssr);
|
|
4310
|
-
vue.provide(
|
|
4834
|
+
vue.provide(paths.kFormContext, state);
|
|
4311
4835
|
}
|
|
4312
4836
|
const formInstanceId = vue.getCurrentInstance() !== null ? vue.useId() : `atta:form-instance:${formInstanceCounter++}`;
|
|
4313
4837
|
if (vue.getCurrentInstance() !== null) {
|
|
4314
|
-
vue.provide(
|
|
4838
|
+
vue.provide(paths.kFormInstanceId, formInstanceId);
|
|
4315
4839
|
}
|
|
4316
4840
|
const apiOptions = {};
|
|
4317
4841
|
if (merged.onInvalidSubmit !== void 0) {
|
|
@@ -4378,10 +4902,13 @@ function buildFreshState(key, schema, configuration, registry) {
|
|
|
4378
4902
|
configuration.defaultValues,
|
|
4379
4903
|
schema
|
|
4380
4904
|
);
|
|
4381
|
-
|
|
4905
|
+
let initialBlankPaths;
|
|
4906
|
+
if (pending === void 0) {
|
|
4907
|
+
initialBlankPaths = walked.paths;
|
|
4908
|
+
}
|
|
4382
4909
|
const resolvedSensitiveNames = configuration.sensitiveNames;
|
|
4383
|
-
const resolvedIsSensitivePath = resolvedSensitiveNames === void 0 ? void 0 :
|
|
4384
|
-
const resolvedSegmentMatchesSensitive = resolvedSensitiveNames === void 0 ? void 0 :
|
|
4910
|
+
const resolvedIsSensitivePath = resolvedSensitiveNames === void 0 ? void 0 : paths.createIsSensitivePath(resolvedSensitiveNames);
|
|
4911
|
+
const resolvedSegmentMatchesSensitive = resolvedSensitiveNames === void 0 ? void 0 : paths.createSegmentMatchesSensitive(resolvedSensitiveNames);
|
|
4385
4912
|
const createOptions = {
|
|
4386
4913
|
formKey: key,
|
|
4387
4914
|
schema,
|
|
@@ -4391,6 +4918,21 @@ function buildFreshState(key, schema, configuration, registry) {
|
|
|
4391
4918
|
...configuration.validateOn !== void 0 ? { validateOn: configuration.validateOn } : {},
|
|
4392
4919
|
...configuration.debounceMs !== void 0 ? { debounceMs: configuration.debounceMs } : {},
|
|
4393
4920
|
ssr: registry.ssr,
|
|
4921
|
+
// Server-only: bind the SSR prefetch coordination handles. `enqueue`
|
|
4922
|
+
// records intent on every `state.activate()` so a wizard skip-list
|
|
4923
|
+
// override or a future transform mark has a consistent set to diff
|
|
4924
|
+
// against; `shouldFire` lets the activate path bail when the
|
|
4925
|
+
// wizard explicitly skipped this key — even an explicit
|
|
4926
|
+
// `form.activate()` defers to the wizard's render-efficiency
|
|
4927
|
+
// skip-list on the server.
|
|
4928
|
+
...registry.ssr ? {
|
|
4929
|
+
ssrPrefetch: {
|
|
4930
|
+
enqueue: () => {
|
|
4931
|
+
registry.enqueuePrefetch(key);
|
|
4932
|
+
},
|
|
4933
|
+
shouldFire: () => registry.shouldPrefetch(key)
|
|
4934
|
+
}
|
|
4935
|
+
} : {},
|
|
4394
4936
|
...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
|
|
4395
4937
|
...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
|
|
4396
4938
|
...configuration.shouldShowErrors !== void 0 ? { shouldShowErrors: configuration.shouldShowErrors } : {},
|
|
@@ -4407,14 +4949,14 @@ function buildFreshState(key, schema, configuration, registry) {
|
|
|
4407
4949
|
}
|
|
4408
4950
|
let anonCounter = 0;
|
|
4409
4951
|
let formInstanceCounter = 0;
|
|
4410
|
-
const ambientProvideHistory =
|
|
4952
|
+
const ambientProvideHistory = paths.__DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
4411
4953
|
function recordAmbientProvide(ssr) {
|
|
4412
|
-
if (!
|
|
4954
|
+
if (!paths.__DEV__ || ssr || ambientProvideHistory === null) return;
|
|
4413
4955
|
const instance = vue.getCurrentInstance();
|
|
4414
4956
|
if (instance === null) return;
|
|
4415
4957
|
const instanceKey = instance;
|
|
4416
4958
|
const entry = {
|
|
4417
|
-
source:
|
|
4959
|
+
source: paths.captureUserCallSite()
|
|
4418
4960
|
};
|
|
4419
4961
|
const existing = ambientProvideHistory.get(instanceKey);
|
|
4420
4962
|
if (existing === void 0) {
|
|
@@ -4426,7 +4968,7 @@ function recordAmbientProvide(ssr) {
|
|
|
4426
4968
|
function resolveFormKey(key) {
|
|
4427
4969
|
if (key !== void 0 && key !== null && key !== "") {
|
|
4428
4970
|
if (key.startsWith(RESERVED_KEY_PREFIX)) {
|
|
4429
|
-
throw new
|
|
4971
|
+
throw new paths.ReservedFormKeyError(key);
|
|
4430
4972
|
}
|
|
4431
4973
|
return key;
|
|
4432
4974
|
}
|
|
@@ -4571,8 +5113,9 @@ function wirePersistence(state, config) {
|
|
|
4571
5113
|
state.originalBlankPaths.delete(k);
|
|
4572
5114
|
}
|
|
4573
5115
|
for (const k of payload.data.blankPaths ?? []) {
|
|
4574
|
-
|
|
4575
|
-
state.
|
|
5116
|
+
const key2 = paths.coerceToPathKey(k);
|
|
5117
|
+
state.blankPaths.add(key2);
|
|
5118
|
+
state.originalBlankPaths.add(key2);
|
|
4576
5119
|
}
|
|
4577
5120
|
if (include === "form+errors") {
|
|
4578
5121
|
if (payload.data.schemaErrors !== void 0) {
|
|
@@ -4711,10 +5254,10 @@ function isEmptyContainer(value) {
|
|
|
4711
5254
|
const warnedAnonPersistKeys = /* @__PURE__ */ new Set();
|
|
4712
5255
|
function enforceAnonPersistRule(formKey, ssr) {
|
|
4713
5256
|
if (!formKey.startsWith(ANONYMOUS_FORM_KEY_PREFIX)) return false;
|
|
4714
|
-
if (
|
|
4715
|
-
throw new
|
|
5257
|
+
if (paths.__DEV__)
|
|
5258
|
+
throw new paths.AnonPersistError({
|
|
4716
5259
|
cause: "no-key",
|
|
4717
|
-
callSite:
|
|
5260
|
+
callSite: paths.captureUserCallSite()
|
|
4718
5261
|
});
|
|
4719
5262
|
if (!ssr && !warnedAnonPersistKeys.has(formKey)) {
|
|
4720
5263
|
warnedAnonPersistKeys.add(formKey);
|
|
@@ -4753,22 +5296,28 @@ function isDescendantPathKey(candidate, ancestor) {
|
|
|
4753
5296
|
}
|
|
4754
5297
|
|
|
4755
5298
|
let injectedInstanceCounter = 0;
|
|
4756
|
-
function injectForm(
|
|
5299
|
+
function injectForm(input) {
|
|
5300
|
+
const key = typeof input === "string" ? input : input?.key;
|
|
5301
|
+
const ssrAccessed = typeof input === "object" && input !== null ? input.__ssrAccessed === true : false;
|
|
4757
5302
|
const instance = vue.getCurrentInstance();
|
|
4758
|
-
if (instance !== null)
|
|
4759
|
-
const registry =
|
|
5303
|
+
if (instance !== null) paths.ensureAttaformInstalled(instance.appContext.app);
|
|
5304
|
+
const registry = paths.useRegistry();
|
|
4760
5305
|
const state = resolveState(key, registry);
|
|
4761
5306
|
if (state === null) return null;
|
|
4762
5307
|
if (vue.getCurrentScope() !== void 0) {
|
|
4763
5308
|
const releaseConsumer = registry.trackConsumer(state.formKey);
|
|
4764
5309
|
vue.onScopeDispose(releaseConsumer);
|
|
4765
5310
|
}
|
|
5311
|
+
if (registry.ssr && ssrAccessed) {
|
|
5312
|
+
registry.enqueuePrefetch(state.formKey);
|
|
5313
|
+
vue.onServerPrefetch(() => state.activate());
|
|
5314
|
+
}
|
|
4766
5315
|
const apiOptions = {};
|
|
4767
5316
|
const history = state.modules.get("history");
|
|
4768
5317
|
if (history !== void 0) {
|
|
4769
5318
|
apiOptions.history = history;
|
|
4770
5319
|
}
|
|
4771
|
-
const ambientInstanceId = vue.getCurrentInstance() !== null ? vue.inject(
|
|
5320
|
+
const ambientInstanceId = vue.getCurrentInstance() !== null ? vue.inject(paths.kFormInstanceId, null) : null;
|
|
4772
5321
|
const formInstanceId = ambientInstanceId ?? (vue.getCurrentInstance() !== null ? vue.useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`);
|
|
4773
5322
|
return buildFormApi(
|
|
4774
5323
|
state,
|
|
@@ -4780,28 +5329,25 @@ function resolveState(key, registry) {
|
|
|
4780
5329
|
if (key !== void 0) {
|
|
4781
5330
|
const stored = registry.forms.get(key);
|
|
4782
5331
|
if (stored === void 0) {
|
|
4783
|
-
warnMiss(`no form registered for key '${key}'`, registry.ssr);
|
|
5332
|
+
warnMiss$1(`no form registered for key '${key}'`, registry.ssr);
|
|
4784
5333
|
return null;
|
|
4785
5334
|
}
|
|
4786
5335
|
return stored;
|
|
4787
5336
|
}
|
|
4788
|
-
const ambient = vue.inject(
|
|
4789
|
-
if (ambient === null)
|
|
4790
|
-
warnMiss("no ambient form context", registry.ssr);
|
|
4791
|
-
return null;
|
|
4792
|
-
}
|
|
5337
|
+
const ambient = vue.inject(paths.kFormContext, null);
|
|
5338
|
+
if (ambient === null) return null;
|
|
4793
5339
|
warnIfAmbientProviderHadDuplicates();
|
|
4794
5340
|
return ambient;
|
|
4795
5341
|
}
|
|
4796
|
-
function warnMiss(detail, ssr) {
|
|
4797
|
-
if (!
|
|
4798
|
-
const frame =
|
|
5342
|
+
function warnMiss$1(detail, ssr) {
|
|
5343
|
+
if (!paths.__DEV__ || ssr) return;
|
|
5344
|
+
const frame = paths.captureUserCallSite();
|
|
4799
5345
|
console.warn(
|
|
4800
5346
|
`[attaform] injectForm: ${detail}. Returning null.` + (frame !== void 0 ? ` ${frame}` : "")
|
|
4801
5347
|
);
|
|
4802
5348
|
}
|
|
4803
5349
|
function warnIfAmbientProviderHadDuplicates() {
|
|
4804
|
-
if (!
|
|
5350
|
+
if (!paths.__DEV__ || ambientProvideHistory === null) return;
|
|
4805
5351
|
let ancestor = vue.getCurrentInstance()?.parent ?? null;
|
|
4806
5352
|
while (ancestor !== null) {
|
|
4807
5353
|
const history = ambientProvideHistory.get(ancestor);
|
|
@@ -4818,6 +5364,1045 @@ function warnIfAmbientProviderHadDuplicates() {
|
|
|
4818
5364
|
}
|
|
4819
5365
|
}
|
|
4820
5366
|
|
|
5367
|
+
const LAZY_BRAND = Symbol.for("attaform/wizard-lazy");
|
|
5368
|
+
function lazy(resolve) {
|
|
5369
|
+
return { [LAZY_BRAND]: true, resolve };
|
|
5370
|
+
}
|
|
5371
|
+
function isLazyMarker(value) {
|
|
5372
|
+
return typeof value === "object" && value !== null && LAZY_BRAND in value;
|
|
5373
|
+
}
|
|
5374
|
+
|
|
5375
|
+
const NOOP_WIZARD_HISTORY = {
|
|
5376
|
+
push() {
|
|
5377
|
+
},
|
|
5378
|
+
replace() {
|
|
5379
|
+
},
|
|
5380
|
+
read() {
|
|
5381
|
+
return void 0;
|
|
5382
|
+
},
|
|
5383
|
+
subscribe() {
|
|
5384
|
+
},
|
|
5385
|
+
dispose() {
|
|
5386
|
+
}
|
|
5387
|
+
};
|
|
5388
|
+
function createWizardHistory(param) {
|
|
5389
|
+
if (typeof window === "undefined") return NOOP_WIZARD_HISTORY;
|
|
5390
|
+
const subscribers = [];
|
|
5391
|
+
let disposed = false;
|
|
5392
|
+
function buildUrl(key) {
|
|
5393
|
+
const url = new URL(window.location.href);
|
|
5394
|
+
url.searchParams.set(param, key);
|
|
5395
|
+
return url.toString();
|
|
5396
|
+
}
|
|
5397
|
+
function handlePopstate() {
|
|
5398
|
+
if (disposed) return;
|
|
5399
|
+
const url = new URL(window.location.href);
|
|
5400
|
+
const value = url.searchParams.get(param) ?? void 0;
|
|
5401
|
+
for (const subscriber of subscribers) subscriber(value);
|
|
5402
|
+
}
|
|
5403
|
+
window.addEventListener("popstate", handlePopstate);
|
|
5404
|
+
function safeWriteState(key, op) {
|
|
5405
|
+
try {
|
|
5406
|
+
const fn = op === "push" ? window.history.pushState : window.history.replaceState;
|
|
5407
|
+
fn.call(window.history, {}, "", buildUrl(key));
|
|
5408
|
+
} catch {
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
return {
|
|
5412
|
+
push(key) {
|
|
5413
|
+
if (disposed) return;
|
|
5414
|
+
safeWriteState(key, "push");
|
|
5415
|
+
},
|
|
5416
|
+
replace(key) {
|
|
5417
|
+
if (disposed) return;
|
|
5418
|
+
safeWriteState(key, "replace");
|
|
5419
|
+
},
|
|
5420
|
+
read() {
|
|
5421
|
+
const url = new URL(window.location.href);
|
|
5422
|
+
return url.searchParams.get(param) ?? void 0;
|
|
5423
|
+
},
|
|
5424
|
+
subscribe(callback) {
|
|
5425
|
+
if (disposed) return;
|
|
5426
|
+
subscribers.push(callback);
|
|
5427
|
+
},
|
|
5428
|
+
dispose() {
|
|
5429
|
+
if (disposed) return;
|
|
5430
|
+
disposed = true;
|
|
5431
|
+
subscribers.length = 0;
|
|
5432
|
+
window.removeEventListener("popstate", handlePopstate);
|
|
5433
|
+
}
|
|
5434
|
+
};
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5437
|
+
const NOOP_FINGERPRINT = "attaform:wizard-noop";
|
|
5438
|
+
const EMPTY_SLIM_KINDS = /* @__PURE__ */ new Set();
|
|
5439
|
+
function buildNoopWizardSchema(formKey) {
|
|
5440
|
+
const emptyValue = {};
|
|
5441
|
+
const success = {
|
|
5442
|
+
success: true,
|
|
5443
|
+
data: emptyValue,
|
|
5444
|
+
errors: void 0,
|
|
5445
|
+
formKey
|
|
5446
|
+
};
|
|
5447
|
+
const defaultsResponse = {
|
|
5448
|
+
success: true,
|
|
5449
|
+
data: emptyValue,
|
|
5450
|
+
errors: void 0,
|
|
5451
|
+
formKey
|
|
5452
|
+
};
|
|
5453
|
+
return {
|
|
5454
|
+
fingerprint: () => NOOP_FINGERPRINT,
|
|
5455
|
+
getDefaultValues: () => defaultsResponse,
|
|
5456
|
+
getDefaultAtPath: () => void 0,
|
|
5457
|
+
getEmptyValueAtPath: () => void 0,
|
|
5458
|
+
isPreprocessOrCoerceLeaf: () => false,
|
|
5459
|
+
arrayShapeAtPath: () => void 0,
|
|
5460
|
+
getSchemasAtPath: () => [],
|
|
5461
|
+
validateAtPath: () => success,
|
|
5462
|
+
getSlimPrimitiveTypesAtPath: () => new Set(EMPTY_SLIM_KINDS),
|
|
5463
|
+
isLeafAtPath: () => false,
|
|
5464
|
+
isRequiredAtPath: () => false,
|
|
5465
|
+
getUnionDiscriminatorAtPath: () => void 0
|
|
5466
|
+
};
|
|
5467
|
+
}
|
|
5468
|
+
|
|
5469
|
+
function buildWizardStatusesProxy(statuses) {
|
|
5470
|
+
const snapshot = vue.computed(() => {
|
|
5471
|
+
const result = {};
|
|
5472
|
+
for (const key of Object.keys(statuses)) {
|
|
5473
|
+
result[key] = statuses[key].value;
|
|
5474
|
+
}
|
|
5475
|
+
return result;
|
|
5476
|
+
});
|
|
5477
|
+
const target = (() => {
|
|
5478
|
+
});
|
|
5479
|
+
const proxyToString = () => JSON.stringify(snapshot.value);
|
|
5480
|
+
const proxyToPrimitive = (hint) => hint === "number" ? NaN : proxyToString();
|
|
5481
|
+
return new Proxy(target, {
|
|
5482
|
+
apply(_, __, args) {
|
|
5483
|
+
const key = args[0];
|
|
5484
|
+
if (key === void 0) return snapshot.value;
|
|
5485
|
+
const computedEntry = statuses[key];
|
|
5486
|
+
if (computedEntry === void 0) return void 0;
|
|
5487
|
+
return computedEntry.value;
|
|
5488
|
+
},
|
|
5489
|
+
get(_, key) {
|
|
5490
|
+
if (typeof key === "symbol") {
|
|
5491
|
+
if (key === Symbol.toPrimitive) return proxyToPrimitive;
|
|
5492
|
+
return Reflect.get(target, key);
|
|
5493
|
+
}
|
|
5494
|
+
if (key === "toJSON") return () => snapshot.value;
|
|
5495
|
+
if (key === "toString") return proxyToString;
|
|
5496
|
+
if (key === "valueOf")
|
|
5497
|
+
return function() {
|
|
5498
|
+
return this;
|
|
5499
|
+
};
|
|
5500
|
+
const computedEntry = statuses[key];
|
|
5501
|
+
if (computedEntry === void 0) return void 0;
|
|
5502
|
+
return computedEntry.value;
|
|
5503
|
+
},
|
|
5504
|
+
has(_, key) {
|
|
5505
|
+
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
5506
|
+
return Object.hasOwn(statuses, key);
|
|
5507
|
+
},
|
|
5508
|
+
ownKeys() {
|
|
5509
|
+
return Object.keys(statuses);
|
|
5510
|
+
},
|
|
5511
|
+
getOwnPropertyDescriptor(_, key) {
|
|
5512
|
+
if (typeof key === "symbol") return void 0;
|
|
5513
|
+
const computedEntry = statuses[key];
|
|
5514
|
+
if (computedEntry === void 0) return void 0;
|
|
5515
|
+
return {
|
|
5516
|
+
configurable: true,
|
|
5517
|
+
enumerable: true,
|
|
5518
|
+
writable: false,
|
|
5519
|
+
value: computedEntry.value
|
|
5520
|
+
};
|
|
5521
|
+
},
|
|
5522
|
+
set(_, key) {
|
|
5523
|
+
if (paths.__DEV__) {
|
|
5524
|
+
console.warn(
|
|
5525
|
+
`[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.`
|
|
5526
|
+
);
|
|
5527
|
+
}
|
|
5528
|
+
return true;
|
|
5529
|
+
},
|
|
5530
|
+
deleteProperty(_, key) {
|
|
5531
|
+
if (paths.__DEV__) {
|
|
5532
|
+
console.warn(
|
|
5533
|
+
`[attaform] wizard.statuses is read-only \u2014 delete of "${String(key)}" was ignored.`
|
|
5534
|
+
);
|
|
5535
|
+
}
|
|
5536
|
+
return true;
|
|
5537
|
+
},
|
|
5538
|
+
defineProperty: () => true
|
|
5539
|
+
});
|
|
5540
|
+
}
|
|
5541
|
+
|
|
5542
|
+
const DEFAULT_STEP_PARAM = "step";
|
|
5543
|
+
const PENDING_STATUS = {
|
|
5544
|
+
valid: false,
|
|
5545
|
+
dirty: false,
|
|
5546
|
+
submitted: false,
|
|
5547
|
+
errorCount: 0
|
|
5548
|
+
};
|
|
5549
|
+
const NOOP_VALID_STATUS = {
|
|
5550
|
+
valid: true,
|
|
5551
|
+
dirty: false,
|
|
5552
|
+
submitted: false,
|
|
5553
|
+
errorCount: 0
|
|
5554
|
+
};
|
|
5555
|
+
function useWizard(options) {
|
|
5556
|
+
const rawSteps = Array.isArray(options.steps) ? options.steps : [];
|
|
5557
|
+
if (rawSteps.length === 0 && paths.__DEV__) {
|
|
5558
|
+
console.error(
|
|
5559
|
+
"[attaform] useWizard({ steps }): expected a non-empty array of step slots. Continuing with an empty step list \u2014 wizard.currentStep reads as undefined, navigation refuses, handleSubmit no-ops."
|
|
5560
|
+
);
|
|
5561
|
+
}
|
|
5562
|
+
const registry = paths.useRegistry();
|
|
5563
|
+
const noopForms = /* @__PURE__ */ new Map();
|
|
5564
|
+
const lazyNoopScope = vue.effectScope(true);
|
|
5565
|
+
for (const slot of rawSteps) {
|
|
5566
|
+
if (typeof slot !== "string") continue;
|
|
5567
|
+
if (noopForms.has(slot)) continue;
|
|
5568
|
+
const noop = useAbstractForm({
|
|
5569
|
+
schema: buildNoopWizardSchema(slot),
|
|
5570
|
+
key: slot
|
|
5571
|
+
});
|
|
5572
|
+
noopForms.set(slot, noop);
|
|
5573
|
+
}
|
|
5574
|
+
function getOrBuildNoop(key) {
|
|
5575
|
+
const existing2 = noopForms.get(key);
|
|
5576
|
+
if (existing2 !== void 0) return existing2;
|
|
5577
|
+
const noop = lazyNoopScope.run(
|
|
5578
|
+
() => useAbstractForm(
|
|
5579
|
+
{
|
|
5580
|
+
schema: buildNoopWizardSchema(key),
|
|
5581
|
+
key
|
|
5582
|
+
},
|
|
5583
|
+
{ registry }
|
|
5584
|
+
)
|
|
5585
|
+
);
|
|
5586
|
+
if (noop === void 0) {
|
|
5587
|
+
const stub = { key };
|
|
5588
|
+
return stub;
|
|
5589
|
+
}
|
|
5590
|
+
noopForms.set(key, noop);
|
|
5591
|
+
formsAccumulator.set(key, noop);
|
|
5592
|
+
return noop;
|
|
5593
|
+
}
|
|
5594
|
+
const trackedKeys = /* @__PURE__ */ new Set();
|
|
5595
|
+
function trackOnce(form) {
|
|
5596
|
+
if (trackedKeys.has(form.key)) return;
|
|
5597
|
+
trackedKeys.add(form.key);
|
|
5598
|
+
if (vue.getCurrentScope() !== void 0) {
|
|
5599
|
+
const release = registry.trackConsumer(form.key);
|
|
5600
|
+
vue.onScopeDispose(release);
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
for (const slot of rawSteps) {
|
|
5604
|
+
if (typeof slot === "string") {
|
|
5605
|
+
const noop = noopForms.get(slot);
|
|
5606
|
+
if (noop !== void 0) trackOnce(noop);
|
|
5607
|
+
} else if (isAnyForm(slot)) {
|
|
5608
|
+
trackOnce(slot);
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
const lazyEpoch = vue.ref(0);
|
|
5612
|
+
const lazyComputeds = /* @__PURE__ */ new Map();
|
|
5613
|
+
const activeKey = vue.ref("");
|
|
5614
|
+
const formsAccumulator = /* @__PURE__ */ new Map();
|
|
5615
|
+
for (const slot of rawSteps) {
|
|
5616
|
+
if (typeof slot === "string") {
|
|
5617
|
+
const noop = noopForms.get(slot);
|
|
5618
|
+
if (noop !== void 0) formsAccumulator.set(noop.key, noop);
|
|
5619
|
+
} else if (isAnyForm(slot)) {
|
|
5620
|
+
formsAccumulator.set(slot.key, slot);
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
const slotForms = new Proxy({}, {
|
|
5624
|
+
get(_, key) {
|
|
5625
|
+
if (typeof key !== "string") return void 0;
|
|
5626
|
+
return formsAccumulator.get(key);
|
|
5627
|
+
},
|
|
5628
|
+
has(_, key) {
|
|
5629
|
+
if (typeof key !== "string") return false;
|
|
5630
|
+
return formsAccumulator.has(key);
|
|
5631
|
+
},
|
|
5632
|
+
ownKeys() {
|
|
5633
|
+
return [...formsAccumulator.keys()];
|
|
5634
|
+
},
|
|
5635
|
+
getOwnPropertyDescriptor(_, key) {
|
|
5636
|
+
if (typeof key !== "string") return void 0;
|
|
5637
|
+
const form = formsAccumulator.get(key);
|
|
5638
|
+
if (form === void 0) return void 0;
|
|
5639
|
+
return { configurable: true, enumerable: true, writable: false, value: form };
|
|
5640
|
+
}
|
|
5641
|
+
});
|
|
5642
|
+
const slotCtx = vue.computed(() => ({
|
|
5643
|
+
forms: slotForms,
|
|
5644
|
+
currentKey: activeKey.value === "" ? void 0 : activeKey.value
|
|
5645
|
+
}));
|
|
5646
|
+
function resolveSlot(slot, index, ctx) {
|
|
5647
|
+
if (typeof slot === "string") {
|
|
5648
|
+
return getOrBuildNoop(slot);
|
|
5649
|
+
}
|
|
5650
|
+
if (isLazyMarker(slot)) {
|
|
5651
|
+
const c = lazyComputeds.get(index);
|
|
5652
|
+
return c === void 0 ? void 0 : resolveSlotResult(c.value);
|
|
5653
|
+
}
|
|
5654
|
+
if (typeof slot === "function") {
|
|
5655
|
+
const result = slot(ctx);
|
|
5656
|
+
return resolveSlotResult(result);
|
|
5657
|
+
}
|
|
5658
|
+
if (isAnyForm(slot)) return slot;
|
|
5659
|
+
return void 0;
|
|
5660
|
+
}
|
|
5661
|
+
function resolveSlotResult(result) {
|
|
5662
|
+
if (result === void 0) return void 0;
|
|
5663
|
+
if (typeof result === "string") {
|
|
5664
|
+
return getOrBuildNoop(result);
|
|
5665
|
+
}
|
|
5666
|
+
return result;
|
|
5667
|
+
}
|
|
5668
|
+
const lazyCtx = {
|
|
5669
|
+
forms: slotForms,
|
|
5670
|
+
get currentKey() {
|
|
5671
|
+
return activeKey.value === "" ? void 0 : activeKey.value;
|
|
5672
|
+
}
|
|
5673
|
+
};
|
|
5674
|
+
for (let i = 0; i < rawSteps.length; i++) {
|
|
5675
|
+
const slot = rawSteps[i];
|
|
5676
|
+
if (isLazyMarker(slot)) {
|
|
5677
|
+
const idx = i;
|
|
5678
|
+
const marker = slot;
|
|
5679
|
+
lazyComputeds.set(
|
|
5680
|
+
idx,
|
|
5681
|
+
vue.computed(() => {
|
|
5682
|
+
void lazyEpoch.value;
|
|
5683
|
+
return marker.resolve(lazyCtx);
|
|
5684
|
+
})
|
|
5685
|
+
);
|
|
5686
|
+
}
|
|
5687
|
+
}
|
|
5688
|
+
const compiledSteps = vue.computed(() => {
|
|
5689
|
+
const ctx = slotCtx.value;
|
|
5690
|
+
const out = [];
|
|
5691
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5692
|
+
for (let i = 0; i < rawSteps.length; i++) {
|
|
5693
|
+
const slot = rawSteps[i];
|
|
5694
|
+
const form = resolveSlot(slot, i, ctx);
|
|
5695
|
+
if (form === void 0) continue;
|
|
5696
|
+
if (seen.has(form.key)) {
|
|
5697
|
+
if (paths.__DEV__) {
|
|
5698
|
+
console.warn(
|
|
5699
|
+
`[attaform] useWizard: step "${form.key}" appears in more than one slot. The wizard treats the first occurrence as canonical and drops later duplicates.`
|
|
5700
|
+
);
|
|
5701
|
+
}
|
|
5702
|
+
continue;
|
|
5703
|
+
}
|
|
5704
|
+
seen.add(form.key);
|
|
5705
|
+
trackOnce(form);
|
|
5706
|
+
out.push({ key: form.key, form });
|
|
5707
|
+
}
|
|
5708
|
+
return out;
|
|
5709
|
+
});
|
|
5710
|
+
const activeIndex = vue.computed(() => {
|
|
5711
|
+
const key = activeKey.value;
|
|
5712
|
+
if (key === "") return -1;
|
|
5713
|
+
const list = compiledSteps.value;
|
|
5714
|
+
for (let i = 0; i < list.length; i++) {
|
|
5715
|
+
if (list[i].key === key) return i;
|
|
5716
|
+
}
|
|
5717
|
+
return -1;
|
|
5718
|
+
});
|
|
5719
|
+
const currentStep = vue.computed(() => {
|
|
5720
|
+
const key = activeKey.value;
|
|
5721
|
+
if (key !== "") return key;
|
|
5722
|
+
const first = compiledSteps.value[0];
|
|
5723
|
+
return first === void 0 ? void 0 : first.key;
|
|
5724
|
+
});
|
|
5725
|
+
const activeForm = vue.computed(() => {
|
|
5726
|
+
const list = compiledSteps.value;
|
|
5727
|
+
const idx = activeIndex.value;
|
|
5728
|
+
if (idx >= 0 && idx < list.length) {
|
|
5729
|
+
return list[idx].form;
|
|
5730
|
+
}
|
|
5731
|
+
const first = list[0];
|
|
5732
|
+
return first === void 0 ? void 0 : first.form;
|
|
5733
|
+
});
|
|
5734
|
+
const isFinalStep = vue.computed(() => {
|
|
5735
|
+
const list = compiledSteps.value;
|
|
5736
|
+
const idx = activeIndex.value;
|
|
5737
|
+
return list.length > 0 && idx === list.length - 1;
|
|
5738
|
+
});
|
|
5739
|
+
const count = vue.computed(() => compiledSteps.value.length);
|
|
5740
|
+
const formsRecord = vue.computed(() => {
|
|
5741
|
+
const out = {};
|
|
5742
|
+
for (const step of compiledSteps.value) out[step.key] = step.form;
|
|
5743
|
+
return out;
|
|
5744
|
+
});
|
|
5745
|
+
const allValues = vue.computed(() => {
|
|
5746
|
+
const out = {};
|
|
5747
|
+
for (const step of compiledSteps.value) {
|
|
5748
|
+
const source = step.form;
|
|
5749
|
+
out[step.key] = source.values;
|
|
5750
|
+
}
|
|
5751
|
+
return out;
|
|
5752
|
+
});
|
|
5753
|
+
const allErrors = vue.computed(() => {
|
|
5754
|
+
const out = {};
|
|
5755
|
+
for (const step of compiledSteps.value) {
|
|
5756
|
+
const source = step.form;
|
|
5757
|
+
const list = [];
|
|
5758
|
+
const store = registry.forms.get(step.key);
|
|
5759
|
+
const resolved = store?.defaultsResolved.value === true;
|
|
5760
|
+
if (resolved) {
|
|
5761
|
+
const errors = source.meta?.errors ?? [];
|
|
5762
|
+
for (const err of errors) {
|
|
5763
|
+
const entry = {
|
|
5764
|
+
formKey: step.key,
|
|
5765
|
+
path: err.path,
|
|
5766
|
+
message: err.message
|
|
5767
|
+
};
|
|
5768
|
+
if (err.code !== void 0) entry.code = err.code;
|
|
5769
|
+
list.push(entry);
|
|
5770
|
+
}
|
|
5771
|
+
}
|
|
5772
|
+
out[step.key] = list;
|
|
5773
|
+
}
|
|
5774
|
+
return out;
|
|
5775
|
+
});
|
|
5776
|
+
const seedRef = vue.ref(void 0);
|
|
5777
|
+
const seedInput = options.defaultStatuses;
|
|
5778
|
+
if (seedInput !== void 0) {
|
|
5779
|
+
const resolved = resolveTrichotomy(seedInput);
|
|
5780
|
+
if (resolved.kind === "sync") {
|
|
5781
|
+
seedRef.value = resolved.value;
|
|
5782
|
+
} else {
|
|
5783
|
+
const eager = resolved.factory();
|
|
5784
|
+
if (eager instanceof Promise) {
|
|
5785
|
+
void eager.then((value) => {
|
|
5786
|
+
seedRef.value = value;
|
|
5787
|
+
});
|
|
5788
|
+
} else {
|
|
5789
|
+
seedRef.value = eager;
|
|
5790
|
+
}
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
const statusCache = /* @__PURE__ */ new Map();
|
|
5794
|
+
function statusFor(form) {
|
|
5795
|
+
const cached = statusCache.get(form.key);
|
|
5796
|
+
if (cached !== void 0) return cached;
|
|
5797
|
+
const source = form;
|
|
5798
|
+
const computedStatus = vue.computed(() => {
|
|
5799
|
+
const store = registry.forms.get(form.key);
|
|
5800
|
+
const resolved = store?.defaultsResolved.value === true;
|
|
5801
|
+
if (resolved) {
|
|
5802
|
+
const meta = source.meta;
|
|
5803
|
+
if (meta !== void 0 && meta !== null) {
|
|
5804
|
+
return {
|
|
5805
|
+
valid: meta.valid,
|
|
5806
|
+
dirty: meta.dirty,
|
|
5807
|
+
submitted: meta.submitted,
|
|
5808
|
+
errorCount: meta.errorCount
|
|
5809
|
+
};
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
if (noopForms.has(form.key)) return NOOP_VALID_STATUS;
|
|
5813
|
+
const seedMap = seedRef.value;
|
|
5814
|
+
if (seedMap !== void 0 && Object.hasOwn(seedMap, form.key)) {
|
|
5815
|
+
return seedMap[form.key];
|
|
5816
|
+
}
|
|
5817
|
+
return PENDING_STATUS;
|
|
5818
|
+
});
|
|
5819
|
+
statusCache.set(form.key, computedStatus);
|
|
5820
|
+
return computedStatus;
|
|
5821
|
+
}
|
|
5822
|
+
const statusesRecord = new Proxy({}, {
|
|
5823
|
+
get(_, key) {
|
|
5824
|
+
if (typeof key !== "string") return void 0;
|
|
5825
|
+
const form = formsRecord.value[key];
|
|
5826
|
+
if (form === void 0) {
|
|
5827
|
+
const cached = statusCache.get(key);
|
|
5828
|
+
if (cached !== void 0) return cached;
|
|
5829
|
+
return void 0;
|
|
5830
|
+
}
|
|
5831
|
+
return statusFor(form);
|
|
5832
|
+
},
|
|
5833
|
+
ownKeys() {
|
|
5834
|
+
return Object.keys(formsRecord.value);
|
|
5835
|
+
},
|
|
5836
|
+
has(_, key) {
|
|
5837
|
+
if (typeof key !== "string") return false;
|
|
5838
|
+
return formsRecord.value[key] !== void 0;
|
|
5839
|
+
},
|
|
5840
|
+
getOwnPropertyDescriptor(_, key) {
|
|
5841
|
+
if (typeof key !== "string") return void 0;
|
|
5842
|
+
const form = formsRecord.value[key];
|
|
5843
|
+
if (form === void 0) return void 0;
|
|
5844
|
+
return {
|
|
5845
|
+
configurable: true,
|
|
5846
|
+
enumerable: true,
|
|
5847
|
+
writable: false,
|
|
5848
|
+
value: statusFor(form)
|
|
5849
|
+
};
|
|
5850
|
+
}
|
|
5851
|
+
});
|
|
5852
|
+
const statuses = buildWizardStatusesProxy(statusesRecord);
|
|
5853
|
+
if (paths.__DEV__ && seedRef.value !== void 0) {
|
|
5854
|
+
const seedMap = seedRef.value;
|
|
5855
|
+
const known = new Set(compiledSteps.value.map((s) => s.key));
|
|
5856
|
+
const unknown = [];
|
|
5857
|
+
for (const key of Object.keys(seedMap)) {
|
|
5858
|
+
if (!known.has(key)) unknown.push(key);
|
|
5859
|
+
}
|
|
5860
|
+
if (unknown.length > 0) {
|
|
5861
|
+
console.warn(
|
|
5862
|
+
`[attaform] useWizard.defaultStatuses: seed contains unknown key(s) ${unknown.map((k) => `"${k}"`).join(", ")}. Known step keys: ${[...known].map((k) => `"${k}"`).join(", ")}.`
|
|
5863
|
+
);
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
const progressOverride = options.progress;
|
|
5867
|
+
const progress = vue.computed(() => {
|
|
5868
|
+
if (progressOverride !== void 0) {
|
|
5869
|
+
return progressOverride(compiledSteps.value);
|
|
5870
|
+
}
|
|
5871
|
+
const list = compiledSteps.value;
|
|
5872
|
+
if (list.length === 0) return 0;
|
|
5873
|
+
let valid = 0;
|
|
5874
|
+
for (const step of list) {
|
|
5875
|
+
const status = statusFor(step.form).value;
|
|
5876
|
+
if (status.valid === true) valid += 1;
|
|
5877
|
+
}
|
|
5878
|
+
return valid / list.length;
|
|
5879
|
+
});
|
|
5880
|
+
const complete = vue.computed(() => {
|
|
5881
|
+
if (!isFinalStep.value) return false;
|
|
5882
|
+
for (const step of compiledSteps.value) {
|
|
5883
|
+
if (statusFor(step.form).value.valid !== true) return false;
|
|
5884
|
+
}
|
|
5885
|
+
return true;
|
|
5886
|
+
});
|
|
5887
|
+
const canAdvance = vue.computed(() => activeIndex.value < count.value - 1);
|
|
5888
|
+
const canGoBack = vue.computed(() => activeIndex.value > 0);
|
|
5889
|
+
const visited = vue.ref([]);
|
|
5890
|
+
const wantsDefaultUrlSync = options.restore !== false || options.persist !== false;
|
|
5891
|
+
const historyHandle = wantsDefaultUrlSync ? createWizardHistory(DEFAULT_STEP_PARAM) : NOOP_WIZARD_HISTORY;
|
|
5892
|
+
const injectedResolver = vue.inject(paths.kAttaformWizardActiveStepResolver, null);
|
|
5893
|
+
const urlMirror = vue.ref(void 0);
|
|
5894
|
+
const initialUrlValue = injectedResolver !== null ? injectedResolver(DEFAULT_STEP_PARAM) : historyHandle.read();
|
|
5895
|
+
urlMirror.value = initialUrlValue;
|
|
5896
|
+
historyHandle.subscribe((value) => {
|
|
5897
|
+
urlMirror.value = value;
|
|
5898
|
+
});
|
|
5899
|
+
const restoreCallback = options.restore === false ? void 0 : options.restore !== void 0 ? options.restore : () => {
|
|
5900
|
+
const value = urlMirror.value;
|
|
5901
|
+
return value === void 0 ? void 0 : { step: value };
|
|
5902
|
+
};
|
|
5903
|
+
const persistCallback = options.persist === false ? void 0 : options.persist !== void 0 ? options.persist : (state) => {
|
|
5904
|
+
if (state.step === void 0) return;
|
|
5905
|
+
historyHandle.replace(state.step);
|
|
5906
|
+
};
|
|
5907
|
+
function isCompiledKey(key) {
|
|
5908
|
+
const list = compiledSteps.value;
|
|
5909
|
+
for (const step of list) if (step.key === key) return true;
|
|
5910
|
+
return false;
|
|
5911
|
+
}
|
|
5912
|
+
function firstKey() {
|
|
5913
|
+
const first = compiledSteps.value[0];
|
|
5914
|
+
return first === void 0 ? void 0 : first.key;
|
|
5915
|
+
}
|
|
5916
|
+
let initialKey;
|
|
5917
|
+
const restoredAtSetup = restoreCallback?.();
|
|
5918
|
+
const restoredStep = restoredAtSetup?.step;
|
|
5919
|
+
if (restoredStep !== void 0 && isCompiledKey(restoredStep)) {
|
|
5920
|
+
initialKey = restoredStep;
|
|
5921
|
+
} else {
|
|
5922
|
+
if (paths.__DEV__ && restoredStep !== void 0 && restoredStep !== "" && !isCompiledKey(restoredStep)) {
|
|
5923
|
+
console.warn(
|
|
5924
|
+
`[attaform] useWizard: restore() yielded step "${restoredStep}" which is not in the compiled step list. Falling back to the first step.`
|
|
5925
|
+
);
|
|
5926
|
+
}
|
|
5927
|
+
initialKey = firstKey();
|
|
5928
|
+
}
|
|
5929
|
+
if (initialKey !== void 0) {
|
|
5930
|
+
activeKey.value = initialKey;
|
|
5931
|
+
visited.value = [initialKey];
|
|
5932
|
+
}
|
|
5933
|
+
if (registry.ssr) {
|
|
5934
|
+
for (const step of compiledSteps.value) {
|
|
5935
|
+
if (step.key === initialKey) {
|
|
5936
|
+
registry.enqueuePrefetch(step.key);
|
|
5937
|
+
} else {
|
|
5938
|
+
registry.skipPrefetch(step.key);
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
if (!registry.ssr) {
|
|
5943
|
+
for (const step of compiledSteps.value) {
|
|
5944
|
+
const source = step.form;
|
|
5945
|
+
if (typeof source.activate === "function") void source.activate();
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
let lastPersisted = initialUrlValue;
|
|
5949
|
+
if (restoreCallback !== void 0) {
|
|
5950
|
+
vue.watch(
|
|
5951
|
+
() => restoreCallback()?.step,
|
|
5952
|
+
(step) => {
|
|
5953
|
+
if (step === void 0) return;
|
|
5954
|
+
if (!isCompiledKey(step)) {
|
|
5955
|
+
if (paths.__DEV__) {
|
|
5956
|
+
console.warn(
|
|
5957
|
+
`[attaform] useWizard: restore() yielded step "${step}" which is not in the compiled step list. Ignoring.`
|
|
5958
|
+
);
|
|
5959
|
+
}
|
|
5960
|
+
return;
|
|
5961
|
+
}
|
|
5962
|
+
if (step === activeKey.value) return;
|
|
5963
|
+
activeKey.value = step;
|
|
5964
|
+
if (!visited.value.includes(step)) visited.value.push(step);
|
|
5965
|
+
}
|
|
5966
|
+
);
|
|
5967
|
+
}
|
|
5968
|
+
if (persistCallback !== void 0) {
|
|
5969
|
+
vue.watch(
|
|
5970
|
+
() => activeKey.value,
|
|
5971
|
+
(next2) => {
|
|
5972
|
+
if (next2 === lastPersisted) return;
|
|
5973
|
+
lastPersisted = next2;
|
|
5974
|
+
persistCallback({ step: next2 });
|
|
5975
|
+
urlMirror.value = next2;
|
|
5976
|
+
}
|
|
5977
|
+
);
|
|
5978
|
+
if (initialKey !== void 0 && initialKey !== initialUrlValue && initialUrlValue === void 0) {
|
|
5979
|
+
lastPersisted = initialKey;
|
|
5980
|
+
persistCallback({ step: initialKey });
|
|
5981
|
+
urlMirror.value = initialKey;
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
const submitting = vue.ref(false);
|
|
5985
|
+
const submissionAttempts = vue.ref(0);
|
|
5986
|
+
const done = vue.ref(false);
|
|
5987
|
+
function activateForm(form) {
|
|
5988
|
+
const source = form;
|
|
5989
|
+
if (typeof source.activate === "function") {
|
|
5990
|
+
void source.activate();
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
function moveTo(key, options2) {
|
|
5994
|
+
if (activeKey.value === key) return;
|
|
5995
|
+
activeKey.value = key;
|
|
5996
|
+
if (!visited.value.includes(key)) visited.value.push(key);
|
|
5997
|
+
const list = compiledSteps.value;
|
|
5998
|
+
for (const step of list) {
|
|
5999
|
+
if (step.key === key) {
|
|
6000
|
+
activateForm(step.form);
|
|
6001
|
+
return;
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
function recordDeparture(key) {
|
|
6006
|
+
const store = registry.forms.get(key);
|
|
6007
|
+
if (store !== void 0) store.departAttempts.value += 1;
|
|
6008
|
+
}
|
|
6009
|
+
async function next() {
|
|
6010
|
+
if (submitting.value) {
|
|
6011
|
+
if (paths.__DEV__) {
|
|
6012
|
+
console.warn(
|
|
6013
|
+
`[attaform] wizard.next(): blocked while a submit is in flight. Wait for handleSubmit to settle.`
|
|
6014
|
+
);
|
|
6015
|
+
}
|
|
6016
|
+
return;
|
|
6017
|
+
}
|
|
6018
|
+
const list = compiledSteps.value;
|
|
6019
|
+
if (list.length === 0) {
|
|
6020
|
+
if (paths.__DEV__) {
|
|
6021
|
+
console.warn(`[attaform] wizard.next(): wizard has no compiled steps; no-op.`);
|
|
6022
|
+
}
|
|
6023
|
+
return;
|
|
6024
|
+
}
|
|
6025
|
+
const idx = activeIndex.value;
|
|
6026
|
+
if (idx < 0 || idx >= list.length - 1) {
|
|
6027
|
+
if (paths.__DEV__) {
|
|
6028
|
+
console.warn(
|
|
6029
|
+
`[attaform] wizard.next(): already on the final step ("${activeKey.value}"). Use wizard.handleSubmit() to submit.`
|
|
6030
|
+
);
|
|
6031
|
+
}
|
|
6032
|
+
return;
|
|
6033
|
+
}
|
|
6034
|
+
recordDeparture(activeKey.value);
|
|
6035
|
+
const target = list[idx + 1];
|
|
6036
|
+
moveTo(target.key);
|
|
6037
|
+
}
|
|
6038
|
+
function back() {
|
|
6039
|
+
if (submitting.value) {
|
|
6040
|
+
if (paths.__DEV__) {
|
|
6041
|
+
console.warn(`[attaform] wizard.back(): blocked while a submit is in flight.`);
|
|
6042
|
+
}
|
|
6043
|
+
return;
|
|
6044
|
+
}
|
|
6045
|
+
if (compiledSteps.value.length === 0) {
|
|
6046
|
+
if (paths.__DEV__) {
|
|
6047
|
+
console.warn(`[attaform] wizard.back(): wizard has no compiled steps; no-op.`);
|
|
6048
|
+
}
|
|
6049
|
+
return;
|
|
6050
|
+
}
|
|
6051
|
+
const idx = activeIndex.value;
|
|
6052
|
+
if (idx <= 0) {
|
|
6053
|
+
if (paths.__DEV__) {
|
|
6054
|
+
console.warn(`[attaform] wizard.back(): already on the first step ("${activeKey.value}").`);
|
|
6055
|
+
}
|
|
6056
|
+
return;
|
|
6057
|
+
}
|
|
6058
|
+
recordDeparture(activeKey.value);
|
|
6059
|
+
const target = compiledSteps.value[idx - 1];
|
|
6060
|
+
moveTo(target.key);
|
|
6061
|
+
}
|
|
6062
|
+
function goTo(key) {
|
|
6063
|
+
if (submitting.value) {
|
|
6064
|
+
if (paths.__DEV__) {
|
|
6065
|
+
console.warn(`[attaform] wizard.goTo(): blocked while a submit is in flight.`);
|
|
6066
|
+
}
|
|
6067
|
+
return;
|
|
6068
|
+
}
|
|
6069
|
+
if (!isCompiledKey(key)) {
|
|
6070
|
+
if (paths.__DEV__) {
|
|
6071
|
+
const known = compiledSteps.value.map((s) => `"${s.key}"`).join(", ");
|
|
6072
|
+
console.warn(`[attaform] wizard.goTo("${key}"): unknown step key. Known keys: ${known}.`);
|
|
6073
|
+
}
|
|
6074
|
+
return;
|
|
6075
|
+
}
|
|
6076
|
+
if (key !== activeKey.value) recordDeparture(activeKey.value);
|
|
6077
|
+
moveTo(key);
|
|
6078
|
+
}
|
|
6079
|
+
function buildSubmitContext(valuesMap, currentKey, isFinal) {
|
|
6080
|
+
return {
|
|
6081
|
+
values: valuesMap,
|
|
6082
|
+
get: ((form) => valuesMap[form.key]),
|
|
6083
|
+
currentKey,
|
|
6084
|
+
isFinal
|
|
6085
|
+
};
|
|
6086
|
+
}
|
|
6087
|
+
async function processOne(form) {
|
|
6088
|
+
const full = form;
|
|
6089
|
+
let activationFailure;
|
|
6090
|
+
try {
|
|
6091
|
+
if (typeof full.activate === "function") await full.activate();
|
|
6092
|
+
} catch (err) {
|
|
6093
|
+
activationFailure = err?.message ?? String(err);
|
|
6094
|
+
}
|
|
6095
|
+
if (activationFailure === void 0 && full.hydrateError != null) {
|
|
6096
|
+
activationFailure = full.hydrateError.message;
|
|
6097
|
+
}
|
|
6098
|
+
if (activationFailure !== void 0) {
|
|
6099
|
+
return {
|
|
6100
|
+
success: false,
|
|
6101
|
+
data: void 0,
|
|
6102
|
+
errors: [
|
|
6103
|
+
{
|
|
6104
|
+
formKey: form.key,
|
|
6105
|
+
path: [],
|
|
6106
|
+
message: `Form '${form.key}' failed to activate: ${activationFailure}`,
|
|
6107
|
+
code: AttaformErrorCode.ActivationFailed
|
|
6108
|
+
}
|
|
6109
|
+
],
|
|
6110
|
+
formKey: form.key
|
|
6111
|
+
};
|
|
6112
|
+
}
|
|
6113
|
+
return full.process();
|
|
6114
|
+
}
|
|
6115
|
+
function collectErrors(results) {
|
|
6116
|
+
const out = [];
|
|
6117
|
+
for (const step of compiledSteps.value) {
|
|
6118
|
+
const processed = results.get(step.key);
|
|
6119
|
+
if (processed === void 0 || processed.success === true) continue;
|
|
6120
|
+
for (const err of processed.errors) {
|
|
6121
|
+
const entry = {
|
|
6122
|
+
formKey: err.formKey,
|
|
6123
|
+
path: err.path,
|
|
6124
|
+
message: err.message
|
|
6125
|
+
};
|
|
6126
|
+
if (err.code !== void 0) entry.code = err.code;
|
|
6127
|
+
out.push(entry);
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
return out;
|
|
6131
|
+
}
|
|
6132
|
+
function handleSubmit(onSubmit, onError) {
|
|
6133
|
+
return async function submitHandler(event) {
|
|
6134
|
+
if (event !== void 0 && typeof event.preventDefault === "function") {
|
|
6135
|
+
event.preventDefault();
|
|
6136
|
+
}
|
|
6137
|
+
if (compiledSteps.value.length === 0) {
|
|
6138
|
+
if (paths.__DEV__) {
|
|
6139
|
+
console.warn(`[attaform] wizard.handleSubmit: wizard has no compiled steps; no-op.`);
|
|
6140
|
+
}
|
|
6141
|
+
return;
|
|
6142
|
+
}
|
|
6143
|
+
if (submitting.value) {
|
|
6144
|
+
if (paths.__DEV__) {
|
|
6145
|
+
console.warn(
|
|
6146
|
+
`[attaform] wizard.handleSubmit: re-entrant submit while a prior call is still in flight; resolving no-op.`
|
|
6147
|
+
);
|
|
6148
|
+
}
|
|
6149
|
+
return;
|
|
6150
|
+
}
|
|
6151
|
+
submitting.value = true;
|
|
6152
|
+
try {
|
|
6153
|
+
const currentKey = activeKey.value;
|
|
6154
|
+
const final = isFinalStep.value;
|
|
6155
|
+
const list = compiledSteps.value;
|
|
6156
|
+
const results = /* @__PURE__ */ new Map();
|
|
6157
|
+
if (final) {
|
|
6158
|
+
await Promise.all(
|
|
6159
|
+
list.map(async (step) => {
|
|
6160
|
+
const result = await processOne(step.form);
|
|
6161
|
+
results.set(step.key, result);
|
|
6162
|
+
})
|
|
6163
|
+
);
|
|
6164
|
+
} else {
|
|
6165
|
+
const active = activeForm.value;
|
|
6166
|
+
const result = await processOne(active);
|
|
6167
|
+
results.set(active.key, result);
|
|
6168
|
+
}
|
|
6169
|
+
for (const key of results.keys()) {
|
|
6170
|
+
const store = registry.forms.get(key);
|
|
6171
|
+
if (store !== void 0) store.submissionAttempts.value += 1;
|
|
6172
|
+
}
|
|
6173
|
+
submissionAttempts.value += 1;
|
|
6174
|
+
const errors = collectErrors(results);
|
|
6175
|
+
if (errors.length === 0) {
|
|
6176
|
+
const valuesMap = {};
|
|
6177
|
+
for (const step of list) {
|
|
6178
|
+
const processed = results.get(step.key);
|
|
6179
|
+
if (processed !== void 0 && processed.success === true) {
|
|
6180
|
+
valuesMap[step.key] = processed.data;
|
|
6181
|
+
} else {
|
|
6182
|
+
const source = step.form;
|
|
6183
|
+
valuesMap[step.key] = source.values;
|
|
6184
|
+
}
|
|
6185
|
+
}
|
|
6186
|
+
const ctx = buildSubmitContext(valuesMap, currentKey, final);
|
|
6187
|
+
await onSubmit(ctx);
|
|
6188
|
+
if (final) {
|
|
6189
|
+
done.value = true;
|
|
6190
|
+
} else {
|
|
6191
|
+
recordDeparture(currentKey);
|
|
6192
|
+
const idx = activeIndex.value;
|
|
6193
|
+
const target = list[idx + 1];
|
|
6194
|
+
if (target !== void 0) moveTo(target.key);
|
|
6195
|
+
}
|
|
6196
|
+
} else {
|
|
6197
|
+
if (onError !== void 0) await onError(errors);
|
|
6198
|
+
if (options.focusFirstError !== false) {
|
|
6199
|
+
const firstFailedKey = errors[0]?.formKey;
|
|
6200
|
+
if (firstFailedKey !== void 0 && isCompiledKey(firstFailedKey)) {
|
|
6201
|
+
moveTo(firstFailedKey);
|
|
6202
|
+
await vue.nextTick();
|
|
6203
|
+
const failedForm = formsRecord.value[firstFailedKey];
|
|
6204
|
+
if (failedForm !== void 0 && typeof failedForm.applyInvalidSubmitPolicy === "function") {
|
|
6205
|
+
failedForm.applyInvalidSubmitPolicy();
|
|
6206
|
+
}
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
} finally {
|
|
6211
|
+
submitting.value = false;
|
|
6212
|
+
}
|
|
6213
|
+
};
|
|
6214
|
+
}
|
|
6215
|
+
function reset() {
|
|
6216
|
+
submissionAttempts.value = 0;
|
|
6217
|
+
done.value = false;
|
|
6218
|
+
lazyEpoch.value += 1;
|
|
6219
|
+
for (const step of compiledSteps.value) {
|
|
6220
|
+
const full = step.form;
|
|
6221
|
+
if (typeof full.reset === "function") full.reset();
|
|
6222
|
+
}
|
|
6223
|
+
const firstStep = compiledSteps.value[0];
|
|
6224
|
+
if (firstStep !== void 0) {
|
|
6225
|
+
activeKey.value = firstStep.key;
|
|
6226
|
+
visited.value = [firstStep.key];
|
|
6227
|
+
if (persistCallback !== void 0) {
|
|
6228
|
+
lastPersisted = firstStep.key;
|
|
6229
|
+
persistCallback({ step: firstStep.key });
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
6233
|
+
if (vue.getCurrentScope() !== void 0) {
|
|
6234
|
+
vue.onScopeDispose(() => {
|
|
6235
|
+
historyHandle.dispose();
|
|
6236
|
+
lazyNoopScope.stop();
|
|
6237
|
+
});
|
|
6238
|
+
}
|
|
6239
|
+
const explicitKey = options.key;
|
|
6240
|
+
const wizardKey = resolveWizardKey(explicitKey);
|
|
6241
|
+
const handle = {
|
|
6242
|
+
key: wizardKey,
|
|
6243
|
+
next,
|
|
6244
|
+
back,
|
|
6245
|
+
goTo,
|
|
6246
|
+
handleSubmit,
|
|
6247
|
+
reset,
|
|
6248
|
+
get currentStep() {
|
|
6249
|
+
return currentStep.value;
|
|
6250
|
+
},
|
|
6251
|
+
get activeForm() {
|
|
6252
|
+
return activeForm.value;
|
|
6253
|
+
},
|
|
6254
|
+
get activeIndex() {
|
|
6255
|
+
return activeIndex.value;
|
|
6256
|
+
},
|
|
6257
|
+
get isFinalStep() {
|
|
6258
|
+
return isFinalStep.value;
|
|
6259
|
+
},
|
|
6260
|
+
get steps() {
|
|
6261
|
+
return compiledSteps.value;
|
|
6262
|
+
},
|
|
6263
|
+
get forms() {
|
|
6264
|
+
return formsRecord.value;
|
|
6265
|
+
},
|
|
6266
|
+
get count() {
|
|
6267
|
+
return count.value;
|
|
6268
|
+
},
|
|
6269
|
+
statuses,
|
|
6270
|
+
get allValues() {
|
|
6271
|
+
return allValues.value;
|
|
6272
|
+
},
|
|
6273
|
+
get allErrors() {
|
|
6274
|
+
return allErrors.value;
|
|
6275
|
+
},
|
|
6276
|
+
get progress() {
|
|
6277
|
+
return progress.value;
|
|
6278
|
+
},
|
|
6279
|
+
get canAdvance() {
|
|
6280
|
+
return canAdvance.value;
|
|
6281
|
+
},
|
|
6282
|
+
get canGoBack() {
|
|
6283
|
+
return canGoBack.value;
|
|
6284
|
+
},
|
|
6285
|
+
get complete() {
|
|
6286
|
+
return complete.value;
|
|
6287
|
+
},
|
|
6288
|
+
get done() {
|
|
6289
|
+
return done.value;
|
|
6290
|
+
},
|
|
6291
|
+
get submitting() {
|
|
6292
|
+
return submitting.value;
|
|
6293
|
+
},
|
|
6294
|
+
get submissionAttempts() {
|
|
6295
|
+
return submissionAttempts.value;
|
|
6296
|
+
},
|
|
6297
|
+
get visited() {
|
|
6298
|
+
return visited.value;
|
|
6299
|
+
}
|
|
6300
|
+
};
|
|
6301
|
+
const existing = registry.wizards.get(wizardKey);
|
|
6302
|
+
if (existing === void 0) {
|
|
6303
|
+
registry.wizards.set(wizardKey, handle);
|
|
6304
|
+
} else if (paths.__DEV__ && explicitKey !== void 0) {
|
|
6305
|
+
console.warn(
|
|
6306
|
+
`[attaform] useWizard({ key: "${wizardKey}" }): a wizard with this key is already registered. Keeping the existing handle. Pass a unique key to each useWizard call, or share the original handle via injectWizard("${wizardKey}").`
|
|
6307
|
+
);
|
|
6308
|
+
}
|
|
6309
|
+
if (vue.getCurrentScope() !== void 0) {
|
|
6310
|
+
const releaseWizard = registry.trackWizardConsumer(wizardKey);
|
|
6311
|
+
vue.onScopeDispose(releaseWizard);
|
|
6312
|
+
}
|
|
6313
|
+
if (vue.getCurrentInstance() !== null && explicitKey === void 0) {
|
|
6314
|
+
recordAmbientWizardProvide(registry.ssr);
|
|
6315
|
+
vue.provide(paths.kAttaformAncestorWizard, handle);
|
|
6316
|
+
}
|
|
6317
|
+
return handle;
|
|
6318
|
+
}
|
|
6319
|
+
let anonWizardCounter = 0;
|
|
6320
|
+
const ambientWizardProvideHistory = paths.__DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
6321
|
+
function recordAmbientWizardProvide(ssr) {
|
|
6322
|
+
if (!paths.__DEV__ || ssr || ambientWizardProvideHistory === null) return;
|
|
6323
|
+
const instance = vue.getCurrentInstance();
|
|
6324
|
+
if (instance === null) return;
|
|
6325
|
+
const instanceKey = instance;
|
|
6326
|
+
const entry = {
|
|
6327
|
+
source: paths.captureUserCallSite()
|
|
6328
|
+
};
|
|
6329
|
+
const existing = ambientWizardProvideHistory.get(instanceKey);
|
|
6330
|
+
if (existing === void 0) {
|
|
6331
|
+
ambientWizardProvideHistory.set(instanceKey, [entry]);
|
|
6332
|
+
return;
|
|
6333
|
+
}
|
|
6334
|
+
existing.push(entry);
|
|
6335
|
+
}
|
|
6336
|
+
function resolveWizardKey(key) {
|
|
6337
|
+
if (key !== void 0 && key !== null && key !== "") return key;
|
|
6338
|
+
if (vue.getCurrentInstance() !== null) {
|
|
6339
|
+
return `${ANONYMOUS_WIZARD_KEY_PREFIX}${vue.useId()}`;
|
|
6340
|
+
}
|
|
6341
|
+
return `${ANONYMOUS_WIZARD_KEY_PREFIX}${anonWizardCounter++}`;
|
|
6342
|
+
}
|
|
6343
|
+
function isAnyForm(value) {
|
|
6344
|
+
if (value === null || typeof value !== "object") return false;
|
|
6345
|
+
if (typeof value.key !== "string") return false;
|
|
6346
|
+
return true;
|
|
6347
|
+
}
|
|
6348
|
+
|
|
6349
|
+
function injectWizard(input) {
|
|
6350
|
+
const key = typeof input === "string" ? input : input?.key;
|
|
6351
|
+
const instance = vue.getCurrentInstance();
|
|
6352
|
+
if (instance !== null) paths.ensureAttaformInstalled(instance.appContext.app);
|
|
6353
|
+
const registry = paths.useRegistry();
|
|
6354
|
+
if (key !== void 0) {
|
|
6355
|
+
const handle = registry.wizards.get(key);
|
|
6356
|
+
if (handle === void 0) {
|
|
6357
|
+
warnMiss(
|
|
6358
|
+
`no wizard registered for key '${key}'`,
|
|
6359
|
+
registry.ssr,
|
|
6360
|
+
availableKeysHint(registry.wizards)
|
|
6361
|
+
);
|
|
6362
|
+
return null;
|
|
6363
|
+
}
|
|
6364
|
+
if (vue.getCurrentScope() !== void 0) {
|
|
6365
|
+
const release = registry.trackWizardConsumer(key);
|
|
6366
|
+
vue.onScopeDispose(release);
|
|
6367
|
+
}
|
|
6368
|
+
return handle;
|
|
6369
|
+
}
|
|
6370
|
+
const ambient = vue.inject(paths.kAttaformAncestorWizard, null);
|
|
6371
|
+
if (ambient === null) return null;
|
|
6372
|
+
warnIfAmbientWizardProviderHadDuplicates();
|
|
6373
|
+
return ambient;
|
|
6374
|
+
}
|
|
6375
|
+
function availableKeysHint(wizards) {
|
|
6376
|
+
if (wizards.size === 0) return void 0;
|
|
6377
|
+
const keys = [...wizards.keys()].map((k) => `"${k}"`).join(", ");
|
|
6378
|
+
return `Registered keys: ${keys}.`;
|
|
6379
|
+
}
|
|
6380
|
+
function warnMiss(detail, ssr, hint) {
|
|
6381
|
+
if (!paths.__DEV__ || ssr) return;
|
|
6382
|
+
const frame = paths.captureUserCallSite();
|
|
6383
|
+
const parts = [`[attaform] injectWizard: ${detail}. Returning null.`];
|
|
6384
|
+
if (hint !== void 0) parts.push(hint);
|
|
6385
|
+
if (frame !== void 0) parts.push(frame);
|
|
6386
|
+
console.warn(parts.join(" "));
|
|
6387
|
+
}
|
|
6388
|
+
function warnIfAmbientWizardProviderHadDuplicates() {
|
|
6389
|
+
if (!paths.__DEV__ || ambientWizardProvideHistory === null) return;
|
|
6390
|
+
let ancestor = vue.getCurrentInstance()?.parent ?? null;
|
|
6391
|
+
while (ancestor !== null) {
|
|
6392
|
+
const history = ambientWizardProvideHistory.get(ancestor);
|
|
6393
|
+
if (history !== void 0) {
|
|
6394
|
+
if (history.length > 1) {
|
|
6395
|
+
const lines = history.map((entry) => ` - ${entry.source ?? "<unknown location>"}`);
|
|
6396
|
+
console.warn(
|
|
6397
|
+
"[attaform] injectWizard() (no key) resolved against an ancestor with multiple anonymous useWizard() calls; descendants only see the last-provided wizard. Anonymous useWizard() calls were:\n" + lines.join("\n") + '\nFix: pass a key to each call (e.g. useWizard({ steps, key: "x" })) and reach them via injectWizard("x"), or split the wizards across separate components.'
|
|
6398
|
+
);
|
|
6399
|
+
}
|
|
6400
|
+
return;
|
|
6401
|
+
}
|
|
6402
|
+
ancestor = ancestor.parent;
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
|
|
4821
6406
|
exports.AttaformErrorCode = AttaformErrorCode;
|
|
4822
6407
|
exports.defaultCoercionRules = defaultCoercionRules;
|
|
4823
6408
|
exports.defaultShouldShowErrors = defaultShouldShowErrors;
|
|
@@ -4825,11 +6410,14 @@ exports.defineCoercion = defineCoercion;
|
|
|
4825
6410
|
exports.getAtPath = getAtPath;
|
|
4826
6411
|
exports.humanize = humanize;
|
|
4827
6412
|
exports.injectForm = injectForm;
|
|
6413
|
+
exports.injectWizard = injectWizard;
|
|
4828
6414
|
exports.isPlainRecord = isPlainRecord;
|
|
4829
6415
|
exports.isUnset = isUnset;
|
|
6416
|
+
exports.lazy = lazy;
|
|
4830
6417
|
exports.normalizeNumericOption = normalizeNumericOption;
|
|
4831
6418
|
exports.setAtPath = setAtPath;
|
|
4832
6419
|
exports.slimKindOf = slimKindOf;
|
|
4833
6420
|
exports.unset = unset;
|
|
4834
6421
|
exports.useAbstractForm = useAbstractForm;
|
|
4835
|
-
|
|
6422
|
+
exports.useWizard = useWizard;
|
|
6423
|
+
//# sourceMappingURL=attaform.II89Pcf4.cjs.map
|