attaform 0.0.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +142 -2
- package/dist/chunks/devtools.cjs +179 -0
- package/dist/chunks/devtools.cjs.map +1 -0
- package/dist/chunks/devtools.mjs +177 -0
- package/dist/chunks/devtools.mjs.map +1 -0
- package/dist/chunks/indexeddb.cjs +119 -0
- package/dist/chunks/indexeddb.cjs.map +1 -0
- package/dist/chunks/indexeddb.mjs +117 -0
- package/dist/chunks/indexeddb.mjs.map +1 -0
- package/dist/chunks/local-storage.cjs +58 -0
- package/dist/chunks/local-storage.cjs.map +1 -0
- package/dist/chunks/local-storage.mjs +56 -0
- package/dist/chunks/local-storage.mjs.map +1 -0
- package/dist/chunks/session-storage.cjs +58 -0
- package/dist/chunks/session-storage.cjs.map +1 -0
- package/dist/chunks/session-storage.mjs +56 -0
- package/dist/chunks/session-storage.mjs.map +1 -0
- package/dist/index.cjs +173 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +493 -0
- package/dist/index.d.mts +493 -0
- package/dist/index.d.ts +493 -0
- package/dist/index.mjs +141 -0
- package/dist/index.mjs.map +1 -0
- package/dist/nuxt.cjs +97 -0
- package/dist/nuxt.cjs.map +1 -0
- package/dist/nuxt.d.cts +38 -0
- package/dist/nuxt.d.mts +38 -0
- package/dist/nuxt.d.ts +38 -0
- package/dist/nuxt.mjs +94 -0
- package/dist/nuxt.mjs.map +1 -0
- package/dist/runtime/plugins/attaform.cjs +32 -0
- package/dist/runtime/plugins/attaform.cjs.map +1 -0
- package/dist/runtime/plugins/attaform.d.cts +5 -0
- package/dist/runtime/plugins/attaform.d.mts +5 -0
- package/dist/runtime/plugins/attaform.d.ts +5 -0
- package/dist/runtime/plugins/attaform.mjs +30 -0
- package/dist/runtime/plugins/attaform.mjs.map +1 -0
- package/dist/shared/attaform.B5GWYl76.cjs +386 -0
- package/dist/shared/attaform.B5GWYl76.cjs.map +1 -0
- package/dist/shared/attaform.BRTxpA3q.mjs +3283 -0
- package/dist/shared/attaform.BRTxpA3q.mjs.map +1 -0
- package/dist/shared/attaform.BYc9kugA.d.ts +124 -0
- package/dist/shared/attaform.Bubm_slq.cjs +622 -0
- package/dist/shared/attaform.Bubm_slq.cjs.map +1 -0
- package/dist/shared/attaform.BwaYWtMs.d.cts +126 -0
- package/dist/shared/attaform.BwaYWtMs.d.mts +126 -0
- package/dist/shared/attaform.BwaYWtMs.d.ts +126 -0
- package/dist/shared/attaform.CNJO3mME.cjs +3295 -0
- package/dist/shared/attaform.CNJO3mME.cjs.map +1 -0
- package/dist/shared/attaform.CRgix6_n.cjs +796 -0
- package/dist/shared/attaform.CRgix6_n.cjs.map +1 -0
- package/dist/shared/attaform.CXZgUECn.d.cts +124 -0
- package/dist/shared/attaform.CXpzmj38.mjs +617 -0
- package/dist/shared/attaform.CXpzmj38.mjs.map +1 -0
- package/dist/shared/attaform.Cc93zNzD.mjs +83 -0
- package/dist/shared/attaform.Cc93zNzD.mjs.map +1 -0
- package/dist/shared/attaform.DDXrY-1Q.d.cts +2568 -0
- package/dist/shared/attaform.DDXrY-1Q.d.mts +2568 -0
- package/dist/shared/attaform.DDXrY-1Q.d.ts +2568 -0
- package/dist/shared/attaform.DOKOyb3Y.d.mts +124 -0
- package/dist/shared/attaform.DlgKK10S.mjs +789 -0
- package/dist/shared/attaform.DlgKK10S.mjs.map +1 -0
- package/dist/shared/attaform.al_rpt7_.mjs +361 -0
- package/dist/shared/attaform.al_rpt7_.mjs.map +1 -0
- package/dist/shared/attaform.xKWYHMdq.cjs +89 -0
- package/dist/shared/attaform.xKWYHMdq.cjs.map +1 -0
- package/dist/transforms.cjs +11 -0
- package/dist/transforms.cjs.map +1 -0
- package/dist/transforms.d.cts +49 -0
- package/dist/transforms.d.mts +49 -0
- package/dist/transforms.d.ts +49 -0
- package/dist/transforms.mjs +2 -0
- package/dist/transforms.mjs.map +1 -0
- package/dist/vite.cjs +39 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +53 -0
- package/dist/vite.d.mts +53 -0
- package/dist/vite.d.ts +53 -0
- package/dist/vite.mjs +37 -0
- package/dist/vite.mjs.map +1 -0
- package/dist/zod-v3.cjs +1511 -0
- package/dist/zod-v3.cjs.map +1 -0
- package/dist/zod-v3.d.cts +164 -0
- package/dist/zod-v3.d.mts +164 -0
- package/dist/zod-v3.d.ts +164 -0
- package/dist/zod-v3.mjs +1504 -0
- package/dist/zod-v3.mjs.map +1 -0
- package/dist/zod.cjs +1548 -0
- package/dist/zod.cjs.map +1 -0
- package/dist/zod.d.cts +67 -0
- package/dist/zod.d.mts +67 -0
- package/dist/zod.d.ts +67 -0
- package/dist/zod.mjs +1541 -0
- package/dist/zod.mjs.map +1 -0
- package/package.json +182 -6
|
@@ -0,0 +1,3283 @@
|
|
|
1
|
+
import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, readonly, reactive, shallowRef, provide, getCurrentInstance, useId, toRaw, inject } from 'vue';
|
|
2
|
+
import { c as canonicalizePath, s as segmentsForPathKey } from './attaform.Cc93zNzD.mjs';
|
|
3
|
+
import { _ as __DEV__, c as SubmitErrorHandlerError, A as AnonPersistError, m as captureUserCallSite, j as enforceSensitiveCheck, n as createPersistOptInRegistry, e as useRegistry, o as kFormContext, p as kFormInstanceId, b as ReservedFormKeyError } from './attaform.al_rpt7_.mjs';
|
|
4
|
+
|
|
5
|
+
const NOT_FOUND = Symbol("NOT_FOUND");
|
|
6
|
+
function descendStep(value, segment) {
|
|
7
|
+
if (value === null || value === void 0) return NOT_FOUND;
|
|
8
|
+
if (typeof value !== "object") return NOT_FOUND;
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
if (typeof segment !== "number") return NOT_FOUND;
|
|
11
|
+
if (segment < 0 || segment >= value.length) return NOT_FOUND;
|
|
12
|
+
return value[segment];
|
|
13
|
+
}
|
|
14
|
+
const record = value;
|
|
15
|
+
const key = typeof segment === "number" ? String(segment) : segment;
|
|
16
|
+
if (!(key in record)) return NOT_FOUND;
|
|
17
|
+
return record[key];
|
|
18
|
+
}
|
|
19
|
+
function getAtPath(root, path) {
|
|
20
|
+
if (path.length === 0) return root;
|
|
21
|
+
let current = root;
|
|
22
|
+
for (const segment of path) {
|
|
23
|
+
const next = descendStep(current, segment);
|
|
24
|
+
if (next === NOT_FOUND) return void 0;
|
|
25
|
+
current = next;
|
|
26
|
+
}
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
function hasAtPath(root, path) {
|
|
30
|
+
if (path.length === 0) return true;
|
|
31
|
+
let current = root;
|
|
32
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
33
|
+
const segment = path[i];
|
|
34
|
+
const next = descendStep(current, segment);
|
|
35
|
+
if (next === NOT_FOUND) return false;
|
|
36
|
+
current = next;
|
|
37
|
+
}
|
|
38
|
+
const last = path[path.length - 1];
|
|
39
|
+
if (current === null || current === void 0) return false;
|
|
40
|
+
if (typeof current !== "object") return false;
|
|
41
|
+
if (Array.isArray(current)) {
|
|
42
|
+
return typeof last === "number" && last >= 0 && last < current.length;
|
|
43
|
+
}
|
|
44
|
+
const key = typeof last === "number" ? String(last) : last;
|
|
45
|
+
return key in current;
|
|
46
|
+
}
|
|
47
|
+
function isPlainRecord(value) {
|
|
48
|
+
if (value === null || typeof value !== "object") return false;
|
|
49
|
+
if (Array.isArray(value)) return false;
|
|
50
|
+
const proto = Object.getPrototypeOf(value);
|
|
51
|
+
return proto === null || proto === Object.prototype;
|
|
52
|
+
}
|
|
53
|
+
function setAtPath(root, path, value) {
|
|
54
|
+
return setAtPathOffset(root, path, value, 0);
|
|
55
|
+
}
|
|
56
|
+
function setAtPathOffset(root, path, value, offset) {
|
|
57
|
+
if (offset >= path.length) return value;
|
|
58
|
+
const head = path[offset];
|
|
59
|
+
const nextOffset = offset + 1;
|
|
60
|
+
if (typeof head === "number") {
|
|
61
|
+
const arr = Array.isArray(root) ? [...root] : [];
|
|
62
|
+
while (arr.length <= head) arr.push(void 0);
|
|
63
|
+
arr[head] = setAtPathOffset(arr[head], path, value, nextOffset);
|
|
64
|
+
return arr;
|
|
65
|
+
}
|
|
66
|
+
const rec = isPlainRecord(root) ? { ...root } : {};
|
|
67
|
+
rec[head] = setAtPathOffset(rec[head], path, value, nextOffset);
|
|
68
|
+
return rec;
|
|
69
|
+
}
|
|
70
|
+
function deleteAtPath(root, path) {
|
|
71
|
+
return deleteAtPathOffset(root, path, 0);
|
|
72
|
+
}
|
|
73
|
+
function deleteAtPathOffset(root, path, offset) {
|
|
74
|
+
if (offset >= path.length) return void 0;
|
|
75
|
+
const head = path[offset];
|
|
76
|
+
const isLeafStep = offset === path.length - 1;
|
|
77
|
+
const nextOffset = offset + 1;
|
|
78
|
+
if (typeof head === "number") {
|
|
79
|
+
if (!Array.isArray(root)) return root;
|
|
80
|
+
if (head < 0 || head >= root.length) return root;
|
|
81
|
+
if (isLeafStep) {
|
|
82
|
+
const arr2 = [...root];
|
|
83
|
+
arr2.splice(head, 1);
|
|
84
|
+
return arr2;
|
|
85
|
+
}
|
|
86
|
+
const arr = [...root];
|
|
87
|
+
arr[head] = deleteAtPathOffset(arr[head], path, nextOffset);
|
|
88
|
+
return arr;
|
|
89
|
+
}
|
|
90
|
+
if (!isPlainRecord(root)) return root;
|
|
91
|
+
if (isLeafStep) {
|
|
92
|
+
const rec2 = { ...root };
|
|
93
|
+
delete rec2[head];
|
|
94
|
+
return rec2;
|
|
95
|
+
}
|
|
96
|
+
if (!(head in root)) return root;
|
|
97
|
+
const rec = { ...root };
|
|
98
|
+
rec[head] = deleteAtPathOffset(rec[head], path, nextOffset);
|
|
99
|
+
return rec;
|
|
100
|
+
}
|
|
101
|
+
function resolveArrayShape(schema, scratch) {
|
|
102
|
+
const shape = schema.arrayShapeAtPath(scratch);
|
|
103
|
+
if (shape !== void 0) return shape;
|
|
104
|
+
const TUPLE_PROBE_INDEX = 1e6;
|
|
105
|
+
scratch.push(TUPLE_PROBE_INDEX);
|
|
106
|
+
const probe = schema.getDefaultAtPath(scratch);
|
|
107
|
+
scratch.pop();
|
|
108
|
+
if (probe !== void 0) return null;
|
|
109
|
+
let n = 0;
|
|
110
|
+
while (n < 1024) {
|
|
111
|
+
scratch.push(n);
|
|
112
|
+
const v = schema.getDefaultAtPath(scratch);
|
|
113
|
+
scratch.pop();
|
|
114
|
+
if (v === void 0) break;
|
|
115
|
+
n++;
|
|
116
|
+
}
|
|
117
|
+
return n;
|
|
118
|
+
}
|
|
119
|
+
function mergeStructural(schema, path, consumer, defaultValue = schema.getDefaultAtPath(path)) {
|
|
120
|
+
const scratch = path.slice();
|
|
121
|
+
return mergeStructuralImpl(schema, scratch, consumer, defaultValue);
|
|
122
|
+
}
|
|
123
|
+
function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
|
|
124
|
+
if (consumer === void 0) return defaultValue;
|
|
125
|
+
if (consumer === null) return null;
|
|
126
|
+
if (Array.isArray(consumer)) {
|
|
127
|
+
const shape = resolveArrayShape(schema, scratch);
|
|
128
|
+
const isTuple = typeof shape === "number";
|
|
129
|
+
const targetLen = isTuple ? shape : consumer.length;
|
|
130
|
+
let cachedElementDefault;
|
|
131
|
+
let cachedElementDefaultRead = false;
|
|
132
|
+
let mutated = targetLen > consumer.length;
|
|
133
|
+
const out = consumer.slice();
|
|
134
|
+
while (out.length < targetLen) out.push(void 0);
|
|
135
|
+
for (let i = 0; i < targetLen; i++) {
|
|
136
|
+
scratch.push(i);
|
|
137
|
+
let elemDefault;
|
|
138
|
+
if (isTuple) {
|
|
139
|
+
elemDefault = schema.getDefaultAtPath(scratch);
|
|
140
|
+
} else {
|
|
141
|
+
if (!cachedElementDefaultRead) {
|
|
142
|
+
cachedElementDefault = schema.getDefaultAtPath(scratch);
|
|
143
|
+
cachedElementDefaultRead = true;
|
|
144
|
+
}
|
|
145
|
+
elemDefault = cachedElementDefault;
|
|
146
|
+
}
|
|
147
|
+
const consumerElem = i < consumer.length ? consumer[i] : void 0;
|
|
148
|
+
const merged = mergeStructuralImpl(schema, scratch, consumerElem, elemDefault);
|
|
149
|
+
scratch.pop();
|
|
150
|
+
if (merged !== consumerElem) {
|
|
151
|
+
out[i] = merged;
|
|
152
|
+
mutated = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return mutated ? out : consumer;
|
|
156
|
+
}
|
|
157
|
+
if (isPlainRecord(consumer)) {
|
|
158
|
+
if (!isPlainRecord(defaultValue)) {
|
|
159
|
+
return consumer;
|
|
160
|
+
}
|
|
161
|
+
let mutated = false;
|
|
162
|
+
const out = { ...consumer };
|
|
163
|
+
for (const key of Object.keys(defaultValue)) {
|
|
164
|
+
if (!(key in consumer) || consumer[key] === void 0) {
|
|
165
|
+
const defAtKey = defaultValue[key];
|
|
166
|
+
scratch.push(key);
|
|
167
|
+
const filled = mergeStructuralImpl(schema, scratch, void 0, defAtKey);
|
|
168
|
+
scratch.pop();
|
|
169
|
+
if (filled !== void 0) {
|
|
170
|
+
out[key] = filled;
|
|
171
|
+
mutated = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const key of Object.keys(consumer)) {
|
|
176
|
+
scratch.push(key);
|
|
177
|
+
const merged = mergeStructuralImpl(schema, scratch, consumer[key], defaultValue[key]);
|
|
178
|
+
scratch.pop();
|
|
179
|
+
if (merged !== consumer[key]) {
|
|
180
|
+
out[key] = merged;
|
|
181
|
+
mutated = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return mutated ? out : consumer;
|
|
185
|
+
}
|
|
186
|
+
return consumer;
|
|
187
|
+
}
|
|
188
|
+
function setAtPathWithSchemaFill(root, schema, fullPath, value) {
|
|
189
|
+
if (fullPath.length === 0) return value;
|
|
190
|
+
return setAtPathWithSchemaFillImpl(root, schema, fullPath, value, 0);
|
|
191
|
+
}
|
|
192
|
+
function setAtPathWithSchemaFillImpl(root, schema, fullPath, value, startIdx) {
|
|
193
|
+
if (startIdx >= fullPath.length) return value;
|
|
194
|
+
const head = fullPath[startIdx];
|
|
195
|
+
const isLeafStep = startIdx === fullPath.length - 1;
|
|
196
|
+
if (typeof head === "number") {
|
|
197
|
+
const arr = Array.isArray(root) ? [...root] : [];
|
|
198
|
+
const prefix = fullPath.slice(0, startIdx);
|
|
199
|
+
if (arr.length < head) {
|
|
200
|
+
const scratch = prefix.slice();
|
|
201
|
+
const shape = resolveArrayShape(schema, scratch);
|
|
202
|
+
const tupleLike = typeof shape === "number";
|
|
203
|
+
let cachedArrayDefault;
|
|
204
|
+
if (!tupleLike) {
|
|
205
|
+
scratch.push(0);
|
|
206
|
+
cachedArrayDefault = schema.getDefaultAtPath(scratch);
|
|
207
|
+
scratch.pop();
|
|
208
|
+
}
|
|
209
|
+
while (arr.length < head) {
|
|
210
|
+
const idx = arr.length;
|
|
211
|
+
if (tupleLike) {
|
|
212
|
+
scratch.push(idx);
|
|
213
|
+
arr.push(schema.getDefaultAtPath(scratch));
|
|
214
|
+
scratch.pop();
|
|
215
|
+
} else {
|
|
216
|
+
arr.push(cachedArrayDefault);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (isLeafStep) {
|
|
221
|
+
arr[head] = value;
|
|
222
|
+
return arr;
|
|
223
|
+
}
|
|
224
|
+
let childRoot2 = arr[head];
|
|
225
|
+
if (childRoot2 === void 0 || childRoot2 !== null && typeof childRoot2 !== "object") {
|
|
226
|
+
childRoot2 = schema.getDefaultAtPath([...prefix, head]);
|
|
227
|
+
}
|
|
228
|
+
arr[head] = setAtPathWithSchemaFillImpl(childRoot2, schema, fullPath, value, startIdx + 1);
|
|
229
|
+
return arr;
|
|
230
|
+
}
|
|
231
|
+
const rec = isPlainRecord(root) ? { ...root } : {};
|
|
232
|
+
if (isLeafStep) {
|
|
233
|
+
rec[head] = value;
|
|
234
|
+
return rec;
|
|
235
|
+
}
|
|
236
|
+
const existing = rec[head];
|
|
237
|
+
let childRoot;
|
|
238
|
+
if (existing === void 0 || existing !== null && typeof existing !== "object") {
|
|
239
|
+
const intermPath = [...fullPath.slice(0, startIdx + 1)];
|
|
240
|
+
const intermDefault = schema.getDefaultAtPath(intermPath);
|
|
241
|
+
childRoot = intermDefault;
|
|
242
|
+
} else {
|
|
243
|
+
childRoot = existing;
|
|
244
|
+
}
|
|
245
|
+
rec[head] = setAtPathWithSchemaFillImpl(childRoot, schema, fullPath, value, startIdx + 1);
|
|
246
|
+
return rec;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
|
|
250
|
+
function keyToSegment(key) {
|
|
251
|
+
return INTEGER_SEGMENT.test(key) ? Number(key) : key;
|
|
252
|
+
}
|
|
253
|
+
function buildSurfaceProxy(opts) {
|
|
254
|
+
const containerCache = /* @__PURE__ */ new Map();
|
|
255
|
+
const leafViewCache = /* @__PURE__ */ new Map();
|
|
256
|
+
const existsCache = /* @__PURE__ */ new Map();
|
|
257
|
+
function schemaHasPath(segs) {
|
|
258
|
+
const cacheKey = JSON.stringify(segs);
|
|
259
|
+
const cached = existsCache.get(cacheKey);
|
|
260
|
+
if (cached !== void 0) return cached;
|
|
261
|
+
const result = opts.schema.getSlimPrimitiveTypesAtPath(segs).size > 0;
|
|
262
|
+
existsCache.set(cacheKey, result);
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
function descendOrTerminate(segs) {
|
|
266
|
+
const isLeaf = opts.schema.isLeafAtPath(segs);
|
|
267
|
+
if (isLeaf) {
|
|
268
|
+
if (opts.leafKeys !== void 0) return leafViewProxyAt(segs);
|
|
269
|
+
return opts.resolveLeaf(segs);
|
|
270
|
+
}
|
|
271
|
+
return containerProxyAt(segs);
|
|
272
|
+
}
|
|
273
|
+
function navigateTo(input) {
|
|
274
|
+
if (input === void 0) return rootProxy;
|
|
275
|
+
const { segments } = canonicalizePath(input);
|
|
276
|
+
return descendOrTerminate(segments);
|
|
277
|
+
}
|
|
278
|
+
function containerProxyAt(segments) {
|
|
279
|
+
const cacheKey = JSON.stringify(segments);
|
|
280
|
+
const existing = containerCache.get(cacheKey);
|
|
281
|
+
if (existing !== void 0) return existing;
|
|
282
|
+
const snapshotContainer = () => opts.materializeContainer === void 0 ? {} : opts.materializeContainer(segments);
|
|
283
|
+
const containerToJSON = () => snapshotContainer();
|
|
284
|
+
const containerToString = () => JSON.stringify(snapshotContainer());
|
|
285
|
+
function containerValueOf() {
|
|
286
|
+
return this;
|
|
287
|
+
}
|
|
288
|
+
const containerToPrimitive = (hint) => hint === "number" ? NaN : containerToString();
|
|
289
|
+
const target = (() => {
|
|
290
|
+
});
|
|
291
|
+
const proxy = new Proxy(target, {
|
|
292
|
+
apply(_, __, args) {
|
|
293
|
+
const arg = args[0];
|
|
294
|
+
if (arg === void 0) return proxy;
|
|
295
|
+
return navigateTo(arg);
|
|
296
|
+
},
|
|
297
|
+
get(_, key) {
|
|
298
|
+
if (typeof key === "symbol") {
|
|
299
|
+
if (key === Symbol.toPrimitive) return containerToPrimitive;
|
|
300
|
+
return Reflect.get(target, key);
|
|
301
|
+
}
|
|
302
|
+
if (typeof key !== "string") return void 0;
|
|
303
|
+
if (key === "toJSON") return containerToJSON;
|
|
304
|
+
const childSegs = [...segments, keyToSegment(key)];
|
|
305
|
+
if (key === "toString" || key === "valueOf") {
|
|
306
|
+
if (!schemaHasPath(childSegs)) {
|
|
307
|
+
return key === "toString" ? containerToString : containerValueOf;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return descendOrTerminate(childSegs);
|
|
311
|
+
},
|
|
312
|
+
has(_, key) {
|
|
313
|
+
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
314
|
+
return true;
|
|
315
|
+
},
|
|
316
|
+
// Containers are descend-only — `JSON.stringify(form.fields.address)`
|
|
317
|
+
// returns `{}` (no leaf keys to enumerate). Consumers who want
|
|
318
|
+
// structural data use `form.values.<container>` instead.
|
|
319
|
+
ownKeys: () => [],
|
|
320
|
+
getOwnPropertyDescriptor: () => void 0,
|
|
321
|
+
// Block writes at the proxy boundary. Mutations go through
|
|
322
|
+
// `setValue`, the directive, or the field-array helpers.
|
|
323
|
+
set: () => false,
|
|
324
|
+
deleteProperty: () => false,
|
|
325
|
+
defineProperty: () => false
|
|
326
|
+
});
|
|
327
|
+
containerCache.set(cacheKey, proxy);
|
|
328
|
+
return proxy;
|
|
329
|
+
}
|
|
330
|
+
function leafViewProxyAt(segments) {
|
|
331
|
+
const cacheKey = JSON.stringify(segments);
|
|
332
|
+
const existing = leafViewCache.get(cacheKey);
|
|
333
|
+
if (existing !== void 0) return existing;
|
|
334
|
+
const leafKeys = opts.leafKeys;
|
|
335
|
+
const readLeafKey = opts.readLeafKey;
|
|
336
|
+
if (leafKeys === void 0 || readLeafKey === void 0) {
|
|
337
|
+
throw new Error("leafViewProxyAt called without leafKeys/readLeafKey configured");
|
|
338
|
+
}
|
|
339
|
+
const snapshotLeaf = () => {
|
|
340
|
+
const leaf = opts.resolveLeaf(segments);
|
|
341
|
+
const snapshot = {};
|
|
342
|
+
for (const leafKey of leafKeys) {
|
|
343
|
+
snapshot[leafKey] = readLeafKey(leaf, leafKey);
|
|
344
|
+
}
|
|
345
|
+
return snapshot;
|
|
346
|
+
};
|
|
347
|
+
const leafToString = () => JSON.stringify(snapshotLeaf());
|
|
348
|
+
function leafValueOf() {
|
|
349
|
+
return this;
|
|
350
|
+
}
|
|
351
|
+
const leafToPrimitive = (hint) => hint === "number" ? NaN : leafToString();
|
|
352
|
+
const target = (() => {
|
|
353
|
+
});
|
|
354
|
+
const proxy = new Proxy(target, {
|
|
355
|
+
apply(_, __, args) {
|
|
356
|
+
const arg = args[0];
|
|
357
|
+
if (arg === void 0) return opts.resolveLeaf(segments);
|
|
358
|
+
return navigateTo(arg);
|
|
359
|
+
},
|
|
360
|
+
get(_, key) {
|
|
361
|
+
if (typeof key === "symbol") {
|
|
362
|
+
if (key === Symbol.toPrimitive) return leafToPrimitive;
|
|
363
|
+
return Reflect.get(target, key);
|
|
364
|
+
}
|
|
365
|
+
if (typeof key !== "string") return void 0;
|
|
366
|
+
if (key === "toString") return leafToString;
|
|
367
|
+
if (key === "valueOf") return leafValueOf;
|
|
368
|
+
if (key === "toJSON") return snapshotLeaf;
|
|
369
|
+
if (leafKeys.has(key)) {
|
|
370
|
+
const leaf = opts.resolveLeaf(segments);
|
|
371
|
+
return readLeafKey(leaf, key);
|
|
372
|
+
}
|
|
373
|
+
return descendOrTerminate([...segments, keyToSegment(key)]);
|
|
374
|
+
},
|
|
375
|
+
has(_, key) {
|
|
376
|
+
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
377
|
+
if (typeof key === "string" && leafKeys.has(key)) return true;
|
|
378
|
+
return true;
|
|
379
|
+
},
|
|
380
|
+
// Iteration: leaf-views expose the leaf-key set so
|
|
381
|
+
// `JSON.stringify(form.fields.email)` produces the expected
|
|
382
|
+
// FieldStateView snapshot (matching the legacy
|
|
383
|
+
// `JSON.stringify(form.getFieldState('email').value)` shape).
|
|
384
|
+
ownKeys: () => Array.from(leafKeys),
|
|
385
|
+
getOwnPropertyDescriptor(_, key) {
|
|
386
|
+
if (typeof key !== "string") return void 0;
|
|
387
|
+
if (!leafKeys.has(key)) return void 0;
|
|
388
|
+
const leaf = opts.resolveLeaf(segments);
|
|
389
|
+
return {
|
|
390
|
+
configurable: true,
|
|
391
|
+
enumerable: true,
|
|
392
|
+
value: readLeafKey(leaf, key),
|
|
393
|
+
writable: false
|
|
394
|
+
};
|
|
395
|
+
},
|
|
396
|
+
set: () => false,
|
|
397
|
+
deleteProperty: () => false,
|
|
398
|
+
defineProperty: () => false
|
|
399
|
+
});
|
|
400
|
+
leafViewCache.set(cacheKey, proxy);
|
|
401
|
+
return proxy;
|
|
402
|
+
}
|
|
403
|
+
const rootProxy = containerProxyAt([]);
|
|
404
|
+
return rootProxy;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function buildErrorsProxy(state) {
|
|
408
|
+
return buildSurfaceProxy({
|
|
409
|
+
schema: state.schema,
|
|
410
|
+
resolveLeaf: (path) => {
|
|
411
|
+
if (!hasAtPath(state.form.value, path)) return void 0;
|
|
412
|
+
const { key } = canonicalizePath(path);
|
|
413
|
+
const schemaForKey = state.schemaErrors.get(key);
|
|
414
|
+
const blankForKey = state.derivedBlankErrors.value.get(key);
|
|
415
|
+
const userForKey = state.userErrors.get(key);
|
|
416
|
+
const merged = [];
|
|
417
|
+
if (schemaForKey !== void 0) merged.push(...schemaForKey);
|
|
418
|
+
if (blankForKey !== void 0) merged.push(...blankForKey);
|
|
419
|
+
if (userForKey !== void 0) merged.push(...userForKey);
|
|
420
|
+
return merged.length === 0 ? void 0 : merged;
|
|
421
|
+
},
|
|
422
|
+
// No leafKeys — at a leaf, the resolved value (the merged array or
|
|
423
|
+
// undefined) IS the terminal.
|
|
424
|
+
materializeContainer: (segments) => materializeErrors(state, segments)
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function materializeErrors(state, containerSegments) {
|
|
428
|
+
const liveContainer = getAtPath(state.form.value, containerSegments);
|
|
429
|
+
const tree = Array.isArray(liveContainer) ? [] : {};
|
|
430
|
+
const collect = (store) => {
|
|
431
|
+
entries: for (const [pathKey, errors] of store) {
|
|
432
|
+
if (errors.length === 0) continue;
|
|
433
|
+
const fullPath = segmentsForPathKey(pathKey);
|
|
434
|
+
if (fullPath === null) continue;
|
|
435
|
+
if (fullPath.length <= containerSegments.length) continue;
|
|
436
|
+
for (let i = 0; i < containerSegments.length; i++) {
|
|
437
|
+
if (fullPath[i] !== containerSegments[i]) continue entries;
|
|
438
|
+
}
|
|
439
|
+
if (!hasAtPath(state.form.value, fullPath)) continue;
|
|
440
|
+
placeAt(tree, fullPath.slice(containerSegments.length), errors);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
collect(state.schemaErrors);
|
|
444
|
+
collect(state.derivedBlankErrors.value);
|
|
445
|
+
collect(state.userErrors);
|
|
446
|
+
return tree;
|
|
447
|
+
}
|
|
448
|
+
function placeAt(tree, path, errors) {
|
|
449
|
+
if (path.length === 0) return;
|
|
450
|
+
let cursor = tree;
|
|
451
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
452
|
+
const seg = path[i];
|
|
453
|
+
const nextSeg = path[i + 1];
|
|
454
|
+
const key = typeof seg === "number" ? String(seg) : seg;
|
|
455
|
+
const cursorRecord2 = cursor;
|
|
456
|
+
let child = cursorRecord2[key];
|
|
457
|
+
if (child === null || child === void 0 || typeof child !== "object") {
|
|
458
|
+
child = typeof nextSeg === "number" ? [] : {};
|
|
459
|
+
cursorRecord2[key] = child;
|
|
460
|
+
}
|
|
461
|
+
cursor = child;
|
|
462
|
+
}
|
|
463
|
+
const lastSeg = path[path.length - 1];
|
|
464
|
+
const lastKey = typeof lastSeg === "number" ? String(lastSeg) : lastSeg;
|
|
465
|
+
const cursorRecord = cursor;
|
|
466
|
+
const existing = cursorRecord[lastKey];
|
|
467
|
+
cursorRecord[lastKey] = Array.isArray(existing) ? [...existing, ...errors] : errors;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function buildFieldArrayApi(state) {
|
|
471
|
+
function readArray(path) {
|
|
472
|
+
const segments = canonicalizePath(path).segments;
|
|
473
|
+
const current = state.getValueAtPath(segments);
|
|
474
|
+
return Array.isArray(current) ? current.slice() : [];
|
|
475
|
+
}
|
|
476
|
+
function writeArray(path, next) {
|
|
477
|
+
const { segments, key } = canonicalizePath(path);
|
|
478
|
+
return state.setValueAtPath(segments, next, {
|
|
479
|
+
persist: state.persistOptIns.hasAnyOptInForPath(key)
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
append(path, value) {
|
|
484
|
+
const next = readArray(path);
|
|
485
|
+
next.push(value);
|
|
486
|
+
return writeArray(path, next);
|
|
487
|
+
},
|
|
488
|
+
prepend(path, value) {
|
|
489
|
+
const next = readArray(path);
|
|
490
|
+
next.unshift(value);
|
|
491
|
+
return writeArray(path, next);
|
|
492
|
+
},
|
|
493
|
+
insert(path, index, value) {
|
|
494
|
+
const next = readArray(path);
|
|
495
|
+
next.splice(index, 0, value);
|
|
496
|
+
return writeArray(path, next);
|
|
497
|
+
},
|
|
498
|
+
remove(path, index) {
|
|
499
|
+
const next = readArray(path);
|
|
500
|
+
if (index < 0 || index >= next.length) return false;
|
|
501
|
+
next.splice(index, 1);
|
|
502
|
+
return writeArray(path, next);
|
|
503
|
+
},
|
|
504
|
+
swap(path, a, b) {
|
|
505
|
+
const next = readArray(path);
|
|
506
|
+
if (a < 0 || a >= next.length) return false;
|
|
507
|
+
if (b < 0 || b >= next.length) return false;
|
|
508
|
+
if (a === b) return false;
|
|
509
|
+
const tmp = next[a];
|
|
510
|
+
next[a] = next[b];
|
|
511
|
+
next[b] = tmp;
|
|
512
|
+
return writeArray(path, next);
|
|
513
|
+
},
|
|
514
|
+
move(path, from, to) {
|
|
515
|
+
const next = readArray(path);
|
|
516
|
+
if (from < 0 || from >= next.length) return false;
|
|
517
|
+
const [item] = next.splice(from, 1);
|
|
518
|
+
const clampedTo = Math.max(0, Math.min(to, next.length));
|
|
519
|
+
next.splice(clampedTo, 0, item);
|
|
520
|
+
return writeArray(path, next);
|
|
521
|
+
},
|
|
522
|
+
replace(path, index, value) {
|
|
523
|
+
const next = readArray(path);
|
|
524
|
+
if (index < 0 || index >= next.length) return false;
|
|
525
|
+
next[index] = value;
|
|
526
|
+
return writeArray(path, next);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function buildFieldStateAccessor(state) {
|
|
532
|
+
return function getFieldState(pathInput) {
|
|
533
|
+
const { segments, key } = canonicalizePath(pathInput);
|
|
534
|
+
return computed(() => {
|
|
535
|
+
const record = state.fields.get(key);
|
|
536
|
+
const value = state.getValueAtPath(segments);
|
|
537
|
+
const original = state.originals.get(key)?.value;
|
|
538
|
+
const pristine = state.isPristineAtPath(segments);
|
|
539
|
+
const schemaForKey = state.schemaErrors.get(key);
|
|
540
|
+
const blankForKey = state.derivedBlankErrors.value.get(key);
|
|
541
|
+
const userForKey = state.userErrors.get(key);
|
|
542
|
+
const errors = [];
|
|
543
|
+
if (schemaForKey !== void 0) errors.push(...schemaForKey);
|
|
544
|
+
if (blankForKey !== void 0) errors.push(...blankForKey);
|
|
545
|
+
if (userForKey !== void 0) errors.push(...userForKey);
|
|
546
|
+
return {
|
|
547
|
+
value,
|
|
548
|
+
original,
|
|
549
|
+
pristine,
|
|
550
|
+
dirty: !pristine,
|
|
551
|
+
focused: record?.focused ?? null,
|
|
552
|
+
blurred: record?.blurred ?? null,
|
|
553
|
+
touched: record?.touched ?? null,
|
|
554
|
+
isConnected: record?.isConnected ?? false,
|
|
555
|
+
updatedAt: record?.updatedAt ?? null,
|
|
556
|
+
errors,
|
|
557
|
+
path: segments,
|
|
558
|
+
blank: state.blankPaths.has(key)
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
|
|
565
|
+
"value",
|
|
566
|
+
"original",
|
|
567
|
+
"pristine",
|
|
568
|
+
"dirty",
|
|
569
|
+
"focused",
|
|
570
|
+
"blurred",
|
|
571
|
+
"touched",
|
|
572
|
+
"isConnected",
|
|
573
|
+
"updatedAt",
|
|
574
|
+
"errors",
|
|
575
|
+
"path",
|
|
576
|
+
"blank"
|
|
577
|
+
]);
|
|
578
|
+
function buildFieldStateProxy(state) {
|
|
579
|
+
const getFieldStateAt = buildFieldStateAccessor(state);
|
|
580
|
+
const snapshotFieldStateAt = (path) => {
|
|
581
|
+
const view = getFieldStateAt(path).value;
|
|
582
|
+
const snapshot = {};
|
|
583
|
+
for (const k of FIELD_STATE_KEYS) snapshot[k] = view[k];
|
|
584
|
+
return snapshot;
|
|
585
|
+
};
|
|
586
|
+
return buildSurfaceProxy({
|
|
587
|
+
schema: state.schema,
|
|
588
|
+
resolveLeaf: (path) => getFieldStateAt(path),
|
|
589
|
+
leafKeys: FIELD_STATE_KEYS,
|
|
590
|
+
readLeafKey: (computed, key) => computed.value[key],
|
|
591
|
+
materializeContainer: (segments) => materializeFields(state, segments, snapshotFieldStateAt)
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
function materializeFields(state, containerSegments, snapshotFieldStateAt) {
|
|
595
|
+
const liveValue = getAtPath(state.form.value, containerSegments);
|
|
596
|
+
return walk$2(liveValue, containerSegments, state.schema, snapshotFieldStateAt);
|
|
597
|
+
}
|
|
598
|
+
function walk$2(value, basePath, schema, snapshotFieldStateAt) {
|
|
599
|
+
if (schema.isLeafAtPath(basePath)) return snapshotFieldStateAt(basePath);
|
|
600
|
+
if (value === null || value === void 0) return value;
|
|
601
|
+
if (typeof value !== "object") {
|
|
602
|
+
return value;
|
|
603
|
+
}
|
|
604
|
+
if (Array.isArray(value)) {
|
|
605
|
+
return value.map((_, i) => walk$2(value[i], [...basePath, i], schema, snapshotFieldStateAt));
|
|
606
|
+
}
|
|
607
|
+
const result = {};
|
|
608
|
+
for (const key of Object.keys(value)) {
|
|
609
|
+
result[key] = walk$2(
|
|
610
|
+
value[key],
|
|
611
|
+
[...basePath, key],
|
|
612
|
+
schema,
|
|
613
|
+
snapshotFieldStateAt
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
|
|
620
|
+
const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
|
|
621
|
+
const DEFAULT_HISTORY_MAX_SNAPSHOTS = 50;
|
|
622
|
+
const PERSISTENCE_KEY_PREFIX = "attaform:";
|
|
623
|
+
const RESERVED_KEY_PREFIX = "__atta:";
|
|
624
|
+
const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
|
|
625
|
+
|
|
626
|
+
const PERSISTENCE_MODULE_KEY = "persistence";
|
|
627
|
+
async function getStorageAdapter(storage) {
|
|
628
|
+
if (typeof storage === "object") return storage;
|
|
629
|
+
switch (storage) {
|
|
630
|
+
case "local": {
|
|
631
|
+
const { createLocalStorageAdapter } = await import('../chunks/local-storage.mjs');
|
|
632
|
+
return createLocalStorageAdapter();
|
|
633
|
+
}
|
|
634
|
+
case "session": {
|
|
635
|
+
const { createSessionStorageAdapter } = await import('../chunks/session-storage.mjs');
|
|
636
|
+
return createSessionStorageAdapter();
|
|
637
|
+
}
|
|
638
|
+
case "indexeddb": {
|
|
639
|
+
const { createIndexedDbAdapter } = await import('../chunks/indexeddb.mjs');
|
|
640
|
+
return createIndexedDbAdapter();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const PERSISTED_ENVELOPE_VERSION = 4;
|
|
645
|
+
function readPersistedPayload(value) {
|
|
646
|
+
if (value === null || value === void 0 || typeof value !== "object") return null;
|
|
647
|
+
const envelope = value;
|
|
648
|
+
if (typeof envelope.v !== "number") return null;
|
|
649
|
+
if (envelope.v !== PERSISTED_ENVELOPE_VERSION) {
|
|
650
|
+
warnVersionMismatch(envelope.v);
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
if (envelope.data === void 0 || typeof envelope.data !== "object") return null;
|
|
654
|
+
return envelope;
|
|
655
|
+
}
|
|
656
|
+
const warnedVersions = __DEV__ ? /* @__PURE__ */ new Set() : null;
|
|
657
|
+
function warnVersionMismatch(observedVersion) {
|
|
658
|
+
if (warnedVersions === null) return;
|
|
659
|
+
if (warnedVersions.has(observedVersion)) return;
|
|
660
|
+
warnedVersions.add(observedVersion);
|
|
661
|
+
console.warn(
|
|
662
|
+
`[attaform] Dropping persisted draft \u2014 envelope v=${observedVersion}, but this version of the library expects v=${PERSISTED_ENVELOPE_VERSION}. The persisted shape changed across releases; older drafts can't be restored. New drafts saved this session will use the current envelope.`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPaths) {
|
|
666
|
+
const transientList = blankPaths !== void 0 && blankPaths.size > 0 ? [...blankPaths] : void 0;
|
|
667
|
+
if (include === "form") {
|
|
668
|
+
if (transientList === void 0) return { v: PERSISTED_ENVELOPE_VERSION, data: { form } };
|
|
669
|
+
return {
|
|
670
|
+
v: PERSISTED_ENVELOPE_VERSION,
|
|
671
|
+
data: { form, blankPaths: transientList }
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
v: PERSISTED_ENVELOPE_VERSION,
|
|
676
|
+
data: {
|
|
677
|
+
form,
|
|
678
|
+
schemaErrors: [...schemaErrors.entries()].map(([k, v]) => [k, [...v]]),
|
|
679
|
+
userErrors: [...userErrors.entries()].map(([k, v]) => [k, [...v]]),
|
|
680
|
+
...transientList !== void 0 ? { blankPaths: transientList } : {}
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function createDebouncedWriter(write, debounceMs) {
|
|
685
|
+
let timer = null;
|
|
686
|
+
let pending = null;
|
|
687
|
+
function schedule() {
|
|
688
|
+
if (timer !== null) clearTimeout(timer);
|
|
689
|
+
if (debounceMs === 0) {
|
|
690
|
+
pending = write().finally(() => {
|
|
691
|
+
pending = null;
|
|
692
|
+
});
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
timer = setTimeout(() => {
|
|
696
|
+
timer = null;
|
|
697
|
+
pending = write().finally(() => {
|
|
698
|
+
pending = null;
|
|
699
|
+
});
|
|
700
|
+
}, debounceMs);
|
|
701
|
+
}
|
|
702
|
+
async function flush() {
|
|
703
|
+
if (timer !== null) {
|
|
704
|
+
clearTimeout(timer);
|
|
705
|
+
timer = null;
|
|
706
|
+
pending = write().finally(() => {
|
|
707
|
+
pending = null;
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
if (pending !== null) await pending;
|
|
711
|
+
}
|
|
712
|
+
function cancel() {
|
|
713
|
+
if (timer !== null) {
|
|
714
|
+
clearTimeout(timer);
|
|
715
|
+
timer = null;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return { schedule, flush, cancel };
|
|
719
|
+
}
|
|
720
|
+
function resolveStorageKeyBase(config, formKey) {
|
|
721
|
+
return config.key ?? `${PERSISTENCE_KEY_PREFIX}${formKey}`;
|
|
722
|
+
}
|
|
723
|
+
async function cleanupOrphanKeys(adapter, base, currentKey) {
|
|
724
|
+
let keys;
|
|
725
|
+
try {
|
|
726
|
+
keys = await adapter.listKeys(base);
|
|
727
|
+
} catch {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
for (const key of keys) {
|
|
731
|
+
if (key === currentKey) continue;
|
|
732
|
+
if (key === base || key.startsWith(`${base}:`)) {
|
|
733
|
+
void adapter.removeItem(key).catch(() => void 0);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const STANDARD_STORAGE_KINDS = ["local", "session", "indexeddb"];
|
|
738
|
+
function normalizePersistConfig(input) {
|
|
739
|
+
if (typeof input === "string") return { storage: input };
|
|
740
|
+
if ("storage" in input) return input;
|
|
741
|
+
return { storage: input };
|
|
742
|
+
}
|
|
743
|
+
async function sweepAllOrphansAcrossStandardStores(base) {
|
|
744
|
+
for (const kind of STANDARD_STORAGE_KINDS) {
|
|
745
|
+
try {
|
|
746
|
+
const adapter = await getStorageAdapter(kind);
|
|
747
|
+
const keys = await adapter.listKeys(base);
|
|
748
|
+
for (const key of keys) {
|
|
749
|
+
if (key === base || key.startsWith(`${base}:`)) {
|
|
750
|
+
void adapter.removeItem(key).catch(() => void 0);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
} catch {
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async function sweepNonConfiguredStandardStoresForOrphans(configured, base) {
|
|
758
|
+
const configuredKind = typeof configured === "string" ? configured : null;
|
|
759
|
+
for (const kind of STANDARD_STORAGE_KINDS) {
|
|
760
|
+
if (kind === configuredKind) continue;
|
|
761
|
+
try {
|
|
762
|
+
const adapter = await getStorageAdapter(kind);
|
|
763
|
+
const keys = await adapter.listKeys(base);
|
|
764
|
+
for (const key of keys) {
|
|
765
|
+
if (key === base || key.startsWith(`${base}:`)) {
|
|
766
|
+
void adapter.removeItem(key).catch(() => void 0);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
} catch {
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function pluckPaths(form, pathKeys) {
|
|
774
|
+
let sparse = void 0;
|
|
775
|
+
for (const pathKey of pathKeys) {
|
|
776
|
+
const segments = segmentsForPathKey(pathKey);
|
|
777
|
+
if (segments === null) continue;
|
|
778
|
+
const value = getAtPath(form, segments);
|
|
779
|
+
if (value === void 0) continue;
|
|
780
|
+
sparse = setAtPath(sparse ?? {}, segments, value);
|
|
781
|
+
}
|
|
782
|
+
return sparse ?? {};
|
|
783
|
+
}
|
|
784
|
+
function filterErrorsByPaths(errors, pathKeys) {
|
|
785
|
+
const out = /* @__PURE__ */ new Map();
|
|
786
|
+
for (const [key, value] of errors) {
|
|
787
|
+
if (pathKeys.has(key)) out.set(key, value);
|
|
788
|
+
}
|
|
789
|
+
return out;
|
|
790
|
+
}
|
|
791
|
+
function mergeSparseHydration(schemaDefaults, sparse, schema) {
|
|
792
|
+
return mergeDeep(schemaDefaults, sparse, [], schema);
|
|
793
|
+
}
|
|
794
|
+
function mergeDeep(target, source, path, schema) {
|
|
795
|
+
if (source === void 0) return target;
|
|
796
|
+
if (source === null || typeof source !== "object") return source;
|
|
797
|
+
if (Array.isArray(source)) return source;
|
|
798
|
+
if (!isPlainRecord(source)) return source;
|
|
799
|
+
let mergeTarget = target;
|
|
800
|
+
if (schema !== void 0) {
|
|
801
|
+
const du = schema.getUnionDiscriminatorAtPath(path);
|
|
802
|
+
if (du !== void 0) {
|
|
803
|
+
const sourceDisc = source[du.discriminatorKey];
|
|
804
|
+
const targetDisc = isPlainRecord(target) ? target[du.discriminatorKey] : void 0;
|
|
805
|
+
if (sourceDisc !== void 0 && !Object.is(sourceDisc, targetDisc)) {
|
|
806
|
+
const variantDefault = du.getVariantDefault(sourceDisc);
|
|
807
|
+
if (isPlainRecord(variantDefault)) {
|
|
808
|
+
mergeTarget = variantDefault;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const out = isPlainRecord(mergeTarget) ? { ...mergeTarget } : {};
|
|
814
|
+
for (const key of Object.keys(source)) {
|
|
815
|
+
out[key] = mergeDeep(out[key], source[key], [...path, key], schema);
|
|
816
|
+
}
|
|
817
|
+
return out;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const AttaformErrorCode = {
|
|
821
|
+
/** A required field is in the blank set — user hasn't supplied a value. */
|
|
822
|
+
NoValueSupplied: "atta:no-value-supplied",
|
|
823
|
+
/** The schema adapter's `validateAtPath` threw synchronously. */
|
|
824
|
+
AdapterThrew: "atta:adapter-threw",
|
|
825
|
+
/** The supplied path didn't resolve to any node in the schema. */
|
|
826
|
+
PathNotFound: "atta:path-not-found"
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
const warnedNoScopeStores = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
|
|
830
|
+
function buildProcessForm(state, formInstanceId, options = {}) {
|
|
831
|
+
const invalidPolicy = options.onInvalidSubmit ?? "none";
|
|
832
|
+
function validate(pathInput) {
|
|
833
|
+
const result = ref({
|
|
834
|
+
pending: true,
|
|
835
|
+
errors: void 0,
|
|
836
|
+
success: false,
|
|
837
|
+
formKey: state.formKey
|
|
838
|
+
});
|
|
839
|
+
let gen = 0;
|
|
840
|
+
async function kickoff(data, path, captured) {
|
|
841
|
+
state.activeValidations.value += 1;
|
|
842
|
+
result.value = {
|
|
843
|
+
pending: true,
|
|
844
|
+
errors: void 0,
|
|
845
|
+
success: false,
|
|
846
|
+
formKey: state.formKey
|
|
847
|
+
};
|
|
848
|
+
try {
|
|
849
|
+
const refinement = await runRefinementValidation(data, path);
|
|
850
|
+
if (captured !== gen) return;
|
|
851
|
+
result.value = settled(composeWithDerivedBlank(refinement, path));
|
|
852
|
+
} catch (err) {
|
|
853
|
+
if (captured !== gen) return;
|
|
854
|
+
result.value = {
|
|
855
|
+
pending: false,
|
|
856
|
+
errors: [
|
|
857
|
+
{
|
|
858
|
+
message: adapterThrowMessage(err),
|
|
859
|
+
path: [],
|
|
860
|
+
formKey: state.formKey,
|
|
861
|
+
code: AttaformErrorCode.AdapterThrew
|
|
862
|
+
}
|
|
863
|
+
],
|
|
864
|
+
success: false,
|
|
865
|
+
formKey: state.formKey
|
|
866
|
+
};
|
|
867
|
+
} finally {
|
|
868
|
+
state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const stop = watchEffect(() => {
|
|
872
|
+
const segments = pathInput === void 0 ? void 0 : toSegments(pathInput);
|
|
873
|
+
const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
|
|
874
|
+
const localGen = ++gen;
|
|
875
|
+
queueMicrotask(() => {
|
|
876
|
+
void kickoff(dataAtPath, segments, localGen);
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
if (getCurrentScope() !== void 0) {
|
|
880
|
+
onScopeDispose(stop);
|
|
881
|
+
} else if (__DEV__ && warnedNoScopeStores !== null && !warnedNoScopeStores.has(state)) {
|
|
882
|
+
warnedNoScopeStores.add(state);
|
|
883
|
+
console.warn(
|
|
884
|
+
"[attaform] validate() called outside a Vue effect scope; its reactive watcher will leak until the form is garbage-collected. Fix: call validate() inside setup() / a child component, or wrap the call in `effectScope().run(...)`."
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
return result;
|
|
888
|
+
}
|
|
889
|
+
async function validateAsync(pathInput) {
|
|
890
|
+
const segments = pathInput === void 0 ? void 0 : toSegments(pathInput);
|
|
891
|
+
const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
|
|
892
|
+
state.activeValidations.value += 1;
|
|
893
|
+
try {
|
|
894
|
+
const refinement = await runRefinementValidation(dataAtPath, segments);
|
|
895
|
+
return stripData(composeWithDerivedBlank(refinement, segments));
|
|
896
|
+
} finally {
|
|
897
|
+
state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
async function runRefinementValidation(data, path) {
|
|
901
|
+
return await state.schema.validateAtPath(data, path);
|
|
902
|
+
}
|
|
903
|
+
function composeWithDerivedBlank(refinement, scope) {
|
|
904
|
+
const blankErrors = collectScopedBlankErrors(state, scope);
|
|
905
|
+
if (blankErrors.length === 0) return refinement;
|
|
906
|
+
if (refinement.success) {
|
|
907
|
+
return {
|
|
908
|
+
data: void 0,
|
|
909
|
+
errors: blankErrors,
|
|
910
|
+
success: false,
|
|
911
|
+
formKey: state.formKey
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
return { ...refinement, errors: [...refinement.errors, ...blankErrors] };
|
|
915
|
+
}
|
|
916
|
+
const handleSubmit = (onSubmit, onError) => {
|
|
917
|
+
const submitHandler = async (event) => {
|
|
918
|
+
if (event !== void 0 && "preventDefault" in event && typeof event.preventDefault === "function") {
|
|
919
|
+
event.preventDefault();
|
|
920
|
+
}
|
|
921
|
+
const genAtEntry = state.submissionGeneration.value;
|
|
922
|
+
state.activeSubmissions.value += 1;
|
|
923
|
+
state.isSubmitting.value = true;
|
|
924
|
+
state.submitError.value = null;
|
|
925
|
+
state.cancelFieldValidation();
|
|
926
|
+
state.activeValidations.value += 1;
|
|
927
|
+
let validationSettled = false;
|
|
928
|
+
try {
|
|
929
|
+
const refinement = await runRefinementValidation(state.form.value, void 0);
|
|
930
|
+
const merged = composeWithDerivedBlank(refinement, void 0);
|
|
931
|
+
state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
|
|
932
|
+
validationSettled = true;
|
|
933
|
+
const generationStillValid = state.submissionGeneration.value === genAtEntry;
|
|
934
|
+
if (!merged.success) {
|
|
935
|
+
if (generationStillValid) {
|
|
936
|
+
if (refinement.success) {
|
|
937
|
+
state.clearSchemaErrors();
|
|
938
|
+
} else {
|
|
939
|
+
state.setAllSchemaErrors(refinement.errors);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
if (generationStillValid) {
|
|
943
|
+
applyInvalidSubmitPolicy(state, formInstanceId, invalidPolicy);
|
|
944
|
+
}
|
|
945
|
+
if (onError !== void 0) {
|
|
946
|
+
try {
|
|
947
|
+
await onError(merged.errors);
|
|
948
|
+
} catch (cause) {
|
|
949
|
+
throw new SubmitErrorHandlerError("User-provided onError threw", { cause });
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (generationStillValid) {
|
|
955
|
+
state.clearSchemaErrors();
|
|
956
|
+
}
|
|
957
|
+
await onSubmit(merged.data);
|
|
958
|
+
state.emitSubmitSuccess();
|
|
959
|
+
} catch (err) {
|
|
960
|
+
if (state.submissionGeneration.value === genAtEntry) {
|
|
961
|
+
state.submitError.value = err;
|
|
962
|
+
}
|
|
963
|
+
throw err;
|
|
964
|
+
} finally {
|
|
965
|
+
if (!validationSettled) {
|
|
966
|
+
state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
|
|
967
|
+
}
|
|
968
|
+
state.activeSubmissions.value = Math.max(0, state.activeSubmissions.value - 1);
|
|
969
|
+
if (state.submissionGeneration.value === genAtEntry) {
|
|
970
|
+
state.isSubmitting.value = state.activeSubmissions.value > 0;
|
|
971
|
+
state.submitCount.value += 1;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
return submitHandler;
|
|
976
|
+
};
|
|
977
|
+
return { validate, validateAsync, handleSubmit };
|
|
978
|
+
}
|
|
979
|
+
function toSegments(pathInput) {
|
|
980
|
+
return canonicalizePath(pathInput).segments;
|
|
981
|
+
}
|
|
982
|
+
function settled(response) {
|
|
983
|
+
if (response.success) {
|
|
984
|
+
return { pending: false, errors: void 0, success: true, formKey: response.formKey };
|
|
985
|
+
}
|
|
986
|
+
return { pending: false, errors: response.errors, success: false, formKey: response.formKey };
|
|
987
|
+
}
|
|
988
|
+
function stripData(response) {
|
|
989
|
+
if (response.success) {
|
|
990
|
+
return { errors: void 0, success: true, formKey: response.formKey };
|
|
991
|
+
}
|
|
992
|
+
return { errors: response.errors, success: false, formKey: response.formKey };
|
|
993
|
+
}
|
|
994
|
+
function adapterThrowMessage(err) {
|
|
995
|
+
if (err instanceof Error) return `Adapter validateAtPath threw: ${err.message}`;
|
|
996
|
+
return "Adapter validateAtPath threw a non-Error value";
|
|
997
|
+
}
|
|
998
|
+
function collectScopedBlankErrors(state, scope) {
|
|
999
|
+
const derived = state.derivedBlankErrors.value;
|
|
1000
|
+
if (derived.size === 0) return [];
|
|
1001
|
+
const errors = [];
|
|
1002
|
+
for (const [pathKey, entries] of derived) {
|
|
1003
|
+
if (scope !== void 0) {
|
|
1004
|
+
const segments = segmentsForPathKey(pathKey);
|
|
1005
|
+
if (segments === null) continue;
|
|
1006
|
+
if (!pathStartsWith(segments, scope)) continue;
|
|
1007
|
+
}
|
|
1008
|
+
errors.push(...entries);
|
|
1009
|
+
}
|
|
1010
|
+
return errors;
|
|
1011
|
+
}
|
|
1012
|
+
function pathStartsWith(target, prefix) {
|
|
1013
|
+
if (prefix.length > target.length) return false;
|
|
1014
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
1015
|
+
if (!Object.is(target[i], prefix[i])) return false;
|
|
1016
|
+
}
|
|
1017
|
+
return true;
|
|
1018
|
+
}
|
|
1019
|
+
function applyInvalidSubmitPolicy(state, formInstanceId, policy) {
|
|
1020
|
+
if (policy === "none") return;
|
|
1021
|
+
const target = state.getFirstErrorElement(formInstanceId);
|
|
1022
|
+
if (target === null) return;
|
|
1023
|
+
if (policy === "scroll-to-first-error") {
|
|
1024
|
+
target.element.scrollIntoView();
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (policy === "focus-first-error") {
|
|
1028
|
+
target.element.focus();
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
target.element.scrollIntoView();
|
|
1032
|
+
target.element.focus({ preventScroll: true });
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function extractSchemaFields(schema) {
|
|
1036
|
+
try {
|
|
1037
|
+
const root = schema.getDefaultAtPath([]);
|
|
1038
|
+
if (root !== null && typeof root === "object" && !Array.isArray(root)) {
|
|
1039
|
+
return Object.keys(root);
|
|
1040
|
+
}
|
|
1041
|
+
} catch {
|
|
1042
|
+
}
|
|
1043
|
+
return [];
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const warnedRejections = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
1047
|
+
function shouldWarnOnce$1(store, key) {
|
|
1048
|
+
if (warnedRejections === null) return false;
|
|
1049
|
+
let set = warnedRejections.get(store);
|
|
1050
|
+
if (set === void 0) {
|
|
1051
|
+
set = /* @__PURE__ */ new Set();
|
|
1052
|
+
warnedRejections.set(store, set);
|
|
1053
|
+
}
|
|
1054
|
+
if (set.has(key)) return false;
|
|
1055
|
+
set.add(key);
|
|
1056
|
+
return true;
|
|
1057
|
+
}
|
|
1058
|
+
function slimKindOf(value) {
|
|
1059
|
+
if (value === null) return "null";
|
|
1060
|
+
if (value === void 0) return "undefined";
|
|
1061
|
+
if (Array.isArray(value)) return "array";
|
|
1062
|
+
if (value instanceof Date) return "date";
|
|
1063
|
+
if (value instanceof Map) return "map";
|
|
1064
|
+
if (value instanceof Set) return "set";
|
|
1065
|
+
const t = typeof value;
|
|
1066
|
+
switch (t) {
|
|
1067
|
+
case "string":
|
|
1068
|
+
return "string";
|
|
1069
|
+
case "number":
|
|
1070
|
+
return "number";
|
|
1071
|
+
case "boolean":
|
|
1072
|
+
return "boolean";
|
|
1073
|
+
case "bigint":
|
|
1074
|
+
return "bigint";
|
|
1075
|
+
case "symbol":
|
|
1076
|
+
return "symbol";
|
|
1077
|
+
case "function":
|
|
1078
|
+
return "function";
|
|
1079
|
+
case "undefined":
|
|
1080
|
+
return "undefined";
|
|
1081
|
+
case "object":
|
|
1082
|
+
return "object";
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function isLeafValue(value) {
|
|
1086
|
+
if (value === null || value === void 0) return true;
|
|
1087
|
+
if (Array.isArray(value)) return false;
|
|
1088
|
+
if (isPlainRecord(value)) return false;
|
|
1089
|
+
return true;
|
|
1090
|
+
}
|
|
1091
|
+
function isSlimPrimitiveValid(schema, store, path, value) {
|
|
1092
|
+
return walk$1(schema, store, path, value);
|
|
1093
|
+
}
|
|
1094
|
+
function walk$1(schema, store, path, value) {
|
|
1095
|
+
const accepted = schema.getSlimPrimitiveTypesAtPath(path);
|
|
1096
|
+
const kind = isLeafValue(value) ? slimKindOf(value) : Array.isArray(value) ? "array" : "object";
|
|
1097
|
+
if (!accepted.has(kind)) {
|
|
1098
|
+
reportRejection(store, path, kind, accepted);
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
if (Array.isArray(value)) {
|
|
1102
|
+
for (let i = 0; i < value.length; i++) {
|
|
1103
|
+
if (!walk$1(schema, store, [...path, i], value[i])) return false;
|
|
1104
|
+
}
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
if (isPlainRecord(value)) {
|
|
1108
|
+
for (const key of Object.keys(value)) {
|
|
1109
|
+
if (!walk$1(schema, store, [...path, key], value[key])) {
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
return true;
|
|
1116
|
+
}
|
|
1117
|
+
function reportRejection(store, path, kind, accepted) {
|
|
1118
|
+
if (!__DEV__) return;
|
|
1119
|
+
const dotted = path.map((s) => String(s)).join(".") || "(root)";
|
|
1120
|
+
const key = `${dotted}::${kind}`;
|
|
1121
|
+
if (!shouldWarnOnce$1(store, key)) return;
|
|
1122
|
+
if (accepted.size === 0) {
|
|
1123
|
+
console.warn(
|
|
1124
|
+
`[attaform] Cannot write to '${dotted}' \u2014 this path is not in your schema.
|
|
1125
|
+
Fix: check for a typo in register('${dotted}'); it should match a leaf key in your schema.
|
|
1126
|
+
(If the path resolves to a never-typed schema, it explicitly admits no values \u2014 relax the schema if intentional.)
|
|
1127
|
+
The write was a no-op.`
|
|
1128
|
+
);
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
const expected = formatExpectedKinds(accepted);
|
|
1132
|
+
if (kind === "string" && accepted.has("number")) {
|
|
1133
|
+
console.warn(
|
|
1134
|
+
`[attaform] Cannot write a string to '${dotted}' \u2014 the schema expects ${expected}.
|
|
1135
|
+
Fix: add type="number" to the input, OR use the .number modifier on v-register:
|
|
1136
|
+
<input type="number" v-register="register('${dotted}')" />
|
|
1137
|
+
<input v-register.number="register('${dotted}')" />
|
|
1138
|
+
The write was a no-op.`
|
|
1139
|
+
);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
console.warn(
|
|
1143
|
+
`[attaform] Cannot write a ${kind} to '${dotted}' \u2014 the schema expects ${expected}.
|
|
1144
|
+
The write was a no-op.`
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
function formatExpectedKinds(accepted) {
|
|
1148
|
+
const list = [...accepted].sort();
|
|
1149
|
+
if (list.length === 1) return list[0];
|
|
1150
|
+
if (list.length === 2) return `${list[0]} or ${list[1]}`;
|
|
1151
|
+
return `one of: ${list.join(", ")}`;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function defineCoercion(entry) {
|
|
1155
|
+
return entry;
|
|
1156
|
+
}
|
|
1157
|
+
const IDENTITY = (v) => v;
|
|
1158
|
+
const EMPTY_INDEX = /* @__PURE__ */ new Map();
|
|
1159
|
+
const defaultCoercionRules = [
|
|
1160
|
+
defineCoercion({
|
|
1161
|
+
input: "string",
|
|
1162
|
+
output: "number",
|
|
1163
|
+
transform: (s) => {
|
|
1164
|
+
const trimmed = s.trim();
|
|
1165
|
+
if (trimmed === "") return { coerced: false };
|
|
1166
|
+
const n = Number(trimmed);
|
|
1167
|
+
if (!Number.isFinite(n)) return { coerced: false };
|
|
1168
|
+
return { coerced: true, value: n };
|
|
1169
|
+
}
|
|
1170
|
+
}),
|
|
1171
|
+
defineCoercion({
|
|
1172
|
+
input: "string",
|
|
1173
|
+
output: "boolean",
|
|
1174
|
+
transform: (s) => {
|
|
1175
|
+
const normalized = s.trim().toLowerCase();
|
|
1176
|
+
if (normalized === "true") return { coerced: true, value: true };
|
|
1177
|
+
if (normalized === "false") return { coerced: true, value: false };
|
|
1178
|
+
return { coerced: false };
|
|
1179
|
+
}
|
|
1180
|
+
})
|
|
1181
|
+
];
|
|
1182
|
+
function resolveCoercionIndex(config) {
|
|
1183
|
+
if (config === false) return EMPTY_INDEX;
|
|
1184
|
+
const rules = config === void 0 || config === true ? defaultCoercionRules : config;
|
|
1185
|
+
return indexRules(rules);
|
|
1186
|
+
}
|
|
1187
|
+
function indexRules(rules) {
|
|
1188
|
+
const idx = /* @__PURE__ */ new Map();
|
|
1189
|
+
for (const entry of rules) {
|
|
1190
|
+
const candidate = entry;
|
|
1191
|
+
if (candidate === null || typeof candidate !== "object" || typeof candidate.transform !== "function") {
|
|
1192
|
+
if (__DEV__) {
|
|
1193
|
+
console.warn("[attaform] coercion entry missing or invalid `transform` \u2014 skipped.");
|
|
1194
|
+
}
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
const key = `${entry.input}->${entry.output}`;
|
|
1198
|
+
if (idx.has(key) && __DEV__) {
|
|
1199
|
+
console.warn(`[attaform] duplicate coercion rule for '${key}' \u2014 last entry wins.`);
|
|
1200
|
+
}
|
|
1201
|
+
idx.set(key, entry);
|
|
1202
|
+
}
|
|
1203
|
+
return idx;
|
|
1204
|
+
}
|
|
1205
|
+
function buildCoerceFn(schema, segments, index) {
|
|
1206
|
+
if (index === EMPTY_INDEX) return IDENTITY;
|
|
1207
|
+
if (index.size === 0) return IDENTITY;
|
|
1208
|
+
const accepted = schema.getSlimPrimitiveTypesAtPath(segments);
|
|
1209
|
+
const elementAccepted = accepted.has("array") || accepted.has("set") ? schema.getSlimPrimitiveTypesAtPath([...segments, 0]) : void 0;
|
|
1210
|
+
return (value) => coerceValue(value, accepted, elementAccepted, index);
|
|
1211
|
+
}
|
|
1212
|
+
function buildElementCoerceFn(schema, segments, index) {
|
|
1213
|
+
if (index === EMPTY_INDEX) return void 0;
|
|
1214
|
+
if (index.size === 0) return void 0;
|
|
1215
|
+
const accepted = schema.getSlimPrimitiveTypesAtPath(segments);
|
|
1216
|
+
if (!accepted.has("array") && !accepted.has("set")) return void 0;
|
|
1217
|
+
const elementAccepted = schema.getSlimPrimitiveTypesAtPath([...segments, 0]);
|
|
1218
|
+
return (value) => coerceScalar(value, elementAccepted, index);
|
|
1219
|
+
}
|
|
1220
|
+
function pickScalarTarget(accepted) {
|
|
1221
|
+
if (accepted.has("string")) return null;
|
|
1222
|
+
if (accepted.has("number")) return "number";
|
|
1223
|
+
if (accepted.has("boolean")) return "boolean";
|
|
1224
|
+
if (accepted.has("bigint")) return "bigint";
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
const warnedCoerce = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
1228
|
+
const sharedWarnStore = {};
|
|
1229
|
+
function shouldWarnOnce(key) {
|
|
1230
|
+
if (warnedCoerce === null) return false;
|
|
1231
|
+
let set = warnedCoerce.get(sharedWarnStore);
|
|
1232
|
+
if (set === void 0) {
|
|
1233
|
+
set = /* @__PURE__ */ new Set();
|
|
1234
|
+
warnedCoerce.set(sharedWarnStore, set);
|
|
1235
|
+
}
|
|
1236
|
+
if (set.has(key)) return false;
|
|
1237
|
+
set.add(key);
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
function coerceScalar(value, accepted, index) {
|
|
1241
|
+
if (accepted.size === 0) return value;
|
|
1242
|
+
const sourceKind = slimKindOf(value);
|
|
1243
|
+
if (accepted.has(sourceKind)) return value;
|
|
1244
|
+
const target = pickScalarTarget(accepted);
|
|
1245
|
+
if (target === null) return value;
|
|
1246
|
+
const entry = index.get(`${sourceKind}->${target}`);
|
|
1247
|
+
if (entry === void 0) return value;
|
|
1248
|
+
let result;
|
|
1249
|
+
try {
|
|
1250
|
+
result = entry.transform(value);
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
if (__DEV__ && shouldWarnOnce(`${entry.input}->${entry.output}::throw`)) {
|
|
1253
|
+
console.warn(
|
|
1254
|
+
`[attaform] coercion '${entry.input}->${entry.output}' threw \u2014 write passes through.`,
|
|
1255
|
+
err
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
return value;
|
|
1259
|
+
}
|
|
1260
|
+
if (!result.coerced) return value;
|
|
1261
|
+
const returnedKind = slimKindOf(result.value);
|
|
1262
|
+
if (returnedKind !== entry.output) {
|
|
1263
|
+
if (__DEV__ && shouldWarnOnce(`${entry.input}->${entry.output}::wrong-kind:${returnedKind}`)) {
|
|
1264
|
+
console.warn(
|
|
1265
|
+
`[attaform] coercion '${entry.input}->${entry.output}' produced a ${returnedKind} \u2014 write passes through.`
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
return value;
|
|
1269
|
+
}
|
|
1270
|
+
if (entry.output === "number" && !Number.isFinite(result.value)) {
|
|
1271
|
+
if (__DEV__ && shouldWarnOnce(`${entry.input}->${entry.output}::nan`)) {
|
|
1272
|
+
console.warn(
|
|
1273
|
+
`[attaform] coercion '${entry.input}->${entry.output}' produced a non-finite number \u2014 write passes through.`
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
return value;
|
|
1277
|
+
}
|
|
1278
|
+
return result.value;
|
|
1279
|
+
}
|
|
1280
|
+
function coerceArrayMembers(arr, elementAccepted, index) {
|
|
1281
|
+
let changed = false;
|
|
1282
|
+
const out = [];
|
|
1283
|
+
for (const el of arr) {
|
|
1284
|
+
const next = coerceScalar(el, elementAccepted, index);
|
|
1285
|
+
if (next !== el) changed = true;
|
|
1286
|
+
out.push(next);
|
|
1287
|
+
}
|
|
1288
|
+
return changed ? out : arr;
|
|
1289
|
+
}
|
|
1290
|
+
function coerceSetMembers(set, elementAccepted, index) {
|
|
1291
|
+
let changed = false;
|
|
1292
|
+
const out = [];
|
|
1293
|
+
for (const el of set) {
|
|
1294
|
+
const next = coerceScalar(el, elementAccepted, index);
|
|
1295
|
+
if (next !== el) changed = true;
|
|
1296
|
+
out.push(next);
|
|
1297
|
+
}
|
|
1298
|
+
return changed ? new Set(out) : set;
|
|
1299
|
+
}
|
|
1300
|
+
function coerceValue(value, accepted, elementAccepted, index) {
|
|
1301
|
+
if (Array.isArray(value)) {
|
|
1302
|
+
if (!accepted.has("array") || elementAccepted === void 0) return value;
|
|
1303
|
+
return coerceArrayMembers(value, elementAccepted, index);
|
|
1304
|
+
}
|
|
1305
|
+
if (value instanceof Set) {
|
|
1306
|
+
if (!accepted.has("set") || elementAccepted === void 0) return value;
|
|
1307
|
+
return coerceSetMembers(value, elementAccepted, index);
|
|
1308
|
+
}
|
|
1309
|
+
return coerceScalar(value, accepted, index);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const EMPTY_TRANSFORMS = Object.freeze([]);
|
|
1313
|
+
const INTERACTIVE_TAG_NAMES = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
|
|
1314
|
+
const attaformListenersSymbol = Symbol.for("attaform:focus-listeners");
|
|
1315
|
+
function attachFocusListeners(state, segments, element) {
|
|
1316
|
+
const target = element;
|
|
1317
|
+
if (target[attaformListenersSymbol] !== void 0) return;
|
|
1318
|
+
const handleFocus = () => state.markFocused(segments, true);
|
|
1319
|
+
const handleBlur = () => state.markFocused(segments, false);
|
|
1320
|
+
element.addEventListener("focus", handleFocus);
|
|
1321
|
+
element.addEventListener("blur", handleBlur);
|
|
1322
|
+
target[attaformListenersSymbol] = { handleFocus, handleBlur };
|
|
1323
|
+
}
|
|
1324
|
+
function detachFocusListeners(element) {
|
|
1325
|
+
const target = element;
|
|
1326
|
+
const listeners = target[attaformListenersSymbol];
|
|
1327
|
+
if (listeners === void 0) return;
|
|
1328
|
+
element.removeEventListener("focus", listeners.handleFocus);
|
|
1329
|
+
element.removeEventListener("blur", listeners.handleBlur);
|
|
1330
|
+
delete target[attaformListenersSymbol];
|
|
1331
|
+
}
|
|
1332
|
+
function buildRegister(state, formInstanceId) {
|
|
1333
|
+
const lastTypedFormByPath = /* @__PURE__ */ new Map();
|
|
1334
|
+
return function register(pathInput, options) {
|
|
1335
|
+
const { segments, key: pathKey } = canonicalizePath(pathInput);
|
|
1336
|
+
const innerRef = computed(() => state.getValueAtPath(segments));
|
|
1337
|
+
let lastTypedForm = lastTypedFormByPath.get(pathKey);
|
|
1338
|
+
if (lastTypedForm === void 0) {
|
|
1339
|
+
lastTypedForm = ref(null);
|
|
1340
|
+
lastTypedFormByPath.set(pathKey, lastTypedForm);
|
|
1341
|
+
}
|
|
1342
|
+
const displayValue = computed(() => {
|
|
1343
|
+
if (state.blankPaths.has(pathKey)) return "";
|
|
1344
|
+
const raw = state.getValueAtPath(segments);
|
|
1345
|
+
if (raw === null || raw === void 0) return "";
|
|
1346
|
+
const typed = lastTypedForm.value;
|
|
1347
|
+
if (typed !== null && typeof raw === "number" && parseFloat(typed) === raw) {
|
|
1348
|
+
return typed;
|
|
1349
|
+
}
|
|
1350
|
+
return String(raw);
|
|
1351
|
+
});
|
|
1352
|
+
const slimDefault = state.schema.getDefaultAtPath(segments);
|
|
1353
|
+
const persist = options?.persist === true;
|
|
1354
|
+
const acknowledgeSensitive = options?.acknowledgeSensitive === true;
|
|
1355
|
+
const transforms = options?.transforms ?? EMPTY_TRANSFORMS;
|
|
1356
|
+
const coerce = buildCoerceFn(
|
|
1357
|
+
state.schema,
|
|
1358
|
+
segments,
|
|
1359
|
+
state.coerceIndex
|
|
1360
|
+
);
|
|
1361
|
+
const coerceElement = buildElementCoerceFn(
|
|
1362
|
+
state.schema,
|
|
1363
|
+
segments,
|
|
1364
|
+
state.coerceIndex
|
|
1365
|
+
);
|
|
1366
|
+
if (persist && !state.isSSR && !state.modules.has(PERSISTENCE_MODULE_KEY)) {
|
|
1367
|
+
throw new AnonPersistError({
|
|
1368
|
+
cause: "register-without-config",
|
|
1369
|
+
schemaFields: extractSchemaFields(state.schema),
|
|
1370
|
+
callSite: captureUserCallSite()
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
return {
|
|
1374
|
+
innerRef,
|
|
1375
|
+
displayValue,
|
|
1376
|
+
lastTypedForm,
|
|
1377
|
+
markBlank: () => {
|
|
1378
|
+
return state.setValueAtPath(segments, slimDefault, {
|
|
1379
|
+
blank: true,
|
|
1380
|
+
persist
|
|
1381
|
+
});
|
|
1382
|
+
},
|
|
1383
|
+
registerElement: (element) => {
|
|
1384
|
+
if (!INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
|
|
1385
|
+
const added = state.registerElement(segments, element, formInstanceId);
|
|
1386
|
+
if (added) attachFocusListeners(state, segments, element);
|
|
1387
|
+
},
|
|
1388
|
+
deregisterElement: (element) => {
|
|
1389
|
+
detachFocusListeners(element);
|
|
1390
|
+
state.deregisterElement(segments, element);
|
|
1391
|
+
},
|
|
1392
|
+
setValueWithInternalPath: (value, meta) => {
|
|
1393
|
+
return state.setValueAtPath(segments, value, meta);
|
|
1394
|
+
},
|
|
1395
|
+
// Called by the `vRegisterHint` compile-time transform's wrapping
|
|
1396
|
+
// IIFE on every server-side render of `<element v-register="…">`.
|
|
1397
|
+
// Without it, every SSR'd FieldState serialises `isConnected: false`
|
|
1398
|
+
// (because Vue skips directive lifecycle during SSR) and the client
|
|
1399
|
+
// briefly shows that stale flag until hydration runs the directive's
|
|
1400
|
+
// `created` hook. The mark only takes effect when `state.isSSR` is
|
|
1401
|
+
// true; on the client this is a no-op so the directive lifecycle
|
|
1402
|
+
// remains the source of truth.
|
|
1403
|
+
markConnectedOptimistically: () => {
|
|
1404
|
+
state.markConnectedOptimistically(segments);
|
|
1405
|
+
},
|
|
1406
|
+
// --- Persistence opt-in (internal; the directive is the only
|
|
1407
|
+
// legitimate consumer) ---
|
|
1408
|
+
path: pathKey,
|
|
1409
|
+
persist,
|
|
1410
|
+
acknowledgeSensitive,
|
|
1411
|
+
persistOptIns: state.persistOptIns,
|
|
1412
|
+
transforms,
|
|
1413
|
+
coerce,
|
|
1414
|
+
...coerceElement !== void 0 ? { coerceElement } : {}
|
|
1415
|
+
};
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
const unset = Symbol.for("attaform/unset");
|
|
1420
|
+
function isUnset(value) {
|
|
1421
|
+
return value === unset;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function walkUnsetSentinels(values, schema) {
|
|
1425
|
+
const paths = [];
|
|
1426
|
+
if (values === void 0) {
|
|
1427
|
+
const rootSlim = schema.getDefaultAtPath([]);
|
|
1428
|
+
walkUnspecified(rootSlim, [], paths);
|
|
1429
|
+
return { cleanedValues: void 0, paths };
|
|
1430
|
+
}
|
|
1431
|
+
const cleaned = walk(values, [], schema, paths);
|
|
1432
|
+
return { cleanedValues: cleaned, paths };
|
|
1433
|
+
}
|
|
1434
|
+
function walk(input, segments, schema, paths) {
|
|
1435
|
+
if (isUnset(input)) {
|
|
1436
|
+
const slim = schema.getDefaultAtPath(segments);
|
|
1437
|
+
if (!isPrimitiveOrEmpty(slim)) {
|
|
1438
|
+
warnNonPrimitiveLeaf(segments, slim);
|
|
1439
|
+
return walkUnspecified(slim, segments, paths);
|
|
1440
|
+
}
|
|
1441
|
+
paths.push(canonicalizePath(segments).key);
|
|
1442
|
+
return slim;
|
|
1443
|
+
}
|
|
1444
|
+
if (input === void 0) {
|
|
1445
|
+
const slim = schema.getDefaultAtPath(segments);
|
|
1446
|
+
return walkUnspecified(slim, segments, paths);
|
|
1447
|
+
}
|
|
1448
|
+
if (input === null) return null;
|
|
1449
|
+
if (input instanceof Date || input instanceof RegExp || input instanceof Map || input instanceof Set || typeof input === "function") {
|
|
1450
|
+
return input;
|
|
1451
|
+
}
|
|
1452
|
+
if (Array.isArray(input)) {
|
|
1453
|
+
const out = new Array(input.length);
|
|
1454
|
+
for (let i = 0; i < input.length; i++) {
|
|
1455
|
+
out[i] = walk(input[i], [...segments, i], schema, paths);
|
|
1456
|
+
}
|
|
1457
|
+
return out;
|
|
1458
|
+
}
|
|
1459
|
+
if (typeof input === "object") {
|
|
1460
|
+
const slim = schema.getDefaultAtPath(segments);
|
|
1461
|
+
const allKeys = new Set(Object.keys(input));
|
|
1462
|
+
if (slim !== null && slim !== void 0 && typeof slim === "object" && !Array.isArray(slim) && !(slim instanceof Date) && !(slim instanceof RegExp) && !(slim instanceof Map) && !(slim instanceof Set)) {
|
|
1463
|
+
for (const k of Object.keys(slim)) allKeys.add(k);
|
|
1464
|
+
}
|
|
1465
|
+
const out = {};
|
|
1466
|
+
for (const key of allKeys) {
|
|
1467
|
+
out[key] = walk(input[key], [...segments, key], schema, paths);
|
|
1468
|
+
}
|
|
1469
|
+
return out;
|
|
1470
|
+
}
|
|
1471
|
+
return input;
|
|
1472
|
+
}
|
|
1473
|
+
function walkUnspecified(slim, segments, paths) {
|
|
1474
|
+
if (isPrimitiveOrEmpty(slim)) {
|
|
1475
|
+
if (isNumericPrimitive(slim)) {
|
|
1476
|
+
paths.push(canonicalizePath(segments).key);
|
|
1477
|
+
}
|
|
1478
|
+
return slim;
|
|
1479
|
+
}
|
|
1480
|
+
if (slim instanceof Date || slim instanceof RegExp || slim instanceof Map || slim instanceof Set || typeof slim === "function") {
|
|
1481
|
+
return slim;
|
|
1482
|
+
}
|
|
1483
|
+
if (Array.isArray(slim)) return slim;
|
|
1484
|
+
if (slim !== null && typeof slim === "object") {
|
|
1485
|
+
const out = {};
|
|
1486
|
+
for (const key of Object.keys(slim)) {
|
|
1487
|
+
out[key] = walkUnspecified(slim[key], [...segments, key], paths);
|
|
1488
|
+
}
|
|
1489
|
+
return out;
|
|
1490
|
+
}
|
|
1491
|
+
return slim;
|
|
1492
|
+
}
|
|
1493
|
+
function isPrimitiveOrEmpty(value) {
|
|
1494
|
+
if (value === null || value === void 0) return true;
|
|
1495
|
+
const t = typeof value;
|
|
1496
|
+
return t === "string" || t === "number" || t === "boolean" || t === "bigint";
|
|
1497
|
+
}
|
|
1498
|
+
function isNumericPrimitive(value) {
|
|
1499
|
+
const t = typeof value;
|
|
1500
|
+
return t === "number" || t === "bigint";
|
|
1501
|
+
}
|
|
1502
|
+
const warnedNonPrimitivePaths = __DEV__ ? /* @__PURE__ */ new Set() : null;
|
|
1503
|
+
function warnNonPrimitiveLeaf(segments, slim) {
|
|
1504
|
+
if (warnedNonPrimitivePaths === null) return;
|
|
1505
|
+
const dotted = segments.map(String).join(".");
|
|
1506
|
+
if (warnedNonPrimitivePaths.has(dotted)) return;
|
|
1507
|
+
warnedNonPrimitivePaths.add(dotted);
|
|
1508
|
+
const slimType = slim === null ? "null" : slim instanceof Date ? "Date" : typeof slim;
|
|
1509
|
+
console.warn(
|
|
1510
|
+
`[attaform] \`unset\` at "${dotted || "<root>"}" is a no-op \u2014 unset only works at primitive leaves (string / number / boolean / bigint, plus their optional / nullable variants), got "${slimType}". The slim default was written but the path is NOT marked blank. (TypeScript catches this at compile time; this warn covers plain-JS callers.)`
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function buildValuesProxy(form) {
|
|
1515
|
+
const inner = computed(() => readonly(form.value));
|
|
1516
|
+
const target = (() => {
|
|
1517
|
+
});
|
|
1518
|
+
return new Proxy(target, {
|
|
1519
|
+
apply(_, __, args) {
|
|
1520
|
+
const arg = args[0];
|
|
1521
|
+
if (arg === void 0) return inner.value;
|
|
1522
|
+
const { segments } = canonicalizePath(arg);
|
|
1523
|
+
let cursor = inner.value;
|
|
1524
|
+
for (const seg of segments) {
|
|
1525
|
+
if (cursor === null || cursor === void 0) return void 0;
|
|
1526
|
+
cursor = cursor[seg];
|
|
1527
|
+
}
|
|
1528
|
+
return cursor;
|
|
1529
|
+
},
|
|
1530
|
+
get(_, key) {
|
|
1531
|
+
if (typeof key === "symbol") return Reflect.get(target, key);
|
|
1532
|
+
if (key === "toJSON") return () => inner.value;
|
|
1533
|
+
return inner.value[key];
|
|
1534
|
+
},
|
|
1535
|
+
has(_, key) {
|
|
1536
|
+
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
1537
|
+
return Reflect.has(inner.value, key);
|
|
1538
|
+
},
|
|
1539
|
+
ownKeys() {
|
|
1540
|
+
return Reflect.ownKeys(inner.value);
|
|
1541
|
+
},
|
|
1542
|
+
getOwnPropertyDescriptor(_, key) {
|
|
1543
|
+
const desc = Reflect.getOwnPropertyDescriptor(inner.value, key);
|
|
1544
|
+
if (desc !== void 0) desc.configurable = true;
|
|
1545
|
+
return desc;
|
|
1546
|
+
},
|
|
1547
|
+
// Match Vue's `readonly()` semantics: writes warn (in dev) and
|
|
1548
|
+
// silently noop (return true). Returning false would throw
|
|
1549
|
+
// TypeError in strict-mode consumers, surprising users who
|
|
1550
|
+
// assigned through the proxy and expected it to be ignored.
|
|
1551
|
+
set(_, key) {
|
|
1552
|
+
if (__DEV__) {
|
|
1553
|
+
console.warn(
|
|
1554
|
+
`[attaform] form.values is read-only \u2014 write to "${String(key)}" was ignored. Use form.setValue / the directive / field-array helpers instead.`
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
return true;
|
|
1558
|
+
},
|
|
1559
|
+
deleteProperty(_, key) {
|
|
1560
|
+
if (__DEV__) {
|
|
1561
|
+
console.warn(
|
|
1562
|
+
`[attaform] form.values is read-only \u2014 delete of "${String(key)}" was ignored.`
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
return true;
|
|
1566
|
+
},
|
|
1567
|
+
defineProperty: () => true
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
function readonlySetSnapshot(source) {
|
|
1572
|
+
const snapshot = new Set(source);
|
|
1573
|
+
return new Proxy(snapshot, {
|
|
1574
|
+
get(target, prop) {
|
|
1575
|
+
if (prop === "add" || prop === "delete" || prop === "clear") {
|
|
1576
|
+
return () => {
|
|
1577
|
+
throw new TypeError(`Cannot mutate readonly Set: '${String(prop)}' is not allowed.`);
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
const value = Reflect.get(target, prop, target);
|
|
1581
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
function buildFormApi(state, formInstanceId, options = {}) {
|
|
1586
|
+
const register = buildRegister(state, formInstanceId);
|
|
1587
|
+
const processOptions = options.onInvalidSubmit !== void 0 ? { onInvalidSubmit: options.onInvalidSubmit } : {};
|
|
1588
|
+
const {
|
|
1589
|
+
validate: validateBuilt,
|
|
1590
|
+
validateAsync: validateAsyncBuilt,
|
|
1591
|
+
handleSubmit
|
|
1592
|
+
} = buildProcessForm(state, formInstanceId, processOptions);
|
|
1593
|
+
const validate = (pathInput) => validateBuilt(pathInput);
|
|
1594
|
+
const validateAsync = (pathInput) => validateAsyncBuilt(pathInput);
|
|
1595
|
+
function pathToRef(pathInput) {
|
|
1596
|
+
const segments = canonicalizePath(pathInput).segments;
|
|
1597
|
+
return computed(() => getAtPath(state.form.value, segments));
|
|
1598
|
+
}
|
|
1599
|
+
function setValueImpl(pathOrValue, maybeValue) {
|
|
1600
|
+
if (arguments.length === 1) {
|
|
1601
|
+
const next = typeof pathOrValue === "function" ? pathOrValue(state.form.value) : pathOrValue;
|
|
1602
|
+
const walked = walkUnsetSentinels(
|
|
1603
|
+
next,
|
|
1604
|
+
state.schema
|
|
1605
|
+
);
|
|
1606
|
+
const ok = state.setValueAtPath([], walked.cleanedValues);
|
|
1607
|
+
if (!ok) return false;
|
|
1608
|
+
for (const pathKey of walked.paths) {
|
|
1609
|
+
const segments2 = segmentsForPathKey(pathKey);
|
|
1610
|
+
if (segments2 === null) continue;
|
|
1611
|
+
state.setValueAtPath(segments2, state.schema.getDefaultAtPath(segments2), {
|
|
1612
|
+
blank: true
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
return true;
|
|
1616
|
+
}
|
|
1617
|
+
const segments = canonicalizePath(pathOrValue).segments;
|
|
1618
|
+
if (isUnset(maybeValue)) {
|
|
1619
|
+
return state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
|
|
1620
|
+
blank: true
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
let resolvedValue;
|
|
1624
|
+
if (typeof maybeValue === "function") {
|
|
1625
|
+
const current = state.getValueAtPath(segments);
|
|
1626
|
+
const prev = current === void 0 ? state.schema.getDefaultAtPath(segments) : current;
|
|
1627
|
+
resolvedValue = maybeValue(prev);
|
|
1628
|
+
if (isUnset(resolvedValue)) {
|
|
1629
|
+
return state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
|
|
1630
|
+
blank: true
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
} else {
|
|
1634
|
+
resolvedValue = maybeValue;
|
|
1635
|
+
}
|
|
1636
|
+
return state.setValueAtPath(segments, resolvedValue);
|
|
1637
|
+
}
|
|
1638
|
+
const errorsProxy = buildErrorsProxy(state);
|
|
1639
|
+
function setFieldErrors(errors) {
|
|
1640
|
+
state.setAllUserErrors(errors);
|
|
1641
|
+
}
|
|
1642
|
+
function addFieldErrors(errors) {
|
|
1643
|
+
state.addUserErrors(errors);
|
|
1644
|
+
}
|
|
1645
|
+
function clearFieldErrors(path) {
|
|
1646
|
+
if (path === void 0) {
|
|
1647
|
+
state.clearSchemaErrors();
|
|
1648
|
+
state.clearUserErrors();
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
const segments = canonicalizePath(path).segments;
|
|
1652
|
+
state.clearSchemaErrors(segments);
|
|
1653
|
+
state.clearUserErrors(segments);
|
|
1654
|
+
}
|
|
1655
|
+
const isDirty = computed(() => {
|
|
1656
|
+
for (const [, { segments, value: original }] of state.originals) {
|
|
1657
|
+
if (!Object.is(getAtPath(state.form.value, segments), original)) return true;
|
|
1658
|
+
}
|
|
1659
|
+
if (state.blankPaths.size !== state.originalBlankPaths.size) return true;
|
|
1660
|
+
for (const key of state.blankPaths) {
|
|
1661
|
+
if (!state.originalBlankPaths.has(key)) return true;
|
|
1662
|
+
}
|
|
1663
|
+
return false;
|
|
1664
|
+
});
|
|
1665
|
+
const isValid = computed(
|
|
1666
|
+
() => state.schemaErrors.size === 0 && state.userErrors.size === 0 && state.derivedBlankErrors.value.size === 0
|
|
1667
|
+
);
|
|
1668
|
+
const isSubmitting = computed(() => state.isSubmitting.value);
|
|
1669
|
+
const submitCount = computed(() => state.submitCount.value);
|
|
1670
|
+
const submitError = computed(() => state.submitError.value);
|
|
1671
|
+
const isValidating = computed(() => state.activeValidations.value > 0);
|
|
1672
|
+
const history = options.history;
|
|
1673
|
+
const undo = history?.undo ?? (() => false);
|
|
1674
|
+
const redo = history?.redo ?? (() => false);
|
|
1675
|
+
const canUndo = history?.canUndo ?? computed(() => false);
|
|
1676
|
+
const canRedo = history?.canRedo ?? computed(() => false);
|
|
1677
|
+
const historySize = history?.historySize ?? computed(() => 0);
|
|
1678
|
+
const metaErrors = computed(() => {
|
|
1679
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1680
|
+
const collect = (errs) => {
|
|
1681
|
+
for (const [pathKey, list] of errs) {
|
|
1682
|
+
if (list.length === 0) continue;
|
|
1683
|
+
const ordinal = state.ensurePathOrdinal(pathKey);
|
|
1684
|
+
const existing = buckets.get(ordinal);
|
|
1685
|
+
if (existing === void 0) buckets.set(ordinal, [...list]);
|
|
1686
|
+
else existing.push(...list);
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
collect(state.schemaErrors);
|
|
1690
|
+
collect(state.derivedBlankErrors.value);
|
|
1691
|
+
collect(state.userErrors);
|
|
1692
|
+
if (buckets.size === 0) return [];
|
|
1693
|
+
return [...buckets.entries()].sort(([a], [b]) => a - b).flatMap(([, errs]) => errs);
|
|
1694
|
+
});
|
|
1695
|
+
const formMeta = readonly(
|
|
1696
|
+
reactive({
|
|
1697
|
+
isDirty,
|
|
1698
|
+
isValid,
|
|
1699
|
+
isSubmitting,
|
|
1700
|
+
isValidating,
|
|
1701
|
+
submitCount,
|
|
1702
|
+
submitError,
|
|
1703
|
+
canUndo,
|
|
1704
|
+
canRedo,
|
|
1705
|
+
historySize,
|
|
1706
|
+
errors: metaErrors,
|
|
1707
|
+
// Per-`useForm()`-call identity. Stable for one mount; new on
|
|
1708
|
+
// re-mount; orthogonal to `form.key` (which is the user-supplied
|
|
1709
|
+
// shared identifier). Useful for devtools panels disambiguating
|
|
1710
|
+
// shared-key instances, telemetry hooks tagging events with
|
|
1711
|
+
// "which mount", and E2E tests stamping `data-form-id`.
|
|
1712
|
+
instanceId: formInstanceId
|
|
1713
|
+
})
|
|
1714
|
+
);
|
|
1715
|
+
const persistence = state.modules.get(PERSISTENCE_MODULE_KEY);
|
|
1716
|
+
const reset = (nextDefaultValues) => {
|
|
1717
|
+
if (nextDefaultValues === void 0) {
|
|
1718
|
+
state.reset();
|
|
1719
|
+
} else {
|
|
1720
|
+
const walked = walkUnsetSentinels(
|
|
1721
|
+
nextDefaultValues,
|
|
1722
|
+
state.schema
|
|
1723
|
+
);
|
|
1724
|
+
state.reset(walked.cleanedValues);
|
|
1725
|
+
for (const pathKey of walked.paths) {
|
|
1726
|
+
const segments = segmentsForPathKey(pathKey);
|
|
1727
|
+
if (segments === null) continue;
|
|
1728
|
+
state.setValueAtPath(segments, state.schema.getDefaultAtPath(segments), {
|
|
1729
|
+
blank: true
|
|
1730
|
+
});
|
|
1731
|
+
state.originalBlankPaths.add(pathKey);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
if (persistence !== void 0) {
|
|
1735
|
+
void persistence.clearPersistedDraft().catch(() => void 0);
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
const resetField = (pathInput) => {
|
|
1739
|
+
const segments = canonicalizePath(pathInput).segments;
|
|
1740
|
+
state.resetField(segments);
|
|
1741
|
+
if (persistence !== void 0) {
|
|
1742
|
+
void persistence.clearPersistedDraft(segments).catch(() => void 0);
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
const persist = async (pathInput, options2) => {
|
|
1746
|
+
const segments = canonicalizePath(pathInput).segments;
|
|
1747
|
+
enforceSensitiveCheck(segments, options2?.acknowledgeSensitive === true);
|
|
1748
|
+
if (persistence === void 0) return;
|
|
1749
|
+
await persistence.writePathImmediately(segments);
|
|
1750
|
+
};
|
|
1751
|
+
const clearPersistedDraft = async (pathInput) => {
|
|
1752
|
+
if (persistence === void 0) return;
|
|
1753
|
+
if (pathInput === void 0) {
|
|
1754
|
+
await persistence.clearPersistedDraft();
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
const segments = canonicalizePath(pathInput).segments;
|
|
1758
|
+
await persistence.clearPersistedDraft(segments);
|
|
1759
|
+
};
|
|
1760
|
+
const focusFirstError = (options2) => {
|
|
1761
|
+
const target = state.getFirstErrorElement(formInstanceId);
|
|
1762
|
+
if (target === null) return false;
|
|
1763
|
+
target.element.focus(options2);
|
|
1764
|
+
return true;
|
|
1765
|
+
};
|
|
1766
|
+
const scrollToFirstError = (options2) => {
|
|
1767
|
+
const target = state.getFirstErrorElement(formInstanceId);
|
|
1768
|
+
if (target === null) return false;
|
|
1769
|
+
target.element.scrollIntoView(options2);
|
|
1770
|
+
return true;
|
|
1771
|
+
};
|
|
1772
|
+
const fieldArrays = buildFieldArrayApi(state);
|
|
1773
|
+
const blankPathsView = computed(() => {
|
|
1774
|
+
return readonlySetSnapshot(state.blankPaths);
|
|
1775
|
+
});
|
|
1776
|
+
const valuesProxy = buildValuesProxy(state.form);
|
|
1777
|
+
const fieldStateProxy = buildFieldStateProxy(state);
|
|
1778
|
+
return {
|
|
1779
|
+
handleSubmit,
|
|
1780
|
+
// `values` is the callable readonly Proxy. Each `get` trap reads
|
|
1781
|
+
// through `inner.value` (a `computed(() => readonly(form.value))`)
|
|
1782
|
+
// so reactivity tracking propagates at the call site. Identity-
|
|
1783
|
+
// stable across whole-form swaps (the inner readonly proxy
|
|
1784
|
+
// re-keys; the outer callable proxy stays the same instance).
|
|
1785
|
+
values: valuesProxy,
|
|
1786
|
+
fields: fieldStateProxy,
|
|
1787
|
+
setValue: setValueImpl,
|
|
1788
|
+
validate,
|
|
1789
|
+
validateAsync,
|
|
1790
|
+
register,
|
|
1791
|
+
key: state.formKey,
|
|
1792
|
+
errors: errorsProxy,
|
|
1793
|
+
toRef: pathToRef,
|
|
1794
|
+
setFieldErrors,
|
|
1795
|
+
addFieldErrors,
|
|
1796
|
+
clearFieldErrors,
|
|
1797
|
+
meta: formMeta,
|
|
1798
|
+
reset,
|
|
1799
|
+
resetField,
|
|
1800
|
+
persist,
|
|
1801
|
+
clearPersistedDraft,
|
|
1802
|
+
focusFirstError,
|
|
1803
|
+
scrollToFirstError,
|
|
1804
|
+
undo,
|
|
1805
|
+
redo,
|
|
1806
|
+
append: fieldArrays.append,
|
|
1807
|
+
prepend: fieldArrays.prepend,
|
|
1808
|
+
insert: fieldArrays.insert,
|
|
1809
|
+
remove: fieldArrays.remove,
|
|
1810
|
+
swap: fieldArrays.swap,
|
|
1811
|
+
move: fieldArrays.move,
|
|
1812
|
+
replace: fieldArrays.replace,
|
|
1813
|
+
blankPaths: blankPathsView
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
function isDescendable(value) {
|
|
1818
|
+
if (value === null || typeof value !== "object") return false;
|
|
1819
|
+
if (Array.isArray(value)) return true;
|
|
1820
|
+
const proto = Object.getPrototypeOf(value);
|
|
1821
|
+
return proto === null || proto === Object.prototype;
|
|
1822
|
+
}
|
|
1823
|
+
function appendSegment(prefix, segment) {
|
|
1824
|
+
const next = new Array(prefix.length + 1);
|
|
1825
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
1826
|
+
const s = prefix[i];
|
|
1827
|
+
next[i] = s;
|
|
1828
|
+
}
|
|
1829
|
+
next[prefix.length] = segment;
|
|
1830
|
+
return next;
|
|
1831
|
+
}
|
|
1832
|
+
function diffAndApply(oldValue, newValue, prefix, visit) {
|
|
1833
|
+
if (Object.is(oldValue, newValue)) return;
|
|
1834
|
+
const oldIsDescendable = isDescendable(oldValue);
|
|
1835
|
+
const newIsDescendable = isDescendable(newValue);
|
|
1836
|
+
if (oldValue === void 0 && newIsDescendable) {
|
|
1837
|
+
if (Array.isArray(newValue)) {
|
|
1838
|
+
for (let i = 0; i < newValue.length; i++) {
|
|
1839
|
+
diffAndApply(void 0, newValue[i], appendSegment(prefix, i), visit);
|
|
1840
|
+
}
|
|
1841
|
+
} else {
|
|
1842
|
+
const rec = newValue;
|
|
1843
|
+
for (const k of Object.keys(rec)) {
|
|
1844
|
+
diffAndApply(void 0, rec[k], appendSegment(prefix, k), visit);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
if (oldIsDescendable && newValue === void 0) {
|
|
1850
|
+
if (Array.isArray(oldValue)) {
|
|
1851
|
+
for (let i = 0; i < oldValue.length; i++) {
|
|
1852
|
+
diffAndApply(oldValue[i], void 0, appendSegment(prefix, i), visit);
|
|
1853
|
+
}
|
|
1854
|
+
} else {
|
|
1855
|
+
const rec = oldValue;
|
|
1856
|
+
for (const k of Object.keys(rec)) {
|
|
1857
|
+
diffAndApply(rec[k], void 0, appendSegment(prefix, k), visit);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
if (oldIsDescendable && newIsDescendable) {
|
|
1863
|
+
const oldIsArray = Array.isArray(oldValue);
|
|
1864
|
+
const newIsArray = Array.isArray(newValue);
|
|
1865
|
+
if (oldIsArray && newIsArray) {
|
|
1866
|
+
const oldArr = oldValue;
|
|
1867
|
+
const newArr = newValue;
|
|
1868
|
+
const max = Math.max(oldArr.length, newArr.length);
|
|
1869
|
+
for (let i = 0; i < max; i++) {
|
|
1870
|
+
diffAndApply(oldArr[i], newArr[i], appendSegment(prefix, i), visit);
|
|
1871
|
+
}
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
if (!oldIsArray && !newIsArray) {
|
|
1875
|
+
const oldRec = oldValue;
|
|
1876
|
+
const newRec = newValue;
|
|
1877
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1878
|
+
for (const k of Object.keys(oldRec)) {
|
|
1879
|
+
seen.add(k);
|
|
1880
|
+
diffAndApply(oldRec[k], newRec[k], appendSegment(prefix, k), visit);
|
|
1881
|
+
}
|
|
1882
|
+
for (const k of Object.keys(newRec)) {
|
|
1883
|
+
if (seen.has(k)) continue;
|
|
1884
|
+
diffAndApply(oldRec[k], newRec[k], appendSegment(prefix, k), visit);
|
|
1885
|
+
}
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
visit({ kind: "changed", path: prefix, oldValue, newValue });
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (oldIsDescendable && !newIsDescendable) {
|
|
1892
|
+
visit({ kind: "changed", path: prefix, oldValue, newValue });
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
if (!oldIsDescendable && newIsDescendable) {
|
|
1896
|
+
visit({ kind: "changed", path: prefix, oldValue, newValue });
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
if (oldValue === void 0) {
|
|
1900
|
+
visit({ kind: "added", path: prefix, newValue });
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
if (newValue === void 0) {
|
|
1904
|
+
visit({ kind: "removed", path: prefix, oldValue });
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
visit({ kind: "changed", path: prefix, oldValue, newValue });
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
function isHydratedFieldRecord(value) {
|
|
1911
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1912
|
+
const r = value;
|
|
1913
|
+
return Array.isArray(r.path) && (typeof r.updatedAt === "string" || r.updatedAt === null) && typeof r.isConnected === "boolean" && (typeof r.focused === "boolean" || r.focused === null) && (typeof r.blurred === "boolean" || r.blurred === null) && (typeof r.touched === "boolean" || r.touched === null);
|
|
1914
|
+
}
|
|
1915
|
+
function isHydratedValidationErrorArray(value) {
|
|
1916
|
+
if (!Array.isArray(value)) return false;
|
|
1917
|
+
for (const entry of value) {
|
|
1918
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
1919
|
+
const e = entry;
|
|
1920
|
+
if (typeof e.message !== "string") return false;
|
|
1921
|
+
if (!Array.isArray(e.path)) return false;
|
|
1922
|
+
if (typeof e.formKey !== "string") return false;
|
|
1923
|
+
if (typeof e.code !== "string") return false;
|
|
1924
|
+
}
|
|
1925
|
+
return true;
|
|
1926
|
+
}
|
|
1927
|
+
function warnMalformedHydration(formKey, kind, rawKey) {
|
|
1928
|
+
if (!__DEV__) return;
|
|
1929
|
+
console.warn(
|
|
1930
|
+
`[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).`
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
function isPathKeyUnder(existingKey, parentPath) {
|
|
1934
|
+
const parsed = segmentsForPathKey(existingKey);
|
|
1935
|
+
if (parsed === null) return false;
|
|
1936
|
+
if (parsed.length <= parentPath.length) return false;
|
|
1937
|
+
for (let i = 0; i < parentPath.length; i++) {
|
|
1938
|
+
if (parsed[i] !== parentPath[i]) return false;
|
|
1939
|
+
}
|
|
1940
|
+
return true;
|
|
1941
|
+
}
|
|
1942
|
+
function createFormStore(options) {
|
|
1943
|
+
const { formKey, schema, defaultValues, strict = true, hydration } = options;
|
|
1944
|
+
const isSSR = options.isSSR === true;
|
|
1945
|
+
const rememberVariants = options.rememberVariants !== false;
|
|
1946
|
+
const fieldValidationMode = options.validateOn ?? "change";
|
|
1947
|
+
const fieldValidationDebounceMs = options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS;
|
|
1948
|
+
const fieldValidationState = /* @__PURE__ */ new Map();
|
|
1949
|
+
const formChangeListeners = /* @__PURE__ */ new Set();
|
|
1950
|
+
const submitSuccessListeners = /* @__PURE__ */ new Set();
|
|
1951
|
+
const resetListeners = /* @__PURE__ */ new Set();
|
|
1952
|
+
const persistOptIns = createPersistOptInRegistry();
|
|
1953
|
+
const coerceIndex = resolveCoercionIndex(options.coerce);
|
|
1954
|
+
const cleanupHooks = [];
|
|
1955
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1956
|
+
const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
|
|
1957
|
+
const schemaResponse = schema.getDefaultValues({
|
|
1958
|
+
useDefaultSchemaValues: true,
|
|
1959
|
+
constraints: completedConstraints,
|
|
1960
|
+
strict
|
|
1961
|
+
});
|
|
1962
|
+
const schemaInitialData = schemaResponse.data;
|
|
1963
|
+
const initialData = hydration !== void 0 ? hydration.form : schemaInitialData;
|
|
1964
|
+
const form = ref(initialData);
|
|
1965
|
+
const fields = reactive(/* @__PURE__ */ new Map());
|
|
1966
|
+
const elements = reactive(/* @__PURE__ */ new Map());
|
|
1967
|
+
const elementToFormInstance = /* @__PURE__ */ new WeakMap();
|
|
1968
|
+
let sortedRegistrationsCache = null;
|
|
1969
|
+
const schemaErrors = reactive(/* @__PURE__ */ new Map());
|
|
1970
|
+
const userErrors = reactive(/* @__PURE__ */ new Map());
|
|
1971
|
+
const originals = /* @__PURE__ */ new Map();
|
|
1972
|
+
const initialTransientList = hydration?.blankPaths ?? options.initialBlankPaths ?? [];
|
|
1973
|
+
const blankPaths = reactive(/* @__PURE__ */ new Set());
|
|
1974
|
+
const originalBlankPaths = /* @__PURE__ */ new Set();
|
|
1975
|
+
for (const raw of initialTransientList) {
|
|
1976
|
+
blankPaths.add(raw);
|
|
1977
|
+
originalBlankPaths.add(raw);
|
|
1978
|
+
}
|
|
1979
|
+
const variantMemory = /* @__PURE__ */ new Map();
|
|
1980
|
+
const pathOrdinals = /* @__PURE__ */ new Map();
|
|
1981
|
+
let nextOrdinal = 0;
|
|
1982
|
+
function ensurePathOrdinal(key) {
|
|
1983
|
+
let ordinal = pathOrdinals.get(key);
|
|
1984
|
+
if (ordinal === void 0) {
|
|
1985
|
+
ordinal = nextOrdinal;
|
|
1986
|
+
pathOrdinals.set(key, ordinal);
|
|
1987
|
+
nextOrdinal += 1;
|
|
1988
|
+
}
|
|
1989
|
+
return ordinal;
|
|
1990
|
+
}
|
|
1991
|
+
const derivedBlankErrors = computed(() => {
|
|
1992
|
+
const result = /* @__PURE__ */ new Map();
|
|
1993
|
+
if (blankPaths.size === 0) return result;
|
|
1994
|
+
for (const pathKey of blankPaths) {
|
|
1995
|
+
const segments = segmentsForPathKey(pathKey);
|
|
1996
|
+
if (segments === null) continue;
|
|
1997
|
+
if (!schema.isRequiredAtPath(segments)) continue;
|
|
1998
|
+
result.set(pathKey, [
|
|
1999
|
+
{
|
|
2000
|
+
message: "No value supplied",
|
|
2001
|
+
path: [...segments],
|
|
2002
|
+
formKey,
|
|
2003
|
+
code: AttaformErrorCode.NoValueSupplied
|
|
2004
|
+
}
|
|
2005
|
+
]);
|
|
2006
|
+
}
|
|
2007
|
+
return result;
|
|
2008
|
+
});
|
|
2009
|
+
const isSubmitting = ref(false);
|
|
2010
|
+
const activeSubmissions = ref(0);
|
|
2011
|
+
const submitCount = ref(0);
|
|
2012
|
+
const submitError = ref(null);
|
|
2013
|
+
const submissionGeneration = ref(0);
|
|
2014
|
+
const activeValidations = ref(0);
|
|
2015
|
+
const initStamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2016
|
+
diffAndApply({}, schemaInitialData, [], (patch) => {
|
|
2017
|
+
if (patch.kind !== "added") return;
|
|
2018
|
+
const { key } = canonicalizePath(patch.path);
|
|
2019
|
+
originals.set(key, { segments: patch.path, value: patch.newValue });
|
|
2020
|
+
ensurePathOrdinal(key);
|
|
2021
|
+
});
|
|
2022
|
+
if (hydration !== void 0) {
|
|
2023
|
+
for (const [rawKey, record] of hydration.fields) {
|
|
2024
|
+
if (typeof rawKey !== "string" || !isHydratedFieldRecord(record)) {
|
|
2025
|
+
warnMalformedHydration(formKey, "FieldRecord", String(rawKey));
|
|
2026
|
+
continue;
|
|
2027
|
+
}
|
|
2028
|
+
fields.set(rawKey, record);
|
|
2029
|
+
}
|
|
2030
|
+
for (const [rawKey, errs] of hydration.schemaErrors) {
|
|
2031
|
+
if (typeof rawKey !== "string" || !isHydratedValidationErrorArray(errs)) {
|
|
2032
|
+
warnMalformedHydration(formKey, "schemaErrors", String(rawKey));
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
schemaErrors.set(rawKey, errs);
|
|
2036
|
+
}
|
|
2037
|
+
for (const [rawKey, errs] of hydration.userErrors) {
|
|
2038
|
+
if (typeof rawKey !== "string" || !isHydratedValidationErrorArray(errs)) {
|
|
2039
|
+
warnMalformedHydration(formKey, "userErrors", String(rawKey));
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
userErrors.set(rawKey, errs);
|
|
2043
|
+
}
|
|
2044
|
+
} else {
|
|
2045
|
+
diffAndApply({}, initialData, [], (patch) => {
|
|
2046
|
+
if (patch.kind !== "added") return;
|
|
2047
|
+
const { key } = canonicalizePath(patch.path);
|
|
2048
|
+
fields.set(key, {
|
|
2049
|
+
path: patch.path,
|
|
2050
|
+
updatedAt: initStamp,
|
|
2051
|
+
isConnected: false,
|
|
2052
|
+
focused: null,
|
|
2053
|
+
blurred: null,
|
|
2054
|
+
touched: null
|
|
2055
|
+
});
|
|
2056
|
+
});
|
|
2057
|
+
if (strict && !schemaResponse.success) {
|
|
2058
|
+
setAllSchemaErrors(schemaResponse.errors);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
if (!isSSR && strict && schema.needsAsyncValidation?.() === true) {
|
|
2062
|
+
queueMicrotask(() => scheduleFieldValidation(
|
|
2063
|
+
[],
|
|
2064
|
+
true
|
|
2065
|
+
/* immediate */
|
|
2066
|
+
));
|
|
2067
|
+
}
|
|
2068
|
+
function touchFieldRecord(pathKey, path, patch) {
|
|
2069
|
+
const current = fields.get(pathKey);
|
|
2070
|
+
fields.set(pathKey, {
|
|
2071
|
+
path,
|
|
2072
|
+
updatedAt: patch.updatedAt ?? current?.updatedAt ?? null,
|
|
2073
|
+
isConnected: patch.isConnected ?? current?.isConnected ?? false,
|
|
2074
|
+
focused: patch.focused ?? current?.focused ?? null,
|
|
2075
|
+
blurred: patch.blurred ?? current?.blurred ?? null,
|
|
2076
|
+
touched: patch.touched ?? current?.touched ?? null
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
function applyFormReplacement(next, meta) {
|
|
2080
|
+
const prev = form.value;
|
|
2081
|
+
if (Object.is(prev, next)) return;
|
|
2082
|
+
form.value = next;
|
|
2083
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2084
|
+
diffAndApply(prev, next, [], (patch) => {
|
|
2085
|
+
const { key } = canonicalizePath(patch.path);
|
|
2086
|
+
if (patch.kind === "added" && !originals.has(key)) {
|
|
2087
|
+
originals.set(key, { segments: patch.path, value: void 0 });
|
|
2088
|
+
}
|
|
2089
|
+
touchFieldRecord(key, patch.path, { updatedAt: now });
|
|
2090
|
+
});
|
|
2091
|
+
for (const listener of formChangeListeners) {
|
|
2092
|
+
try {
|
|
2093
|
+
listener(next, meta);
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
console.error("[attaform] onFormChange threw:", err);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
function setValueAtPath(path, value, meta) {
|
|
2100
|
+
if (!isSlimPrimitiveValid(schema, form, path, value)) {
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
if (meta?.skipDiscriminatorReshape !== true) {
|
|
2104
|
+
if (path.length > 0) {
|
|
2105
|
+
const last = path[path.length - 1];
|
|
2106
|
+
if (typeof last === "string") {
|
|
2107
|
+
const parentPath = path.slice(0, -1);
|
|
2108
|
+
const parentDU = schema.getUnionDiscriminatorAtPath(parentPath);
|
|
2109
|
+
if (parentDU?.discriminatorKey === last) {
|
|
2110
|
+
const oldValue = getAtPath(form.value, path);
|
|
2111
|
+
if (!Object.is(oldValue, value)) {
|
|
2112
|
+
const variantDefault = parentDU.getVariantDefault(value);
|
|
2113
|
+
if (variantDefault !== void 0) {
|
|
2114
|
+
return reshapeUnionVariant(
|
|
2115
|
+
parentPath,
|
|
2116
|
+
oldValue,
|
|
2117
|
+
value,
|
|
2118
|
+
variantDefault,
|
|
2119
|
+
void 0,
|
|
2120
|
+
meta
|
|
2121
|
+
);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
if (isPlainRecord(value)) {
|
|
2128
|
+
const selfDU = schema.getUnionDiscriminatorAtPath(path);
|
|
2129
|
+
if (selfDU !== void 0) {
|
|
2130
|
+
const valueRecord = value;
|
|
2131
|
+
const discValue = valueRecord[selfDU.discriminatorKey];
|
|
2132
|
+
if (discValue !== void 0) {
|
|
2133
|
+
const variantDefault = selfDU.getVariantDefault(discValue);
|
|
2134
|
+
if (variantDefault !== void 0 && isPlainRecord(variantDefault)) {
|
|
2135
|
+
const currentUnionValue = getAtPath(form.value, path);
|
|
2136
|
+
const oldDiscValue = isPlainRecord(currentUnionValue) ? currentUnionValue[selfDU.discriminatorKey] : void 0;
|
|
2137
|
+
return reshapeUnionVariant(
|
|
2138
|
+
path,
|
|
2139
|
+
oldDiscValue,
|
|
2140
|
+
discValue,
|
|
2141
|
+
variantDefault,
|
|
2142
|
+
valueRecord,
|
|
2143
|
+
meta
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
const pathKey = canonicalizePath(path).key;
|
|
2151
|
+
if (meta?.blank === true) {
|
|
2152
|
+
blankPaths.add(pathKey);
|
|
2153
|
+
} else if (blankPaths.has(pathKey)) {
|
|
2154
|
+
blankPaths.delete(pathKey);
|
|
2155
|
+
}
|
|
2156
|
+
const completedValue = mergeStructural(schema, path, value);
|
|
2157
|
+
const currentValue = getAtPath(form.value, path);
|
|
2158
|
+
if (Object.is(currentValue, completedValue)) {
|
|
2159
|
+
return true;
|
|
2160
|
+
}
|
|
2161
|
+
const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
|
|
2162
|
+
applyFormReplacement(nextForm, meta);
|
|
2163
|
+
if (fieldValidationMode === "change") {
|
|
2164
|
+
scheduleFieldValidation(
|
|
2165
|
+
path,
|
|
2166
|
+
false
|
|
2167
|
+
/* debounced */
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
return true;
|
|
2171
|
+
}
|
|
2172
|
+
function reshapeUnionVariant(parentPath, oldDiscValue, newDiscValue, variantDefault, consumerOverrides, meta) {
|
|
2173
|
+
const sameDisc = Object.is(oldDiscValue, newDiscValue);
|
|
2174
|
+
const parentKey = canonicalizePath(parentPath).key;
|
|
2175
|
+
let baseline = variantDefault;
|
|
2176
|
+
let restoredBlanks;
|
|
2177
|
+
if (rememberVariants && !sameDisc) {
|
|
2178
|
+
if (oldDiscValue !== void 0) {
|
|
2179
|
+
const currentValue2 = JSON.parse(JSON.stringify(getAtPath(form.value, parentPath)));
|
|
2180
|
+
const outgoingBlanks = [];
|
|
2181
|
+
for (const k of blankPaths) {
|
|
2182
|
+
if (isPathKeyUnder(k, parentPath)) outgoingBlanks.push(k);
|
|
2183
|
+
}
|
|
2184
|
+
let memoryForUnion2 = variantMemory.get(parentKey);
|
|
2185
|
+
if (memoryForUnion2 === void 0) {
|
|
2186
|
+
memoryForUnion2 = /* @__PURE__ */ new Map();
|
|
2187
|
+
variantMemory.set(parentKey, memoryForUnion2);
|
|
2188
|
+
}
|
|
2189
|
+
memoryForUnion2.set(oldDiscValue, {
|
|
2190
|
+
value: currentValue2,
|
|
2191
|
+
blankPaths: outgoingBlanks
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
const memoryForUnion = variantMemory.get(parentKey);
|
|
2195
|
+
const restored = memoryForUnion?.get(newDiscValue);
|
|
2196
|
+
if (restored !== void 0) {
|
|
2197
|
+
baseline = restored.value;
|
|
2198
|
+
restoredBlanks = [...restored.blankPaths];
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
const finalValue = consumerOverrides !== void 0 ? { ...baseline, ...consumerOverrides } : baseline;
|
|
2202
|
+
let newBlankPaths;
|
|
2203
|
+
if (restoredBlanks !== void 0) {
|
|
2204
|
+
newBlankPaths = restoredBlanks;
|
|
2205
|
+
} else {
|
|
2206
|
+
newBlankPaths = [];
|
|
2207
|
+
walkUnspecified(finalValue, [...parentPath], newBlankPaths);
|
|
2208
|
+
}
|
|
2209
|
+
const survivingBlankKeys = new Set(newBlankPaths);
|
|
2210
|
+
for (const existingKey of [...blankPaths]) {
|
|
2211
|
+
if (isPathKeyUnder(existingKey, parentPath) && !survivingBlankKeys.has(existingKey)) {
|
|
2212
|
+
blankPaths.delete(existingKey);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
const currentValue = getAtPath(form.value, parentPath);
|
|
2216
|
+
if (Object.is(currentValue, finalValue)) {
|
|
2217
|
+
for (const k of newBlankPaths) blankPaths.add(k);
|
|
2218
|
+
return true;
|
|
2219
|
+
}
|
|
2220
|
+
const nextForm = parentPath.length === 0 ? finalValue : setAtPath(form.value, parentPath, finalValue);
|
|
2221
|
+
let appliedSync = false;
|
|
2222
|
+
if (fieldValidationMode === "change") {
|
|
2223
|
+
const syncOrPromise = schema.validateAtPath(finalValue, parentPath, { sync: true });
|
|
2224
|
+
if (!(syncOrPromise instanceof Promise)) {
|
|
2225
|
+
const reStamped = syncOrPromise.success ? [] : syncOrPromise.errors.map((err) => ({
|
|
2226
|
+
...err,
|
|
2227
|
+
path: [...parentPath, ...err.path]
|
|
2228
|
+
}));
|
|
2229
|
+
applySchemaErrorsForSubtree(parentPath, reStamped);
|
|
2230
|
+
const { key: parentKey2 } = canonicalizePath(parentPath);
|
|
2231
|
+
const prevValidation = fieldValidationState.get(parentKey2);
|
|
2232
|
+
if (prevValidation !== void 0) {
|
|
2233
|
+
if (prevValidation.timer !== null) clearTimeout(prevValidation.timer);
|
|
2234
|
+
prevValidation.controller.abort();
|
|
2235
|
+
fieldValidationState.delete(parentKey2);
|
|
2236
|
+
}
|
|
2237
|
+
appliedSync = true;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
applyFormReplacement(nextForm, meta);
|
|
2241
|
+
for (const k of newBlankPaths) blankPaths.add(k);
|
|
2242
|
+
if (fieldValidationMode === "change" && !appliedSync) {
|
|
2243
|
+
scheduleFieldValidation(
|
|
2244
|
+
parentPath,
|
|
2245
|
+
false
|
|
2246
|
+
/* debounced */
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
return true;
|
|
2250
|
+
}
|
|
2251
|
+
function scheduleFieldValidation(path, immediate) {
|
|
2252
|
+
if (fieldValidationMode === "submit") return;
|
|
2253
|
+
const { key } = canonicalizePath(path);
|
|
2254
|
+
const prev = fieldValidationState.get(key);
|
|
2255
|
+
if (prev !== void 0) {
|
|
2256
|
+
if (prev.timer !== null) clearTimeout(prev.timer);
|
|
2257
|
+
prev.controller.abort();
|
|
2258
|
+
}
|
|
2259
|
+
const controller = new AbortController();
|
|
2260
|
+
const fresh = { controller, timer: null };
|
|
2261
|
+
fieldValidationState.set(key, fresh);
|
|
2262
|
+
const run = () => {
|
|
2263
|
+
fresh.timer = null;
|
|
2264
|
+
if (controller.signal.aborted) return;
|
|
2265
|
+
const data = getAtPath(form.value, path);
|
|
2266
|
+
activeValidations.value += 1;
|
|
2267
|
+
void Promise.resolve().then(() => schema.validateAtPath(data, path)).then((response) => {
|
|
2268
|
+
if (controller.signal.aborted) return;
|
|
2269
|
+
const reStamped = response.success ? [] : response.errors.map((err) => ({
|
|
2270
|
+
...err,
|
|
2271
|
+
path: [...path, ...err.path]
|
|
2272
|
+
}));
|
|
2273
|
+
applySchemaErrorsForSubtree(path, reStamped);
|
|
2274
|
+
}).catch(() => {
|
|
2275
|
+
}).finally(() => {
|
|
2276
|
+
activeValidations.value = Math.max(0, activeValidations.value - 1);
|
|
2277
|
+
});
|
|
2278
|
+
};
|
|
2279
|
+
if (immediate || fieldValidationDebounceMs === 0) {
|
|
2280
|
+
run();
|
|
2281
|
+
} else {
|
|
2282
|
+
fresh.timer = setTimeout(run, fieldValidationDebounceMs);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
function cancelFieldValidation() {
|
|
2286
|
+
for (const entry of fieldValidationState.values()) {
|
|
2287
|
+
if (entry.timer !== null) clearTimeout(entry.timer);
|
|
2288
|
+
entry.controller.abort();
|
|
2289
|
+
}
|
|
2290
|
+
fieldValidationState.clear();
|
|
2291
|
+
}
|
|
2292
|
+
function onFormChange(listener) {
|
|
2293
|
+
formChangeListeners.add(listener);
|
|
2294
|
+
return () => {
|
|
2295
|
+
formChangeListeners.delete(listener);
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
function onSubmitSuccess(listener) {
|
|
2299
|
+
submitSuccessListeners.add(listener);
|
|
2300
|
+
return () => {
|
|
2301
|
+
submitSuccessListeners.delete(listener);
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
function onReset(listener) {
|
|
2305
|
+
resetListeners.add(listener);
|
|
2306
|
+
return () => {
|
|
2307
|
+
resetListeners.delete(listener);
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
function emitSubmitSuccess() {
|
|
2311
|
+
for (const listener of submitSuccessListeners) {
|
|
2312
|
+
try {
|
|
2313
|
+
listener();
|
|
2314
|
+
} catch (err) {
|
|
2315
|
+
console.error("[attaform] onSubmitSuccess threw:", err);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
function registerCleanup(fn) {
|
|
2320
|
+
cleanupHooks.push(fn);
|
|
2321
|
+
}
|
|
2322
|
+
const drainHooks = [];
|
|
2323
|
+
function registerDrain(fn) {
|
|
2324
|
+
drainHooks.push(fn);
|
|
2325
|
+
}
|
|
2326
|
+
async function awaitPendingWrites() {
|
|
2327
|
+
if (drainHooks.length === 0) return;
|
|
2328
|
+
await Promise.allSettled(drainHooks.map((fn) => fn()));
|
|
2329
|
+
}
|
|
2330
|
+
function dispose() {
|
|
2331
|
+
for (const hook of cleanupHooks) {
|
|
2332
|
+
try {
|
|
2333
|
+
hook();
|
|
2334
|
+
} catch (err) {
|
|
2335
|
+
console.error("[attaform] cleanup threw:", err);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
cleanupHooks.length = 0;
|
|
2339
|
+
drainHooks.length = 0;
|
|
2340
|
+
modules.clear();
|
|
2341
|
+
cancelFieldValidation();
|
|
2342
|
+
formChangeListeners.clear();
|
|
2343
|
+
submitSuccessListeners.clear();
|
|
2344
|
+
resetListeners.clear();
|
|
2345
|
+
persistOptIns.clear();
|
|
2346
|
+
}
|
|
2347
|
+
function getValueAtPath(path) {
|
|
2348
|
+
return getAtPath(form.value, path);
|
|
2349
|
+
}
|
|
2350
|
+
function appendErrorsTo(map, entries) {
|
|
2351
|
+
for (const err of entries) {
|
|
2352
|
+
const { key } = canonicalizePath(err.path);
|
|
2353
|
+
const current = map.get(key);
|
|
2354
|
+
if (current === void 0) {
|
|
2355
|
+
map.set(key, [err]);
|
|
2356
|
+
} else {
|
|
2357
|
+
map.set(key, [...current, err]);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
function replaceErrorsIn(map, entries) {
|
|
2362
|
+
map.clear();
|
|
2363
|
+
appendErrorsTo(map, entries);
|
|
2364
|
+
}
|
|
2365
|
+
function clearErrorsIn(map, path) {
|
|
2366
|
+
if (path === void 0) {
|
|
2367
|
+
map.clear();
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
const { key } = canonicalizePath(path);
|
|
2371
|
+
map.delete(key);
|
|
2372
|
+
}
|
|
2373
|
+
function setSchemaErrorsForPath(path, entries) {
|
|
2374
|
+
const { key } = canonicalizePath(path);
|
|
2375
|
+
if (entries.length === 0) {
|
|
2376
|
+
schemaErrors.delete(key);
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
schemaErrors.set(key, [...entries]);
|
|
2380
|
+
}
|
|
2381
|
+
function applySchemaErrorsForSubtree(path, entries) {
|
|
2382
|
+
const { key: parentKey } = canonicalizePath(path);
|
|
2383
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2384
|
+
for (const err of entries) {
|
|
2385
|
+
const { key } = canonicalizePath(err.path);
|
|
2386
|
+
const list = grouped.get(key);
|
|
2387
|
+
if (list === void 0) grouped.set(key, [err]);
|
|
2388
|
+
else list.push(err);
|
|
2389
|
+
}
|
|
2390
|
+
if (!grouped.has(parentKey)) schemaErrors.delete(parentKey);
|
|
2391
|
+
for (const existingKey of [...schemaErrors.keys()]) {
|
|
2392
|
+
if (isPathKeyUnder(existingKey, path) && !grouped.has(existingKey)) {
|
|
2393
|
+
schemaErrors.delete(existingKey);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
for (const [leafKey, group] of grouped) {
|
|
2397
|
+
schemaErrors.set(leafKey, group);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
function setAllSchemaErrors(entries) {
|
|
2401
|
+
replaceErrorsIn(schemaErrors, entries);
|
|
2402
|
+
}
|
|
2403
|
+
function clearSchemaErrors(path) {
|
|
2404
|
+
clearErrorsIn(schemaErrors, path);
|
|
2405
|
+
}
|
|
2406
|
+
function setAllUserErrors(entries) {
|
|
2407
|
+
replaceErrorsIn(userErrors, entries);
|
|
2408
|
+
}
|
|
2409
|
+
function addUserErrors(entries) {
|
|
2410
|
+
appendErrorsTo(userErrors, entries);
|
|
2411
|
+
}
|
|
2412
|
+
function clearUserErrors(path) {
|
|
2413
|
+
clearErrorsIn(userErrors, path);
|
|
2414
|
+
}
|
|
2415
|
+
function getErrorsForPath(path) {
|
|
2416
|
+
const { key } = canonicalizePath(path);
|
|
2417
|
+
const schemaForKey = schemaErrors.get(key);
|
|
2418
|
+
const userForKey = userErrors.get(key);
|
|
2419
|
+
const blankForKey = derivedBlankErrors.value.get(key);
|
|
2420
|
+
if (schemaForKey === void 0 && userForKey === void 0 && blankForKey === void 0) {
|
|
2421
|
+
return [];
|
|
2422
|
+
}
|
|
2423
|
+
const result = [];
|
|
2424
|
+
if (schemaForKey !== void 0) result.push(...schemaForKey);
|
|
2425
|
+
if (blankForKey !== void 0) result.push(...blankForKey);
|
|
2426
|
+
if (userForKey !== void 0) result.push(...userForKey);
|
|
2427
|
+
return result;
|
|
2428
|
+
}
|
|
2429
|
+
function registerElement(path, element, formInstanceId) {
|
|
2430
|
+
const { key } = canonicalizePath(path);
|
|
2431
|
+
const record = elements.get(key);
|
|
2432
|
+
if (record === void 0) {
|
|
2433
|
+
elements.set(key, { path, elements: /* @__PURE__ */ new Set([element]) });
|
|
2434
|
+
} else {
|
|
2435
|
+
if (record.elements.has(element)) return false;
|
|
2436
|
+
record.elements.add(element);
|
|
2437
|
+
}
|
|
2438
|
+
elementToFormInstance.set(element, formInstanceId);
|
|
2439
|
+
sortedRegistrationsCache = null;
|
|
2440
|
+
touchFieldRecord(key, path, { isConnected: true });
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
function deregisterElement(path, element) {
|
|
2444
|
+
const { key } = canonicalizePath(path);
|
|
2445
|
+
const record = elements.get(key);
|
|
2446
|
+
if (record === void 0) return 0;
|
|
2447
|
+
const removed = record.elements.delete(element);
|
|
2448
|
+
if (removed) {
|
|
2449
|
+
elementToFormInstance.delete(element);
|
|
2450
|
+
sortedRegistrationsCache = null;
|
|
2451
|
+
}
|
|
2452
|
+
const remaining = record.elements.size;
|
|
2453
|
+
if (remaining === 0) {
|
|
2454
|
+
elements.delete(key);
|
|
2455
|
+
touchFieldRecord(key, path, { isConnected: false });
|
|
2456
|
+
}
|
|
2457
|
+
return remaining;
|
|
2458
|
+
}
|
|
2459
|
+
function markConnectedOptimistically(path) {
|
|
2460
|
+
if (!isSSR) return;
|
|
2461
|
+
const { key } = canonicalizePath(path);
|
|
2462
|
+
const current = fields.get(key);
|
|
2463
|
+
if (current?.isConnected === true) return;
|
|
2464
|
+
touchFieldRecord(key, path, { isConnected: true });
|
|
2465
|
+
}
|
|
2466
|
+
function markFocused(path, focused) {
|
|
2467
|
+
const { key } = canonicalizePath(path);
|
|
2468
|
+
touchFieldRecord(key, path, {
|
|
2469
|
+
focused,
|
|
2470
|
+
blurred: !focused,
|
|
2471
|
+
// `touched` flips to true on blur and stays true thereafter; while
|
|
2472
|
+
// a field is currently focused we keep whatever value it held.
|
|
2473
|
+
touched: focused ? fields.get(key)?.touched ?? null : true
|
|
2474
|
+
});
|
|
2475
|
+
if (!focused && fieldValidationMode === "blur") {
|
|
2476
|
+
scheduleFieldValidation(
|
|
2477
|
+
path,
|
|
2478
|
+
true
|
|
2479
|
+
/* immediate */
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
function markTouched(path) {
|
|
2484
|
+
const { key } = canonicalizePath(path);
|
|
2485
|
+
touchFieldRecord(key, path, { touched: true });
|
|
2486
|
+
}
|
|
2487
|
+
function reset(nextDefaultValues) {
|
|
2488
|
+
const next = schema.getDefaultValues({
|
|
2489
|
+
useDefaultSchemaValues: true,
|
|
2490
|
+
constraints: nextDefaultValues ?? defaultValues,
|
|
2491
|
+
strict
|
|
2492
|
+
}).data;
|
|
2493
|
+
applyFormReplacement(next);
|
|
2494
|
+
originals.clear();
|
|
2495
|
+
diffAndApply({}, next, [], (patch) => {
|
|
2496
|
+
if (patch.kind !== "added") return;
|
|
2497
|
+
const { key } = canonicalizePath(patch.path);
|
|
2498
|
+
originals.set(key, { segments: patch.path, value: patch.newValue });
|
|
2499
|
+
});
|
|
2500
|
+
if (nextDefaultValues !== void 0) {
|
|
2501
|
+
blankPaths.clear();
|
|
2502
|
+
originalBlankPaths.clear();
|
|
2503
|
+
} else {
|
|
2504
|
+
blankPaths.clear();
|
|
2505
|
+
for (const key of originalBlankPaths) {
|
|
2506
|
+
blankPaths.add(key);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
schemaErrors.clear();
|
|
2510
|
+
userErrors.clear();
|
|
2511
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2512
|
+
for (const [pathKey, record] of fields) {
|
|
2513
|
+
fields.set(pathKey, {
|
|
2514
|
+
path: record.path,
|
|
2515
|
+
updatedAt: now,
|
|
2516
|
+
isConnected: record.isConnected,
|
|
2517
|
+
focused: null,
|
|
2518
|
+
blurred: null,
|
|
2519
|
+
touched: null
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
submissionGeneration.value += 1;
|
|
2523
|
+
isSubmitting.value = false;
|
|
2524
|
+
activeSubmissions.value = 0;
|
|
2525
|
+
submitCount.value = 0;
|
|
2526
|
+
submitError.value = null;
|
|
2527
|
+
cancelFieldValidation();
|
|
2528
|
+
variantMemory.clear();
|
|
2529
|
+
for (const listener of resetListeners) {
|
|
2530
|
+
try {
|
|
2531
|
+
listener();
|
|
2532
|
+
} catch (err) {
|
|
2533
|
+
console.error("[attaform] onReset threw:", err);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
function resetField(path) {
|
|
2538
|
+
const { key: targetKey, segments: targetSegments } = canonicalizePath(path);
|
|
2539
|
+
for (const memKey of [...variantMemory.keys()]) {
|
|
2540
|
+
const memSegments = segmentsForPathKey(memKey);
|
|
2541
|
+
if (memSegments === null) continue;
|
|
2542
|
+
if (isPathPrefix(targetSegments, memSegments)) {
|
|
2543
|
+
variantMemory.delete(memKey);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
const leafEntry = originals.get(targetKey);
|
|
2547
|
+
if (leafEntry !== void 0) {
|
|
2548
|
+
const wrote = setValueAtPath(targetSegments, leafEntry.value);
|
|
2549
|
+
if (!wrote) {
|
|
2550
|
+
console.error(
|
|
2551
|
+
`[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.`
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
schemaErrors.delete(targetKey);
|
|
2555
|
+
userErrors.delete(targetKey);
|
|
2556
|
+
clearFieldRecordFlags(targetKey);
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
let subtree = void 0;
|
|
2560
|
+
let anyMatch = false;
|
|
2561
|
+
for (const [, entry] of originals) {
|
|
2562
|
+
const leafSegments = entry.segments;
|
|
2563
|
+
if (!isPathPrefix(targetSegments, leafSegments)) continue;
|
|
2564
|
+
if (leafSegments.length === targetSegments.length) continue;
|
|
2565
|
+
anyMatch = true;
|
|
2566
|
+
const relative = leafSegments.slice(targetSegments.length);
|
|
2567
|
+
if (subtree === void 0) {
|
|
2568
|
+
subtree = typeof relative[0] === "number" ? [] : {};
|
|
2569
|
+
}
|
|
2570
|
+
subtree = setAtPath(subtree, relative, entry.value);
|
|
2571
|
+
}
|
|
2572
|
+
if (!anyMatch) return;
|
|
2573
|
+
const wroteSubtree = setValueAtPath(targetSegments, subtree);
|
|
2574
|
+
if (!wroteSubtree) {
|
|
2575
|
+
console.error(
|
|
2576
|
+
`[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.`
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
deleteErrorsUnderPrefix(schemaErrors, targetSegments);
|
|
2580
|
+
deleteErrorsUnderPrefix(userErrors, targetSegments);
|
|
2581
|
+
for (const [fieldKey, record] of Array.from(fields.entries())) {
|
|
2582
|
+
if (isPathPrefix(targetSegments, record.path)) clearFieldRecordFlags(fieldKey);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
function deleteErrorsUnderPrefix(map, prefix) {
|
|
2586
|
+
for (const [errorKey, errs] of Array.from(map.entries())) {
|
|
2587
|
+
const first = errs[0];
|
|
2588
|
+
if (first === void 0) continue;
|
|
2589
|
+
if (isPathPrefix(prefix, first.path)) {
|
|
2590
|
+
map.delete(errorKey);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
function clearFieldRecordFlags(pathKey) {
|
|
2595
|
+
const record = fields.get(pathKey);
|
|
2596
|
+
if (record === void 0) return;
|
|
2597
|
+
fields.set(pathKey, {
|
|
2598
|
+
path: record.path,
|
|
2599
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2600
|
+
isConnected: record.isConnected,
|
|
2601
|
+
focused: null,
|
|
2602
|
+
blurred: null,
|
|
2603
|
+
touched: null
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
function isPathPrefix(prefix, candidate) {
|
|
2607
|
+
if (prefix.length > candidate.length) return false;
|
|
2608
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
2609
|
+
if (prefix[i] !== candidate[i]) return false;
|
|
2610
|
+
}
|
|
2611
|
+
return true;
|
|
2612
|
+
}
|
|
2613
|
+
function isPristineAtPath(path) {
|
|
2614
|
+
const { key, segments } = canonicalizePath(path);
|
|
2615
|
+
if (blankPaths.has(key) !== originalBlankPaths.has(key)) return false;
|
|
2616
|
+
const entry = originals.get(key);
|
|
2617
|
+
if (entry === void 0) return true;
|
|
2618
|
+
return Object.is(getAtPath(form.value, segments), entry.value);
|
|
2619
|
+
}
|
|
2620
|
+
function getFieldRecord(path) {
|
|
2621
|
+
const { key } = canonicalizePath(path);
|
|
2622
|
+
return fields.get(key);
|
|
2623
|
+
}
|
|
2624
|
+
function getOriginalAtPath(path) {
|
|
2625
|
+
const { key } = canonicalizePath(path);
|
|
2626
|
+
return originals.get(key)?.value;
|
|
2627
|
+
}
|
|
2628
|
+
function getFirstErrorElement(formInstanceId) {
|
|
2629
|
+
sortedRegistrationsCache ?? (sortedRegistrationsCache = rebuildSortedRegistrations());
|
|
2630
|
+
for (const entry of sortedRegistrationsCache) {
|
|
2631
|
+
if (elementToFormInstance.get(entry.element) !== formInstanceId) continue;
|
|
2632
|
+
if (!entry.element.isConnected) continue;
|
|
2633
|
+
if (entry.element.offsetParent === null) continue;
|
|
2634
|
+
const { key } = canonicalizePath(entry.path);
|
|
2635
|
+
const hasSchemaErr = (schemaErrors.get(key)?.length ?? 0) > 0;
|
|
2636
|
+
const hasUserErr = (userErrors.get(key)?.length ?? 0) > 0;
|
|
2637
|
+
if (!hasSchemaErr && !hasUserErr) continue;
|
|
2638
|
+
return { path: entry.path, element: entry.element };
|
|
2639
|
+
}
|
|
2640
|
+
return null;
|
|
2641
|
+
}
|
|
2642
|
+
function rebuildSortedRegistrations() {
|
|
2643
|
+
const flat = [];
|
|
2644
|
+
for (const [, record] of elements) {
|
|
2645
|
+
for (const el of record.elements) flat.push({ path: record.path, element: el });
|
|
2646
|
+
}
|
|
2647
|
+
flat.sort(
|
|
2648
|
+
(a, b) => a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
|
|
2649
|
+
);
|
|
2650
|
+
return flat;
|
|
2651
|
+
}
|
|
2652
|
+
return {
|
|
2653
|
+
formKey,
|
|
2654
|
+
form,
|
|
2655
|
+
fields,
|
|
2656
|
+
elements,
|
|
2657
|
+
schemaErrors,
|
|
2658
|
+
userErrors,
|
|
2659
|
+
derivedBlankErrors,
|
|
2660
|
+
originals,
|
|
2661
|
+
schema,
|
|
2662
|
+
isSSR,
|
|
2663
|
+
isSubmitting,
|
|
2664
|
+
activeSubmissions,
|
|
2665
|
+
submitCount,
|
|
2666
|
+
submitError,
|
|
2667
|
+
submissionGeneration,
|
|
2668
|
+
activeValidations,
|
|
2669
|
+
applyFormReplacement,
|
|
2670
|
+
setValueAtPath,
|
|
2671
|
+
getValueAtPath,
|
|
2672
|
+
reset,
|
|
2673
|
+
resetField,
|
|
2674
|
+
setSchemaErrorsForPath,
|
|
2675
|
+
setAllSchemaErrors,
|
|
2676
|
+
clearSchemaErrors,
|
|
2677
|
+
setAllUserErrors,
|
|
2678
|
+
addUserErrors,
|
|
2679
|
+
clearUserErrors,
|
|
2680
|
+
getErrorsForPath,
|
|
2681
|
+
ensurePathOrdinal,
|
|
2682
|
+
registerElement,
|
|
2683
|
+
deregisterElement,
|
|
2684
|
+
markFocused,
|
|
2685
|
+
markTouched,
|
|
2686
|
+
markConnectedOptimistically,
|
|
2687
|
+
isPristineAtPath,
|
|
2688
|
+
getFieldRecord,
|
|
2689
|
+
getOriginalAtPath,
|
|
2690
|
+
getFirstErrorElement,
|
|
2691
|
+
cancelFieldValidation,
|
|
2692
|
+
scheduleFieldValidation,
|
|
2693
|
+
onFormChange,
|
|
2694
|
+
onSubmitSuccess,
|
|
2695
|
+
onReset,
|
|
2696
|
+
emitSubmitSuccess,
|
|
2697
|
+
registerCleanup,
|
|
2698
|
+
registerDrain,
|
|
2699
|
+
awaitPendingWrites,
|
|
2700
|
+
modules,
|
|
2701
|
+
persistOptIns,
|
|
2702
|
+
coerceIndex,
|
|
2703
|
+
blankPaths,
|
|
2704
|
+
originalBlankPaths,
|
|
2705
|
+
dispose
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
function getComputedSchema(formKey, schemaOrCallback) {
|
|
2710
|
+
if (typeof schemaOrCallback === "function") return schemaOrCallback(formKey);
|
|
2711
|
+
return schemaOrCallback;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
function createHistoryModule(state, config) {
|
|
2715
|
+
const max = typeof config === "object" ? config.max ?? DEFAULT_HISTORY_MAX_SNAPSHOTS : DEFAULT_HISTORY_MAX_SNAPSHOTS;
|
|
2716
|
+
const undoStack = shallowRef([]);
|
|
2717
|
+
const redoStack = shallowRef([]);
|
|
2718
|
+
let suppressNext = false;
|
|
2719
|
+
function captureSnapshot() {
|
|
2720
|
+
return {
|
|
2721
|
+
form: state.form.value,
|
|
2722
|
+
blankPaths: [...state.blankPaths],
|
|
2723
|
+
schemaErrors: [...state.schemaErrors.entries()].map(([k, v]) => [k, [...v]]),
|
|
2724
|
+
userErrors: [...state.userErrors.entries()].map(([k, v]) => [k, [...v]])
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
function pushSnapshot(snap) {
|
|
2728
|
+
const next = [...undoStack.value, snap];
|
|
2729
|
+
undoStack.value = next.length > max ? next.slice(-max) : next;
|
|
2730
|
+
redoStack.value = [];
|
|
2731
|
+
}
|
|
2732
|
+
pushSnapshot(captureSnapshot());
|
|
2733
|
+
const unsubscribeChange = state.onFormChange(() => {
|
|
2734
|
+
if (suppressNext) {
|
|
2735
|
+
suppressNext = false;
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
pushSnapshot(captureSnapshot());
|
|
2739
|
+
});
|
|
2740
|
+
const unsubscribeReset = state.onReset(() => {
|
|
2741
|
+
undoStack.value = [];
|
|
2742
|
+
redoStack.value = [];
|
|
2743
|
+
pushSnapshot(captureSnapshot());
|
|
2744
|
+
});
|
|
2745
|
+
function restore(snap) {
|
|
2746
|
+
suppressNext = true;
|
|
2747
|
+
state.blankPaths.clear();
|
|
2748
|
+
for (const key of snap.blankPaths) state.blankPaths.add(key);
|
|
2749
|
+
state.applyFormReplacement(snap.form, {
|
|
2750
|
+
persist: !state.persistOptIns.isEmpty()
|
|
2751
|
+
});
|
|
2752
|
+
const schemaFlat = snap.schemaErrors.flatMap(([, errs]) => errs);
|
|
2753
|
+
const userFlat = snap.userErrors.flatMap(([, errs]) => errs);
|
|
2754
|
+
state.setAllSchemaErrors(schemaFlat);
|
|
2755
|
+
state.setAllUserErrors(userFlat);
|
|
2756
|
+
}
|
|
2757
|
+
function undo() {
|
|
2758
|
+
if (undoStack.value.length <= 1) return false;
|
|
2759
|
+
const current = undoStack.value[undoStack.value.length - 1];
|
|
2760
|
+
const prev = undoStack.value[undoStack.value.length - 2];
|
|
2761
|
+
if (current === void 0 || prev === void 0) return false;
|
|
2762
|
+
redoStack.value = [...redoStack.value, current];
|
|
2763
|
+
undoStack.value = undoStack.value.slice(0, -1);
|
|
2764
|
+
restore(prev);
|
|
2765
|
+
return true;
|
|
2766
|
+
}
|
|
2767
|
+
function redo() {
|
|
2768
|
+
if (redoStack.value.length === 0) return false;
|
|
2769
|
+
const next = redoStack.value[redoStack.value.length - 1];
|
|
2770
|
+
if (next === void 0) return false;
|
|
2771
|
+
redoStack.value = redoStack.value.slice(0, -1);
|
|
2772
|
+
undoStack.value = [...undoStack.value, next];
|
|
2773
|
+
restore(next);
|
|
2774
|
+
return true;
|
|
2775
|
+
}
|
|
2776
|
+
const canUndo = computed(() => undoStack.value.length > 1);
|
|
2777
|
+
const canRedo = computed(() => redoStack.value.length > 0);
|
|
2778
|
+
const historySize = computed(() => undoStack.value.length + redoStack.value.length);
|
|
2779
|
+
return {
|
|
2780
|
+
undo,
|
|
2781
|
+
redo,
|
|
2782
|
+
canUndo,
|
|
2783
|
+
canRedo,
|
|
2784
|
+
historySize,
|
|
2785
|
+
dispose() {
|
|
2786
|
+
unsubscribeChange();
|
|
2787
|
+
unsubscribeReset();
|
|
2788
|
+
undoStack.value = [];
|
|
2789
|
+
redoStack.value = [];
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
function hashStableString(input, seed = 0) {
|
|
2795
|
+
let h1 = 3735928559 ^ seed;
|
|
2796
|
+
let h2 = 1103547991 ^ seed;
|
|
2797
|
+
for (let i = 0; i < input.length; i++) {
|
|
2798
|
+
const ch = input.charCodeAt(i);
|
|
2799
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
2800
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
2801
|
+
}
|
|
2802
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
2803
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
2804
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
function useAbstractForm(configuration) {
|
|
2808
|
+
const key = resolveFormKey(configuration.key);
|
|
2809
|
+
const resolvedSchema = getComputedSchema(key, configuration.schema);
|
|
2810
|
+
if (configuration.persist !== void 0 && configuration.key === void 0) {
|
|
2811
|
+
throw new AnonPersistError({
|
|
2812
|
+
cause: "no-key",
|
|
2813
|
+
schemaFields: extractSchemaFields(resolvedSchema),
|
|
2814
|
+
callSite: captureUserCallSite()
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2817
|
+
const registry = useRegistry();
|
|
2818
|
+
const merged = mergeWithDefaults(registry.defaults, configuration);
|
|
2819
|
+
const existing = registry.forms.get(key);
|
|
2820
|
+
if (__DEV__ && existing !== void 0) {
|
|
2821
|
+
warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
|
|
2822
|
+
}
|
|
2823
|
+
const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
|
|
2824
|
+
if (getCurrentScope() !== void 0) {
|
|
2825
|
+
const releaseConsumer = registry.trackConsumer(key);
|
|
2826
|
+
onScopeDispose(releaseConsumer);
|
|
2827
|
+
}
|
|
2828
|
+
const persistDisabledByAnonRule = merged.persist !== void 0 && enforceAnonPersistRule(state.formKey, registry.isSSR);
|
|
2829
|
+
if (existing === void 0 && !registry.isSSR) {
|
|
2830
|
+
if (merged.persist !== void 0 && !persistDisabledByAnonRule) {
|
|
2831
|
+
const resolvedPersist = normalizePersistConfig(merged.persist);
|
|
2832
|
+
const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
|
|
2833
|
+
void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
|
|
2834
|
+
const persistenceModule = wirePersistence(state, resolvedPersist);
|
|
2835
|
+
state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
|
|
2836
|
+
state.registerDrain(() => persistenceModule.awaitPendingWrites());
|
|
2837
|
+
state.registerCleanup(() => persistenceModule.dispose());
|
|
2838
|
+
} else {
|
|
2839
|
+
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
if (existing === void 0 && merged.history !== void 0) {
|
|
2843
|
+
const historyModule = createHistoryModule(state, merged.history);
|
|
2844
|
+
state.modules.set(HISTORY_MODULE_KEY, historyModule);
|
|
2845
|
+
state.registerCleanup(() => historyModule.dispose());
|
|
2846
|
+
}
|
|
2847
|
+
if (configuration.key === void 0) {
|
|
2848
|
+
recordAmbientProvide(registry.isSSR);
|
|
2849
|
+
provide(kFormContext, state);
|
|
2850
|
+
}
|
|
2851
|
+
const formInstanceId = getCurrentInstance() !== null ? useId() : `atta:form-instance:${formInstanceCounter++}`;
|
|
2852
|
+
if (getCurrentInstance() !== null) {
|
|
2853
|
+
provide(kFormInstanceId, formInstanceId);
|
|
2854
|
+
}
|
|
2855
|
+
const apiOptions = {};
|
|
2856
|
+
if (merged.onInvalidSubmit !== void 0) {
|
|
2857
|
+
apiOptions.onInvalidSubmit = merged.onInvalidSubmit;
|
|
2858
|
+
}
|
|
2859
|
+
const history = state.modules.get(HISTORY_MODULE_KEY);
|
|
2860
|
+
if (history !== void 0) {
|
|
2861
|
+
apiOptions.history = history;
|
|
2862
|
+
}
|
|
2863
|
+
return buildFormApi(state, formInstanceId, apiOptions);
|
|
2864
|
+
}
|
|
2865
|
+
function mergeWithDefaults(defaults, configuration) {
|
|
2866
|
+
const strict = configuration.strict ?? defaults.strict;
|
|
2867
|
+
const onInvalidSubmit = configuration.onInvalidSubmit ?? defaults.onInvalidSubmit;
|
|
2868
|
+
const history = configuration.history ?? defaults.history;
|
|
2869
|
+
const rememberVariants = configuration.rememberVariants ?? defaults.rememberVariants;
|
|
2870
|
+
const coerce = configuration.coerce ?? defaults.coerce;
|
|
2871
|
+
const validateOn = configuration.validateOn ?? defaults.validateOn;
|
|
2872
|
+
const debounceMs = configuration.debounceMs ?? defaults.debounceMs;
|
|
2873
|
+
return {
|
|
2874
|
+
...configuration,
|
|
2875
|
+
...strict === void 0 ? {} : { strict },
|
|
2876
|
+
...onInvalidSubmit === void 0 ? {} : { onInvalidSubmit },
|
|
2877
|
+
...history === void 0 ? {} : { history },
|
|
2878
|
+
...rememberVariants === void 0 ? {} : { rememberVariants },
|
|
2879
|
+
...coerce === void 0 ? {} : { coerce },
|
|
2880
|
+
...validateOn === void 0 ? {} : { validateOn },
|
|
2881
|
+
...debounceMs === void 0 ? {} : { debounceMs }
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
const HISTORY_MODULE_KEY = "history";
|
|
2885
|
+
function buildFreshState(key, schema, configuration, registry) {
|
|
2886
|
+
const pending = registry.pendingHydration.get(key);
|
|
2887
|
+
if (pending !== void 0) registry.pendingHydration.delete(key);
|
|
2888
|
+
const walked = walkUnsetSentinels(
|
|
2889
|
+
configuration.defaultValues,
|
|
2890
|
+
schema
|
|
2891
|
+
);
|
|
2892
|
+
const initialBlankPaths = pending === void 0 ? walked.paths : void 0;
|
|
2893
|
+
const createOptions = {
|
|
2894
|
+
formKey: key,
|
|
2895
|
+
schema,
|
|
2896
|
+
defaultValues: walked.cleanedValues,
|
|
2897
|
+
...configuration.strict !== void 0 ? { strict: configuration.strict } : {},
|
|
2898
|
+
hydration: pending,
|
|
2899
|
+
...configuration.validateOn !== void 0 ? { validateOn: configuration.validateOn } : {},
|
|
2900
|
+
...configuration.debounceMs !== void 0 ? { debounceMs: configuration.debounceMs } : {},
|
|
2901
|
+
isSSR: registry.isSSR,
|
|
2902
|
+
...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
|
|
2903
|
+
...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
|
|
2904
|
+
...initialBlankPaths !== void 0 ? { initialBlankPaths } : {}
|
|
2905
|
+
};
|
|
2906
|
+
const state = createFormStore(createOptions);
|
|
2907
|
+
registry.forms.set(
|
|
2908
|
+
key,
|
|
2909
|
+
state
|
|
2910
|
+
);
|
|
2911
|
+
return state;
|
|
2912
|
+
}
|
|
2913
|
+
let anonCounter = 0;
|
|
2914
|
+
let formInstanceCounter = 0;
|
|
2915
|
+
const ambientProvideHistory = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
2916
|
+
function recordAmbientProvide(isSSR) {
|
|
2917
|
+
if (!__DEV__ || isSSR || ambientProvideHistory === null) return;
|
|
2918
|
+
const instance = getCurrentInstance();
|
|
2919
|
+
if (instance === null) return;
|
|
2920
|
+
const instanceKey = instance;
|
|
2921
|
+
const entry = {
|
|
2922
|
+
source: captureUserCallSite()
|
|
2923
|
+
};
|
|
2924
|
+
const existing = ambientProvideHistory.get(instanceKey);
|
|
2925
|
+
if (existing === void 0) {
|
|
2926
|
+
ambientProvideHistory.set(instanceKey, [entry]);
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
existing.push(entry);
|
|
2930
|
+
}
|
|
2931
|
+
function resolveFormKey(key) {
|
|
2932
|
+
if (key !== void 0 && key !== null && key !== "") {
|
|
2933
|
+
if (key.startsWith(RESERVED_KEY_PREFIX)) {
|
|
2934
|
+
throw new ReservedFormKeyError(key);
|
|
2935
|
+
}
|
|
2936
|
+
return key;
|
|
2937
|
+
}
|
|
2938
|
+
if (getCurrentInstance() !== null) {
|
|
2939
|
+
return `${ANONYMOUS_FORM_KEY_PREFIX}${useId()}`;
|
|
2940
|
+
}
|
|
2941
|
+
return `${ANONYMOUS_FORM_KEY_PREFIX}${anonCounter++}`;
|
|
2942
|
+
}
|
|
2943
|
+
function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
|
|
2944
|
+
let existingFp;
|
|
2945
|
+
let incomingFp;
|
|
2946
|
+
try {
|
|
2947
|
+
existingFp = existing.fingerprint();
|
|
2948
|
+
incomingFp = incoming.fingerprint();
|
|
2949
|
+
} catch (error) {
|
|
2950
|
+
console.error(
|
|
2951
|
+
`[attaform] fingerprint() threw for key "${key}"; skipping mismatch check.`,
|
|
2952
|
+
error
|
|
2953
|
+
);
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
if (existingFp === incomingFp) return;
|
|
2957
|
+
console.warn(
|
|
2958
|
+
`[attaform] useForm() calls with key "${key}" use different schemas; first wins, second is ignored. Use identical schemas or unique keys.
|
|
2959
|
+
existing: ${existingFp}
|
|
2960
|
+
incoming: ${incomingFp}`
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
function wirePersistence(state, config) {
|
|
2964
|
+
const fingerprint = hashStableString(state.schema.fingerprint());
|
|
2965
|
+
const base = resolveStorageKeyBase(config, state.formKey);
|
|
2966
|
+
const key = `${base}:${fingerprint}`;
|
|
2967
|
+
const debounceMs = config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS;
|
|
2968
|
+
const include = config.include ?? "form";
|
|
2969
|
+
const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
|
|
2970
|
+
const adapterPromise = getStorageAdapter(config.storage);
|
|
2971
|
+
let disposed = false;
|
|
2972
|
+
let inFlightFinalFlush = null;
|
|
2973
|
+
let pendingOptedInPaths = null;
|
|
2974
|
+
const writer = createDebouncedWriter(async () => {
|
|
2975
|
+
const optedInPaths = pendingOptedInPaths ?? new Set(state.persistOptIns.optedInPaths());
|
|
2976
|
+
pendingOptedInPaths = null;
|
|
2977
|
+
const adapter = await adapterPromise;
|
|
2978
|
+
if (disposed) return;
|
|
2979
|
+
if (optedInPaths.size === 0) {
|
|
2980
|
+
await adapter.removeItem(key);
|
|
2981
|
+
return;
|
|
2982
|
+
}
|
|
2983
|
+
const rawForm = toRaw(state.form.value);
|
|
2984
|
+
const filteredForm = pluckPaths(rawForm, optedInPaths);
|
|
2985
|
+
const filteredSchemaErrors = filterErrorsByPaths(state.schemaErrors, optedInPaths);
|
|
2986
|
+
const filteredUserErrors = filterErrorsByPaths(state.userErrors, optedInPaths);
|
|
2987
|
+
const filteredTransientEmpty = /* @__PURE__ */ new Set();
|
|
2988
|
+
for (const tk of state.blankPaths) {
|
|
2989
|
+
if (optedInPaths.has(tk)) filteredTransientEmpty.add(tk);
|
|
2990
|
+
}
|
|
2991
|
+
const payload = buildPersistedPayload(
|
|
2992
|
+
filteredForm,
|
|
2993
|
+
include,
|
|
2994
|
+
filteredSchemaErrors,
|
|
2995
|
+
filteredUserErrors,
|
|
2996
|
+
filteredTransientEmpty
|
|
2997
|
+
);
|
|
2998
|
+
await adapter.setItem(key, payload);
|
|
2999
|
+
}, debounceMs);
|
|
3000
|
+
const unsubscribeChange = state.onFormChange((_next, meta) => {
|
|
3001
|
+
if (disposed || inFlightFinalFlush !== null) return;
|
|
3002
|
+
if (meta?.persist !== true) return;
|
|
3003
|
+
pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
|
|
3004
|
+
writer.schedule();
|
|
3005
|
+
});
|
|
3006
|
+
const unsubscribeSuccess = clearOnSubmitSuccess ? state.onSubmitSuccess(() => {
|
|
3007
|
+
if (disposed) return;
|
|
3008
|
+
void (async () => {
|
|
3009
|
+
await writer.flush();
|
|
3010
|
+
if (disposed) return;
|
|
3011
|
+
const adapter = await adapterPromise;
|
|
3012
|
+
if (disposed) return;
|
|
3013
|
+
await adapter.removeItem(key);
|
|
3014
|
+
})();
|
|
3015
|
+
}) : () => void 0;
|
|
3016
|
+
void (async () => {
|
|
3017
|
+
const adapter = await adapterPromise;
|
|
3018
|
+
if (disposed) return;
|
|
3019
|
+
void cleanupOrphanKeys(adapter, base, key);
|
|
3020
|
+
try {
|
|
3021
|
+
const raw = await adapter.getItem(key);
|
|
3022
|
+
const payload = readPersistedPayload(raw);
|
|
3023
|
+
if (payload === null) {
|
|
3024
|
+
if (raw !== null && raw !== void 0) {
|
|
3025
|
+
await adapter.removeItem(key);
|
|
3026
|
+
}
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
if (disposed) return;
|
|
3030
|
+
const merged = mergeSparseHydration(
|
|
3031
|
+
toRaw(state.form.value),
|
|
3032
|
+
payload.data.form,
|
|
3033
|
+
state.schema
|
|
3034
|
+
);
|
|
3035
|
+
state.applyFormReplacement(merged);
|
|
3036
|
+
const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
|
|
3037
|
+
for (const k of persistedLeafPaths) {
|
|
3038
|
+
state.blankPaths.delete(k);
|
|
3039
|
+
state.originalBlankPaths.delete(k);
|
|
3040
|
+
}
|
|
3041
|
+
for (const k of payload.data.blankPaths ?? []) {
|
|
3042
|
+
state.blankPaths.add(k);
|
|
3043
|
+
state.originalBlankPaths.add(k);
|
|
3044
|
+
}
|
|
3045
|
+
if (include === "form+errors") {
|
|
3046
|
+
if (payload.data.schemaErrors !== void 0) {
|
|
3047
|
+
const flat = payload.data.schemaErrors.flatMap(([, errs]) => errs);
|
|
3048
|
+
state.setAllSchemaErrors(flat);
|
|
3049
|
+
}
|
|
3050
|
+
if (payload.data.userErrors !== void 0) {
|
|
3051
|
+
const flat = payload.data.userErrors.flatMap(([, errs]) => errs);
|
|
3052
|
+
state.setAllUserErrors(flat);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
state.scheduleFieldValidation(
|
|
3056
|
+
[],
|
|
3057
|
+
true
|
|
3058
|
+
/* immediate */
|
|
3059
|
+
);
|
|
3060
|
+
} catch {
|
|
3061
|
+
}
|
|
3062
|
+
})();
|
|
3063
|
+
async function writePathImmediately(path) {
|
|
3064
|
+
if (disposed) return;
|
|
3065
|
+
await writer.flush();
|
|
3066
|
+
if (disposed) return;
|
|
3067
|
+
const adapter = await adapterPromise;
|
|
3068
|
+
if (disposed) return;
|
|
3069
|
+
const raw = await adapter.getItem(key);
|
|
3070
|
+
const existing = readPersistedPayload(raw);
|
|
3071
|
+
const baseForm = existing?.data.form ?? {};
|
|
3072
|
+
const value = getAtPath(toRaw(state.form.value), path);
|
|
3073
|
+
const nextForm = setAtPath(baseForm, path, value);
|
|
3074
|
+
const { key: pathKey } = canonicalizePath(path);
|
|
3075
|
+
const transientSet = new Set(
|
|
3076
|
+
(existing?.data.blankPaths ?? []).filter(
|
|
3077
|
+
(k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
|
|
3078
|
+
)
|
|
3079
|
+
);
|
|
3080
|
+
for (const liveKey of state.blankPaths) {
|
|
3081
|
+
if (liveKey === pathKey || isDescendantPathKey(liveKey, pathKey)) {
|
|
3082
|
+
transientSet.add(liveKey);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
if (include === "form") {
|
|
3086
|
+
await adapter.setItem(
|
|
3087
|
+
key,
|
|
3088
|
+
buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
|
|
3089
|
+
);
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
const schemaMap = new Map(existing?.data.schemaErrors ?? []);
|
|
3093
|
+
const userMap = new Map(existing?.data.userErrors ?? []);
|
|
3094
|
+
const currentSchema = state.schemaErrors.get(pathKey);
|
|
3095
|
+
const currentUser = state.userErrors.get(pathKey);
|
|
3096
|
+
if (currentSchema !== void 0 && currentSchema.length > 0) {
|
|
3097
|
+
schemaMap.set(pathKey, [...currentSchema]);
|
|
3098
|
+
} else {
|
|
3099
|
+
schemaMap.delete(pathKey);
|
|
3100
|
+
}
|
|
3101
|
+
if (currentUser !== void 0 && currentUser.length > 0) {
|
|
3102
|
+
userMap.set(pathKey, [...currentUser]);
|
|
3103
|
+
} else {
|
|
3104
|
+
userMap.delete(pathKey);
|
|
3105
|
+
}
|
|
3106
|
+
await adapter.setItem(
|
|
3107
|
+
key,
|
|
3108
|
+
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
async function clearPersistedDraft(path) {
|
|
3112
|
+
if (disposed) return;
|
|
3113
|
+
await writer.flush();
|
|
3114
|
+
if (disposed) return;
|
|
3115
|
+
const adapter = await adapterPromise;
|
|
3116
|
+
if (disposed) return;
|
|
3117
|
+
if (path === void 0) {
|
|
3118
|
+
await adapter.removeItem(key);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
const raw = await adapter.getItem(key);
|
|
3122
|
+
const existing = readPersistedPayload(raw);
|
|
3123
|
+
if (existing === null) return;
|
|
3124
|
+
const nextForm = deleteAtPath(existing.data.form, path);
|
|
3125
|
+
if (isEmptyContainer(nextForm)) {
|
|
3126
|
+
await adapter.removeItem(key);
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
const { key: pathKey } = canonicalizePath(path);
|
|
3130
|
+
const transientSet = new Set(
|
|
3131
|
+
(existing.data.blankPaths ?? []).filter(
|
|
3132
|
+
(k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
|
|
3133
|
+
)
|
|
3134
|
+
);
|
|
3135
|
+
if (include === "form") {
|
|
3136
|
+
await adapter.setItem(
|
|
3137
|
+
key,
|
|
3138
|
+
buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
|
|
3139
|
+
);
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
const schemaErrors = (existing.data.schemaErrors ?? []).filter(([k]) => k !== pathKey);
|
|
3143
|
+
const userErrors = (existing.data.userErrors ?? []).filter(([k]) => k !== pathKey);
|
|
3144
|
+
const schemaMap = new Map(schemaErrors.map(([k, v]) => [k, [...v]]));
|
|
3145
|
+
const userMap = new Map(userErrors.map(([k, v]) => [k, [...v]]));
|
|
3146
|
+
await adapter.setItem(
|
|
3147
|
+
key,
|
|
3148
|
+
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
function awaitPendingWrites() {
|
|
3152
|
+
if (inFlightFinalFlush !== null) return inFlightFinalFlush;
|
|
3153
|
+
if (disposed) return Promise.resolve();
|
|
3154
|
+
return writer.flush().catch(() => void 0);
|
|
3155
|
+
}
|
|
3156
|
+
function dispose() {
|
|
3157
|
+
if (disposed || inFlightFinalFlush !== null) return;
|
|
3158
|
+
unsubscribeChange();
|
|
3159
|
+
unsubscribeSuccess();
|
|
3160
|
+
inFlightFinalFlush = writer.flush().catch(() => void 0).finally(() => {
|
|
3161
|
+
disposed = true;
|
|
3162
|
+
inFlightFinalFlush = null;
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
return {
|
|
3166
|
+
writePathImmediately,
|
|
3167
|
+
clearPersistedDraft,
|
|
3168
|
+
awaitPendingWrites,
|
|
3169
|
+
dispose
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
function isEmptyContainer(value) {
|
|
3173
|
+
if (value === void 0 || value === null) return true;
|
|
3174
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
3175
|
+
if (isPlainRecord(value)) return Object.keys(value).length === 0;
|
|
3176
|
+
return false;
|
|
3177
|
+
}
|
|
3178
|
+
const warnedAnonPersistKeys = /* @__PURE__ */ new Set();
|
|
3179
|
+
function enforceAnonPersistRule(formKey, isSSR) {
|
|
3180
|
+
if (!formKey.startsWith(ANONYMOUS_FORM_KEY_PREFIX)) return false;
|
|
3181
|
+
if (__DEV__)
|
|
3182
|
+
throw new AnonPersistError({
|
|
3183
|
+
cause: "no-key",
|
|
3184
|
+
callSite: captureUserCallSite()
|
|
3185
|
+
});
|
|
3186
|
+
if (!isSSR && !warnedAnonPersistKeys.has(formKey)) {
|
|
3187
|
+
warnedAnonPersistKeys.add(formKey);
|
|
3188
|
+
console.warn(
|
|
3189
|
+
"[attaform] persist: ignored \u2014 anonymous useForm() can't safely persist (key drift + cross-form collision risk).\n Persistence is disabled for this form; the app keeps working.\n Fix: useForm({ schema, key: 'login', persist: '...' })"
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
return true;
|
|
3193
|
+
}
|
|
3194
|
+
function collectPersistedLeafPaths(form) {
|
|
3195
|
+
const out = [];
|
|
3196
|
+
walk(form, []);
|
|
3197
|
+
return out;
|
|
3198
|
+
function walk(node, prefix) {
|
|
3199
|
+
if (Array.isArray(node)) {
|
|
3200
|
+
for (let i = 0; i < node.length; i++) {
|
|
3201
|
+
walk(node[i], [...prefix, i]);
|
|
3202
|
+
}
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
if (isPlainRecord(node)) {
|
|
3206
|
+
for (const key of Object.keys(node)) {
|
|
3207
|
+
walk(node[key], [...prefix, key]);
|
|
3208
|
+
}
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
3211
|
+
if (prefix.length === 0) return;
|
|
3212
|
+
out.push(canonicalizePath(prefix).key);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
function isDescendantPathKey(candidate, ancestor) {
|
|
3216
|
+
if (candidate.length <= ancestor.length) return false;
|
|
3217
|
+
if (!ancestor.endsWith("]")) return false;
|
|
3218
|
+
const childPrefix = `${ancestor.slice(0, -1)},`;
|
|
3219
|
+
return candidate.startsWith(childPrefix);
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
let injectedInstanceCounter = 0;
|
|
3223
|
+
function injectForm(key) {
|
|
3224
|
+
const registry = useRegistry();
|
|
3225
|
+
const state = resolveState(key, registry);
|
|
3226
|
+
if (state === null) return null;
|
|
3227
|
+
if (getCurrentScope() !== void 0) {
|
|
3228
|
+
const releaseConsumer = registry.trackConsumer(state.formKey);
|
|
3229
|
+
onScopeDispose(releaseConsumer);
|
|
3230
|
+
}
|
|
3231
|
+
const apiOptions = {};
|
|
3232
|
+
const history = state.modules.get("history");
|
|
3233
|
+
if (history !== void 0) {
|
|
3234
|
+
apiOptions.history = history;
|
|
3235
|
+
}
|
|
3236
|
+
const ambientInstanceId = getCurrentInstance() !== null ? inject(kFormInstanceId, null) : null;
|
|
3237
|
+
const formInstanceId = ambientInstanceId ?? (getCurrentInstance() !== null ? useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`);
|
|
3238
|
+
return buildFormApi(state, formInstanceId, apiOptions);
|
|
3239
|
+
}
|
|
3240
|
+
function resolveState(key, registry) {
|
|
3241
|
+
if (key !== void 0) {
|
|
3242
|
+
const stored = registry.forms.get(key);
|
|
3243
|
+
if (stored === void 0) {
|
|
3244
|
+
warnMiss(`no form registered for key '${key}'`, registry.isSSR);
|
|
3245
|
+
return null;
|
|
3246
|
+
}
|
|
3247
|
+
return stored;
|
|
3248
|
+
}
|
|
3249
|
+
const ambient = inject(kFormContext, null);
|
|
3250
|
+
if (ambient === null) {
|
|
3251
|
+
warnMiss("no ambient form context", registry.isSSR);
|
|
3252
|
+
return null;
|
|
3253
|
+
}
|
|
3254
|
+
warnIfAmbientProviderHadDuplicates();
|
|
3255
|
+
return ambient;
|
|
3256
|
+
}
|
|
3257
|
+
function warnMiss(detail, isSSR) {
|
|
3258
|
+
if (!__DEV__ || isSSR) return;
|
|
3259
|
+
const frame = captureUserCallSite();
|
|
3260
|
+
console.warn(
|
|
3261
|
+
`[attaform] injectForm: ${detail}. Returning null.` + (frame !== void 0 ? ` ${frame}` : "")
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3264
|
+
function warnIfAmbientProviderHadDuplicates() {
|
|
3265
|
+
if (!__DEV__ || ambientProvideHistory === null) return;
|
|
3266
|
+
let ancestor = getCurrentInstance()?.parent ?? null;
|
|
3267
|
+
while (ancestor !== null) {
|
|
3268
|
+
const history = ambientProvideHistory.get(ancestor);
|
|
3269
|
+
if (history !== void 0) {
|
|
3270
|
+
if (history.length > 1) {
|
|
3271
|
+
const lines = history.map((entry) => ` - ${entry.source ?? "<unknown location>"}`);
|
|
3272
|
+
console.warn(
|
|
3273
|
+
"[attaform] injectForm<F>() (no key) resolved against an ancestor with multiple anonymous useForm() calls; descendants only see the last-provided form. Anonymous useForm() calls were:\n" + lines.join("\n") + '\nFix: pass a key to each call (e.g. useForm({ schema, key: "x" })) and reach them via injectForm<F>("x"), or split the forms across separate components.'
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
ancestor = ancestor.parent;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
export { AttaformErrorCode as A, defineCoercion as a, isUnset as b, useAbstractForm as c, defaultCoercionRules as d, setAtPath as e, isPlainRecord as f, getAtPath as g, injectForm as i, slimKindOf as s, unset as u };
|
|
3283
|
+
//# sourceMappingURL=attaform.BRTxpA3q.mjs.map
|