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