attaform 0.18.2 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/chunks/devtools.cjs +1 -1
- package/dist/chunks/devtools.mjs +1 -1
- package/dist/chunks/indexeddb.cjs +1 -1
- package/dist/chunks/indexeddb.mjs +1 -1
- package/dist/chunks/local-storage.cjs +1 -1
- package/dist/chunks/local-storage.mjs +1 -1
- package/dist/chunks/session-storage.cjs +1 -1
- package/dist/chunks/session-storage.mjs +1 -1
- package/dist/index.cjs +4 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -110
- package/dist/index.d.mts +77 -110
- package/dist/index.d.ts +77 -110
- package/dist/index.mjs +5 -5
- package/dist/nuxt.d.cts +1 -1
- package/dist/nuxt.d.mts +1 -1
- package/dist/nuxt.d.ts +1 -1
- package/dist/runtime/components/AttaformDevtoolsPanel.vue +2 -2
- package/dist/runtime/components/DevtoolsValueTree.d.vue.ts +1 -3
- package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +1 -3
- package/dist/runtime/plugins/attaform.cjs +2 -2
- package/dist/runtime/plugins/attaform.mjs +2 -2
- package/dist/shared/{attaform.CDmaxrt2.mjs → attaform.BKozEdTr.mjs} +305 -178
- package/dist/shared/attaform.BKozEdTr.mjs.map +1 -0
- package/dist/shared/{attaform.Bubm_slq.cjs → attaform.BM6YD9kZ.cjs} +212 -269
- package/dist/shared/attaform.BM6YD9kZ.cjs.map +1 -0
- package/dist/shared/{attaform.5UhpSVFI.cjs → attaform.BPxsYtTe.cjs} +2 -26
- package/dist/shared/attaform.BPxsYtTe.cjs.map +1 -0
- package/dist/shared/{attaform.BqK_L4gK.cjs → attaform.BPy-4qRx.cjs} +305 -180
- package/dist/shared/attaform.BPy-4qRx.cjs.map +1 -0
- package/dist/shared/attaform.BWgAFnsj.mjs +770 -0
- package/dist/shared/attaform.BWgAFnsj.mjs.map +1 -0
- package/dist/shared/{attaform.CGX1CNpz.d.ts → attaform.Bh3ACtts.d.ts} +152 -111
- package/dist/shared/{attaform.CXpzmj38.mjs → attaform.BupwXkj_.mjs} +213 -270
- package/dist/shared/attaform.BupwXkj_.mjs.map +1 -0
- package/dist/shared/{attaform.Dlk1jMuv.cjs → attaform.CIn4bMsD.cjs} +263 -799
- package/dist/shared/attaform.CIn4bMsD.cjs.map +1 -0
- package/dist/shared/{attaform.CZ-XtZt_.mjs → attaform.CKFbKFb6.mjs} +2265 -1509
- package/dist/shared/attaform.CKFbKFb6.mjs.map +1 -0
- package/dist/shared/{attaform.CuN7ZhBy.d.cts → attaform.D5-1XGQU.d.cts} +152 -111
- package/dist/shared/{attaform.-1GQTX2T.mjs → attaform.DEBvCjeH.mjs} +257 -793
- package/dist/shared/attaform.DEBvCjeH.mjs.map +1 -0
- package/dist/shared/{attaform.II89Pcf4.cjs → attaform.DL4CQ-oW.cjs} +2270 -1514
- package/dist/shared/attaform.DL4CQ-oW.cjs.map +1 -0
- package/dist/shared/{attaform.FnEwjhvX.d.ts → attaform.DSD85fHb.d.cts} +1 -19
- package/dist/shared/{attaform.CRmmNAYp.d.cts → attaform.DSD85fHb.d.mts} +1 -19
- package/dist/shared/{attaform.D9wuTGu9.d.mts → attaform.DSD85fHb.d.ts} +1 -19
- package/dist/shared/{attaform.B7rzpK1U.d.cts → attaform.DkA5J8NW.d.cts} +1 -17
- package/dist/shared/{attaform.B7rzpK1U.d.mts → attaform.DkA5J8NW.d.mts} +1 -17
- package/dist/shared/{attaform.B7rzpK1U.d.ts → attaform.DkA5J8NW.d.ts} +1 -17
- package/dist/shared/{attaform.B957T6NU.d.ts → attaform.Dl5kDY-A.d.ts} +1 -1
- package/dist/shared/attaform.Dmb6itxC.cjs +781 -0
- package/dist/shared/attaform.Dmb6itxC.cjs.map +1 -0
- package/dist/shared/{attaform.M-RanbyV.d.mts → attaform.DoKXru-a.d.mts} +1 -1
- package/dist/shared/attaform.DvA-CJJW.mjs +1876 -0
- package/dist/shared/attaform.DvA-CJJW.mjs.map +1 -0
- package/dist/shared/{attaform.D1gzu2GL.d.mts → attaform.EMzJcQci.d.mts} +152 -111
- package/dist/shared/attaform.EZG6fOFb.mjs +35 -0
- package/dist/shared/attaform.EZG6fOFb.mjs.map +1 -0
- package/dist/shared/{attaform.XDjA7sRz.d.cts → attaform.GbDo_lJi.d.cts} +1 -1
- package/dist/shared/{attaform.Ca5_6Ky-.d.mts → attaform.SfhU0OEY.d.cts} +499 -116
- package/dist/shared/{attaform.Ca5_6Ky-.d.cts → attaform.SfhU0OEY.d.mts} +499 -116
- package/dist/shared/{attaform.Ca5_6Ky-.d.ts → attaform.SfhU0OEY.d.ts} +499 -116
- package/dist/shared/attaform.jgzuNZVC.cjs +1882 -0
- package/dist/shared/attaform.jgzuNZVC.cjs.map +1 -0
- package/dist/transforms.cjs +2 -2
- package/dist/transforms.d.cts +22 -13
- package/dist/transforms.d.mts +22 -13
- package/dist/transforms.d.ts +22 -13
- package/dist/transforms.mjs +1 -1
- package/dist/vite.cjs +8 -7
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.mjs +8 -7
- package/dist/vite.mjs.map +1 -1
- package/dist/zod-v3.cjs +3 -3
- package/dist/zod-v3.d.cts +32 -6
- package/dist/zod-v3.d.mts +32 -6
- package/dist/zod-v3.d.ts +32 -6
- package/dist/zod-v3.mjs +3 -3
- package/dist/zod-v4.cjs +3 -3
- package/dist/zod-v4.d.cts +12 -8
- package/dist/zod-v4.d.mts +12 -8
- package/dist/zod-v4.d.ts +12 -8
- package/dist/zod-v4.mjs +3 -3
- package/dist/zod.cjs +8 -8
- package/dist/zod.cjs.map +1 -1
- package/dist/zod.d.cts +6 -6
- package/dist/zod.d.mts +6 -6
- package/dist/zod.d.ts +6 -6
- package/dist/zod.mjs +6 -6
- package/package.json +2 -2
- package/dist/shared/attaform.-1GQTX2T.mjs.map +0 -1
- package/dist/shared/attaform.5UhpSVFI.cjs.map +0 -1
- package/dist/shared/attaform.BqK_L4gK.cjs.map +0 -1
- package/dist/shared/attaform.Bubm_slq.cjs.map +0 -1
- package/dist/shared/attaform.C8CyvYa_.cjs +0 -36
- package/dist/shared/attaform.C8CyvYa_.cjs.map +0 -1
- package/dist/shared/attaform.CDmaxrt2.mjs.map +0 -1
- package/dist/shared/attaform.CXpzmj38.mjs.map +0 -1
- package/dist/shared/attaform.CZ-XtZt_.mjs.map +0 -1
- package/dist/shared/attaform.D13GMFgK.mjs +0 -32
- package/dist/shared/attaform.D13GMFgK.mjs.map +0 -1
- package/dist/shared/attaform.DUHru0OF.cjs +0 -1600
- package/dist/shared/attaform.DUHru0OF.cjs.map +0 -1
- package/dist/shared/attaform.Df0tU0Ut.mjs +0 -1594
- package/dist/shared/attaform.Df0tU0Ut.mjs.map +0 -1
- package/dist/shared/attaform.Dl161U6E.mjs +0 -57
- package/dist/shared/attaform.Dl161U6E.mjs.map +0 -1
- package/dist/shared/attaform.Dlk1jMuv.cjs.map +0 -1
- package/dist/shared/attaform.II89Pcf4.cjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const vue = require('vue');
|
|
4
|
-
const paths = require('./attaform.
|
|
4
|
+
const paths = require('./attaform.BPy-4qRx.cjs');
|
|
5
5
|
|
|
6
6
|
const NOT_FOUND = Symbol("NOT_FOUND");
|
|
7
7
|
function descendStep(value, segment) {
|
|
@@ -64,7 +64,7 @@ function setAtPathOffset(root, path, value, offset) {
|
|
|
64
64
|
arr[head] = setAtPathOffset(arr[head], path, value, nextOffset);
|
|
65
65
|
return arr;
|
|
66
66
|
}
|
|
67
|
-
const rec = isPlainRecord(root) ?
|
|
67
|
+
const rec = isPlainRecord(root) ? Object.assign(/* @__PURE__ */ Object.create(null), root) : /* @__PURE__ */ Object.create(null);
|
|
68
68
|
rec[head] = setAtPathOffset(rec[head], path, value, nextOffset);
|
|
69
69
|
return rec;
|
|
70
70
|
}
|
|
@@ -165,7 +165,7 @@ function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
|
|
|
165
165
|
return consumer;
|
|
166
166
|
}
|
|
167
167
|
let mutated = false;
|
|
168
|
-
const out =
|
|
168
|
+
const out = Object.assign(/* @__PURE__ */ Object.create(null), consumer);
|
|
169
169
|
for (const key of Object.keys(defaultValue)) {
|
|
170
170
|
if (!(key in consumer)) {
|
|
171
171
|
const defAtKey = defaultValue[key];
|
|
@@ -429,13 +429,81 @@ function structuralSnapshot(value) {
|
|
|
429
429
|
return out2;
|
|
430
430
|
}
|
|
431
431
|
const src = value;
|
|
432
|
-
const out =
|
|
432
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
433
433
|
for (const k of Object.keys(src)) {
|
|
434
434
|
out[k] = structuralSnapshot(src[k]);
|
|
435
435
|
}
|
|
436
436
|
return out;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
const defaultDisplayState = (field, formMeta) => {
|
|
440
|
+
const gateOpen = formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
|
|
441
|
+
if (!gateOpen) return "idle";
|
|
442
|
+
if (field.validating === true) return "pending";
|
|
443
|
+
const hasOwnError = field.errors.some(
|
|
444
|
+
(e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
|
|
445
|
+
);
|
|
446
|
+
if (hasOwnError) return "error";
|
|
447
|
+
if (field.valid === true && field.blank !== true && field.dirty === true) return "success";
|
|
448
|
+
return "idle";
|
|
449
|
+
};
|
|
450
|
+
function resolveGetDisplayState(config) {
|
|
451
|
+
return config ?? defaultDisplayState;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
|
|
455
|
+
const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
|
|
456
|
+
const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
|
|
457
|
+
const PERSISTENCE_KEY_PREFIX = "attaform:";
|
|
458
|
+
const RESERVED_KEY_PREFIX = "__atta:";
|
|
459
|
+
const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
|
|
460
|
+
const ANONYMOUS_WIZARD_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon-wizard:`;
|
|
461
|
+
const DEFAULT_MAX_RECURSION_DEPTH = 64;
|
|
462
|
+
function normalizeNumericOption(config) {
|
|
463
|
+
const { value, source, allowInfinity, min, defaultValue } = config;
|
|
464
|
+
if (allowInfinity && value === Infinity) return Infinity;
|
|
465
|
+
if (typeof value !== "number" || Number.isNaN(value) || value === Infinity || value === -Infinity) {
|
|
466
|
+
if (paths.__DEV__) {
|
|
467
|
+
const acceptedDescription = allowInfinity ? "a non-negative integer or Infinity" : "a non-negative finite integer";
|
|
468
|
+
console.warn(
|
|
469
|
+
`[attaform] ${source} must be ${acceptedDescription}; got ${String(value)}. Falling back to ${String(defaultValue)}.`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
return defaultValue;
|
|
473
|
+
}
|
|
474
|
+
return Math.max(min, Math.floor(value));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function hashStableString(input, seed = 0) {
|
|
478
|
+
let h1 = 3735928559 ^ seed;
|
|
479
|
+
let h2 = 1103547991 ^ seed;
|
|
480
|
+
for (let i = 0; i < input.length; i++) {
|
|
481
|
+
const ch = input.charCodeAt(i);
|
|
482
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
483
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
484
|
+
}
|
|
485
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
486
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
487
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const ANON_STEM = "atta";
|
|
491
|
+
function readableFormKeyStem(formKey) {
|
|
492
|
+
if (formKey === "" || formKey.startsWith(ANONYMOUS_FORM_KEY_PREFIX)) return ANON_STEM;
|
|
493
|
+
const sanitized = formKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|(?<!-)-+$/g, "");
|
|
494
|
+
return sanitized === "" ? ANON_STEM : sanitized;
|
|
495
|
+
}
|
|
496
|
+
function fieldIdToken(formInstanceId, pathKey) {
|
|
497
|
+
return hashStableString(`${formInstanceId}:${pathKey}`).slice(-7);
|
|
498
|
+
}
|
|
499
|
+
function computeFieldIdentity(formInstanceId, formKey, pathKey) {
|
|
500
|
+
const id = `${readableFormKeyStem(formKey)}-${fieldIdToken(formInstanceId, pathKey)}`;
|
|
501
|
+
return {
|
|
502
|
+
id,
|
|
503
|
+
aria: Object.freeze({ errorId: `${id}-error`, descriptionId: `${id}-description` })
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
439
507
|
const EMPTY_RESOLVED_FIELD_META = Object.freeze({
|
|
440
508
|
label: "",
|
|
441
509
|
description: void 0,
|
|
@@ -456,6 +524,7 @@ function humanize(segment) {
|
|
|
456
524
|
}).join(" ");
|
|
457
525
|
}
|
|
458
526
|
|
|
527
|
+
const warnedDisplayStatePredicates = /* @__PURE__ */ new WeakSet();
|
|
459
528
|
function isUnderStubAncestor(state, segments) {
|
|
460
529
|
for (let i = 0; i < segments.length; i++) {
|
|
461
530
|
const ancestorPath = segments.slice(0, i);
|
|
@@ -469,21 +538,21 @@ function isUnderStubAncestor(state, segments) {
|
|
|
469
538
|
}
|
|
470
539
|
return false;
|
|
471
540
|
}
|
|
472
|
-
function buildFieldStateAccessor(state, getFormMetaBase, options) {
|
|
541
|
+
function buildFieldStateAccessor(state, formInstanceId, getFormMetaBase, options) {
|
|
473
542
|
const cache = /* @__PURE__ */ new Map();
|
|
474
|
-
const predicate = options?.
|
|
543
|
+
const predicate = options?.getDisplayState;
|
|
475
544
|
return function getFieldState(pathInput) {
|
|
476
545
|
const { segments, key } = paths.canonicalizePath(pathInput);
|
|
477
546
|
const cached = cache.get(key);
|
|
478
547
|
if (cached !== void 0) return cached;
|
|
479
548
|
const c = vue.computed(
|
|
480
|
-
() => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, getFormMetaBase, predicate) : buildContainerFieldState(state, segments, key, getFormMetaBase, predicate)
|
|
549
|
+
() => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, predicate) : buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, predicate)
|
|
481
550
|
);
|
|
482
551
|
cache.set(key, c);
|
|
483
552
|
return c;
|
|
484
553
|
};
|
|
485
554
|
}
|
|
486
|
-
function buildLeafFieldStateBase(state, segments, key) {
|
|
555
|
+
function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
|
|
487
556
|
const record = state.fields.get(key);
|
|
488
557
|
const value = state.getValueAtPath(segments);
|
|
489
558
|
const original = state.originals.get(key)?.value;
|
|
@@ -513,6 +582,8 @@ function buildLeafFieldStateBase(state, segments, key) {
|
|
|
513
582
|
focused: record?.focused ?? null,
|
|
514
583
|
blurred: record?.blurred ?? null,
|
|
515
584
|
touched: record?.touched ?? false,
|
|
585
|
+
interacted: record?.interacted ?? false,
|
|
586
|
+
blurredAfterInteraction: record?.blurredAfterInteraction ?? false,
|
|
516
587
|
connected: record?.connected ?? false,
|
|
517
588
|
element: firstElement,
|
|
518
589
|
elements: elementsArr,
|
|
@@ -521,6 +592,8 @@ function buildLeafFieldStateBase(state, segments, key) {
|
|
|
521
592
|
validating,
|
|
522
593
|
valid,
|
|
523
594
|
path: segments,
|
|
595
|
+
...computeFieldIdentity(formInstanceId, state.formKey, key),
|
|
596
|
+
key: state.arrayElementKey(segments),
|
|
524
597
|
blank: state.blankPaths.has(key),
|
|
525
598
|
label,
|
|
526
599
|
description: resolved.description,
|
|
@@ -528,31 +601,32 @@ function buildLeafFieldStateBase(state, segments, key) {
|
|
|
528
601
|
meta: resolved.meta
|
|
529
602
|
};
|
|
530
603
|
}
|
|
531
|
-
function buildLeafFieldState(state, segments, key, getFormMetaBase,
|
|
532
|
-
const base = buildLeafFieldStateBase(state, segments, key);
|
|
533
|
-
return decorateWithDerivedProps(base, state, getFormMetaBase,
|
|
604
|
+
function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
|
|
605
|
+
const base = buildLeafFieldStateBase(state, segments, key, formInstanceId);
|
|
606
|
+
return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
|
|
534
607
|
}
|
|
535
|
-
function buildContainerFieldStateBase(state, segments,
|
|
608
|
+
function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
|
|
536
609
|
const formValue = state.form.value;
|
|
537
610
|
const value = state.getValueAtPath(segments);
|
|
538
|
-
const original = state.originals.get(
|
|
611
|
+
const original = state.originals.get(key)?.value;
|
|
539
612
|
let pristine = true;
|
|
540
613
|
let blank = true;
|
|
541
614
|
let dirty = false;
|
|
542
615
|
let focused = false;
|
|
543
616
|
let blurred = false;
|
|
544
617
|
let touched = false;
|
|
618
|
+
let interacted = false;
|
|
619
|
+
let blurredAfterInteraction = false;
|
|
545
620
|
let connected = false;
|
|
546
621
|
let validating = false;
|
|
547
622
|
let updatedAt = null;
|
|
548
623
|
let asyncPending = false;
|
|
549
|
-
for (const [, entry] of state.originals) {
|
|
624
|
+
for (const [leafKey, entry] of state.originals) {
|
|
550
625
|
if (!paths.isPathPrefix(segments, entry.segments)) continue;
|
|
551
626
|
if (segments.length === entry.segments.length) continue;
|
|
552
627
|
if (!hasAtPath(formValue, entry.segments)) continue;
|
|
553
|
-
const leafKey = paths.canonicalizePath(entry.segments).key;
|
|
554
628
|
const leafRecord = state.fields.get(leafKey);
|
|
555
|
-
if (!state.
|
|
629
|
+
if (!state.isPristineAtPathByKey(leafKey, entry.segments)) {
|
|
556
630
|
pristine = false;
|
|
557
631
|
dirty = true;
|
|
558
632
|
}
|
|
@@ -560,14 +634,20 @@ function buildContainerFieldStateBase(state, segments, _key) {
|
|
|
560
634
|
if (leafRecord?.focused === true) focused = true;
|
|
561
635
|
if (leafRecord?.blurred === true) blurred = true;
|
|
562
636
|
if (leafRecord?.touched === true) touched = true;
|
|
637
|
+
if (leafRecord?.interacted === true) interacted = true;
|
|
638
|
+
if (leafRecord?.blurredAfterInteraction === true) blurredAfterInteraction = true;
|
|
563
639
|
if (leafRecord?.connected === true) connected = true;
|
|
564
640
|
if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) validating = true;
|
|
565
|
-
if (state.
|
|
641
|
+
if (state.pathHasAsyncValidationByKey(leafKey, entry.segments)) asyncPending = true;
|
|
566
642
|
const ts = leafRecord?.updatedAt;
|
|
567
643
|
if (ts !== void 0 && ts !== null) {
|
|
568
644
|
if (updatedAt === null || ts > updatedAt) updatedAt = ts;
|
|
569
645
|
}
|
|
570
646
|
}
|
|
647
|
+
if (!dirty && state.hasStructuralChangeUnder(segments)) {
|
|
648
|
+
pristine = false;
|
|
649
|
+
dirty = true;
|
|
650
|
+
}
|
|
571
651
|
const errors = aggregateErrorsAt(state, segments);
|
|
572
652
|
if (!asyncPending && state.pathHasAsyncValidation(segments)) asyncPending = true;
|
|
573
653
|
const gated = asyncPending && !state.firstValidationDone.value;
|
|
@@ -583,6 +663,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
|
|
|
583
663
|
focused,
|
|
584
664
|
blurred,
|
|
585
665
|
touched,
|
|
666
|
+
interacted,
|
|
667
|
+
blurredAfterInteraction,
|
|
586
668
|
connected,
|
|
587
669
|
element: null,
|
|
588
670
|
elements: EMPTY_ELEMENTS,
|
|
@@ -591,6 +673,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
|
|
|
591
673
|
validating,
|
|
592
674
|
valid,
|
|
593
675
|
path: segments,
|
|
676
|
+
...computeFieldIdentity(formInstanceId, state.formKey, key),
|
|
677
|
+
key: state.arrayElementKey(segments),
|
|
594
678
|
blank,
|
|
595
679
|
label,
|
|
596
680
|
description: resolved.description,
|
|
@@ -598,15 +682,36 @@ function buildContainerFieldStateBase(state, segments, _key) {
|
|
|
598
682
|
meta: resolved.meta
|
|
599
683
|
};
|
|
600
684
|
}
|
|
601
|
-
function buildContainerFieldState(state, segments, key, getFormMetaBase,
|
|
602
|
-
const base = buildContainerFieldStateBase(state, segments);
|
|
603
|
-
return decorateWithDerivedProps(base, state, getFormMetaBase,
|
|
685
|
+
function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
|
|
686
|
+
const base = buildContainerFieldStateBase(state, segments, key, formInstanceId);
|
|
687
|
+
return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
|
|
604
688
|
}
|
|
605
|
-
function decorateWithDerivedProps(base, state, getFormMetaBase,
|
|
689
|
+
function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState) {
|
|
606
690
|
const firstError = base.errors[0];
|
|
607
|
-
const predicate =
|
|
608
|
-
const
|
|
609
|
-
|
|
691
|
+
const predicate = getDisplayState ?? state.getDisplayState;
|
|
692
|
+
const formMeta = getFormMetaBase();
|
|
693
|
+
let displayState;
|
|
694
|
+
try {
|
|
695
|
+
displayState = predicate(base, formMeta);
|
|
696
|
+
} catch (err) {
|
|
697
|
+
if (paths.__DEV__ && !warnedDisplayStatePredicates.has(predicate)) {
|
|
698
|
+
warnedDisplayStatePredicates.add(predicate);
|
|
699
|
+
console.warn(
|
|
700
|
+
"[attaform] custom getDisplayState threw \u2014 falling back to defaultDisplayState. Subsequent throws from the same predicate will not warn again.",
|
|
701
|
+
err
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
displayState = defaultDisplayState(base, formMeta);
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
...base,
|
|
708
|
+
displayState,
|
|
709
|
+
showErrors: displayState === "error",
|
|
710
|
+
showPending: displayState === "pending",
|
|
711
|
+
showSuccess: displayState === "success",
|
|
712
|
+
showIdle: displayState === "idle",
|
|
713
|
+
firstError
|
|
714
|
+
};
|
|
610
715
|
}
|
|
611
716
|
function aggregateErrorsAt(state, prefix) {
|
|
612
717
|
const formValue = state.form.value;
|
|
@@ -632,6 +737,41 @@ function aggregateErrorsAt(state, prefix) {
|
|
|
632
737
|
}
|
|
633
738
|
const EMPTY_ELEMENTS = Object.freeze([]);
|
|
634
739
|
|
|
740
|
+
function liveKeysAtPath(state, segments) {
|
|
741
|
+
const value = getAtPath(state.form.value, segments);
|
|
742
|
+
if (value === null || value === void 0) return [];
|
|
743
|
+
if (Array.isArray(value)) {
|
|
744
|
+
const keys = new Array(value.length);
|
|
745
|
+
for (let i = 0; i < value.length; i += 1) keys[i] = String(i);
|
|
746
|
+
return keys;
|
|
747
|
+
}
|
|
748
|
+
if (typeof value === "object") return Object.keys(value);
|
|
749
|
+
return [];
|
|
750
|
+
}
|
|
751
|
+
function isArrayPath(state, segments) {
|
|
752
|
+
if (segments.length === 0) return false;
|
|
753
|
+
return Array.isArray(getAtPath(state.form.value, segments));
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function warnReadOnly(surface, action, key) {
|
|
757
|
+
if (!paths.__DEV__) return;
|
|
758
|
+
const phrase = action === "write" ? `write to "${String(key)}"` : `${action} of "${String(key)}"`;
|
|
759
|
+
console.warn(
|
|
760
|
+
`[attaform] ${surface} is read-only \u2014 ${phrase} was ignored. Mutate the form via setValue / the directive / field-array helpers instead.`
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
function makeReadonlyCoercion(snapshot) {
|
|
764
|
+
const toString = () => JSON.stringify(snapshot());
|
|
765
|
+
return {
|
|
766
|
+
toString,
|
|
767
|
+
valueOf() {
|
|
768
|
+
return this;
|
|
769
|
+
},
|
|
770
|
+
toJSON: snapshot,
|
|
771
|
+
toPrimitive: (hint) => hint === "number" ? NaN : toString()
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
635
775
|
const INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
|
|
636
776
|
function keyToSegment(key) {
|
|
637
777
|
return INTEGER_SEGMENT.test(key) ? Number(key) : key;
|
|
@@ -663,12 +803,12 @@ function buildSurfaceProxy(opts) {
|
|
|
663
803
|
const existing = containerCache.get(cacheKey);
|
|
664
804
|
if (existing !== void 0) return existing;
|
|
665
805
|
const snapshotContainer = () => opts.materializeContainer === void 0 ? {} : opts.materializeContainer(segments);
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
806
|
+
const {
|
|
807
|
+
toString: containerToString,
|
|
808
|
+
valueOf: containerValueOf,
|
|
809
|
+
toJSON: containerToJSON,
|
|
810
|
+
toPrimitive: containerToPrimitive
|
|
811
|
+
} = makeReadonlyCoercion(snapshotContainer);
|
|
672
812
|
const target = isArrayLike ? [] : (() => {
|
|
673
813
|
});
|
|
674
814
|
const proxy = new Proxy(target, {
|
|
@@ -693,6 +833,9 @@ function buildSurfaceProxy(opts) {
|
|
|
693
833
|
return opts.containerOwnKeys === void 0 ? 0 : opts.containerOwnKeys(segments).length;
|
|
694
834
|
}
|
|
695
835
|
const childSegs = [...segments, keyToSegment(key)];
|
|
836
|
+
if ((isArrayLike || opts.isArrayContainer?.(segments) === true) && typeof keyToSegment(key) === "string" && key in Array.prototype) {
|
|
837
|
+
return Reflect.get(Array.prototype, key);
|
|
838
|
+
}
|
|
696
839
|
if (key === "toString" || key === "valueOf") {
|
|
697
840
|
if (!schemaHasPath(childSegs)) {
|
|
698
841
|
return key === "toString" ? containerToString : containerValueOf;
|
|
@@ -737,10 +880,25 @@ function buildSurfaceProxy(opts) {
|
|
|
737
880
|
};
|
|
738
881
|
},
|
|
739
882
|
// Block writes at the proxy boundary. Mutations go through
|
|
740
|
-
// `setValue`, the directive, or the field-array helpers.
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
883
|
+
// `setValue`, the directive, or the field-array helpers. Each
|
|
884
|
+
// trap returns `true` (warn-and-noop) — returning `false` from a
|
|
885
|
+
// `set`/`delete`/`defineProperty` trap throws `TypeError` under
|
|
886
|
+
// strict mode (every ESM / `<script setup>`), which would surface
|
|
887
|
+
// a host-level exception in consumer code that the library
|
|
888
|
+
// documents as "writes are ignored." Aligns with the contract on
|
|
889
|
+
// `form.values` / `wizard.statuses`.
|
|
890
|
+
set: (_, key) => {
|
|
891
|
+
warnReadOnly("form.fields / form.errors", "write", key);
|
|
892
|
+
return true;
|
|
893
|
+
},
|
|
894
|
+
deleteProperty: (_, key) => {
|
|
895
|
+
warnReadOnly("form.fields / form.errors", "delete", key);
|
|
896
|
+
return true;
|
|
897
|
+
},
|
|
898
|
+
defineProperty: (_, key) => {
|
|
899
|
+
warnReadOnly("form.fields / form.errors", "define", key);
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
744
902
|
});
|
|
745
903
|
containerCache.set(cacheKey, proxy);
|
|
746
904
|
return proxy;
|
|
@@ -762,11 +920,12 @@ function buildSurfaceProxy(opts) {
|
|
|
762
920
|
}
|
|
763
921
|
return snapshot;
|
|
764
922
|
};
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
923
|
+
const {
|
|
924
|
+
toString: leafToString,
|
|
925
|
+
valueOf: leafValueOf,
|
|
926
|
+
toJSON: leafToJSONHandler,
|
|
927
|
+
toPrimitive: leafToPrimitive
|
|
928
|
+
} = makeReadonlyCoercion(snapshotLeaf);
|
|
770
929
|
const target = (() => {
|
|
771
930
|
});
|
|
772
931
|
const proxy = new Proxy(target, {
|
|
@@ -784,7 +943,7 @@ function buildSurfaceProxy(opts) {
|
|
|
784
943
|
if (typeof key !== "string") return void 0;
|
|
785
944
|
if (key === "toString") return leafToString;
|
|
786
945
|
if (key === "valueOf") return leafValueOf;
|
|
787
|
-
if (key === "toJSON") return
|
|
946
|
+
if (key === "toJSON") return leafToJSONHandler;
|
|
788
947
|
if (leafKeys.has(key)) {
|
|
789
948
|
const leaf = opts.resolveLeaf(segments);
|
|
790
949
|
return readLeafKey(leaf, key);
|
|
@@ -811,9 +970,22 @@ function buildSurfaceProxy(opts) {
|
|
|
811
970
|
writable: false
|
|
812
971
|
};
|
|
813
972
|
},
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
973
|
+
// Same warn-and-noop contract as the container traps above.
|
|
974
|
+
// Returning `true` keeps strict-mode callers from throwing on
|
|
975
|
+
// `form.fields.email.value = …`; the actual readonly guarantee
|
|
976
|
+
// is the absence of any mutation, not the host-level reject.
|
|
977
|
+
set: (_, key) => {
|
|
978
|
+
warnReadOnly("form.fields.<leaf>", "write", key);
|
|
979
|
+
return true;
|
|
980
|
+
},
|
|
981
|
+
deleteProperty: (_, key) => {
|
|
982
|
+
warnReadOnly("form.fields.<leaf>", "delete", key);
|
|
983
|
+
return true;
|
|
984
|
+
},
|
|
985
|
+
defineProperty: (_, key) => {
|
|
986
|
+
warnReadOnly("form.fields.<leaf>", "define", key);
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
817
989
|
});
|
|
818
990
|
leafViewCache.set(cacheKey, proxy);
|
|
819
991
|
return proxy;
|
|
@@ -877,33 +1049,53 @@ function buildErrorsProxy(state) {
|
|
|
877
1049
|
// working — the call-form just extends that semantic to
|
|
878
1050
|
// containers and dynamic paths.
|
|
879
1051
|
resolveCallTarget: (path) => aggregateErrorsAt(state, path),
|
|
880
|
-
//
|
|
881
|
-
//
|
|
882
|
-
//
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
|
|
886
|
-
|
|
1052
|
+
// Enumeration unions the live form-data keys at this path with the
|
|
1053
|
+
// first-child segments drawn from every error store. Without the
|
|
1054
|
+
// union, `Object.keys(form.errors)` / `{...form.errors}` /
|
|
1055
|
+
// `v-for="(errs, k) in form.errors"` silently dropped two
|
|
1056
|
+
// important error classes that the dot / call / JSON.stringify
|
|
1057
|
+
// surfaces already exposed:
|
|
1058
|
+
//
|
|
1059
|
+
// - **Form-level** errors at the synthetic `['']` path (set via
|
|
1060
|
+
// `setFormErrors` or root cross-field refines).
|
|
1061
|
+
// - **Server-only** errors at a key the schema doesn't know
|
|
1062
|
+
// about (`['ghost']`, `['address', 'ghost']`).
|
|
1063
|
+
//
|
|
1064
|
+
// The union closes that gap so `ownKeys` agrees with the rest of
|
|
1065
|
+
// the surface. Active-path filter mirrors `resolveLeaf`:
|
|
1066
|
+
// library-produced verdicts (schema + derived-blank) at unreachable
|
|
1067
|
+
// paths stay hidden; user-supplied errors are unconditional.
|
|
1068
|
+
containerOwnKeys: (segments) => errorAwareContainerKeys(state, segments),
|
|
1069
|
+
isArrayContainer: (segments) => isArrayPath(state, segments)
|
|
887
1070
|
});
|
|
888
1071
|
}
|
|
889
|
-
function
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1072
|
+
function errorAwareContainerKeys(state, segments) {
|
|
1073
|
+
const keys = new Set(liveKeysAtPath(state, segments));
|
|
1074
|
+
const formValue = state.form.value;
|
|
1075
|
+
const walk = (store, applyActivePathFilter) => {
|
|
1076
|
+
for (const [pathKey, errors] of store) {
|
|
1077
|
+
if (errors.length === 0) continue;
|
|
1078
|
+
if (pathKey === paths.FORM_ERRORS_PATH_KEY) {
|
|
1079
|
+
if (segments.length === 0) keys.add("");
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
const decoded = paths.segmentsForPathKey(pathKey);
|
|
1083
|
+
if (decoded === null) continue;
|
|
1084
|
+
if (decoded.length <= segments.length) continue;
|
|
1085
|
+
if (!paths.isPathPrefix(segments, decoded)) continue;
|
|
1086
|
+
if (applyActivePathFilter && !hasAtPath(formValue, decoded)) continue;
|
|
1087
|
+
const nextSeg = decoded[segments.length];
|
|
1088
|
+
keys.add(typeof nextSeg === "number" ? String(nextSeg) : nextSeg);
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
walk(state.schemaErrors, true);
|
|
1092
|
+
walk(state.derivedBlankErrors.value, true);
|
|
1093
|
+
walk(state.userErrors, false);
|
|
1094
|
+
return [...keys];
|
|
903
1095
|
}
|
|
904
1096
|
function materializeErrors(state, containerSegments) {
|
|
905
1097
|
const liveContainer = getAtPath(state.form.value, containerSegments);
|
|
906
|
-
const tree = Array.isArray(liveContainer) ? [] :
|
|
1098
|
+
const tree = Array.isArray(liveContainer) ? [] : /* @__PURE__ */ Object.create(null);
|
|
907
1099
|
const collect = (store, applyActivePathFilter) => {
|
|
908
1100
|
entries: for (const [pathKey, errors] of store) {
|
|
909
1101
|
if (errors.length === 0) continue;
|
|
@@ -953,7 +1145,7 @@ function placeAt(tree, path, errors) {
|
|
|
953
1145
|
const cursorRecord2 = cursor;
|
|
954
1146
|
let child = cursorRecord2[key];
|
|
955
1147
|
if (child === null || child === void 0 || typeof child !== "object") {
|
|
956
|
-
child = typeof nextSeg === "number" ? [] :
|
|
1148
|
+
child = typeof nextSeg === "number" ? [] : /* @__PURE__ */ Object.create(null);
|
|
957
1149
|
cursorRecord2[key] = child;
|
|
958
1150
|
}
|
|
959
1151
|
cursor = child;
|
|
@@ -988,19 +1180,20 @@ function buildFieldArrayApi(state) {
|
|
|
988
1180
|
prepend(path, value) {
|
|
989
1181
|
const next = readArray(path);
|
|
990
1182
|
next.unshift(value);
|
|
991
|
-
return writeArray(path, next, { kind: "
|
|
1183
|
+
return writeArray(path, next, { kind: "insert", index: 0 });
|
|
992
1184
|
},
|
|
993
1185
|
insert(path, index, value) {
|
|
994
1186
|
const next = readArray(path);
|
|
995
|
-
next.
|
|
996
|
-
const
|
|
997
|
-
|
|
1187
|
+
const preLen = next.length;
|
|
1188
|
+
const insertIndex = index < 0 ? Math.max(0, preLen + index) : Math.min(index, preLen);
|
|
1189
|
+
next.splice(insertIndex, 0, value);
|
|
1190
|
+
return writeArray(path, next, { kind: "insert", index: insertIndex });
|
|
998
1191
|
},
|
|
999
1192
|
remove(path, index) {
|
|
1000
1193
|
const next = readArray(path);
|
|
1001
1194
|
if (index < 0 || index >= next.length) return false;
|
|
1002
1195
|
next.splice(index, 1);
|
|
1003
|
-
return writeArray(path, next, { kind: "
|
|
1196
|
+
return writeArray(path, next, { kind: "remove", index });
|
|
1004
1197
|
},
|
|
1005
1198
|
swap(path, a, b) {
|
|
1006
1199
|
const next = readArray(path);
|
|
@@ -1018,11 +1211,7 @@ function buildFieldArrayApi(state) {
|
|
|
1018
1211
|
const [item] = next.splice(from, 1);
|
|
1019
1212
|
const clampedTo = Math.max(0, Math.min(to, next.length));
|
|
1020
1213
|
next.splice(clampedTo, 0, item);
|
|
1021
|
-
return writeArray(path, next, {
|
|
1022
|
-
kind: "shift-range",
|
|
1023
|
-
fromIndex: Math.min(from, clampedTo),
|
|
1024
|
-
toIndex: Math.max(from, clampedTo)
|
|
1025
|
-
});
|
|
1214
|
+
return writeArray(path, next, { kind: "move", from, to: clampedTo });
|
|
1026
1215
|
},
|
|
1027
1216
|
replace(path, index, value) {
|
|
1028
1217
|
const next = readArray(path);
|
|
@@ -1041,6 +1230,8 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
|
|
|
1041
1230
|
"focused",
|
|
1042
1231
|
"blurred",
|
|
1043
1232
|
"touched",
|
|
1233
|
+
"interacted",
|
|
1234
|
+
"blurredAfterInteraction",
|
|
1044
1235
|
"connected",
|
|
1045
1236
|
"element",
|
|
1046
1237
|
"elements",
|
|
@@ -1048,20 +1239,28 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
|
|
|
1048
1239
|
"errors",
|
|
1049
1240
|
"validating",
|
|
1050
1241
|
"valid",
|
|
1242
|
+
"displayState",
|
|
1051
1243
|
"showErrors",
|
|
1244
|
+
"showPending",
|
|
1245
|
+
"showSuccess",
|
|
1246
|
+
"showIdle",
|
|
1052
1247
|
"firstError",
|
|
1053
1248
|
"path",
|
|
1249
|
+
"id",
|
|
1250
|
+
"aria",
|
|
1251
|
+
"key",
|
|
1054
1252
|
"blank",
|
|
1055
1253
|
"label",
|
|
1056
1254
|
"description",
|
|
1057
1255
|
"placeholder",
|
|
1058
1256
|
"meta"
|
|
1059
1257
|
]);
|
|
1060
|
-
function buildFieldStateProxy(state, getFormMetaBase, options) {
|
|
1258
|
+
function buildFieldStateProxy(state, formInstanceId, getFormMetaBase, options) {
|
|
1061
1259
|
const getFieldStateAt = buildFieldStateAccessor(
|
|
1062
1260
|
state,
|
|
1261
|
+
formInstanceId,
|
|
1063
1262
|
getFormMetaBase,
|
|
1064
|
-
options?.
|
|
1263
|
+
options?.getDisplayState !== void 0 ? { getDisplayState: options.getDisplayState } : void 0
|
|
1065
1264
|
);
|
|
1066
1265
|
const snapshotFieldStateAt = (path) => {
|
|
1067
1266
|
const view = getFieldStateAt(path).value;
|
|
@@ -1110,9 +1309,34 @@ function buildFieldStateProxy(state, getFormMetaBase, options) {
|
|
|
1110
1309
|
writable: false
|
|
1111
1310
|
};
|
|
1112
1311
|
},
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1312
|
+
// Warn-and-noop: returning `false` here throws `TypeError` under
|
|
1313
|
+
// strict mode (`form.fields('email').value = 1` from any ESM
|
|
1314
|
+
// module), which would violate the documented "writes are
|
|
1315
|
+
// ignored" contract. Mirrors `values-proxy` / `wizard-statuses-proxy`.
|
|
1316
|
+
set: (_, key) => {
|
|
1317
|
+
if (paths.__DEV__) {
|
|
1318
|
+
console.warn(
|
|
1319
|
+
`[attaform] form.fields(path) is read-only \u2014 write to "${String(key)}" was ignored. Use form.setValue / the directive / field-array helpers instead.`
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
return true;
|
|
1323
|
+
},
|
|
1324
|
+
deleteProperty: (_, key) => {
|
|
1325
|
+
if (paths.__DEV__) {
|
|
1326
|
+
console.warn(
|
|
1327
|
+
`[attaform] form.fields(path) is read-only \u2014 delete of "${String(key)}" was ignored.`
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
return true;
|
|
1331
|
+
},
|
|
1332
|
+
defineProperty: (_, key) => {
|
|
1333
|
+
if (paths.__DEV__) {
|
|
1334
|
+
console.warn(
|
|
1335
|
+
`[attaform] form.fields(path) is read-only \u2014 define of "${String(key)}" was ignored.`
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1116
1340
|
});
|
|
1117
1341
|
terminalCache.set(cacheKey, proxy);
|
|
1118
1342
|
return proxy;
|
|
@@ -1128,21 +1352,6 @@ function buildFieldStateProxy(state, getFormMetaBase, options) {
|
|
|
1128
1352
|
isArrayContainer: (segments) => isArrayPath(state, segments)
|
|
1129
1353
|
});
|
|
1130
1354
|
}
|
|
1131
|
-
function liveKeysAtPath(state, segments) {
|
|
1132
|
-
const value = getAtPath(state.form.value, segments);
|
|
1133
|
-
if (value === null || value === void 0) return [];
|
|
1134
|
-
if (Array.isArray(value)) {
|
|
1135
|
-
const keys = new Array(value.length);
|
|
1136
|
-
for (let i = 0; i < value.length; i += 1) keys[i] = String(i);
|
|
1137
|
-
return keys;
|
|
1138
|
-
}
|
|
1139
|
-
if (typeof value === "object") return Object.keys(value);
|
|
1140
|
-
return [];
|
|
1141
|
-
}
|
|
1142
|
-
function isArrayPath(state, segments) {
|
|
1143
|
-
if (segments.length === 0) return false;
|
|
1144
|
-
return Array.isArray(getAtPath(state.form.value, segments));
|
|
1145
|
-
}
|
|
1146
1355
|
function materializeFields(state, containerSegments, snapshotFieldStateAt) {
|
|
1147
1356
|
const liveValue = getAtPath(state.form.value, containerSegments);
|
|
1148
1357
|
return walk$2(liveValue, containerSegments, state.schema, snapshotFieldStateAt);
|
|
@@ -1168,29 +1377,6 @@ function walk$2(value, basePath, schema, snapshotFieldStateAt) {
|
|
|
1168
1377
|
return result;
|
|
1169
1378
|
}
|
|
1170
1379
|
|
|
1171
|
-
const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
|
|
1172
|
-
const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
|
|
1173
|
-
const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
|
|
1174
|
-
const PERSISTENCE_KEY_PREFIX = "attaform:";
|
|
1175
|
-
const RESERVED_KEY_PREFIX = "__atta:";
|
|
1176
|
-
const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
|
|
1177
|
-
const ANONYMOUS_WIZARD_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon-wizard:`;
|
|
1178
|
-
const DEFAULT_MAX_RECURSION_DEPTH = 64;
|
|
1179
|
-
function normalizeNumericOption(config) {
|
|
1180
|
-
const { value, source, allowInfinity, min, defaultValue } = config;
|
|
1181
|
-
if (allowInfinity && value === Infinity) return Infinity;
|
|
1182
|
-
if (typeof value !== "number" || Number.isNaN(value) || value === Infinity || value === -Infinity) {
|
|
1183
|
-
if (paths.__DEV__) {
|
|
1184
|
-
const acceptedDescription = allowInfinity ? "a non-negative integer or Infinity" : "a non-negative finite integer";
|
|
1185
|
-
console.warn(
|
|
1186
|
-
`[attaform] ${source} must be ${acceptedDescription}; got ${String(value)}. Falling back to ${String(defaultValue)}.`
|
|
1187
|
-
);
|
|
1188
|
-
}
|
|
1189
|
-
return defaultValue;
|
|
1190
|
-
}
|
|
1191
|
-
return Math.max(min, Math.floor(value));
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
1380
|
const PERSISTENCE_MODULE_KEY = "persistence";
|
|
1195
1381
|
async function getStorageAdapter(storage) {
|
|
1196
1382
|
if (typeof storage === "object") return storage;
|
|
@@ -1209,7 +1395,7 @@ async function getStorageAdapter(storage) {
|
|
|
1209
1395
|
}
|
|
1210
1396
|
}
|
|
1211
1397
|
}
|
|
1212
|
-
const PERSISTED_ENVELOPE_VERSION =
|
|
1398
|
+
const PERSISTED_ENVELOPE_VERSION = 6;
|
|
1213
1399
|
function readPersistedPayload(value) {
|
|
1214
1400
|
if (value === null || value === void 0 || typeof value !== "object") return null;
|
|
1215
1401
|
const envelope = value;
|
|
@@ -1233,12 +1419,7 @@ function warnVersionMismatch(observedVersion) {
|
|
|
1233
1419
|
function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPaths) {
|
|
1234
1420
|
let transientList;
|
|
1235
1421
|
if (blankPaths !== void 0 && blankPaths.size > 0) {
|
|
1236
|
-
|
|
1237
|
-
for (const key of blankPaths) {
|
|
1238
|
-
const d = paths.pathKeyToDotted(key);
|
|
1239
|
-
if (d !== null) dotted.push(d);
|
|
1240
|
-
}
|
|
1241
|
-
transientList = dotted.length > 0 ? dotted : void 0;
|
|
1422
|
+
transientList = [...blankPaths];
|
|
1242
1423
|
}
|
|
1243
1424
|
if (include === "form") {
|
|
1244
1425
|
if (transientList === void 0) return { v: PERSISTED_ENVELOPE_VERSION, data: { form } };
|
|
@@ -1260,30 +1441,35 @@ function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPat
|
|
|
1260
1441
|
function createDebouncedWriter(write, debounceMs) {
|
|
1261
1442
|
let timer = null;
|
|
1262
1443
|
let pending = null;
|
|
1444
|
+
let writeGeneration = 0;
|
|
1445
|
+
function runWrite() {
|
|
1446
|
+
const gen = ++writeGeneration;
|
|
1447
|
+
pending = write().finally(() => {
|
|
1448
|
+
if (writeGeneration === gen) pending = null;
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1263
1451
|
function schedule() {
|
|
1264
1452
|
if (timer !== null) clearTimeout(timer);
|
|
1265
1453
|
if (debounceMs === 0) {
|
|
1266
|
-
|
|
1267
|
-
pending = null;
|
|
1268
|
-
});
|
|
1454
|
+
runWrite();
|
|
1269
1455
|
return;
|
|
1270
1456
|
}
|
|
1271
1457
|
timer = setTimeout(() => {
|
|
1272
1458
|
timer = null;
|
|
1273
|
-
|
|
1274
|
-
pending = null;
|
|
1275
|
-
});
|
|
1459
|
+
runWrite();
|
|
1276
1460
|
}, debounceMs);
|
|
1277
1461
|
}
|
|
1278
1462
|
async function flush() {
|
|
1279
1463
|
if (timer !== null) {
|
|
1280
1464
|
clearTimeout(timer);
|
|
1281
1465
|
timer = null;
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1466
|
+
runWrite();
|
|
1467
|
+
}
|
|
1468
|
+
while (pending !== null) {
|
|
1469
|
+
const awaited = pending;
|
|
1470
|
+
await awaited;
|
|
1471
|
+
if (pending === awaited) break;
|
|
1285
1472
|
}
|
|
1286
|
-
if (pending !== null) await pending;
|
|
1287
1473
|
}
|
|
1288
1474
|
function cancel() {
|
|
1289
1475
|
if (timer !== null) {
|
|
@@ -1296,7 +1482,7 @@ function createDebouncedWriter(write, debounceMs) {
|
|
|
1296
1482
|
function resolveStorageKeyBase(config, formKey) {
|
|
1297
1483
|
return config.key ?? `${PERSISTENCE_KEY_PREFIX}${formKey}`;
|
|
1298
1484
|
}
|
|
1299
|
-
async function
|
|
1485
|
+
async function removeMatchingKeys(adapter, base, keepKey) {
|
|
1300
1486
|
let keys;
|
|
1301
1487
|
try {
|
|
1302
1488
|
keys = await adapter.listKeys(base);
|
|
@@ -1304,12 +1490,15 @@ async function cleanupOrphanKeys(adapter, base, currentKey) {
|
|
|
1304
1490
|
return;
|
|
1305
1491
|
}
|
|
1306
1492
|
for (const key of keys) {
|
|
1307
|
-
if (key ===
|
|
1493
|
+
if (key === keepKey) continue;
|
|
1308
1494
|
if (key === base || key.startsWith(`${base}:`)) {
|
|
1309
1495
|
void adapter.removeItem(key).catch(() => void 0);
|
|
1310
1496
|
}
|
|
1311
1497
|
}
|
|
1312
1498
|
}
|
|
1499
|
+
async function cleanupOrphanKeys(adapter, base, currentKey) {
|
|
1500
|
+
await removeMatchingKeys(adapter, base, currentKey);
|
|
1501
|
+
}
|
|
1313
1502
|
const STANDARD_STORAGE_KINDS = ["local", "session", "indexeddb"];
|
|
1314
1503
|
function normalizePersistConfig(input) {
|
|
1315
1504
|
if (typeof input === "string") return { storage: input };
|
|
@@ -1320,12 +1509,7 @@ async function sweepAllOrphansAcrossStandardStores(base) {
|
|
|
1320
1509
|
for (const kind of STANDARD_STORAGE_KINDS) {
|
|
1321
1510
|
try {
|
|
1322
1511
|
const adapter = await getStorageAdapter(kind);
|
|
1323
|
-
|
|
1324
|
-
for (const key of keys) {
|
|
1325
|
-
if (key === base || key.startsWith(`${base}:`)) {
|
|
1326
|
-
void adapter.removeItem(key).catch(() => void 0);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1512
|
+
await removeMatchingKeys(adapter, base);
|
|
1329
1513
|
} catch {
|
|
1330
1514
|
}
|
|
1331
1515
|
}
|
|
@@ -1336,12 +1520,7 @@ async function sweepNonConfiguredStandardStoresForOrphans(configured, base) {
|
|
|
1336
1520
|
if (kind === configuredKind) continue;
|
|
1337
1521
|
try {
|
|
1338
1522
|
const adapter = await getStorageAdapter(kind);
|
|
1339
|
-
|
|
1340
|
-
for (const key of keys) {
|
|
1341
|
-
if (key === base || key.startsWith(`${base}:`)) {
|
|
1342
|
-
void adapter.removeItem(key).catch(() => void 0);
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1523
|
+
await removeMatchingKeys(adapter, base);
|
|
1345
1524
|
} catch {
|
|
1346
1525
|
}
|
|
1347
1526
|
}
|
|
@@ -1357,6 +1536,29 @@ function pluckPaths(form, pathKeys) {
|
|
|
1357
1536
|
}
|
|
1358
1537
|
return sparse ?? {};
|
|
1359
1538
|
}
|
|
1539
|
+
function stripUnacknowledgedSensitiveLeaves(form, optedInPaths, isSensitivePath) {
|
|
1540
|
+
const acknowledgedSensitive = [];
|
|
1541
|
+
for (const key of optedInPaths) {
|
|
1542
|
+
const segs = paths.segmentsForPathKey(key);
|
|
1543
|
+
if (segs !== null && isSensitivePath(segs)) acknowledgedSensitive.push(segs);
|
|
1544
|
+
}
|
|
1545
|
+
const coveredByAcknowledged = (path) => acknowledgedSensitive.some((prefix) => paths.isPathPrefix(prefix, path));
|
|
1546
|
+
const walk = (path, value) => {
|
|
1547
|
+
if (path.length > 0 && isSensitivePath(path) && !coveredByAcknowledged(path)) {
|
|
1548
|
+
return void 0;
|
|
1549
|
+
}
|
|
1550
|
+
if (value === null || typeof value !== "object") return value;
|
|
1551
|
+
if (Array.isArray(value)) return value.map((item, i) => walk([...path, i], item));
|
|
1552
|
+
if (!isPlainRecord(value)) return value;
|
|
1553
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
1554
|
+
for (const key of Object.keys(value)) {
|
|
1555
|
+
const walked = walk([...path, key], value[key]);
|
|
1556
|
+
if (walked !== void 0) out[key] = walked;
|
|
1557
|
+
}
|
|
1558
|
+
return out;
|
|
1559
|
+
};
|
|
1560
|
+
return walk([], form);
|
|
1561
|
+
}
|
|
1360
1562
|
function filterErrorsByPaths(errors, pathKeys) {
|
|
1361
1563
|
const out = /* @__PURE__ */ new Map();
|
|
1362
1564
|
for (const [key, value] of errors) {
|
|
@@ -1383,7 +1585,7 @@ function mergeDeep(target, source, path, schema) {
|
|
|
1383
1585
|
if (sourceDisc !== void 0) {
|
|
1384
1586
|
const variantDefault = du.getVariantDefault(sourceDisc);
|
|
1385
1587
|
if (isPlainRecord(variantDefault)) {
|
|
1386
|
-
const out2 =
|
|
1588
|
+
const out2 = Object.assign(/* @__PURE__ */ Object.create(null), variantDefault);
|
|
1387
1589
|
for (const key of Object.keys(sourceRecord)) {
|
|
1388
1590
|
if (!(key in variantDefault) && key !== du.discriminatorKey) continue;
|
|
1389
1591
|
out2[key] = mergeDeep(out2[key], sourceRecord[key], [...path, key], schema);
|
|
@@ -1395,7 +1597,7 @@ function mergeDeep(target, source, path, schema) {
|
|
|
1395
1597
|
}
|
|
1396
1598
|
}
|
|
1397
1599
|
const mergeTarget = target;
|
|
1398
|
-
const out = isPlainRecord(mergeTarget) ?
|
|
1600
|
+
const out = isPlainRecord(mergeTarget) ? Object.assign(/* @__PURE__ */ Object.create(null), mergeTarget) : /* @__PURE__ */ Object.create(null);
|
|
1399
1601
|
for (const key of Object.keys(source)) {
|
|
1400
1602
|
out[key] = mergeDeep(out[key], source[key], [...path, key], schema);
|
|
1401
1603
|
}
|
|
@@ -1498,39 +1700,44 @@ function buildProcessForm(state, formInstanceId, options = {}) {
|
|
|
1498
1700
|
}
|
|
1499
1701
|
return result;
|
|
1500
1702
|
}
|
|
1501
|
-
async function
|
|
1703
|
+
async function runImperativeValidation(pathInput, config) {
|
|
1502
1704
|
const segments = pathInput === void 0 ? void 0 : toSegments(pathInput);
|
|
1503
1705
|
const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
|
|
1504
1706
|
try {
|
|
1505
1707
|
state.activeValidations.value += 1;
|
|
1506
|
-
state.cancelFieldValidation();
|
|
1708
|
+
if (config.cancelInFlight) state.cancelFieldValidation();
|
|
1507
1709
|
const refinement = await runRefinementValidation(dataAtPath, segments);
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1710
|
+
if (config.commitToSchemaErrors) {
|
|
1711
|
+
const scopePath = segments ?? [];
|
|
1712
|
+
const errors = refinement.success ? [] : refinement.errors;
|
|
1713
|
+
const reStamped = segments === void 0 ? errors : errors.map((err) => ({
|
|
1714
|
+
...err,
|
|
1715
|
+
path: [...segments, ...err.path]
|
|
1716
|
+
}));
|
|
1717
|
+
state.applySchemaErrorsForSubtree(scopePath, reStamped);
|
|
1718
|
+
}
|
|
1719
|
+
return { ok: true, refinement, segments };
|
|
1516
1720
|
} catch (err) {
|
|
1517
|
-
return adapterThrowResponse(err);
|
|
1721
|
+
return { ok: false, error: adapterThrowResponse(err) };
|
|
1518
1722
|
} finally {
|
|
1519
1723
|
state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
|
|
1520
1724
|
}
|
|
1521
1725
|
}
|
|
1726
|
+
async function validateAsync(pathInput) {
|
|
1727
|
+
const result = await runImperativeValidation(pathInput, {
|
|
1728
|
+
cancelInFlight: true,
|
|
1729
|
+
commitToSchemaErrors: true
|
|
1730
|
+
});
|
|
1731
|
+
if (!result.ok) return result.error;
|
|
1732
|
+
return stripData(composeWithDerivedBlank(result.refinement, result.segments));
|
|
1733
|
+
}
|
|
1522
1734
|
async function process(pathInput) {
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
} catch (err) {
|
|
1530
|
-
return adapterThrowResponse(err);
|
|
1531
|
-
} finally {
|
|
1532
|
-
state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
|
|
1533
|
-
}
|
|
1735
|
+
const result = await runImperativeValidation(pathInput, {
|
|
1736
|
+
cancelInFlight: false,
|
|
1737
|
+
commitToSchemaErrors: false
|
|
1738
|
+
});
|
|
1739
|
+
if (!result.ok) return result.error;
|
|
1740
|
+
return composeWithDerivedBlank(result.refinement, result.segments);
|
|
1534
1741
|
}
|
|
1535
1742
|
function adapterThrowResponse(err) {
|
|
1536
1743
|
return {
|
|
@@ -1968,7 +2175,6 @@ function coerceValue(value, accepted, elementAccepted, index) {
|
|
|
1968
2175
|
}
|
|
1969
2176
|
|
|
1970
2177
|
const EMPTY_TRANSFORMS = Object.freeze([]);
|
|
1971
|
-
const INTERACTIVE_TAG_NAMES = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
|
|
1972
2178
|
const attaformListenersSymbol = Symbol.for("attaform:focus-listeners");
|
|
1973
2179
|
function attachFocusListeners(state, segments, element, instanceMeta) {
|
|
1974
2180
|
const target = element;
|
|
@@ -1996,6 +2202,8 @@ function detachFocusListeners(element) {
|
|
|
1996
2202
|
function buildRegister(state, formInstanceId, instanceConfig) {
|
|
1997
2203
|
const coerceIndex = instanceConfig?.coerce !== void 0 ? resolveCoercionIndex(instanceConfig.coerce) : state.coerceIndex;
|
|
1998
2204
|
const instanceMeta = instanceConfig?.instanceMeta;
|
|
2205
|
+
const formAutoAria = instanceConfig?.autoAria ?? true;
|
|
2206
|
+
const getDisplayStateAt = instanceConfig?.getDisplayStateAt;
|
|
1999
2207
|
const withInstanceMeta = (meta) => {
|
|
2000
2208
|
if (instanceMeta === void 0) return meta;
|
|
2001
2209
|
return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
|
|
@@ -2017,7 +2225,11 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
2017
2225
|
if (typed !== null && typeof raw === "number" && parseFloat(typed) === raw) {
|
|
2018
2226
|
return typed;
|
|
2019
2227
|
}
|
|
2020
|
-
|
|
2228
|
+
try {
|
|
2229
|
+
return String(raw);
|
|
2230
|
+
} catch {
|
|
2231
|
+
return Object.prototype.toString.call(raw);
|
|
2232
|
+
}
|
|
2021
2233
|
});
|
|
2022
2234
|
const slimDefault = state.schema.getDefaultAtPath(segments);
|
|
2023
2235
|
const slimTypes = state.schema.getSlimPrimitiveTypesAtPath(segments);
|
|
@@ -2050,6 +2262,10 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
2050
2262
|
callSite: paths.captureUserCallSite()
|
|
2051
2263
|
});
|
|
2052
2264
|
}
|
|
2265
|
+
const { aria } = computeFieldIdentity(formInstanceId, state.formKey, pathKey);
|
|
2266
|
+
const isRequired = state.schema.isRequiredAtPath(segments);
|
|
2267
|
+
const ariaEnabled = options?.autoAria ?? formAutoAria;
|
|
2268
|
+
const ariaDisplayState = getDisplayStateAt !== void 0 ? vue.computed(() => getDisplayStateAt(segments)) : void 0;
|
|
2053
2269
|
const internalRv = {
|
|
2054
2270
|
innerRef,
|
|
2055
2271
|
displayValue,
|
|
@@ -2064,8 +2280,11 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
2064
2280
|
})
|
|
2065
2281
|
);
|
|
2066
2282
|
},
|
|
2283
|
+
markInteracted: () => {
|
|
2284
|
+
state.markInteracted(segments);
|
|
2285
|
+
},
|
|
2067
2286
|
registerElement: (element) => {
|
|
2068
|
-
if (!INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
|
|
2287
|
+
if (!paths.INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
|
|
2069
2288
|
const added = state.registerElement(segments, element, formInstanceId);
|
|
2070
2289
|
if (added) attachFocusListeners(state, segments, element, instanceMeta);
|
|
2071
2290
|
},
|
|
@@ -2108,7 +2327,12 @@ function buildRegister(state, formInstanceId, instanceConfig) {
|
|
|
2108
2327
|
coerce,
|
|
2109
2328
|
...coerceElement !== void 0 ? { coerceElement } : {},
|
|
2110
2329
|
acceptsUndefined,
|
|
2111
|
-
acceptsString
|
|
2330
|
+
acceptsString,
|
|
2331
|
+
// --- Aria (internal; consumed by the directive) ---
|
|
2332
|
+
aria,
|
|
2333
|
+
isRequired,
|
|
2334
|
+
ariaEnabled,
|
|
2335
|
+
...ariaDisplayState !== void 0 ? { ariaDisplayState } : {}
|
|
2112
2336
|
};
|
|
2113
2337
|
return vue.shallowReadonly(internalRv);
|
|
2114
2338
|
};
|
|
@@ -2159,7 +2383,7 @@ function walk(input, segments, schema, paths) {
|
|
|
2159
2383
|
if (slim !== null && slim !== void 0 && typeof slim === "object" && !Array.isArray(slim) && !(slim instanceof Date) && !(slim instanceof RegExp) && !(slim instanceof Map) && !(slim instanceof Set)) {
|
|
2160
2384
|
for (const k of Object.keys(slim)) allKeys.add(k);
|
|
2161
2385
|
}
|
|
2162
|
-
const out =
|
|
2386
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2163
2387
|
let mutated = allKeys.size !== inputKeys.length;
|
|
2164
2388
|
for (const key of allKeys) {
|
|
2165
2389
|
const orig = input[key];
|
|
@@ -2188,7 +2412,7 @@ function walkUnspecified(slim, segments, paths$1) {
|
|
|
2188
2412
|
}
|
|
2189
2413
|
if (Array.isArray(slim)) return slim;
|
|
2190
2414
|
if (slim !== null && typeof slim === "object") {
|
|
2191
|
-
const out =
|
|
2415
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2192
2416
|
for (const key of Object.keys(slim)) {
|
|
2193
2417
|
out[key] = walkUnspecified(slim[key], [...segments, key], paths$1);
|
|
2194
2418
|
}
|
|
@@ -2221,7 +2445,7 @@ function substitute(input, segments, schema, paths) {
|
|
|
2221
2445
|
}
|
|
2222
2446
|
if (typeof input === "object") {
|
|
2223
2447
|
let mutated = false;
|
|
2224
|
-
const out =
|
|
2448
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2225
2449
|
for (const key of Object.keys(input)) {
|
|
2226
2450
|
const orig = input[key];
|
|
2227
2451
|
const walked = substitute(orig, [...segments, key], schema, paths);
|
|
@@ -2273,70 +2497,86 @@ function expandUnsetAt(segments, schema, paths$1) {
|
|
|
2273
2497
|
return result;
|
|
2274
2498
|
}
|
|
2275
2499
|
|
|
2276
|
-
function
|
|
2277
|
-
const inner = vue.computed(() => vue.readonly(form.value));
|
|
2500
|
+
function buildCallableReadonlySnapshotProxy(opts) {
|
|
2278
2501
|
const target = (() => {
|
|
2279
2502
|
});
|
|
2280
|
-
const
|
|
2281
|
-
const
|
|
2503
|
+
const { toString, valueOf, toJSON, toPrimitive } = makeReadonlyCoercion(opts.snapshot);
|
|
2504
|
+
const callResolve = opts.resolveCall ?? ((arg) => opts.resolveKey(String(arg)));
|
|
2282
2505
|
return new Proxy(target, {
|
|
2283
2506
|
apply(_, __, args) {
|
|
2284
2507
|
const arg = args[0];
|
|
2285
|
-
if (arg === void 0) return
|
|
2286
|
-
|
|
2287
|
-
let cursor = inner.value;
|
|
2288
|
-
for (const seg of segments) {
|
|
2289
|
-
if (cursor === null || cursor === void 0) return void 0;
|
|
2290
|
-
cursor = cursor[seg];
|
|
2291
|
-
}
|
|
2292
|
-
return cursor;
|
|
2508
|
+
if (arg === void 0) return opts.snapshot();
|
|
2509
|
+
return callResolve(arg);
|
|
2293
2510
|
},
|
|
2294
2511
|
get(_, key) {
|
|
2295
2512
|
if (typeof key === "symbol") {
|
|
2296
|
-
if (key === Symbol.toPrimitive) return
|
|
2513
|
+
if (key === Symbol.toPrimitive) return toPrimitive;
|
|
2297
2514
|
return Reflect.get(target, key);
|
|
2298
2515
|
}
|
|
2299
|
-
if (key === "toJSON") return
|
|
2300
|
-
if (key === "toString") return
|
|
2301
|
-
if (key === "valueOf")
|
|
2302
|
-
|
|
2303
|
-
return this;
|
|
2304
|
-
};
|
|
2305
|
-
return inner.value[key];
|
|
2516
|
+
if (key === "toJSON") return toJSON;
|
|
2517
|
+
if (key === "toString") return toString;
|
|
2518
|
+
if (key === "valueOf") return valueOf;
|
|
2519
|
+
return opts.resolveKey(key);
|
|
2306
2520
|
},
|
|
2307
2521
|
has(_, key) {
|
|
2308
2522
|
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
2309
|
-
return
|
|
2310
|
-
},
|
|
2311
|
-
ownKeys() {
|
|
2312
|
-
return Reflect.ownKeys(inner.value);
|
|
2523
|
+
return opts.hasKey(key);
|
|
2313
2524
|
},
|
|
2525
|
+
ownKeys: () => opts.ownKeys(),
|
|
2314
2526
|
getOwnPropertyDescriptor(_, key) {
|
|
2315
|
-
|
|
2316
|
-
if (
|
|
2317
|
-
return
|
|
2527
|
+
if (typeof key !== "string") return void 0;
|
|
2528
|
+
if (opts.describeKey !== void 0) return opts.describeKey(key);
|
|
2529
|
+
if (!opts.hasKey(key)) return void 0;
|
|
2530
|
+
return {
|
|
2531
|
+
configurable: true,
|
|
2532
|
+
enumerable: true,
|
|
2533
|
+
writable: false,
|
|
2534
|
+
value: opts.resolveKey(key)
|
|
2535
|
+
};
|
|
2318
2536
|
},
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
// TypeError in strict-mode consumers, surprising users who
|
|
2322
|
-
// assigned through the proxy and expected it to be ignored.
|
|
2323
|
-
set(_, key) {
|
|
2324
|
-
if (paths.__DEV__) {
|
|
2325
|
-
console.warn(
|
|
2326
|
-
`[attaform] form.values is read-only \u2014 write to "${String(key)}" was ignored. Use form.setValue / the directive / field-array helpers instead.`
|
|
2327
|
-
);
|
|
2328
|
-
}
|
|
2537
|
+
set: (_, key) => {
|
|
2538
|
+
warnReadOnly(opts.surface, "write", key);
|
|
2329
2539
|
return true;
|
|
2330
2540
|
},
|
|
2331
|
-
deleteProperty(_, key) {
|
|
2332
|
-
|
|
2333
|
-
console.warn(
|
|
2334
|
-
`[attaform] form.values is read-only \u2014 delete of "${String(key)}" was ignored.`
|
|
2335
|
-
);
|
|
2336
|
-
}
|
|
2541
|
+
deleteProperty: (_, key) => {
|
|
2542
|
+
warnReadOnly(opts.surface, "delete", key);
|
|
2337
2543
|
return true;
|
|
2338
2544
|
},
|
|
2339
|
-
defineProperty: () =>
|
|
2545
|
+
defineProperty: (_, key) => {
|
|
2546
|
+
warnReadOnly(opts.surface, "define", key);
|
|
2547
|
+
return true;
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
function buildValuesProxy(form) {
|
|
2553
|
+
const inner = vue.computed(() => vue.readonly(form.value));
|
|
2554
|
+
return buildCallableReadonlySnapshotProxy({
|
|
2555
|
+
surface: "form.values",
|
|
2556
|
+
snapshot: () => inner.value,
|
|
2557
|
+
// Read through the readonly proxy at access time so Vue's
|
|
2558
|
+
// dependency tracking lands inside the consumer's active effect
|
|
2559
|
+
// — `inner.value[key]` is what triggers per-key tracking.
|
|
2560
|
+
resolveKey: (key) => inner.value[key],
|
|
2561
|
+
// Dynamic path: walk segments through the readonly proxy. Each
|
|
2562
|
+
// step reads through the proxy's own get traps so dependency
|
|
2563
|
+
// tracking propagates at every level.
|
|
2564
|
+
resolveCall: (arg) => {
|
|
2565
|
+
const { segments } = paths.canonicalizePath(arg);
|
|
2566
|
+
let cursor = inner.value;
|
|
2567
|
+
for (const seg of segments) {
|
|
2568
|
+
if (cursor === null || cursor === void 0) return void 0;
|
|
2569
|
+
cursor = cursor[seg];
|
|
2570
|
+
}
|
|
2571
|
+
return cursor;
|
|
2572
|
+
},
|
|
2573
|
+
ownKeys: () => Reflect.ownKeys(inner.value),
|
|
2574
|
+
hasKey: (key) => Reflect.has(inner.value, key),
|
|
2575
|
+
describeKey: (key) => {
|
|
2576
|
+
const desc = Reflect.getOwnPropertyDescriptor(inner.value, key);
|
|
2577
|
+
if (desc !== void 0) desc.configurable = true;
|
|
2578
|
+
return desc;
|
|
2579
|
+
}
|
|
2340
2580
|
});
|
|
2341
2581
|
}
|
|
2342
2582
|
|
|
@@ -2352,15 +2592,34 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2352
2592
|
if (instanceMeta === void 0) return meta;
|
|
2353
2593
|
return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
|
|
2354
2594
|
};
|
|
2355
|
-
const
|
|
2356
|
-
|
|
2357
|
-
|
|
2595
|
+
const getFormMetaBase = () => {
|
|
2596
|
+
const rootBase = buildContainerFieldStateBase(state, paths.ROOT_PATH, paths.ROOT_PATH_KEY, formInstanceId);
|
|
2597
|
+
return {
|
|
2598
|
+
...rootBase,
|
|
2599
|
+
submitting: state.submitting.value,
|
|
2600
|
+
submissionAttempts: state.submissionAttempts.value,
|
|
2601
|
+
departAttempts: state.departAttempts.value,
|
|
2602
|
+
submitError: state.submitError.value,
|
|
2603
|
+
errorCount: rootBase.errors.length,
|
|
2604
|
+
submitted: state.submitted.value,
|
|
2605
|
+
instanceId: formInstanceId
|
|
2606
|
+
};
|
|
2358
2607
|
};
|
|
2359
|
-
const
|
|
2608
|
+
const fieldStateAccessorOptions = options.getDisplayState !== void 0 ? { getDisplayState: options.getDisplayState } : void 0;
|
|
2609
|
+
const getRootFieldStateAt = buildFieldStateAccessor(
|
|
2360
2610
|
state,
|
|
2361
2611
|
formInstanceId,
|
|
2362
|
-
|
|
2612
|
+
getFormMetaBase,
|
|
2613
|
+
fieldStateAccessorOptions
|
|
2363
2614
|
);
|
|
2615
|
+
const getDisplayStateAt = (segments) => getRootFieldStateAt(segments).value.displayState;
|
|
2616
|
+
const registerConfig = {
|
|
2617
|
+
...instanceMeta !== void 0 ? { instanceMeta } : {},
|
|
2618
|
+
...options.coerce !== void 0 ? { coerce: options.coerce } : {},
|
|
2619
|
+
...options.autoAria !== void 0 ? { autoAria: options.autoAria } : {},
|
|
2620
|
+
getDisplayStateAt
|
|
2621
|
+
};
|
|
2622
|
+
const register = buildRegister(state, formInstanceId, registerConfig);
|
|
2364
2623
|
const processOptions = options.onInvalidSubmit !== void 0 ? { onInvalidSubmit: options.onInvalidSubmit } : {};
|
|
2365
2624
|
const defaultInvalidSubmitPolicy = options.onInvalidSubmit ?? "focus-first-error";
|
|
2366
2625
|
const {
|
|
@@ -2383,10 +2642,18 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2383
2642
|
next,
|
|
2384
2643
|
state.schema
|
|
2385
2644
|
);
|
|
2645
|
+
const ok2 = state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
|
|
2646
|
+
if (!ok2) return false;
|
|
2386
2647
|
for (const pathKey of walked2.paths) {
|
|
2387
|
-
|
|
2648
|
+
const blankSegments = paths.segmentsForPathKey(pathKey);
|
|
2649
|
+
if (blankSegments === null) continue;
|
|
2650
|
+
state.setValueAtPath(
|
|
2651
|
+
blankSegments,
|
|
2652
|
+
state.getValueAtPath(blankSegments),
|
|
2653
|
+
withInstanceMeta({ blank: true })
|
|
2654
|
+
);
|
|
2388
2655
|
}
|
|
2389
|
-
return
|
|
2656
|
+
return true;
|
|
2390
2657
|
}
|
|
2391
2658
|
const segments = paths.canonicalizePath(pathOrValue).segments;
|
|
2392
2659
|
const writeUnsetAt = () => {
|
|
@@ -2532,43 +2799,48 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2532
2799
|
const metaErrors = vue.computed(
|
|
2533
2800
|
() => aggregateErrorsAt(state, [])
|
|
2534
2801
|
);
|
|
2535
|
-
const getFormMetaBase = () => {
|
|
2536
|
-
const rootBase = buildContainerFieldStateBase(state, paths.ROOT_PATH);
|
|
2537
|
-
return {
|
|
2538
|
-
...rootBase,
|
|
2539
|
-
submitting: state.submitting.value,
|
|
2540
|
-
submissionAttempts: state.submissionAttempts.value,
|
|
2541
|
-
departAttempts: state.departAttempts.value,
|
|
2542
|
-
submitError: state.submitError.value,
|
|
2543
|
-
errorCount: rootBase.errors.length,
|
|
2544
|
-
submitted: state.submitted.value,
|
|
2545
|
-
instanceId: formInstanceId
|
|
2546
|
-
};
|
|
2547
|
-
};
|
|
2548
|
-
const fieldStateAccessorOptions = options.shouldShowErrors !== void 0 ? { shouldShowErrors: options.shouldShowErrors } : void 0;
|
|
2549
|
-
const getRootFieldStateAt = buildFieldStateAccessor(
|
|
2550
|
-
state,
|
|
2551
|
-
getFormMetaBase,
|
|
2552
|
-
fieldStateAccessorOptions
|
|
2553
|
-
);
|
|
2554
2802
|
const rootFieldState = getRootFieldStateAt([]);
|
|
2555
2803
|
const formMeta = vue.readonly(
|
|
2556
2804
|
vue.reactive({
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
pristine
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2805
|
+
get value() {
|
|
2806
|
+
return rootFieldState.value.value;
|
|
2807
|
+
},
|
|
2808
|
+
get original() {
|
|
2809
|
+
return rootFieldState.value.original;
|
|
2810
|
+
},
|
|
2811
|
+
get pristine() {
|
|
2812
|
+
return rootFieldState.value.pristine;
|
|
2813
|
+
},
|
|
2814
|
+
get dirty() {
|
|
2815
|
+
return rootFieldState.value.dirty;
|
|
2816
|
+
},
|
|
2817
|
+
get focused() {
|
|
2818
|
+
return rootFieldState.value.focused;
|
|
2819
|
+
},
|
|
2820
|
+
get blurred() {
|
|
2821
|
+
return rootFieldState.value.blurred;
|
|
2822
|
+
},
|
|
2823
|
+
get touched() {
|
|
2824
|
+
return rootFieldState.value.touched;
|
|
2825
|
+
},
|
|
2826
|
+
get interacted() {
|
|
2827
|
+
return rootFieldState.value.interacted;
|
|
2828
|
+
},
|
|
2829
|
+
get blurredAfterInteraction() {
|
|
2830
|
+
return rootFieldState.value.blurredAfterInteraction;
|
|
2831
|
+
},
|
|
2832
|
+
get connected() {
|
|
2833
|
+
return rootFieldState.value.connected;
|
|
2834
|
+
},
|
|
2835
|
+
get element() {
|
|
2836
|
+
return rootFieldState.value.element;
|
|
2837
|
+
},
|
|
2838
|
+
get elements() {
|
|
2839
|
+
return rootFieldState.value.elements;
|
|
2840
|
+
},
|
|
2841
|
+
get updatedAt() {
|
|
2842
|
+
return rootFieldState.value.updatedAt;
|
|
2843
|
+
},
|
|
2572
2844
|
// Whole-form validating mirrors the LIFECYCLE counter
|
|
2573
2845
|
// (`state.activeValidations`) ORed with any per-leaf validation
|
|
2574
2846
|
// in flight (via `rootFieldState.validating`). A submit-time
|
|
@@ -2586,19 +2858,56 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2586
2858
|
// keep the explicit form-level computation for the gate.
|
|
2587
2859
|
valid,
|
|
2588
2860
|
errors: metaErrors,
|
|
2589
|
-
// `
|
|
2590
|
-
// field-state computed as the rest of the
|
|
2591
|
-
// so `form.meta.
|
|
2592
|
-
// exactly — the predicate runs once
|
|
2593
|
-
// is shared.
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2861
|
+
// `displayState` / the `show*` booleans / `firstError` flow
|
|
2862
|
+
// through the same root field-state computed as the rest of the
|
|
2863
|
+
// FieldState surface, so `form.meta.displayState` matches
|
|
2864
|
+
// `form.fields().displayState` exactly — the predicate runs once
|
|
2865
|
+
// at the root and the result is shared.
|
|
2866
|
+
get displayState() {
|
|
2867
|
+
return rootFieldState.value.displayState;
|
|
2868
|
+
},
|
|
2869
|
+
get showErrors() {
|
|
2870
|
+
return rootFieldState.value.showErrors;
|
|
2871
|
+
},
|
|
2872
|
+
get showPending() {
|
|
2873
|
+
return rootFieldState.value.showPending;
|
|
2874
|
+
},
|
|
2875
|
+
get showSuccess() {
|
|
2876
|
+
return rootFieldState.value.showSuccess;
|
|
2877
|
+
},
|
|
2878
|
+
get showIdle() {
|
|
2879
|
+
return rootFieldState.value.showIdle;
|
|
2880
|
+
},
|
|
2881
|
+
get firstError() {
|
|
2882
|
+
return rootFieldState.value.firstError;
|
|
2883
|
+
},
|
|
2884
|
+
get path() {
|
|
2885
|
+
return rootFieldState.value.path;
|
|
2886
|
+
},
|
|
2887
|
+
get id() {
|
|
2888
|
+
return rootFieldState.value.id;
|
|
2889
|
+
},
|
|
2890
|
+
get aria() {
|
|
2891
|
+
return rootFieldState.value.aria;
|
|
2892
|
+
},
|
|
2893
|
+
get key() {
|
|
2894
|
+
return rootFieldState.value.key;
|
|
2895
|
+
},
|
|
2896
|
+
get blank() {
|
|
2897
|
+
return rootFieldState.value.blank;
|
|
2898
|
+
},
|
|
2899
|
+
get label() {
|
|
2900
|
+
return rootFieldState.value.label;
|
|
2901
|
+
},
|
|
2902
|
+
get description() {
|
|
2903
|
+
return rootFieldState.value.description;
|
|
2904
|
+
},
|
|
2905
|
+
get placeholder() {
|
|
2906
|
+
return rootFieldState.value.placeholder;
|
|
2907
|
+
},
|
|
2908
|
+
get meta() {
|
|
2909
|
+
return rootFieldState.value.meta;
|
|
2910
|
+
},
|
|
2602
2911
|
// Lifecycle (form-level only — not on FieldState).
|
|
2603
2912
|
submitting,
|
|
2604
2913
|
submissionAttempts,
|
|
@@ -2607,7 +2916,9 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2607
2916
|
// Scalar mirror over the array — meta is a single sticky surface
|
|
2608
2917
|
// for both templates and `useWizard`'s `FormStatus`, so the
|
|
2609
2918
|
// projection lives here.
|
|
2610
|
-
errorCount
|
|
2919
|
+
get errorCount() {
|
|
2920
|
+
return metaErrors.value.length;
|
|
2921
|
+
},
|
|
2611
2922
|
submitted,
|
|
2612
2923
|
// Per-`useForm()`-call identity. Stable for one mount; new on
|
|
2613
2924
|
// re-mount; orthogonal to `form.key` (which is the user-supplied
|
|
@@ -2644,12 +2955,20 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2644
2955
|
}
|
|
2645
2956
|
};
|
|
2646
2957
|
function clear(pathInput) {
|
|
2647
|
-
|
|
2648
|
-
|
|
2958
|
+
if (pathInput === void 0) {
|
|
2959
|
+
return setValueImpl(unset);
|
|
2960
|
+
}
|
|
2961
|
+
return setValueImpl(pathInput, unset);
|
|
2649
2962
|
}
|
|
2650
2963
|
const persist = async (pathInput, options2) => {
|
|
2651
2964
|
const segments = paths.canonicalizePath(pathInput).segments;
|
|
2652
|
-
paths.
|
|
2965
|
+
if (!paths.allowSensitivePersist(
|
|
2966
|
+
segments,
|
|
2967
|
+
options2?.acknowledgeSensitive === true,
|
|
2968
|
+
state.isSensitivePath
|
|
2969
|
+
)) {
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2653
2972
|
if (persistence === void 0) return;
|
|
2654
2973
|
await persistence.writePathImmediately(segments);
|
|
2655
2974
|
};
|
|
@@ -2709,13 +3028,43 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2709
3028
|
return Object.freeze(view);
|
|
2710
3029
|
});
|
|
2711
3030
|
const valuesProxy = buildValuesProxy(state.form);
|
|
2712
|
-
const fieldStateProxy = buildFieldStateProxy(
|
|
3031
|
+
const fieldStateProxy = buildFieldStateProxy(
|
|
3032
|
+
state,
|
|
3033
|
+
formInstanceId,
|
|
3034
|
+
getFormMetaBase,
|
|
3035
|
+
fieldStateAccessorOptions
|
|
3036
|
+
);
|
|
3037
|
+
const needsLazyGate = state.defaultValuesFactory.value !== void 0 || state.hasSsrPrefetch;
|
|
2713
3038
|
function gated(fn) {
|
|
3039
|
+
if (!needsLazyGate) return fn;
|
|
2714
3040
|
return ((...args) => {
|
|
2715
3041
|
void state.activate();
|
|
2716
3042
|
return fn(...args);
|
|
2717
3043
|
});
|
|
2718
3044
|
}
|
|
3045
|
+
const callTerminal = fieldStateProxy;
|
|
3046
|
+
const EMPTY_FIELD_LIST = Object.freeze([]);
|
|
3047
|
+
function list(path) {
|
|
3048
|
+
const { segments } = paths.canonicalizePath(path);
|
|
3049
|
+
const value = state.getValueAtPath(segments);
|
|
3050
|
+
if (!Array.isArray(value)) return EMPTY_FIELD_LIST;
|
|
3051
|
+
const out = new Array(value.length);
|
|
3052
|
+
for (let i = 0; i < value.length; i += 1) out[i] = callTerminal(`${path}.${i}`);
|
|
3053
|
+
return Object.freeze(out);
|
|
3054
|
+
}
|
|
3055
|
+
const EMPTY_FIELD_RECORD = Object.freeze({});
|
|
3056
|
+
function record(path) {
|
|
3057
|
+
const { segments } = paths.canonicalizePath(path);
|
|
3058
|
+
const value = state.getValueAtPath(segments);
|
|
3059
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3060
|
+
return EMPTY_FIELD_RECORD;
|
|
3061
|
+
}
|
|
3062
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
3063
|
+
for (const key of Object.keys(value)) {
|
|
3064
|
+
out[key] = callTerminal(`${path}.${key}`);
|
|
3065
|
+
}
|
|
3066
|
+
return Object.freeze(out);
|
|
3067
|
+
}
|
|
2719
3068
|
return {
|
|
2720
3069
|
handleSubmit: gated(handleSubmit),
|
|
2721
3070
|
// Callable readonly Proxies (`values`, `fields`, `errors`) and the
|
|
@@ -2797,6 +3146,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2797
3146
|
swap: gated(fieldArrays.swap),
|
|
2798
3147
|
move: gated(fieldArrays.move),
|
|
2799
3148
|
replace: gated(fieldArrays.replace),
|
|
3149
|
+
list: gated(list),
|
|
3150
|
+
record: gated(record),
|
|
2800
3151
|
get blankPaths() {
|
|
2801
3152
|
void state.activate();
|
|
2802
3153
|
return blankPathsView;
|
|
@@ -2804,47 +3155,6 @@ function buildFormApi(state, formInstanceId, options = {}) {
|
|
|
2804
3155
|
};
|
|
2805
3156
|
}
|
|
2806
3157
|
|
|
2807
|
-
const defaultShouldShowErrors = (field, formMeta) => {
|
|
2808
|
-
const hasOwnError = field.errors.some(
|
|
2809
|
-
(e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
|
|
2810
|
-
);
|
|
2811
|
-
if (!hasOwnError) return false;
|
|
2812
|
-
if (field.validating === true) return false;
|
|
2813
|
-
if (formMeta.submissionAttempts > 0) return true;
|
|
2814
|
-
return field.touched === true && field.focused !== true;
|
|
2815
|
-
};
|
|
2816
|
-
const SHOW_ALWAYS = () => true;
|
|
2817
|
-
const SHOW_NEVER = () => false;
|
|
2818
|
-
function resolveShouldShowErrors(config) {
|
|
2819
|
-
if (config === void 0) return defaultShouldShowErrors;
|
|
2820
|
-
if (config === true) return SHOW_ALWAYS;
|
|
2821
|
-
if (config === false) return SHOW_NEVER;
|
|
2822
|
-
return config;
|
|
2823
|
-
}
|
|
2824
|
-
|
|
2825
|
-
function isHydratedFieldRecord(value) {
|
|
2826
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2827
|
-
const r = value;
|
|
2828
|
-
return Array.isArray(r.path) && (typeof r.updatedAt === "string" || r.updatedAt === null) && typeof r.connected === "boolean" && (typeof r.focused === "boolean" || r.focused === null) && (typeof r.blurred === "boolean" || r.blurred === null) && typeof r.touched === "boolean";
|
|
2829
|
-
}
|
|
2830
|
-
function isHydratedValidationErrorArray(value) {
|
|
2831
|
-
if (!Array.isArray(value)) return false;
|
|
2832
|
-
for (const entry of value) {
|
|
2833
|
-
if (typeof entry !== "object" || entry === null) return false;
|
|
2834
|
-
const e = entry;
|
|
2835
|
-
if (typeof e.message !== "string") return false;
|
|
2836
|
-
if (!Array.isArray(e.path)) return false;
|
|
2837
|
-
if (typeof e.formKey !== "string") return false;
|
|
2838
|
-
if (typeof e.code !== "string") return false;
|
|
2839
|
-
}
|
|
2840
|
-
return true;
|
|
2841
|
-
}
|
|
2842
|
-
function warnMalformedHydration(formKey, kind, rawKey) {
|
|
2843
|
-
if (!paths.__DEV__) return;
|
|
2844
|
-
console.warn(
|
|
2845
|
-
`[attaform] hydration: skipping malformed ${kind} entry at key '${rawKey}' on form '${formKey}'. This usually means the SSR bundle is on a different version than the client (rolling deploy / stale cache).`
|
|
2846
|
-
);
|
|
2847
|
-
}
|
|
2848
3158
|
function applyDuStubs(schema, data, options = {}) {
|
|
2849
3159
|
const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
|
|
2850
3160
|
return walkDuStubs(schema, data, options.basePath ?? [], warned);
|
|
@@ -2873,49 +3183,74 @@ function walkDuStubs(schema, value, path, warned) {
|
|
|
2873
3183
|
);
|
|
2874
3184
|
}
|
|
2875
3185
|
}
|
|
2876
|
-
|
|
3186
|
+
const stub = /* @__PURE__ */ Object.create(null);
|
|
3187
|
+
stub[du.discriminatorKey] = discValue;
|
|
3188
|
+
return stub;
|
|
2877
3189
|
}
|
|
2878
3190
|
}
|
|
2879
|
-
const out =
|
|
3191
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2880
3192
|
for (const k of Object.keys(rec)) {
|
|
2881
3193
|
out[k] = walkDuStubs(schema, rec[k], [...path, k], warned);
|
|
2882
3194
|
}
|
|
2883
3195
|
return out;
|
|
2884
3196
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
let mutated2 = false;
|
|
2898
|
-
const out2 = new Array(value.length);
|
|
2899
|
-
for (let i = 0; i < value.length; i++) {
|
|
2900
|
-
const cleaned = stripSymbolsDeep(value[i]);
|
|
2901
|
-
out2[i] = cleaned;
|
|
2902
|
-
if (cleaned !== value[i]) mutated2 = true;
|
|
3197
|
+
|
|
3198
|
+
function createVariantMemory() {
|
|
3199
|
+
const memory = /* @__PURE__ */ new Map();
|
|
3200
|
+
function clearAtArrayIndices(arrayPath, indexFilter) {
|
|
3201
|
+
for (const memKey of [...memory.keys()]) {
|
|
3202
|
+
const segs = paths.segmentsForPathKey(memKey);
|
|
3203
|
+
if (segs === null) continue;
|
|
3204
|
+
if (!paths.isPathPrefix(arrayPath, segs)) continue;
|
|
3205
|
+
if (segs.length <= arrayPath.length) continue;
|
|
3206
|
+
const idxSeg = segs[arrayPath.length];
|
|
3207
|
+
if (typeof idxSeg !== "number") continue;
|
|
3208
|
+
if (indexFilter(idxSeg)) memory.delete(memKey);
|
|
2903
3209
|
}
|
|
2904
|
-
return mutated2 ? out2 : value;
|
|
2905
|
-
}
|
|
2906
|
-
const proto = Object.getPrototypeOf(value);
|
|
2907
|
-
if (proto !== Object.prototype && proto !== null) return value;
|
|
2908
|
-
const symKeys = Object.getOwnPropertySymbols(value);
|
|
2909
|
-
const stringKeys = Object.keys(value);
|
|
2910
|
-
let mutated = symKeys.length > 0;
|
|
2911
|
-
const out = {};
|
|
2912
|
-
const src = value;
|
|
2913
|
-
for (const k of stringKeys) {
|
|
2914
|
-
const cleaned = stripSymbolsDeep(src[k]);
|
|
2915
|
-
out[k] = cleaned;
|
|
2916
|
-
if (cleaned !== src[k]) mutated = true;
|
|
2917
3210
|
}
|
|
2918
|
-
return
|
|
3211
|
+
return {
|
|
3212
|
+
clear() {
|
|
3213
|
+
memory.clear();
|
|
3214
|
+
},
|
|
3215
|
+
clearUnderPath(parentPath) {
|
|
3216
|
+
for (const memKey of [...memory.keys()]) {
|
|
3217
|
+
const segs = paths.segmentsForPathKey(memKey);
|
|
3218
|
+
if (segs === null) continue;
|
|
3219
|
+
if (paths.isPathPrefix(parentPath, segs)) memory.delete(memKey);
|
|
3220
|
+
}
|
|
3221
|
+
},
|
|
3222
|
+
applyArrayOp(arrayPath, op) {
|
|
3223
|
+
switch (op.kind) {
|
|
3224
|
+
case "insert":
|
|
3225
|
+
case "remove":
|
|
3226
|
+
clearAtArrayIndices(arrayPath, (i) => i >= op.index);
|
|
3227
|
+
return;
|
|
3228
|
+
case "move": {
|
|
3229
|
+
const lo = Math.min(op.from, op.to);
|
|
3230
|
+
const hi = Math.max(op.from, op.to);
|
|
3231
|
+
clearAtArrayIndices(arrayPath, (i) => i >= lo && i <= hi);
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
case "swap":
|
|
3235
|
+
clearAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
|
|
3236
|
+
return;
|
|
3237
|
+
case "replace-at":
|
|
3238
|
+
clearAtArrayIndices(arrayPath, (i) => i === op.index);
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
},
|
|
3242
|
+
recordOutgoing(unionKey, discValue, snapshot) {
|
|
3243
|
+
let perUnion = memory.get(unionKey);
|
|
3244
|
+
if (perUnion === void 0) {
|
|
3245
|
+
perUnion = /* @__PURE__ */ new Map();
|
|
3246
|
+
memory.set(unionKey, perUnion);
|
|
3247
|
+
}
|
|
3248
|
+
perUnion.set(discValue, snapshot);
|
|
3249
|
+
},
|
|
3250
|
+
lookupIncoming(unionKey, discValue) {
|
|
3251
|
+
return memory.get(unionKey)?.get(discValue);
|
|
3252
|
+
}
|
|
3253
|
+
};
|
|
2919
3254
|
}
|
|
2920
3255
|
function cloneVariantSnapshot(value) {
|
|
2921
3256
|
if (value === null || typeof value !== "object") return value;
|
|
@@ -2938,51 +3273,404 @@ function cloneVariantSnapshot(value) {
|
|
|
2938
3273
|
return out2;
|
|
2939
3274
|
}
|
|
2940
3275
|
const src = raw;
|
|
2941
|
-
const out =
|
|
3276
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2942
3277
|
for (const k of Object.keys(src)) out[k] = cloneVariantSnapshot(src[k]);
|
|
2943
3278
|
return out;
|
|
2944
3279
|
}
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
3280
|
+
|
|
3281
|
+
function createArrayIdentity(getArrayLength) {
|
|
3282
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
3283
|
+
const baselines = /* @__PURE__ */ new Map();
|
|
3284
|
+
let counter = 0;
|
|
3285
|
+
const allocate = () => `k${(counter++).toString(36)}`;
|
|
3286
|
+
function ensure(arrayKey, expectedLen) {
|
|
3287
|
+
let ids = tokens.get(arrayKey);
|
|
3288
|
+
const firstTrack = ids === void 0;
|
|
3289
|
+
if (ids === void 0) {
|
|
3290
|
+
ids = [];
|
|
3291
|
+
tokens.set(arrayKey, ids);
|
|
3292
|
+
}
|
|
3293
|
+
while (ids.length < expectedLen) ids.push(allocate());
|
|
3294
|
+
if (ids.length > expectedLen) ids.length = expectedLen;
|
|
3295
|
+
if (firstTrack) baselines.set(arrayKey, [...ids]);
|
|
3296
|
+
return ids;
|
|
3297
|
+
}
|
|
3298
|
+
function orderPristineForKey(arrayKey) {
|
|
3299
|
+
const baseline = baselines.get(arrayKey);
|
|
3300
|
+
const current = tokens.get(arrayKey);
|
|
3301
|
+
if (baseline === void 0 || current === void 0) return true;
|
|
3302
|
+
if (baseline.length !== current.length) return false;
|
|
3303
|
+
for (let i = 0; i < current.length; i++) {
|
|
3304
|
+
if (current[i] !== baseline[i]) return false;
|
|
2950
3305
|
}
|
|
2951
|
-
return;
|
|
3306
|
+
return true;
|
|
2952
3307
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
3308
|
+
return {
|
|
3309
|
+
tokenAt(arraySegs, index) {
|
|
3310
|
+
const len = getArrayLength(arraySegs);
|
|
3311
|
+
if (index < 0 || index >= len) return "";
|
|
3312
|
+
const ids = ensure(paths.canonicalizePath(arraySegs).key, len);
|
|
3313
|
+
return ids[index] ?? "";
|
|
3314
|
+
},
|
|
3315
|
+
applyOp(arraySegs, op) {
|
|
3316
|
+
const arrayKey = paths.canonicalizePath(arraySegs).key;
|
|
3317
|
+
const postLen = getArrayLength(arraySegs);
|
|
3318
|
+
const preLen = op.kind === "insert" ? postLen - 1 : op.kind === "remove" ? postLen + 1 : postLen;
|
|
3319
|
+
const ids = ensure(arrayKey, Math.max(0, preLen));
|
|
3320
|
+
switch (op.kind) {
|
|
3321
|
+
case "insert":
|
|
3322
|
+
ids.splice(op.index, 0, allocate());
|
|
3323
|
+
return;
|
|
3324
|
+
case "remove":
|
|
3325
|
+
ids.splice(op.index, 1);
|
|
3326
|
+
return;
|
|
3327
|
+
case "move": {
|
|
3328
|
+
const [moved] = ids.splice(op.from, 1);
|
|
3329
|
+
ids.splice(op.to, 0, moved ?? allocate());
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
case "swap": {
|
|
3333
|
+
const tmp = ids[op.a] ?? allocate();
|
|
3334
|
+
ids[op.a] = ids[op.b] ?? allocate();
|
|
3335
|
+
ids[op.b] = tmp;
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
case "replace-at":
|
|
3339
|
+
ids[op.index] = allocate();
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
},
|
|
3343
|
+
applyRemap(arrayPath, remap) {
|
|
3344
|
+
if (remap.moved.size === 0 && remap.vacated.size === 0 && remap.fresh.size === 0) return;
|
|
3345
|
+
const relocate = (store) => {
|
|
3346
|
+
const idxPos = arrayPath.length;
|
|
3347
|
+
const snapshots = [];
|
|
3348
|
+
for (const [key, value] of store) {
|
|
3349
|
+
const segments = paths.segmentsForPathKey(key);
|
|
3350
|
+
if (segments === null) continue;
|
|
3351
|
+
if (!paths.isPathPrefix(arrayPath, segments)) continue;
|
|
3352
|
+
if (segments.length <= idxPos) continue;
|
|
3353
|
+
const idxSeg = segments[idxPos];
|
|
3354
|
+
if (typeof idxSeg !== "number") continue;
|
|
3355
|
+
if (!remap.moved.has(idxSeg) && !remap.vacated.has(idxSeg) && !remap.fresh.has(idxSeg)) {
|
|
3356
|
+
continue;
|
|
3357
|
+
}
|
|
3358
|
+
snapshots.push({ segments: [...segments], index: idxSeg, value });
|
|
3359
|
+
}
|
|
3360
|
+
if (snapshots.length === 0) return;
|
|
3361
|
+
for (const snap of snapshots) store.delete(paths.canonicalizePath(snap.segments).key);
|
|
3362
|
+
for (const snap of snapshots) {
|
|
3363
|
+
const target = remap.moved.get(snap.index);
|
|
3364
|
+
if (target === void 0) continue;
|
|
3365
|
+
const relocated = snap.segments.slice();
|
|
3366
|
+
relocated[idxPos] = target;
|
|
3367
|
+
store.set(paths.canonicalizePath(relocated).key, snap.value);
|
|
3368
|
+
}
|
|
3369
|
+
};
|
|
3370
|
+
relocate(tokens);
|
|
3371
|
+
relocate(baselines);
|
|
3372
|
+
},
|
|
3373
|
+
realign(arraySegs) {
|
|
3374
|
+
ensure(paths.canonicalizePath(arraySegs).key, getArrayLength(arraySegs));
|
|
3375
|
+
},
|
|
3376
|
+
hasStructuralChangeUnder(prefix) {
|
|
3377
|
+
for (const arrayKey of tokens.keys()) {
|
|
3378
|
+
if (orderPristineForKey(arrayKey)) continue;
|
|
3379
|
+
const segs = paths.segmentsForPathKey(arrayKey);
|
|
3380
|
+
if (segs === null) continue;
|
|
3381
|
+
if (paths.isPathPrefix(prefix, segs)) return true;
|
|
3382
|
+
}
|
|
3383
|
+
return false;
|
|
3384
|
+
},
|
|
3385
|
+
rebaselineAll() {
|
|
3386
|
+
for (const arrayKey of [...tokens.keys()]) {
|
|
3387
|
+
const segs = paths.segmentsForPathKey(arrayKey);
|
|
3388
|
+
if (segs === null) continue;
|
|
3389
|
+
const ids = ensure(arrayKey, getArrayLength(segs));
|
|
3390
|
+
baselines.set(arrayKey, [...ids]);
|
|
3391
|
+
}
|
|
2956
3392
|
}
|
|
2957
|
-
}
|
|
3393
|
+
};
|
|
2958
3394
|
}
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
3395
|
+
|
|
3396
|
+
function remapForOp(op, oldLen) {
|
|
3397
|
+
const moved = /* @__PURE__ */ new Map();
|
|
3398
|
+
const vacated = /* @__PURE__ */ new Set();
|
|
3399
|
+
const fresh = /* @__PURE__ */ new Set();
|
|
3400
|
+
switch (op.kind) {
|
|
3401
|
+
case "insert":
|
|
3402
|
+
for (let i = op.index; i < oldLen; i++) moved.set(i, i + 1);
|
|
3403
|
+
fresh.add(op.index);
|
|
3404
|
+
return { moved, vacated, fresh };
|
|
3405
|
+
case "remove":
|
|
3406
|
+
vacated.add(op.index);
|
|
3407
|
+
for (let i = op.index + 1; i < oldLen; i++) moved.set(i, i - 1);
|
|
3408
|
+
return { moved, vacated, fresh };
|
|
3409
|
+
case "move":
|
|
3410
|
+
if (op.from !== op.to) {
|
|
3411
|
+
moved.set(op.from, op.to);
|
|
3412
|
+
if (op.from < op.to) {
|
|
3413
|
+
for (let i = op.from + 1; i <= op.to; i++) moved.set(i, i - 1);
|
|
3414
|
+
} else {
|
|
3415
|
+
for (let i = op.to; i < op.from; i++) moved.set(i, i + 1);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
return { moved, vacated, fresh };
|
|
3419
|
+
case "swap":
|
|
3420
|
+
if (op.a !== op.b) {
|
|
3421
|
+
moved.set(op.a, op.b);
|
|
3422
|
+
moved.set(op.b, op.a);
|
|
3423
|
+
}
|
|
3424
|
+
return { moved, vacated, fresh };
|
|
3425
|
+
case "replace-at":
|
|
3426
|
+
vacated.add(op.index);
|
|
3427
|
+
fresh.add(op.index);
|
|
3428
|
+
return { moved, vacated, fresh };
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
function changedIndices(remap) {
|
|
3432
|
+
const changed = new Set(remap.vacated);
|
|
3433
|
+
for (const [from, to] of remap.moved) {
|
|
3434
|
+
changed.add(from);
|
|
3435
|
+
changed.add(to);
|
|
3436
|
+
}
|
|
3437
|
+
for (const index of remap.fresh) changed.add(index);
|
|
3438
|
+
return changed;
|
|
3439
|
+
}
|
|
3440
|
+
function elementIndexUnder(arrayPath, key, idxPos) {
|
|
3441
|
+
const segments = paths.segmentsForPathKey(key);
|
|
3442
|
+
if (segments === null) return null;
|
|
3443
|
+
if (!paths.isPathPrefix(arrayPath, segments)) return null;
|
|
3444
|
+
if (segments.length <= idxPos) return null;
|
|
3445
|
+
const index = segments[idxPos];
|
|
3446
|
+
return typeof index === "number" ? index : null;
|
|
3447
|
+
}
|
|
3448
|
+
function migrateMapSubtree(map, arrayPath, remap, rewriteValue) {
|
|
3449
|
+
const idxPos = arrayPath.length;
|
|
3450
|
+
const snapshots = [];
|
|
3451
|
+
for (const [key, value] of map) {
|
|
3452
|
+
const index = elementIndexUnder(arrayPath, key, idxPos);
|
|
3453
|
+
if (index === null) continue;
|
|
3454
|
+
if (!remap.moved.has(index) && !remap.vacated.has(index)) continue;
|
|
3455
|
+
snapshots.push({ segments: [...paths.segmentsForPathKey(key)], index, value });
|
|
3456
|
+
}
|
|
3457
|
+
if (snapshots.length === 0) return;
|
|
3458
|
+
for (const snap of snapshots) map.delete(paths.canonicalizePath(snap.segments).key);
|
|
3459
|
+
for (const snap of snapshots) {
|
|
3460
|
+
const target = remap.moved.get(snap.index);
|
|
3461
|
+
if (target === void 0) continue;
|
|
3462
|
+
const relocated = snap.segments.slice();
|
|
3463
|
+
relocated[idxPos] = target;
|
|
3464
|
+
map.set(paths.canonicalizePath(relocated).key, rewriteValue(snap.value, relocated));
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
function migrateSetSubtree(set, arrayPath, remap) {
|
|
3468
|
+
const idxPos = arrayPath.length;
|
|
3469
|
+
const snapshots = [];
|
|
3470
|
+
for (const key of set) {
|
|
3471
|
+
const index = elementIndexUnder(arrayPath, key, idxPos);
|
|
3472
|
+
if (index === null) continue;
|
|
3473
|
+
if (!remap.moved.has(index) && !remap.vacated.has(index)) continue;
|
|
3474
|
+
snapshots.push({ segments: [...paths.segmentsForPathKey(key)], index });
|
|
3475
|
+
}
|
|
3476
|
+
if (snapshots.length === 0) return;
|
|
3477
|
+
for (const snap of snapshots) set.delete(paths.canonicalizePath(snap.segments).key);
|
|
3478
|
+
for (const snap of snapshots) {
|
|
3479
|
+
const target = remap.moved.get(snap.index);
|
|
3480
|
+
if (target === void 0) continue;
|
|
3481
|
+
const relocated = snap.segments.slice();
|
|
3482
|
+
relocated[idxPos] = target;
|
|
3483
|
+
set.add(paths.canonicalizePath(relocated).key);
|
|
2978
3484
|
}
|
|
2979
3485
|
}
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
const
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3486
|
+
|
|
3487
|
+
function createArrayBookkeeping(deps) {
|
|
3488
|
+
const {
|
|
3489
|
+
form,
|
|
3490
|
+
fields,
|
|
3491
|
+
userErrors,
|
|
3492
|
+
originals,
|
|
3493
|
+
blankPaths,
|
|
3494
|
+
originalBlankPaths,
|
|
3495
|
+
fieldValidationCounts,
|
|
3496
|
+
fieldValidationState,
|
|
3497
|
+
schemaErrors,
|
|
3498
|
+
activeValidations,
|
|
3499
|
+
arrayIdentity,
|
|
3500
|
+
touchFieldRecord,
|
|
3501
|
+
decFieldValidation
|
|
3502
|
+
} = deps;
|
|
3503
|
+
function migrateElementState(arrayPath, remap) {
|
|
3504
|
+
if (remap.moved.size === 0 && remap.vacated.size === 0) return;
|
|
3505
|
+
migrateMapSubtree(fields, arrayPath, remap, (record, segments) => ({
|
|
3506
|
+
...record,
|
|
3507
|
+
path: segments
|
|
3508
|
+
}));
|
|
3509
|
+
migrateMapSubtree(
|
|
3510
|
+
userErrors,
|
|
3511
|
+
arrayPath,
|
|
3512
|
+
remap,
|
|
3513
|
+
(errors, segments) => errors.map((error) => ({ ...error, path: [...segments] }))
|
|
3514
|
+
);
|
|
3515
|
+
migrateMapSubtree(originals, arrayPath, remap, (record, segments) => ({
|
|
3516
|
+
segments,
|
|
3517
|
+
value: record.value
|
|
3518
|
+
}));
|
|
3519
|
+
migrateSetSubtree(blankPaths, arrayPath, remap);
|
|
3520
|
+
migrateSetSubtree(originalBlankPaths, arrayPath, remap);
|
|
3521
|
+
migrateMapSubtree(fieldValidationCounts, arrayPath, remap, (count) => count);
|
|
3522
|
+
arrayIdentity.applyRemap(arrayPath, remap);
|
|
3523
|
+
}
|
|
3524
|
+
function seedFreshElement(arrayPath, freshIndex) {
|
|
3525
|
+
const elementPath = [...arrayPath, freshIndex];
|
|
3526
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3527
|
+
diffAndApply(void 0, getAtPath(form.value, elementPath), elementPath, (patch) => {
|
|
3528
|
+
if (patch.kind !== "added") return;
|
|
3529
|
+
const { key } = paths.canonicalizePath(patch.path);
|
|
3530
|
+
if (!originals.has(key)) originals.set(key, { segments: patch.path, value: void 0 });
|
|
3531
|
+
touchFieldRecord(key, patch.path, { updatedAt: now });
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3534
|
+
function dropSchemaErrorsAtChangedIndices(arrayPath, remap) {
|
|
3535
|
+
const changed = changedIndices(remap);
|
|
3536
|
+
if (changed.size === 0) return;
|
|
3537
|
+
const idxPos = arrayPath.length;
|
|
3538
|
+
for (const key of [...schemaErrors.keys()]) {
|
|
3539
|
+
const segs = paths.segmentsForPathKey(key);
|
|
3540
|
+
if (segs === null) continue;
|
|
3541
|
+
if (!paths.isPathPrefix(arrayPath, segs)) continue;
|
|
3542
|
+
if (segs.length <= idxPos) continue;
|
|
3543
|
+
const idx = segs[idxPos];
|
|
3544
|
+
if (typeof idx === "number" && changed.has(idx)) schemaErrors.delete(key);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
function abortValidationAtVacatedIndices(arrayPath, remap) {
|
|
3548
|
+
if (remap.vacated.size === 0) return;
|
|
3549
|
+
const idxPos = arrayPath.length;
|
|
3550
|
+
for (const [key, entry] of [...fieldValidationState]) {
|
|
3551
|
+
const segs = paths.segmentsForPathKey(key);
|
|
3552
|
+
if (segs === null) continue;
|
|
3553
|
+
if (!paths.isPathPrefix(arrayPath, segs)) continue;
|
|
3554
|
+
if (segs.length <= idxPos) continue;
|
|
3555
|
+
const idx = segs[idxPos];
|
|
3556
|
+
if (typeof idx !== "number" || !remap.vacated.has(idx)) continue;
|
|
3557
|
+
if (entry.timer !== null) {
|
|
3558
|
+
clearTimeout(entry.timer);
|
|
3559
|
+
} else if (!entry.settled) {
|
|
3560
|
+
activeValidations.value = Math.max(0, activeValidations.value - 1);
|
|
3561
|
+
decFieldValidation(key);
|
|
3562
|
+
}
|
|
3563
|
+
entry.controller.abort();
|
|
3564
|
+
fieldValidationState.delete(key);
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
return {
|
|
3568
|
+
migrateElementState,
|
|
3569
|
+
seedFreshElement,
|
|
3570
|
+
dropSchemaErrorsAtChangedIndices,
|
|
3571
|
+
abortValidationAtVacatedIndices
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
function isHydratedFieldRecord(value) {
|
|
3576
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3577
|
+
const r = value;
|
|
3578
|
+
return Array.isArray(r.path) && (typeof r.updatedAt === "string" || r.updatedAt === null) && typeof r.connected === "boolean" && (typeof r.focused === "boolean" || r.focused === null) && (typeof r.blurred === "boolean" || r.blurred === null) && typeof r.touched === "boolean" && typeof r.interacted === "boolean" && typeof r.blurredAfterInteraction === "boolean";
|
|
3579
|
+
}
|
|
3580
|
+
function isHydratedValidationErrorArray(value) {
|
|
3581
|
+
if (!Array.isArray(value)) return false;
|
|
3582
|
+
for (const entry of value) {
|
|
3583
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
3584
|
+
const e = entry;
|
|
3585
|
+
if (typeof e.message !== "string") return false;
|
|
3586
|
+
if (!Array.isArray(e.path)) return false;
|
|
3587
|
+
if (typeof e.formKey !== "string") return false;
|
|
3588
|
+
if (typeof e.code !== "string") return false;
|
|
3589
|
+
}
|
|
3590
|
+
return true;
|
|
3591
|
+
}
|
|
3592
|
+
function warnMalformedHydration(formKey, kind, rawKey) {
|
|
3593
|
+
if (!paths.__DEV__) return;
|
|
3594
|
+
console.warn(
|
|
3595
|
+
`[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).`
|
|
3596
|
+
);
|
|
3597
|
+
}
|
|
3598
|
+
function isPathKeyUnder(existingKey, parentPath) {
|
|
3599
|
+
const parsed = paths.segmentsForPathKey(existingKey);
|
|
3600
|
+
if (parsed === null) return false;
|
|
3601
|
+
if (parsed.length <= parentPath.length) return false;
|
|
3602
|
+
for (let i = 0; i < parentPath.length; i++) {
|
|
3603
|
+
if (parsed[i] !== parentPath[i]) return false;
|
|
3604
|
+
}
|
|
3605
|
+
return true;
|
|
3606
|
+
}
|
|
3607
|
+
function stripSymbolsDeep(value) {
|
|
3608
|
+
if (value === null || typeof value !== "object") return value;
|
|
3609
|
+
if (Array.isArray(value)) {
|
|
3610
|
+
let mutated2 = false;
|
|
3611
|
+
const out2 = new Array(value.length);
|
|
3612
|
+
for (let i = 0; i < value.length; i++) {
|
|
3613
|
+
const cleaned = stripSymbolsDeep(value[i]);
|
|
3614
|
+
out2[i] = cleaned;
|
|
3615
|
+
if (cleaned !== value[i]) mutated2 = true;
|
|
3616
|
+
}
|
|
3617
|
+
return mutated2 ? out2 : value;
|
|
3618
|
+
}
|
|
3619
|
+
const proto = Object.getPrototypeOf(value);
|
|
3620
|
+
if (proto !== Object.prototype && proto !== null) return value;
|
|
3621
|
+
const symKeys = Object.getOwnPropertySymbols(value);
|
|
3622
|
+
const stringKeys = Object.keys(value);
|
|
3623
|
+
let mutated = symKeys.length > 0;
|
|
3624
|
+
const out = {};
|
|
3625
|
+
const src = value;
|
|
3626
|
+
for (const k of stringKeys) {
|
|
3627
|
+
const cleaned = stripSymbolsDeep(src[k]);
|
|
3628
|
+
out[k] = cleaned;
|
|
3629
|
+
if (cleaned !== src[k]) mutated = true;
|
|
3630
|
+
}
|
|
3631
|
+
return mutated ? out : value;
|
|
3632
|
+
}
|
|
3633
|
+
function walkAuthoredFromConstraints(value, prefix, out) {
|
|
3634
|
+
if (prefix.length > 0) out.add(paths.canonicalizePath(prefix).key);
|
|
3635
|
+
if (isPlainRecord(value)) {
|
|
3636
|
+
for (const k of Object.keys(value)) {
|
|
3637
|
+
walkAuthoredFromConstraints(value[k], [...prefix, k], out);
|
|
3638
|
+
}
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3641
|
+
if (Array.isArray(value)) {
|
|
3642
|
+
for (let i = 0; i < value.length; i++) {
|
|
3643
|
+
walkAuthoredFromConstraints(value[i], [...prefix, i], out);
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
function walkAuthoredFromSchemaDiff(withDefaults, withoutDefaults, prefix, out) {
|
|
3648
|
+
if (isPlainRecord(withDefaults) && isPlainRecord(withoutDefaults)) {
|
|
3649
|
+
const left = withDefaults;
|
|
3650
|
+
const right = withoutDefaults;
|
|
3651
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
3652
|
+
for (const k of keys) {
|
|
3653
|
+
walkAuthoredFromSchemaDiff(left[k], right[k], [...prefix, k], out);
|
|
3654
|
+
}
|
|
3655
|
+
return;
|
|
3656
|
+
}
|
|
3657
|
+
if (Array.isArray(withDefaults) && Array.isArray(withoutDefaults)) {
|
|
3658
|
+
const len = Math.max(withDefaults.length, withoutDefaults.length);
|
|
3659
|
+
for (let i = 0; i < len; i++) {
|
|
3660
|
+
walkAuthoredFromSchemaDiff(withDefaults[i], withoutDefaults[i], [...prefix, i], out);
|
|
3661
|
+
}
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3664
|
+
if (!Object.is(withDefaults, withoutDefaults) && prefix.length > 0) {
|
|
3665
|
+
out.add(paths.canonicalizePath(prefix).key);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
function createFormStore(options) {
|
|
3669
|
+
const { formKey, schema, defaultValues, strict = true, hydration } = options;
|
|
3670
|
+
const ssr = options.ssr === true;
|
|
3671
|
+
const ssrPrefetch = options.ssrPrefetch;
|
|
3672
|
+
const rememberVariants = options.rememberVariants !== false;
|
|
3673
|
+
const fieldValidationMode = options.validateOn ?? "change";
|
|
2986
3674
|
const fieldValidationDebounceMs = normalizeNumericOption({
|
|
2987
3675
|
value: options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS,
|
|
2988
3676
|
source: "useForm.debounceMs",
|
|
@@ -3012,11 +3700,8 @@ function createFormStore(options) {
|
|
|
3012
3700
|
noSyncPathCounts.set(path, current - 1);
|
|
3013
3701
|
}
|
|
3014
3702
|
const coerceIndex = resolveCoercionIndex(options.coerce);
|
|
3015
|
-
const
|
|
3016
|
-
options.shouldShowErrors
|
|
3017
|
-
);
|
|
3703
|
+
const resolvedGetDisplayState = resolveGetDisplayState(options.getDisplayState);
|
|
3018
3704
|
const resolvedIsSensitivePath = options.isSensitivePath ?? paths.isSensitivePath;
|
|
3019
|
-
const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ?? paths.segmentMatchesSensitive;
|
|
3020
3705
|
const cleanupHooks = [];
|
|
3021
3706
|
const modules = /* @__PURE__ */ new Map();
|
|
3022
3707
|
const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
|
|
@@ -3054,6 +3739,16 @@ function createFormStore(options) {
|
|
|
3054
3739
|
warn: true
|
|
3055
3740
|
});
|
|
3056
3741
|
const form = vue.ref(stubbedInitialData);
|
|
3742
|
+
const arrayIdentity = createArrayIdentity((arraySegs) => {
|
|
3743
|
+
const v = getAtPath(form.value, arraySegs);
|
|
3744
|
+
return Array.isArray(v) ? v.length : 0;
|
|
3745
|
+
});
|
|
3746
|
+
function arrayElementKey(path) {
|
|
3747
|
+
if (path.length === 0) return "";
|
|
3748
|
+
const last = path[path.length - 1];
|
|
3749
|
+
if (typeof last === "number") return arrayIdentity.tokenAt(path.slice(0, -1), last);
|
|
3750
|
+
return "";
|
|
3751
|
+
}
|
|
3057
3752
|
const fields = vue.reactive(/* @__PURE__ */ new Map());
|
|
3058
3753
|
const elements = vue.reactive(/* @__PURE__ */ new Map());
|
|
3059
3754
|
const elementToFormInstance = /* @__PURE__ */ new WeakMap();
|
|
@@ -3069,41 +3764,7 @@ function createFormStore(options) {
|
|
|
3069
3764
|
blankPaths.add(key);
|
|
3070
3765
|
originalBlankPaths.add(key);
|
|
3071
3766
|
}
|
|
3072
|
-
const variantMemory =
|
|
3073
|
-
function clearVariantMemoryUnderPath(arrayPath) {
|
|
3074
|
-
for (const memKey of [...variantMemory.keys()]) {
|
|
3075
|
-
const segs = paths.segmentsForPathKey(memKey);
|
|
3076
|
-
if (segs === null) continue;
|
|
3077
|
-
if (isPathPrefix(arrayPath, segs)) variantMemory.delete(memKey);
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
function clearVariantMemoryAtArrayIndices(arrayPath, indexFilter) {
|
|
3081
|
-
for (const memKey of [...variantMemory.keys()]) {
|
|
3082
|
-
const segs = paths.segmentsForPathKey(memKey);
|
|
3083
|
-
if (segs === null) continue;
|
|
3084
|
-
if (!isPathPrefix(arrayPath, segs)) continue;
|
|
3085
|
-
if (segs.length <= arrayPath.length) continue;
|
|
3086
|
-
const idxSeg = segs[arrayPath.length];
|
|
3087
|
-
if (typeof idxSeg !== "number") continue;
|
|
3088
|
-
if (indexFilter(idxSeg)) variantMemory.delete(memKey);
|
|
3089
|
-
}
|
|
3090
|
-
}
|
|
3091
|
-
function applyArrayOpToMemory(arrayPath, op) {
|
|
3092
|
-
switch (op.kind) {
|
|
3093
|
-
case "shift-from":
|
|
3094
|
-
clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.index);
|
|
3095
|
-
return;
|
|
3096
|
-
case "shift-range":
|
|
3097
|
-
clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.fromIndex && i <= op.toIndex);
|
|
3098
|
-
return;
|
|
3099
|
-
case "swap":
|
|
3100
|
-
clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
|
|
3101
|
-
return;
|
|
3102
|
-
case "replace-at":
|
|
3103
|
-
clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.index);
|
|
3104
|
-
return;
|
|
3105
|
-
}
|
|
3106
|
-
}
|
|
3767
|
+
const variantMemory = createVariantMemory();
|
|
3107
3768
|
const pathOrdinals = /* @__PURE__ */ new Map();
|
|
3108
3769
|
let nextOrdinal = 0;
|
|
3109
3770
|
function ensurePathOrdinal(key) {
|
|
@@ -3141,6 +3802,9 @@ function createFormStore(options) {
|
|
|
3141
3802
|
const departAttempts = vue.ref(0);
|
|
3142
3803
|
const submissionGeneration = vue.ref(0);
|
|
3143
3804
|
const activeValidations = vue.ref(0);
|
|
3805
|
+
const pathSnapshots = /* @__PURE__ */ new Map();
|
|
3806
|
+
let scheduleEpoch = 0;
|
|
3807
|
+
let lastCommittedEpoch = 0;
|
|
3144
3808
|
const hydrating = vue.ref(false);
|
|
3145
3809
|
const hydrateError = vue.ref(null);
|
|
3146
3810
|
const defaultValuesFactory = vue.ref(void 0);
|
|
@@ -3156,9 +3820,12 @@ function createFormStore(options) {
|
|
|
3156
3820
|
const pathAsyncCache = /* @__PURE__ */ new Map();
|
|
3157
3821
|
function pathHasAsyncValidation(path) {
|
|
3158
3822
|
const { key } = paths.canonicalizePath(path);
|
|
3823
|
+
return pathHasAsyncValidationByKey(key, path);
|
|
3824
|
+
}
|
|
3825
|
+
function pathHasAsyncValidationByKey(key, segments) {
|
|
3159
3826
|
const cached = pathAsyncCache.get(key);
|
|
3160
3827
|
if (cached !== void 0) return cached;
|
|
3161
|
-
const candidates = schema.getSchemasAtPath(
|
|
3828
|
+
const candidates = schema.getSchemasAtPath(segments);
|
|
3162
3829
|
const hasAsync = candidates.some((sub) => sub.needsAsyncValidation?.() === true);
|
|
3163
3830
|
pathAsyncCache.set(key, hasAsync);
|
|
3164
3831
|
return hasAsync;
|
|
@@ -3211,7 +3878,9 @@ function createFormStore(options) {
|
|
|
3211
3878
|
connected: false,
|
|
3212
3879
|
focused: null,
|
|
3213
3880
|
blurred: null,
|
|
3214
|
-
touched: false
|
|
3881
|
+
touched: false,
|
|
3882
|
+
interacted: false,
|
|
3883
|
+
blurredAfterInteraction: false
|
|
3215
3884
|
});
|
|
3216
3885
|
});
|
|
3217
3886
|
if (strict && !schemaResponse.success) {
|
|
@@ -3240,9 +3909,29 @@ function createFormStore(options) {
|
|
|
3240
3909
|
blurred: patch.blurred !== void 0 ? patch.blurred : current?.blurred ?? null,
|
|
3241
3910
|
// touched is plain `boolean`; `??` is equivalent to the explicit
|
|
3242
3911
|
// guard here because `false` is not nullish.
|
|
3243
|
-
touched: patch.touched ?? current?.touched ?? false
|
|
3912
|
+
touched: patch.touched ?? current?.touched ?? false,
|
|
3913
|
+
// interacted is sticky-true; a merge patch only ever sets it, so
|
|
3914
|
+
// `??` preserves the current bit. It flips back to false solely
|
|
3915
|
+
// through the reset paths, which reconstruct the record outright.
|
|
3916
|
+
interacted: patch.interacted ?? current?.interacted ?? false,
|
|
3917
|
+
blurredAfterInteraction: patch.blurredAfterInteraction ?? current?.blurredAfterInteraction ?? false
|
|
3244
3918
|
});
|
|
3245
3919
|
}
|
|
3920
|
+
const arrayBookkeeping = createArrayBookkeeping({
|
|
3921
|
+
form,
|
|
3922
|
+
fields,
|
|
3923
|
+
userErrors,
|
|
3924
|
+
originals,
|
|
3925
|
+
blankPaths,
|
|
3926
|
+
originalBlankPaths,
|
|
3927
|
+
fieldValidationCounts,
|
|
3928
|
+
fieldValidationState,
|
|
3929
|
+
schemaErrors,
|
|
3930
|
+
activeValidations,
|
|
3931
|
+
arrayIdentity,
|
|
3932
|
+
touchFieldRecord,
|
|
3933
|
+
decFieldValidation
|
|
3934
|
+
});
|
|
3246
3935
|
function applyFormReplacement(next, meta) {
|
|
3247
3936
|
const prev = form.value;
|
|
3248
3937
|
if (Object.is(prev, next)) return;
|
|
@@ -3366,8 +4055,13 @@ function createFormStore(options) {
|
|
|
3366
4055
|
const pathKey = paths.canonicalizePath(path).key;
|
|
3367
4056
|
if (meta?.blank === true) {
|
|
3368
4057
|
blankPaths.add(pathKey);
|
|
3369
|
-
} else
|
|
3370
|
-
blankPaths.delete(pathKey);
|
|
4058
|
+
} else {
|
|
4059
|
+
if (blankPaths.has(pathKey)) blankPaths.delete(pathKey);
|
|
4060
|
+
if (meta?.arrayOp === void 0) {
|
|
4061
|
+
for (const existingKey of [...blankPaths]) {
|
|
4062
|
+
if (isPathKeyUnder(existingKey, path)) blankPaths.delete(existingKey);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
3371
4065
|
}
|
|
3372
4066
|
const wasAuthoredBefore = authoredPaths.has(pathKey);
|
|
3373
4067
|
walkAuthoredFromConstraints(value, path, authoredPaths);
|
|
@@ -3386,12 +4080,20 @@ function createFormStore(options) {
|
|
|
3386
4080
|
}
|
|
3387
4081
|
return true;
|
|
3388
4082
|
}
|
|
4083
|
+
const oldArrayLength = Array.isArray(currentValue) ? currentValue.length : 0;
|
|
3389
4084
|
const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
|
|
3390
4085
|
applyFormReplacement(nextForm, meta);
|
|
3391
4086
|
if (meta?.arrayOp !== void 0) {
|
|
3392
|
-
|
|
4087
|
+
const remap = remapForOp(meta.arrayOp, oldArrayLength);
|
|
4088
|
+
arrayBookkeeping.migrateElementState(path, remap);
|
|
4089
|
+
for (const freshIndex of remap.fresh) arrayBookkeeping.seedFreshElement(path, freshIndex);
|
|
4090
|
+
arrayBookkeeping.dropSchemaErrorsAtChangedIndices(path, remap);
|
|
4091
|
+
arrayBookkeeping.abortValidationAtVacatedIndices(path, remap);
|
|
4092
|
+
variantMemory.applyArrayOp(path, meta.arrayOp);
|
|
4093
|
+
arrayIdentity.applyOp(path, meta.arrayOp);
|
|
3393
4094
|
} else if (Array.isArray(value) && Array.isArray(currentValue)) {
|
|
3394
|
-
|
|
4095
|
+
variantMemory.clearUnderPath(path);
|
|
4096
|
+
arrayIdentity.realign(path);
|
|
3395
4097
|
}
|
|
3396
4098
|
const effectiveModeAfterWrite = meta?.instance?.validateOn ?? fieldValidationMode;
|
|
3397
4099
|
if (effectiveModeAfterWrite === "change") {
|
|
@@ -3415,18 +4117,12 @@ function createFormStore(options) {
|
|
|
3415
4117
|
for (const k of blankPaths) {
|
|
3416
4118
|
if (isPathKeyUnder(k, parentPath)) outgoingBlanks.push(k);
|
|
3417
4119
|
}
|
|
3418
|
-
|
|
3419
|
-
if (memoryForUnion2 === void 0) {
|
|
3420
|
-
memoryForUnion2 = /* @__PURE__ */ new Map();
|
|
3421
|
-
variantMemory.set(parentKey, memoryForUnion2);
|
|
3422
|
-
}
|
|
3423
|
-
memoryForUnion2.set(oldDiscValue, {
|
|
4120
|
+
variantMemory.recordOutgoing(parentKey, oldDiscValue, {
|
|
3424
4121
|
value: currentValue2,
|
|
3425
4122
|
blankPaths: outgoingBlanks
|
|
3426
4123
|
});
|
|
3427
4124
|
}
|
|
3428
|
-
const
|
|
3429
|
-
const restored = memoryForUnion?.get(newDiscValue);
|
|
4125
|
+
const restored = variantMemory.lookupIncoming(parentKey, newDiscValue);
|
|
3430
4126
|
if (restored !== void 0) {
|
|
3431
4127
|
baseline = restored.value;
|
|
3432
4128
|
restoredBlanks = [...restored.blankPaths];
|
|
@@ -3498,6 +4194,7 @@ function createFormStore(options) {
|
|
|
3498
4194
|
const controller = new AbortController();
|
|
3499
4195
|
const fresh = { controller, timer: null, settled: false };
|
|
3500
4196
|
fieldValidationState.set(key, fresh);
|
|
4197
|
+
const myEpoch = ++scheduleEpoch;
|
|
3501
4198
|
const run = () => {
|
|
3502
4199
|
fresh.timer = null;
|
|
3503
4200
|
if (controller.signal.aborted) return;
|
|
@@ -3512,11 +4209,25 @@ function createFormStore(options) {
|
|
|
3512
4209
|
}
|
|
3513
4210
|
throw err;
|
|
3514
4211
|
}
|
|
3515
|
-
|
|
4212
|
+
const subtreeScope = path.length > 0 && schema.hasContainerOrRootRefine?.() === false;
|
|
4213
|
+
const scopePath = subtreeScope ? path : void 0;
|
|
4214
|
+
const dataAtScope = subtreeScope ? getAtPath(form.value, path) : form.value;
|
|
4215
|
+
const scopeKey = subtreeScope ? paths.canonicalizePath(path).key : paths.ROOT_PATH_KEY;
|
|
4216
|
+
void Promise.resolve().then(() => schema.validateAtPath(dataAtScope, scopePath)).then((response) => {
|
|
3516
4217
|
if (controller.signal.aborted) return;
|
|
4218
|
+
if (myEpoch <= lastCommittedEpoch) return;
|
|
4219
|
+
lastCommittedEpoch = myEpoch;
|
|
4220
|
+
if (effectiveMode === "blur") {
|
|
4221
|
+
const snapshotSource = scopePath !== void 0 ? getAtPath(form.value, scopePath) : form.value;
|
|
4222
|
+
pathSnapshots.set(scopeKey, structuralSnapshot(snapshotSource));
|
|
4223
|
+
}
|
|
3517
4224
|
const errors = response.success ? [] : response.errors;
|
|
3518
4225
|
const filtered = filterAuthoredErrors(errors);
|
|
3519
|
-
|
|
4226
|
+
const restamped = subtreeScope ? filtered.map((err) => ({
|
|
4227
|
+
...err,
|
|
4228
|
+
path: [...path, ...err.path]
|
|
4229
|
+
})) : filtered;
|
|
4230
|
+
applySchemaErrorsForSubtree(scopePath ?? [], restamped);
|
|
3520
4231
|
}).catch(() => {
|
|
3521
4232
|
}).finally(() => {
|
|
3522
4233
|
activeValidations.value = Math.max(0, activeValidations.value - 1);
|
|
@@ -3743,30 +4454,61 @@ function createFormStore(options) {
|
|
|
3743
4454
|
}
|
|
3744
4455
|
function markFocused(path, focused, meta) {
|
|
3745
4456
|
const { key } = paths.canonicalizePath(path);
|
|
4457
|
+
const current = fields.get(key);
|
|
3746
4458
|
touchFieldRecord(key, path, {
|
|
3747
4459
|
focused,
|
|
3748
4460
|
blurred: !focused,
|
|
3749
4461
|
// `touched` flips to true on blur and stays true thereafter; while
|
|
3750
4462
|
// a field is currently focused we keep whatever value it held.
|
|
3751
|
-
touched: focused ?
|
|
4463
|
+
touched: focused ? current?.touched ?? false : true,
|
|
4464
|
+
// `blurredAfterInteraction` flips true on the first blur that lands
|
|
4465
|
+
// after a value edit and stays true. A tab-through blur before any
|
|
4466
|
+
// edit leaves it false (`interacted` is still false at that blur),
|
|
4467
|
+
// which is what keeps a clean tab-through from arming the gate.
|
|
4468
|
+
blurredAfterInteraction: !focused && current?.interacted === true ? true : current?.blurredAfterInteraction ?? false
|
|
3752
4469
|
});
|
|
3753
4470
|
const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
|
|
3754
4471
|
if (!focused && focusMode === "blur") {
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
4472
|
+
const firstInteractiveBlur = current?.interacted === true && current.blurredAfterInteraction !== true;
|
|
4473
|
+
let snapshot = void 0;
|
|
4474
|
+
let snapshotScopeLength = 0;
|
|
4475
|
+
for (let i = path.length; i >= 0; i--) {
|
|
4476
|
+
const ancestorKey = paths.canonicalizePath(path.slice(0, i)).key;
|
|
4477
|
+
const entry = pathSnapshots.get(ancestorKey);
|
|
4478
|
+
if (entry !== void 0) {
|
|
4479
|
+
snapshot = entry;
|
|
4480
|
+
snapshotScopeLength = i;
|
|
4481
|
+
break;
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
let changed = true;
|
|
4485
|
+
if (!firstInteractiveBlur && snapshot !== void 0) {
|
|
4486
|
+
const relPath = path.slice(snapshotScopeLength);
|
|
4487
|
+
const snapshotSubtree = getAtPath(snapshot, relPath);
|
|
4488
|
+
const liveSubtree = getAtPath(form.value, path);
|
|
4489
|
+
changed = false;
|
|
4490
|
+
diffAndApply(snapshotSubtree, liveSubtree, path, () => {
|
|
4491
|
+
changed = true;
|
|
4492
|
+
});
|
|
4493
|
+
}
|
|
4494
|
+
if (changed) {
|
|
4495
|
+
scheduleFieldValidation(path, true, {
|
|
4496
|
+
...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
|
|
4497
|
+
...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
|
|
4498
|
+
});
|
|
4499
|
+
}
|
|
3759
4500
|
}
|
|
3760
4501
|
}
|
|
3761
|
-
function
|
|
4502
|
+
function markInteracted(path) {
|
|
3762
4503
|
const { key } = paths.canonicalizePath(path);
|
|
3763
|
-
|
|
4504
|
+
if (fields.get(key)?.interacted === true) return;
|
|
4505
|
+
touchFieldRecord(key, path, { interacted: true });
|
|
3764
4506
|
}
|
|
3765
4507
|
function touchAtPath(segments) {
|
|
3766
4508
|
const formValue = form.value;
|
|
3767
4509
|
let touchedAny = false;
|
|
3768
4510
|
for (const [, entry] of originals) {
|
|
3769
|
-
if (!isPathPrefix(segments, entry.segments)) continue;
|
|
4511
|
+
if (!paths.isPathPrefix(segments, entry.segments)) continue;
|
|
3770
4512
|
if (!hasAtPath(formValue, entry.segments)) continue;
|
|
3771
4513
|
touchedAny = true;
|
|
3772
4514
|
const leafKey = paths.canonicalizePath(entry.segments).key;
|
|
@@ -3780,9 +4522,6 @@ function createFormStore(options) {
|
|
|
3780
4522
|
);
|
|
3781
4523
|
}
|
|
3782
4524
|
}
|
|
3783
|
-
function clear(path) {
|
|
3784
|
-
return setValueAtPath(path, schema.getEmptyValueAtPath(path));
|
|
3785
|
-
}
|
|
3786
4525
|
function rehydrate() {
|
|
3787
4526
|
const factory = defaultValuesFactory.value;
|
|
3788
4527
|
if (factory === void 0) {
|
|
@@ -3872,6 +4611,7 @@ function createFormStore(options) {
|
|
|
3872
4611
|
const next = resetResponse.data;
|
|
3873
4612
|
rebuildAuthoredPaths(resetSource, next);
|
|
3874
4613
|
applyFormReplacement(next);
|
|
4614
|
+
arrayIdentity.rebaselineAll();
|
|
3875
4615
|
originals.clear();
|
|
3876
4616
|
diffAndApply({}, next, [], (patch) => {
|
|
3877
4617
|
if (patch.kind !== "added") return;
|
|
@@ -3915,7 +4655,9 @@ function createFormStore(options) {
|
|
|
3915
4655
|
connected: record.connected,
|
|
3916
4656
|
focused: record.focused,
|
|
3917
4657
|
blurred: record.blurred,
|
|
3918
|
-
touched: false
|
|
4658
|
+
touched: false,
|
|
4659
|
+
interacted: false,
|
|
4660
|
+
blurredAfterInteraction: false
|
|
3919
4661
|
});
|
|
3920
4662
|
}
|
|
3921
4663
|
submissionGeneration.value += 1;
|
|
@@ -3926,6 +4668,9 @@ function createFormStore(options) {
|
|
|
3926
4668
|
submitError.value = null;
|
|
3927
4669
|
departAttempts.value = 0;
|
|
3928
4670
|
cancelFieldValidation();
|
|
4671
|
+
pathSnapshots.clear();
|
|
4672
|
+
scheduleEpoch = 0;
|
|
4673
|
+
lastCommittedEpoch = 0;
|
|
3929
4674
|
variantMemory.clear();
|
|
3930
4675
|
for (const listener of resetListeners) {
|
|
3931
4676
|
try {
|
|
@@ -3937,13 +4682,7 @@ function createFormStore(options) {
|
|
|
3937
4682
|
}
|
|
3938
4683
|
function resetField(path) {
|
|
3939
4684
|
const { key: targetKey, segments: targetSegments } = paths.canonicalizePath(path);
|
|
3940
|
-
|
|
3941
|
-
const memSegments = paths.segmentsForPathKey(memKey);
|
|
3942
|
-
if (memSegments === null) continue;
|
|
3943
|
-
if (isPathPrefix(targetSegments, memSegments)) {
|
|
3944
|
-
variantMemory.delete(memKey);
|
|
3945
|
-
}
|
|
3946
|
-
}
|
|
4685
|
+
variantMemory.clearUnderPath(targetSegments);
|
|
3947
4686
|
const leafEntry = originals.get(targetKey);
|
|
3948
4687
|
if (leafEntry !== void 0) {
|
|
3949
4688
|
const wrote = setValueAtPath(targetSegments, leafEntry.value);
|
|
@@ -3957,7 +4696,7 @@ function createFormStore(options) {
|
|
|
3957
4696
|
let anyMatch = false;
|
|
3958
4697
|
for (const [, entry] of originals) {
|
|
3959
4698
|
const leafSegments = entry.segments;
|
|
3960
|
-
if (!isPathPrefix(targetSegments, leafSegments)) continue;
|
|
4699
|
+
if (!paths.isPathPrefix(targetSegments, leafSegments)) continue;
|
|
3961
4700
|
if (leafSegments.length === targetSegments.length) continue;
|
|
3962
4701
|
anyMatch = true;
|
|
3963
4702
|
const relative = leafSegments.slice(targetSegments.length);
|
|
@@ -3978,14 +4717,14 @@ function createFormStore(options) {
|
|
|
3978
4717
|
deleteErrorsUnderPrefix(schemaErrors, targetSegments);
|
|
3979
4718
|
deleteErrorsUnderPrefix(userErrors, targetSegments);
|
|
3980
4719
|
for (const [fieldKey, record] of Array.from(fields.entries())) {
|
|
3981
|
-
if (isPathPrefix(targetSegments, record.path)) clearFieldRecordFlags(fieldKey);
|
|
4720
|
+
if (paths.isPathPrefix(targetSegments, record.path)) clearFieldRecordFlags(fieldKey);
|
|
3982
4721
|
}
|
|
3983
4722
|
}
|
|
3984
4723
|
function deleteErrorsUnderPrefix(map, prefix) {
|
|
3985
4724
|
for (const [errorKey, errs] of Array.from(map.entries())) {
|
|
3986
4725
|
const first = errs[0];
|
|
3987
4726
|
if (first === void 0) continue;
|
|
3988
|
-
if (isPathPrefix(prefix, first.path)) {
|
|
4727
|
+
if (paths.isPathPrefix(prefix, first.path)) {
|
|
3989
4728
|
map.delete(errorKey);
|
|
3990
4729
|
}
|
|
3991
4730
|
}
|
|
@@ -3999,23 +4738,24 @@ function createFormStore(options) {
|
|
|
3999
4738
|
connected: record.connected,
|
|
4000
4739
|
focused: record.focused,
|
|
4001
4740
|
blurred: record.blurred,
|
|
4002
|
-
touched: false
|
|
4741
|
+
touched: false,
|
|
4742
|
+
interacted: false,
|
|
4743
|
+
blurredAfterInteraction: false
|
|
4003
4744
|
});
|
|
4004
4745
|
}
|
|
4005
|
-
function isPathPrefix(prefix, candidate) {
|
|
4006
|
-
if (prefix.length > candidate.length) return false;
|
|
4007
|
-
for (let i = 0; i < prefix.length; i++) {
|
|
4008
|
-
if (prefix[i] !== candidate[i]) return false;
|
|
4009
|
-
}
|
|
4010
|
-
return true;
|
|
4011
|
-
}
|
|
4012
4746
|
function isPristineAtPath(path) {
|
|
4013
4747
|
const { key, segments } = paths.canonicalizePath(path);
|
|
4748
|
+
return isPristineAtPathByKey(key, segments);
|
|
4749
|
+
}
|
|
4750
|
+
function isPristineAtPathByKey(key, segments) {
|
|
4014
4751
|
if (blankPaths.has(key) !== originalBlankPaths.has(key)) return false;
|
|
4015
4752
|
const entry = originals.get(key);
|
|
4016
4753
|
if (entry === void 0) return true;
|
|
4017
4754
|
return Object.is(getAtPath(form.value, segments), entry.value);
|
|
4018
4755
|
}
|
|
4756
|
+
function hasStructuralChangeUnder(path) {
|
|
4757
|
+
return arrayIdentity.hasStructuralChangeUnder(path);
|
|
4758
|
+
}
|
|
4019
4759
|
function getFieldRecord(path) {
|
|
4020
4760
|
const { key } = paths.canonicalizePath(path);
|
|
4021
4761
|
return fields.get(key);
|
|
@@ -4059,7 +4799,7 @@ function createFormStore(options) {
|
|
|
4059
4799
|
originals,
|
|
4060
4800
|
schema,
|
|
4061
4801
|
ssr,
|
|
4062
|
-
|
|
4802
|
+
getDisplayState: resolvedGetDisplayState,
|
|
4063
4803
|
submitting,
|
|
4064
4804
|
activeSubmissions,
|
|
4065
4805
|
submissionAttempts,
|
|
@@ -4069,6 +4809,7 @@ function createFormStore(options) {
|
|
|
4069
4809
|
hydrating,
|
|
4070
4810
|
hydrateError,
|
|
4071
4811
|
defaultValuesFactory,
|
|
4812
|
+
hasSsrPrefetch: ssrPrefetch !== void 0,
|
|
4072
4813
|
defaultsResolved,
|
|
4073
4814
|
activated,
|
|
4074
4815
|
activationPromise,
|
|
@@ -4078,13 +4819,14 @@ function createFormStore(options) {
|
|
|
4078
4819
|
activeValidations,
|
|
4079
4820
|
firstValidationDone,
|
|
4080
4821
|
pathHasAsyncValidation,
|
|
4822
|
+
pathHasAsyncValidationByKey,
|
|
4081
4823
|
fieldValidationCounts,
|
|
4082
4824
|
applyFormReplacement,
|
|
4083
4825
|
setValueAtPath,
|
|
4084
4826
|
getValueAtPath,
|
|
4827
|
+
arrayElementKey,
|
|
4085
4828
|
reset,
|
|
4086
4829
|
resetField,
|
|
4087
|
-
clear,
|
|
4088
4830
|
setSchemaErrorsForPath,
|
|
4089
4831
|
setAllSchemaErrors,
|
|
4090
4832
|
clearSchemaErrors,
|
|
@@ -4097,10 +4839,12 @@ function createFormStore(options) {
|
|
|
4097
4839
|
registerElement,
|
|
4098
4840
|
deregisterElement,
|
|
4099
4841
|
markFocused,
|
|
4100
|
-
|
|
4842
|
+
markInteracted,
|
|
4101
4843
|
touchAtPath,
|
|
4102
4844
|
markConnectedOptimistically,
|
|
4103
4845
|
isPristineAtPath,
|
|
4846
|
+
isPristineAtPathByKey,
|
|
4847
|
+
hasStructuralChangeUnder,
|
|
4104
4848
|
getFieldRecord,
|
|
4105
4849
|
getOriginalAtPath,
|
|
4106
4850
|
getFirstErrorElement,
|
|
@@ -4116,7 +4860,6 @@ function createFormStore(options) {
|
|
|
4116
4860
|
modules,
|
|
4117
4861
|
persistOptIns,
|
|
4118
4862
|
isSensitivePath: resolvedIsSensitivePath,
|
|
4119
|
-
segmentMatchesSensitive: resolvedSegmentMatchesSensitive,
|
|
4120
4863
|
noSyncPaths,
|
|
4121
4864
|
incrementNoSyncOptOut,
|
|
4122
4865
|
decrementNoSyncOptOut,
|
|
@@ -4317,939 +5060,988 @@ function createHistoryModule(state, config) {
|
|
|
4317
5060
|
};
|
|
4318
5061
|
}
|
|
4319
5062
|
|
|
4320
|
-
function
|
|
4321
|
-
let
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
4330
|
-
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
|
|
4331
|
-
}
|
|
4332
|
-
|
|
4333
|
-
const PROTOCOL_VERSION = 1;
|
|
4334
|
-
const JOIN_COLLECTION_WINDOW_MS = 50;
|
|
4335
|
-
const SNAPSHOT_TIMEOUT_MS = 200;
|
|
4336
|
-
const MAX_LEADER_ATTEMPTS = 3;
|
|
4337
|
-
function isFileLikeValue(value) {
|
|
4338
|
-
if (typeof File !== "undefined" && value instanceof File) return true;
|
|
4339
|
-
if (typeof Blob !== "undefined" && value instanceof Blob) return true;
|
|
4340
|
-
return false;
|
|
4341
|
-
}
|
|
4342
|
-
function isDangerousSegment(s) {
|
|
4343
|
-
return s === "__proto__" || s === "constructor" || s === "prototype";
|
|
4344
|
-
}
|
|
4345
|
-
function pathContainsDangerousSegment(path) {
|
|
4346
|
-
for (let i = 0; i < path.length; i++) {
|
|
4347
|
-
if (isDangerousSegment(path[i])) return true;
|
|
4348
|
-
}
|
|
4349
|
-
return false;
|
|
4350
|
-
}
|
|
4351
|
-
function isInboundShapeAcceptable(schema, path, value) {
|
|
4352
|
-
if (isFileLikeValue(value)) return false;
|
|
4353
|
-
let kind;
|
|
4354
|
-
if (Array.isArray(value)) {
|
|
4355
|
-
kind = "array";
|
|
4356
|
-
} else if (value !== null && typeof value === "object" && isPlainRecord(value)) {
|
|
4357
|
-
kind = "object";
|
|
4358
|
-
} else {
|
|
4359
|
-
kind = slimKindOf(value);
|
|
4360
|
-
}
|
|
4361
|
-
const accepted = schema.getSlimPrimitiveTypesAtPath(path);
|
|
4362
|
-
if (!accepted.has(kind)) return false;
|
|
4363
|
-
if (Array.isArray(value)) {
|
|
4364
|
-
for (let i = 0; i < value.length; i++) {
|
|
4365
|
-
if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
|
|
4366
|
-
}
|
|
4367
|
-
return true;
|
|
4368
|
-
}
|
|
4369
|
-
if (isPlainRecord(value)) {
|
|
4370
|
-
for (const key of Object.keys(value)) {
|
|
4371
|
-
if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false;
|
|
5063
|
+
function wirePersistence(state, config) {
|
|
5064
|
+
let fingerprint;
|
|
5065
|
+
try {
|
|
5066
|
+
fingerprint = hashStableString(state.schema.fingerprint());
|
|
5067
|
+
} catch (err) {
|
|
5068
|
+
if (paths.__DEV__) {
|
|
5069
|
+
console.warn(
|
|
5070
|
+
`[attaform] Could not fingerprint the schema for form '${state.formKey}': ${err instanceof Error ? err.message : String(err)}. Persistence falls back to a fingerprint-free key, so a schema change won't auto-invalidate a saved draft.`
|
|
5071
|
+
);
|
|
4372
5072
|
}
|
|
4373
|
-
|
|
5073
|
+
fingerprint = "unfingerprinted";
|
|
4374
5074
|
}
|
|
4375
|
-
|
|
4376
|
-
}
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
}
|
|
4388
|
-
function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
|
|
4389
|
-
if (isFileLikeValue(value)) return void 0;
|
|
4390
|
-
if (value === null || typeof value !== "object") return value;
|
|
4391
|
-
if (Array.isArray(value)) {
|
|
4392
|
-
return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
|
|
4393
|
-
}
|
|
4394
|
-
const proto = Object.getPrototypeOf(value);
|
|
4395
|
-
if (proto !== Object.prototype && proto !== null) return value;
|
|
4396
|
-
const out = {};
|
|
4397
|
-
const src = value;
|
|
4398
|
-
for (const key of Object.keys(src)) {
|
|
4399
|
-
const childPath = [...pathSoFar, key];
|
|
4400
|
-
if (isSensitivePath(childPath)) {
|
|
4401
|
-
out[key] = void 0;
|
|
4402
|
-
continue;
|
|
4403
|
-
}
|
|
4404
|
-
out[key] = stripSensitivePathsDeep(src[key], childPath, isSensitivePath);
|
|
4405
|
-
}
|
|
4406
|
-
return out;
|
|
4407
|
-
}
|
|
4408
|
-
function isValidSyncMessage(data) {
|
|
4409
|
-
if (data === null || typeof data !== "object") return false;
|
|
4410
|
-
const m = data;
|
|
4411
|
-
if (m["v"] !== PROTOCOL_VERSION) return false;
|
|
4412
|
-
if (typeof m["senderId"] !== "string") return false;
|
|
4413
|
-
if (typeof m["kind"] !== "string") return false;
|
|
4414
|
-
switch (m["kind"]) {
|
|
4415
|
-
case "hello":
|
|
4416
|
-
case "announce":
|
|
4417
|
-
return true;
|
|
4418
|
-
case "requestSnapshot":
|
|
4419
|
-
return typeof m["targetId"] === "string";
|
|
4420
|
-
case "snapshot":
|
|
4421
|
-
return Array.isArray(m["blankPaths"]) && "form" in m;
|
|
4422
|
-
case "patches":
|
|
4423
|
-
return Array.isArray(m["formPatches"]) && Array.isArray(m["blankPathsAdded"]) && Array.isArray(m["blankPathsRemoved"]);
|
|
4424
|
-
default:
|
|
4425
|
-
return false;
|
|
4426
|
-
}
|
|
4427
|
-
}
|
|
4428
|
-
function generateSenderId() {
|
|
4429
|
-
try {
|
|
4430
|
-
return globalThis.crypto.randomUUID();
|
|
4431
|
-
} catch {
|
|
4432
|
-
return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
|
|
4433
|
-
}
|
|
4434
|
-
}
|
|
4435
|
-
function createMultiTabSyncModule(state, channelName, options) {
|
|
4436
|
-
if (typeof BroadcastChannel === "undefined") {
|
|
4437
|
-
return {
|
|
4438
|
-
dispose: () => void 0,
|
|
4439
|
-
lifecycle: () => "established",
|
|
4440
|
-
senderId: "",
|
|
4441
|
-
channelName
|
|
4442
|
-
};
|
|
4443
|
-
}
|
|
4444
|
-
let channel;
|
|
4445
|
-
try {
|
|
4446
|
-
channel = new BroadcastChannel(channelName);
|
|
4447
|
-
} catch {
|
|
4448
|
-
return {
|
|
4449
|
-
dispose: () => void 0,
|
|
4450
|
-
lifecycle: () => "established",
|
|
4451
|
-
senderId: "",
|
|
4452
|
-
channelName
|
|
4453
|
-
};
|
|
4454
|
-
}
|
|
4455
|
-
const senderId = generateSenderId();
|
|
4456
|
-
let lifecycle = "joining";
|
|
5075
|
+
const base = resolveStorageKeyBase(config, state.formKey);
|
|
5076
|
+
const key = `${base}:${fingerprint}`;
|
|
5077
|
+
const debounceMs = normalizeNumericOption({
|
|
5078
|
+
value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
|
|
5079
|
+
source: "useForm.persist.debounceMs",
|
|
5080
|
+
allowInfinity: false,
|
|
5081
|
+
min: 0,
|
|
5082
|
+
defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
|
|
5083
|
+
});
|
|
5084
|
+
const include = config.include ?? "form";
|
|
5085
|
+
const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
|
|
5086
|
+
const adapterPromise = getStorageAdapter(config.storage);
|
|
4457
5087
|
let disposed = false;
|
|
4458
|
-
const
|
|
4459
|
-
let
|
|
4460
|
-
let
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
try {
|
|
4469
|
-
channel.postMessage(msg);
|
|
4470
|
-
} catch {
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4473
|
-
function refreshPrior() {
|
|
4474
|
-
prior = {
|
|
4475
|
-
form: snapshotForm(state.form.value),
|
|
4476
|
-
blankPathsSnapshot: [...state.blankPaths]
|
|
4477
|
-
};
|
|
4478
|
-
}
|
|
4479
|
-
function isPathLocallySuppressed(path) {
|
|
4480
|
-
if (pathContainsDangerousSegment(path)) return true;
|
|
4481
|
-
if (options.isSensitivePath(path)) return true;
|
|
4482
|
-
const { key } = paths.canonicalizePath([...path]);
|
|
4483
|
-
if (options.noSyncPaths.has(key)) return true;
|
|
4484
|
-
return false;
|
|
4485
|
-
}
|
|
4486
|
-
function postPatches() {
|
|
4487
|
-
if (lifecycle !== "established") return;
|
|
4488
|
-
const next = snapshotForm(state.form.value);
|
|
4489
|
-
const rawPatches = [];
|
|
4490
|
-
diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
|
|
4491
|
-
const safePatches = [];
|
|
4492
|
-
for (const p of rawPatches) {
|
|
4493
|
-
if (isPathLocallySuppressed(p.path)) continue;
|
|
4494
|
-
if ("value" in p && isFileLikeValue(p.value)) continue;
|
|
4495
|
-
safePatches.push(p);
|
|
4496
|
-
}
|
|
4497
|
-
const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
|
|
4498
|
-
if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
|
|
4499
|
-
prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
|
|
5088
|
+
const isDisposed = () => disposed;
|
|
5089
|
+
let inFlightFinalFlush = null;
|
|
5090
|
+
let pendingOptedInPaths = null;
|
|
5091
|
+
const writer = createDebouncedWriter(async () => {
|
|
5092
|
+
const optedInPaths = pendingOptedInPaths ?? new Set(state.persistOptIns.optedInPaths());
|
|
5093
|
+
pendingOptedInPaths = null;
|
|
5094
|
+
const adapter = await adapterPromise;
|
|
5095
|
+
if (isDisposed()) return;
|
|
5096
|
+
if (optedInPaths.size === 0) {
|
|
5097
|
+
await adapter.removeItem(key);
|
|
4500
5098
|
return;
|
|
4501
5099
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
5100
|
+
const rawForm = vue.toRaw(state.form.value);
|
|
5101
|
+
const filteredForm = stripUnacknowledgedSensitiveLeaves(
|
|
5102
|
+
pluckPaths(rawForm, optedInPaths),
|
|
5103
|
+
optedInPaths,
|
|
5104
|
+
state.isSensitivePath
|
|
5105
|
+
);
|
|
5106
|
+
const filteredSchemaErrors = filterErrorsByPaths(state.schemaErrors, optedInPaths);
|
|
5107
|
+
const filteredUserErrors = filterErrorsByPaths(state.userErrors, optedInPaths);
|
|
5108
|
+
const filteredTransientEmpty = /* @__PURE__ */ new Set();
|
|
5109
|
+
for (const tk of state.blankPaths) {
|
|
5110
|
+
if (optedInPaths.has(tk)) filteredTransientEmpty.add(tk);
|
|
5111
|
+
}
|
|
5112
|
+
const payload = buildPersistedPayload(
|
|
5113
|
+
filteredForm,
|
|
5114
|
+
include,
|
|
5115
|
+
filteredSchemaErrors,
|
|
5116
|
+
filteredUserErrors,
|
|
5117
|
+
filteredTransientEmpty
|
|
5118
|
+
);
|
|
5119
|
+
await adapter.setItem(key, payload);
|
|
5120
|
+
}, debounceMs);
|
|
4512
5121
|
const unsubscribeChange = state.onFormChange((_next, meta) => {
|
|
4513
|
-
if (
|
|
4514
|
-
if (lifecycle !== "established") return;
|
|
5122
|
+
if (isDisposed() || inFlightFinalFlush !== null) return;
|
|
4515
5123
|
if (meta?.crossTab === true) return;
|
|
4516
|
-
if (meta?.
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
}
|
|
4520
|
-
postPatches();
|
|
5124
|
+
if (meta?.persist !== true) return;
|
|
5125
|
+
pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
|
|
5126
|
+
writer.schedule();
|
|
4521
5127
|
});
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
5128
|
+
const unsubscribeSuccess = clearOnSubmitSuccess ? state.onSubmitSuccess(() => {
|
|
5129
|
+
if (isDisposed()) return;
|
|
5130
|
+
void (async () => {
|
|
5131
|
+
await writer.flush();
|
|
5132
|
+
if (isDisposed()) return;
|
|
5133
|
+
const adapter = await adapterPromise;
|
|
5134
|
+
if (isDisposed()) return;
|
|
5135
|
+
await adapter.removeItem(key);
|
|
5136
|
+
})();
|
|
5137
|
+
}) : () => void 0;
|
|
5138
|
+
const handlePageHide = () => {
|
|
5139
|
+
if (isDisposed()) return;
|
|
5140
|
+
void writer.flush();
|
|
5141
|
+
};
|
|
5142
|
+
if (typeof window !== "undefined") {
|
|
5143
|
+
window.addEventListener("pagehide", handlePageHide);
|
|
4527
5144
|
}
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
if (!Array.isArray(p.path)) continue;
|
|
4533
|
-
if (isPathLocallySuppressed(p.path)) continue;
|
|
4534
|
-
if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
|
|
4535
|
-
continue;
|
|
4536
|
-
}
|
|
4537
|
-
safePatches.push(p);
|
|
4538
|
-
}
|
|
4539
|
-
const safeBlankAdded = [];
|
|
4540
|
-
for (const k of msg.blankPathsAdded) {
|
|
4541
|
-
const segs = paths.canonicalizePath(k).segments;
|
|
4542
|
-
if (isPathLocallySuppressed(segs)) continue;
|
|
4543
|
-
safeBlankAdded.push(k);
|
|
4544
|
-
}
|
|
4545
|
-
const safeBlankRemoved = [];
|
|
4546
|
-
for (const k of msg.blankPathsRemoved) {
|
|
4547
|
-
const segs = paths.canonicalizePath(k).segments;
|
|
4548
|
-
if (isPathLocallySuppressed(segs)) continue;
|
|
4549
|
-
safeBlankRemoved.push(k);
|
|
4550
|
-
}
|
|
4551
|
-
if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
|
|
4552
|
-
return;
|
|
4553
|
-
}
|
|
4554
|
-
const candidate = applyPatchesForward(state.form.value, safePatches);
|
|
5145
|
+
void (async () => {
|
|
5146
|
+
const adapter = await adapterPromise;
|
|
5147
|
+
if (isDisposed()) return;
|
|
5148
|
+
void cleanupOrphanKeys(adapter, base, key);
|
|
4555
5149
|
try {
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
5150
|
+
const raw = await adapter.getItem(key);
|
|
5151
|
+
const payload = readPersistedPayload(raw);
|
|
5152
|
+
if (payload === null) {
|
|
5153
|
+
if (raw !== null && raw !== void 0) {
|
|
5154
|
+
await adapter.removeItem(key);
|
|
5155
|
+
}
|
|
4560
5156
|
return;
|
|
4561
5157
|
}
|
|
5158
|
+
if (isDisposed()) return;
|
|
5159
|
+
const merged = mergeSparseHydration(
|
|
5160
|
+
vue.toRaw(state.form.value),
|
|
5161
|
+
payload.data.form,
|
|
5162
|
+
state.schema
|
|
5163
|
+
);
|
|
5164
|
+
state.applyFormReplacement(merged, { hydration: true });
|
|
5165
|
+
const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
|
|
5166
|
+
for (const k of persistedLeafPaths) {
|
|
5167
|
+
state.blankPaths.delete(k);
|
|
5168
|
+
state.originalBlankPaths.delete(k);
|
|
5169
|
+
}
|
|
5170
|
+
for (const k of payload.data.blankPaths ?? []) {
|
|
5171
|
+
state.blankPaths.add(k);
|
|
5172
|
+
state.originalBlankPaths.add(k);
|
|
5173
|
+
}
|
|
5174
|
+
if (include === "form+errors") {
|
|
5175
|
+
if (payload.data.schemaErrors !== void 0) {
|
|
5176
|
+
const flat = payload.data.schemaErrors.flatMap(([, errs]) => errs);
|
|
5177
|
+
state.setAllSchemaErrors(flat);
|
|
5178
|
+
}
|
|
5179
|
+
if (payload.data.userErrors !== void 0) {
|
|
5180
|
+
const flat = payload.data.userErrors.flatMap(([, errs]) => errs);
|
|
5181
|
+
state.setAllUserErrors(flat);
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
state.scheduleFieldValidation(
|
|
5185
|
+
[],
|
|
5186
|
+
true
|
|
5187
|
+
/* immediate */
|
|
5188
|
+
);
|
|
4562
5189
|
} catch {
|
|
4563
5190
|
}
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
if (
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
5191
|
+
})();
|
|
5192
|
+
async function writePathImmediately(path) {
|
|
5193
|
+
if (isDisposed()) return;
|
|
5194
|
+
await writer.flush();
|
|
5195
|
+
if (isDisposed()) return;
|
|
5196
|
+
const adapter = await adapterPromise;
|
|
5197
|
+
if (isDisposed()) return;
|
|
5198
|
+
const raw = await adapter.getItem(key);
|
|
5199
|
+
const existing = readPersistedPayload(raw);
|
|
5200
|
+
const baseForm = existing?.data.form ?? /* @__PURE__ */ Object.create(null);
|
|
5201
|
+
const value = getAtPath(vue.toRaw(state.form.value), path);
|
|
5202
|
+
const nextForm = setAtPath(baseForm, path, value);
|
|
5203
|
+
const { key: pathKey } = paths.canonicalizePath(path);
|
|
5204
|
+
const transientSet = new Set(
|
|
5205
|
+
(existing?.data.blankPaths ?? []).filter(
|
|
5206
|
+
(k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
|
|
5207
|
+
)
|
|
5208
|
+
);
|
|
5209
|
+
for (const liveKey of state.blankPaths) {
|
|
5210
|
+
if (liveKey === pathKey || isDescendantPathKey(liveKey, pathKey)) {
|
|
5211
|
+
transientSet.add(liveKey);
|
|
5212
|
+
}
|
|
4575
5213
|
}
|
|
4576
|
-
if (
|
|
4577
|
-
|
|
4578
|
-
|
|
5214
|
+
if (include === "form") {
|
|
5215
|
+
await adapter.setItem(
|
|
5216
|
+
key,
|
|
5217
|
+
buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
|
|
5218
|
+
);
|
|
5219
|
+
return;
|
|
4579
5220
|
}
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
5221
|
+
const schemaMap = new Map(existing?.data.schemaErrors ?? []);
|
|
5222
|
+
const userMap = new Map(existing?.data.userErrors ?? []);
|
|
5223
|
+
const currentSchema = state.schemaErrors.get(pathKey);
|
|
5224
|
+
const currentUser = state.userErrors.get(pathKey);
|
|
5225
|
+
if (currentSchema !== void 0 && currentSchema.length > 0) {
|
|
5226
|
+
schemaMap.set(pathKey, [...currentSchema]);
|
|
5227
|
+
} else {
|
|
5228
|
+
schemaMap.delete(pathKey);
|
|
5229
|
+
}
|
|
5230
|
+
if (currentUser !== void 0 && currentUser.length > 0) {
|
|
5231
|
+
userMap.set(pathKey, [...currentUser]);
|
|
5232
|
+
} else {
|
|
5233
|
+
userMap.delete(pathKey);
|
|
5234
|
+
}
|
|
5235
|
+
await adapter.setItem(
|
|
5236
|
+
key,
|
|
5237
|
+
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
5238
|
+
);
|
|
4596
5239
|
}
|
|
4597
|
-
|
|
4598
|
-
if (
|
|
4599
|
-
|
|
4600
|
-
if (
|
|
4601
|
-
const
|
|
4602
|
-
if (
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
respondToHello();
|
|
4607
|
-
break;
|
|
4608
|
-
case "announce":
|
|
4609
|
-
if (lifecycle === "joining") peerIds.add(msg.senderId);
|
|
4610
|
-
break;
|
|
4611
|
-
case "requestSnapshot":
|
|
4612
|
-
if (lifecycle !== "established") return;
|
|
4613
|
-
if (msg.targetId !== senderId) return;
|
|
4614
|
-
respondToSnapshotRequest();
|
|
4615
|
-
break;
|
|
4616
|
-
case "snapshot":
|
|
4617
|
-
handleSnapshot(msg);
|
|
4618
|
-
break;
|
|
4619
|
-
case "patches":
|
|
4620
|
-
handlePatches(msg);
|
|
4621
|
-
break;
|
|
5240
|
+
async function clearPersistedDraft(path) {
|
|
5241
|
+
if (isDisposed()) return;
|
|
5242
|
+
await writer.flush();
|
|
5243
|
+
if (isDisposed()) return;
|
|
5244
|
+
const adapter = await adapterPromise;
|
|
5245
|
+
if (isDisposed()) return;
|
|
5246
|
+
if (path === void 0) {
|
|
5247
|
+
await adapter.removeItem(key);
|
|
5248
|
+
return;
|
|
4622
5249
|
}
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
if (
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
5250
|
+
const raw = await adapter.getItem(key);
|
|
5251
|
+
const existing = readPersistedPayload(raw);
|
|
5252
|
+
if (existing === null) return;
|
|
5253
|
+
const nextForm = deleteAtPath(existing.data.form, path);
|
|
5254
|
+
if (isEmptyContainer(nextForm)) {
|
|
5255
|
+
await adapter.removeItem(key);
|
|
4629
5256
|
return;
|
|
4630
5257
|
}
|
|
4631
|
-
const
|
|
4632
|
-
const
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
5258
|
+
const { key: pathKey } = paths.canonicalizePath(path);
|
|
5259
|
+
const transientSet = new Set(
|
|
5260
|
+
(existing.data.blankPaths ?? []).filter(
|
|
5261
|
+
(k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
|
|
5262
|
+
)
|
|
5263
|
+
);
|
|
5264
|
+
if (include === "form") {
|
|
5265
|
+
await adapter.setItem(
|
|
5266
|
+
key,
|
|
5267
|
+
buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
|
|
5268
|
+
);
|
|
5269
|
+
return;
|
|
5270
|
+
}
|
|
5271
|
+
const schemaErrors = (existing.data.schemaErrors ?? []).filter(([k]) => k !== pathKey);
|
|
5272
|
+
const userErrors = (existing.data.userErrors ?? []).filter(([k]) => k !== pathKey);
|
|
5273
|
+
const schemaMap = new Map(schemaErrors.map(([k, v]) => [k, [...v]]));
|
|
5274
|
+
const userMap = new Map(userErrors.map(([k, v]) => [k, [...v]]));
|
|
5275
|
+
await adapter.setItem(
|
|
5276
|
+
key,
|
|
5277
|
+
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
5278
|
+
);
|
|
4652
5279
|
}
|
|
4653
|
-
function
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
if (disposed) return;
|
|
4658
|
-
if (lifecycle === "established") return;
|
|
4659
|
-
electLeaderAndRequest();
|
|
4660
|
-
}, JOIN_COLLECTION_WINDOW_MS);
|
|
5280
|
+
function awaitPendingWrites() {
|
|
5281
|
+
if (inFlightFinalFlush !== null) return inFlightFinalFlush;
|
|
5282
|
+
if (isDisposed()) return Promise.resolve();
|
|
5283
|
+
return writer.flush().catch(() => void 0);
|
|
4661
5284
|
}
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
5285
|
+
function dispose() {
|
|
5286
|
+
if (isDisposed() || inFlightFinalFlush !== null) return;
|
|
5287
|
+
unsubscribeChange();
|
|
5288
|
+
unsubscribeSuccess();
|
|
5289
|
+
if (typeof window !== "undefined") {
|
|
5290
|
+
window.removeEventListener("pagehide", handlePageHide);
|
|
5291
|
+
}
|
|
5292
|
+
inFlightFinalFlush = writer.flush().catch(() => void 0).finally(() => {
|
|
4666
5293
|
disposed = true;
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
try {
|
|
4677
|
-
channel.close();
|
|
4678
|
-
} catch {
|
|
4679
|
-
}
|
|
4680
|
-
},
|
|
4681
|
-
lifecycle: () => lifecycle,
|
|
4682
|
-
senderId,
|
|
4683
|
-
channelName
|
|
5294
|
+
inFlightFinalFlush = null;
|
|
5295
|
+
});
|
|
5296
|
+
}
|
|
5297
|
+
return {
|
|
5298
|
+
wiredConfig: config,
|
|
5299
|
+
writePathImmediately,
|
|
5300
|
+
clearPersistedDraft,
|
|
5301
|
+
awaitPendingWrites,
|
|
5302
|
+
dispose
|
|
4684
5303
|
};
|
|
4685
5304
|
}
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
if (warned.has(feature)) return;
|
|
4692
|
-
warned.add(feature);
|
|
4693
|
-
const message = featureMessage(feature);
|
|
4694
|
-
console.warn(`[attaform] ${message}`);
|
|
5305
|
+
function isEmptyContainer(value) {
|
|
5306
|
+
if (value === void 0 || value === null) return true;
|
|
5307
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
5308
|
+
if (isPlainRecord(value)) return Object.keys(value).length === 0;
|
|
5309
|
+
return false;
|
|
4695
5310
|
}
|
|
4696
|
-
function
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
5311
|
+
function collectPersistedLeafPaths(form) {
|
|
5312
|
+
const out = [];
|
|
5313
|
+
walk(form, []);
|
|
5314
|
+
return out;
|
|
5315
|
+
function walk(node, prefix) {
|
|
5316
|
+
if (Array.isArray(node)) {
|
|
5317
|
+
for (let i = 0; i < node.length; i++) {
|
|
5318
|
+
walk(node[i], [...prefix, i]);
|
|
5319
|
+
}
|
|
5320
|
+
return;
|
|
5321
|
+
}
|
|
5322
|
+
if (isPlainRecord(node)) {
|
|
5323
|
+
for (const key of Object.keys(node)) {
|
|
5324
|
+
walk(node[key], [...prefix, key]);
|
|
5325
|
+
}
|
|
5326
|
+
return;
|
|
5327
|
+
}
|
|
5328
|
+
if (prefix.length === 0) return;
|
|
5329
|
+
out.push(paths.canonicalizePath(prefix).key);
|
|
4704
5330
|
}
|
|
4705
5331
|
}
|
|
4706
|
-
function
|
|
4707
|
-
|
|
5332
|
+
function isDescendantPathKey(candidate, ancestor) {
|
|
5333
|
+
if (candidate.length <= ancestor.length) return false;
|
|
5334
|
+
if (!ancestor.endsWith("]")) return false;
|
|
5335
|
+
const childPrefix = `${ancestor.slice(0, -1)},`;
|
|
5336
|
+
return candidate.startsWith(childPrefix);
|
|
4708
5337
|
}
|
|
4709
5338
|
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
5339
|
+
const PROTOCOL_VERSION = 1;
|
|
5340
|
+
const JOIN_COLLECTION_WINDOW_MS = 50;
|
|
5341
|
+
const SNAPSHOT_TIMEOUT_MS = 200;
|
|
5342
|
+
const MAX_LEADER_ATTEMPTS = 3;
|
|
5343
|
+
const SNAPSHOT_RESPONSE_MIN_INTERVAL_MS = 500;
|
|
5344
|
+
function isFileLikeValue(value) {
|
|
5345
|
+
if (typeof File !== "undefined" && value instanceof File) return true;
|
|
5346
|
+
if (typeof Blob !== "undefined" && value instanceof Blob) return true;
|
|
5347
|
+
return false;
|
|
4715
5348
|
}
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
5349
|
+
function isInboundShapeAcceptable(schema, path, value) {
|
|
5350
|
+
if (isFileLikeValue(value)) return false;
|
|
5351
|
+
let kind;
|
|
5352
|
+
if (Array.isArray(value)) {
|
|
5353
|
+
kind = "array";
|
|
5354
|
+
} else if (value !== null && typeof value === "object" && isPlainRecord(value)) {
|
|
5355
|
+
kind = "object";
|
|
5356
|
+
} else {
|
|
5357
|
+
kind = slimKindOf(value);
|
|
4720
5358
|
}
|
|
4721
|
-
const
|
|
4722
|
-
|
|
4723
|
-
if (
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
const materialisedDefaults = resolvedDefaults.kind === "sync" ? resolvedDefaults.value : void 0;
|
|
4727
|
-
const { defaultValues: _droppedDefaults, ...configWithoutDefaults } = configuration;
|
|
4728
|
-
const trichotomyOverride = materialisedDefaults === void 0 ? configWithoutDefaults : { ...configWithoutDefaults, defaultValues: materialisedDefaults };
|
|
4729
|
-
const merged = mergeWithDefaults(registry.defaults, trichotomyOverride);
|
|
4730
|
-
const maxRecursionDepth = normalizeNumericOption({
|
|
4731
|
-
value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
|
|
4732
|
-
source: "useForm.maxRecursionDepth",
|
|
4733
|
-
allowInfinity: true,
|
|
4734
|
-
min: 0,
|
|
4735
|
-
defaultValue: DEFAULT_MAX_RECURSION_DEPTH
|
|
4736
|
-
});
|
|
4737
|
-
const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
|
|
4738
|
-
if (configuration.persist !== void 0 && configuration.key === void 0) {
|
|
4739
|
-
throw new paths.AnonPersistError({
|
|
4740
|
-
cause: "no-key",
|
|
4741
|
-
schemaFields: extractSchemaFields(resolvedSchema),
|
|
4742
|
-
callSite: paths.captureUserCallSite()
|
|
4743
|
-
});
|
|
4744
|
-
}
|
|
4745
|
-
const existing = registry.forms.get(key);
|
|
4746
|
-
if (paths.__DEV__ && existing !== void 0) {
|
|
4747
|
-
warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
|
|
4748
|
-
warnOnPersistDivergence(key, existing, configuration.persist);
|
|
4749
|
-
}
|
|
4750
|
-
const hadPendingHydration = registry.pendingHydration.has(key);
|
|
4751
|
-
const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
|
|
4752
|
-
if (existing !== void 0) ; else if (resolvedDefaults.kind === "sync") {
|
|
4753
|
-
state.defaultsResolved.value = true;
|
|
4754
|
-
}
|
|
4755
|
-
if (existing === void 0 && resolvedDefaults.kind === "async") {
|
|
4756
|
-
const factory = resolvedDefaults.factory;
|
|
4757
|
-
state.defaultValuesFactory.value = factory;
|
|
4758
|
-
if (hadPendingHydration) {
|
|
4759
|
-
state.hydrating.value = false;
|
|
4760
|
-
state.defaultsResolved.value = true;
|
|
4761
|
-
} else if (registry.ssr) {
|
|
4762
|
-
if (configuration.__ssrAccessed === true) {
|
|
4763
|
-
registry.enqueuePrefetch(key);
|
|
4764
|
-
}
|
|
4765
|
-
vue.onServerPrefetch(() => {
|
|
4766
|
-
if (!registry.shouldPrefetch(key)) return;
|
|
4767
|
-
return state.activate();
|
|
4768
|
-
});
|
|
4769
|
-
}
|
|
4770
|
-
}
|
|
4771
|
-
if (vue.getCurrentScope() !== void 0) {
|
|
4772
|
-
const releaseConsumer = registry.trackConsumer(key);
|
|
4773
|
-
vue.onScopeDispose(releaseConsumer);
|
|
4774
|
-
}
|
|
4775
|
-
const persistDisabledByAnonRule = merged.persist !== void 0 && enforceAnonPersistRule(state.formKey, registry.ssr);
|
|
4776
|
-
if (existing === void 0 && !registry.ssr) {
|
|
4777
|
-
if (merged.persist !== void 0 && !persistDisabledByAnonRule) {
|
|
4778
|
-
const resolvedPersist = normalizePersistConfig(merged.persist);
|
|
4779
|
-
const storageKind = resolvedPersist.storage;
|
|
4780
|
-
const isBuiltinStorage = typeof storageKind === "string";
|
|
4781
|
-
const secureContextOk = !isBuiltinStorage || isSecureContext();
|
|
4782
|
-
if (!secureContextOk) {
|
|
4783
|
-
const feature = storageKind === "session" ? "persist:session" : "persist:local";
|
|
4784
|
-
warnOnceInsecureContext(feature);
|
|
4785
|
-
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
4786
|
-
} else {
|
|
4787
|
-
const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
|
|
4788
|
-
void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
|
|
4789
|
-
const persistenceModule = wirePersistence(state, resolvedPersist);
|
|
4790
|
-
state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
|
|
4791
|
-
state.registerDrain(() => persistenceModule.awaitPendingWrites());
|
|
4792
|
-
state.registerCleanup(() => persistenceModule.dispose());
|
|
4793
|
-
}
|
|
4794
|
-
} else {
|
|
4795
|
-
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
5359
|
+
const accepted = schema.getSlimPrimitiveTypesAtPath(path);
|
|
5360
|
+
if (!accepted.has(kind)) return false;
|
|
5361
|
+
if (Array.isArray(value)) {
|
|
5362
|
+
for (let i = 0; i < value.length; i++) {
|
|
5363
|
+
if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
|
|
4796
5364
|
}
|
|
5365
|
+
return true;
|
|
4797
5366
|
}
|
|
4798
|
-
if (
|
|
4799
|
-
const
|
|
4800
|
-
|
|
4801
|
-
if (hasBroadcastChannel && secureContext) {
|
|
4802
|
-
let channelName;
|
|
4803
|
-
try {
|
|
4804
|
-
channelName = `attaform:sync:${state.formKey}:${hashStableString(state.schema.fingerprint())}`;
|
|
4805
|
-
} catch {
|
|
4806
|
-
channelName = null;
|
|
4807
|
-
}
|
|
4808
|
-
if (channelName !== null) {
|
|
4809
|
-
const syncModule = createMultiTabSyncModule(state, channelName, {
|
|
4810
|
-
isSensitivePath: state.isSensitivePath,
|
|
4811
|
-
noSyncPaths: state.noSyncPaths,
|
|
4812
|
-
validateForm: (form) => {
|
|
4813
|
-
const result = state.schema.validateAtPath(form, void 0, { sync: true });
|
|
4814
|
-
if (result instanceof Promise) return;
|
|
4815
|
-
if (!result.success) {
|
|
4816
|
-
throw new Error("attaform multi-tab sync: post-apply schema validation failed");
|
|
4817
|
-
}
|
|
4818
|
-
}
|
|
4819
|
-
});
|
|
4820
|
-
state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
|
|
4821
|
-
state.registerCleanup(() => syncModule.dispose());
|
|
4822
|
-
}
|
|
4823
|
-
} else if (hasBroadcastChannel && !secureContext) {
|
|
4824
|
-
warnOnceInsecureContext("multiTab");
|
|
5367
|
+
if (isPlainRecord(value)) {
|
|
5368
|
+
for (const key of Object.keys(value)) {
|
|
5369
|
+
if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false;
|
|
4825
5370
|
}
|
|
5371
|
+
return true;
|
|
4826
5372
|
}
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
5373
|
+
return true;
|
|
5374
|
+
}
|
|
5375
|
+
function diffBlankPaths(prev, curr) {
|
|
5376
|
+
const added = [];
|
|
5377
|
+
const removed = [];
|
|
5378
|
+
const prevSet = new Set(prev);
|
|
5379
|
+
for (const k of curr) if (!prevSet.has(k)) added.push(k);
|
|
5380
|
+
for (const k of prev) if (!curr.has(k)) removed.push(k);
|
|
5381
|
+
return { added, removed };
|
|
5382
|
+
}
|
|
5383
|
+
function snapshotForm(form) {
|
|
5384
|
+
return structuralSnapshot(form);
|
|
5385
|
+
}
|
|
5386
|
+
function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
|
|
5387
|
+
if (isFileLikeValue(value)) return void 0;
|
|
5388
|
+
if (value === null || typeof value !== "object") return value;
|
|
5389
|
+
if (Array.isArray(value)) {
|
|
5390
|
+
return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
|
|
4831
5391
|
}
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
5392
|
+
const proto = Object.getPrototypeOf(value);
|
|
5393
|
+
if (proto !== Object.prototype && proto !== null) return value;
|
|
5394
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
5395
|
+
const src = value;
|
|
5396
|
+
for (const key of Object.keys(src)) {
|
|
5397
|
+
const childPath = [...pathSoFar, key];
|
|
5398
|
+
if (isSensitivePath(childPath)) {
|
|
5399
|
+
out[key] = void 0;
|
|
5400
|
+
continue;
|
|
5401
|
+
}
|
|
5402
|
+
out[key] = stripSensitivePathsDeep(src[key], childPath, isSensitivePath);
|
|
4835
5403
|
}
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
5404
|
+
return out;
|
|
5405
|
+
}
|
|
5406
|
+
function isStringArray(value) {
|
|
5407
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
5408
|
+
}
|
|
5409
|
+
function isPatchArray(value) {
|
|
5410
|
+
return Array.isArray(value) && value.every(
|
|
5411
|
+
(p) => p !== null && typeof p === "object" && Array.isArray(p.path)
|
|
5412
|
+
);
|
|
5413
|
+
}
|
|
5414
|
+
function isValidSyncMessage(data) {
|
|
5415
|
+
if (data === null || typeof data !== "object") return false;
|
|
5416
|
+
const m = data;
|
|
5417
|
+
if (m["v"] !== PROTOCOL_VERSION) return false;
|
|
5418
|
+
if (typeof m["senderId"] !== "string") return false;
|
|
5419
|
+
if (typeof m["kind"] !== "string") return false;
|
|
5420
|
+
switch (m["kind"]) {
|
|
5421
|
+
case "hello":
|
|
5422
|
+
case "announce":
|
|
5423
|
+
return true;
|
|
5424
|
+
case "requestSnapshot":
|
|
5425
|
+
return typeof m["targetId"] === "string";
|
|
5426
|
+
case "snapshot":
|
|
5427
|
+
return isStringArray(m["blankPaths"]) && "form" in m;
|
|
5428
|
+
case "patches":
|
|
5429
|
+
return isPatchArray(m["formPatches"]) && isStringArray(m["blankPathsAdded"]) && isStringArray(m["blankPathsRemoved"]);
|
|
5430
|
+
default:
|
|
5431
|
+
return false;
|
|
4839
5432
|
}
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
5433
|
+
}
|
|
5434
|
+
function generateSenderId() {
|
|
5435
|
+
try {
|
|
5436
|
+
return globalThis.crypto.randomUUID();
|
|
5437
|
+
} catch {
|
|
5438
|
+
return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
|
|
4843
5439
|
}
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
5440
|
+
}
|
|
5441
|
+
function createMultiTabSyncModule(state, channelName, options) {
|
|
5442
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
5443
|
+
return {
|
|
5444
|
+
dispose: () => void 0,
|
|
5445
|
+
lifecycle: () => "established",
|
|
5446
|
+
senderId: "",
|
|
5447
|
+
channelName
|
|
5448
|
+
};
|
|
4847
5449
|
}
|
|
4848
|
-
|
|
4849
|
-
|
|
5450
|
+
let channel;
|
|
5451
|
+
try {
|
|
5452
|
+
channel = new BroadcastChannel(channelName);
|
|
5453
|
+
} catch {
|
|
5454
|
+
return {
|
|
5455
|
+
dispose: () => void 0,
|
|
5456
|
+
lifecycle: () => "established",
|
|
5457
|
+
senderId: "",
|
|
5458
|
+
channelName
|
|
5459
|
+
};
|
|
4850
5460
|
}
|
|
4851
|
-
const
|
|
4852
|
-
|
|
4853
|
-
|
|
5461
|
+
const senderId = generateSenderId();
|
|
5462
|
+
let lifecycle = "joining";
|
|
5463
|
+
let disposed = false;
|
|
5464
|
+
const peerIds = /* @__PURE__ */ new Set();
|
|
5465
|
+
const lastSnapshotResponseAt = /* @__PURE__ */ new Map();
|
|
5466
|
+
let joinCollectionTimer = null;
|
|
5467
|
+
let snapshotTimeoutTimer = null;
|
|
5468
|
+
let leaderAttempts = 0;
|
|
5469
|
+
let prior = {
|
|
5470
|
+
form: snapshotForm(state.form.value),
|
|
5471
|
+
blankPathsSnapshot: [...state.blankPaths]
|
|
5472
|
+
};
|
|
5473
|
+
function safePost(msg) {
|
|
5474
|
+
if (disposed) return;
|
|
5475
|
+
try {
|
|
5476
|
+
channel.postMessage(msg);
|
|
5477
|
+
} catch {
|
|
5478
|
+
}
|
|
4854
5479
|
}
|
|
4855
|
-
|
|
4856
|
-
|
|
5480
|
+
function refreshPrior() {
|
|
5481
|
+
prior = {
|
|
5482
|
+
form: snapshotForm(state.form.value),
|
|
5483
|
+
blankPathsSnapshot: [...state.blankPaths]
|
|
5484
|
+
};
|
|
4857
5485
|
}
|
|
4858
|
-
|
|
4859
|
-
|
|
5486
|
+
function isPathLocallySuppressed(path) {
|
|
5487
|
+
if (options.isSensitivePath(path)) return true;
|
|
5488
|
+
const { key } = paths.canonicalizePath([...path]);
|
|
5489
|
+
if (options.noSyncPaths.has(key)) return true;
|
|
5490
|
+
return false;
|
|
4860
5491
|
}
|
|
4861
|
-
|
|
4862
|
-
|
|
5492
|
+
function postPatches() {
|
|
5493
|
+
if (lifecycle !== "established") return;
|
|
5494
|
+
const next = snapshotForm(state.form.value);
|
|
5495
|
+
const rawPatches = [];
|
|
5496
|
+
diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
|
|
5497
|
+
const safePatches = [];
|
|
5498
|
+
for (const p of rawPatches) {
|
|
5499
|
+
if (isPathLocallySuppressed(p.path)) continue;
|
|
5500
|
+
if ("value" in p && isFileLikeValue(p.value)) continue;
|
|
5501
|
+
safePatches.push(p);
|
|
5502
|
+
}
|
|
5503
|
+
const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
|
|
5504
|
+
if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
|
|
5505
|
+
prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
|
|
5506
|
+
return;
|
|
5507
|
+
}
|
|
5508
|
+
safePost({
|
|
5509
|
+
v: PROTOCOL_VERSION,
|
|
5510
|
+
kind: "patches",
|
|
5511
|
+
senderId,
|
|
5512
|
+
formPatches: safePatches,
|
|
5513
|
+
blankPathsAdded: added,
|
|
5514
|
+
blankPathsRemoved: removed
|
|
5515
|
+
});
|
|
5516
|
+
prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
|
|
4863
5517
|
}
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
const maxRecursionDepth = configuration.maxRecursionDepth ?? defaults.maxRecursionDepth;
|
|
4880
|
-
const sensitiveNames = configuration.sensitiveNames ?? defaults.sensitiveNames;
|
|
4881
|
-
const multiTab = configuration.multiTab ?? defaults.multiTab;
|
|
4882
|
-
return {
|
|
4883
|
-
...configuration,
|
|
4884
|
-
...strict === void 0 ? {} : { strict },
|
|
4885
|
-
...onInvalidSubmit === void 0 ? {} : { onInvalidSubmit },
|
|
4886
|
-
...history === void 0 ? {} : { history },
|
|
4887
|
-
...rememberVariants === void 0 ? {} : { rememberVariants },
|
|
4888
|
-
...coerce === void 0 ? {} : { coerce },
|
|
4889
|
-
...validateOn === void 0 ? {} : { validateOn },
|
|
4890
|
-
...debounceMs === void 0 ? {} : { debounceMs },
|
|
4891
|
-
...shouldShowErrors === void 0 ? {} : { shouldShowErrors },
|
|
4892
|
-
...maxRecursionDepth === void 0 ? {} : { maxRecursionDepth },
|
|
4893
|
-
...sensitiveNames === void 0 ? {} : { sensitiveNames },
|
|
4894
|
-
...multiTab === void 0 ? {} : { multiTab }
|
|
4895
|
-
};
|
|
4896
|
-
}
|
|
4897
|
-
const HISTORY_MODULE_KEY = "history";
|
|
4898
|
-
function buildFreshState(key, schema, configuration, registry) {
|
|
4899
|
-
const pending = registry.pendingHydration.get(key);
|
|
4900
|
-
if (pending !== void 0) registry.pendingHydration.delete(key);
|
|
4901
|
-
const walked = walkUnsetSentinels(
|
|
4902
|
-
configuration.defaultValues,
|
|
4903
|
-
schema
|
|
4904
|
-
);
|
|
4905
|
-
let initialBlankPaths;
|
|
4906
|
-
if (pending === void 0) {
|
|
4907
|
-
initialBlankPaths = walked.paths;
|
|
5518
|
+
const unsubscribeChange = state.onFormChange((_next, meta) => {
|
|
5519
|
+
if (disposed) return;
|
|
5520
|
+
if (lifecycle !== "established") return;
|
|
5521
|
+
if (meta?.crossTab === true) return;
|
|
5522
|
+
if (meta?.hydration === true) {
|
|
5523
|
+
refreshPrior();
|
|
5524
|
+
return;
|
|
5525
|
+
}
|
|
5526
|
+
postPatches();
|
|
5527
|
+
});
|
|
5528
|
+
function applyIncomingForm(form, blankPaths) {
|
|
5529
|
+
state.blankPaths.clear();
|
|
5530
|
+
for (const k of blankPaths) state.blankPaths.add(k);
|
|
5531
|
+
state.applyFormReplacement(form, { crossTab: true, persist: false });
|
|
5532
|
+
refreshPrior();
|
|
4908
5533
|
}
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
hydration: pending,
|
|
4918
|
-
...configuration.validateOn !== void 0 ? { validateOn: configuration.validateOn } : {},
|
|
4919
|
-
...configuration.debounceMs !== void 0 ? { debounceMs: configuration.debounceMs } : {},
|
|
4920
|
-
ssr: registry.ssr,
|
|
4921
|
-
// Server-only: bind the SSR prefetch coordination handles. `enqueue`
|
|
4922
|
-
// records intent on every `state.activate()` so a wizard skip-list
|
|
4923
|
-
// override or a future transform mark has a consistent set to diff
|
|
4924
|
-
// against; `shouldFire` lets the activate path bail when the
|
|
4925
|
-
// wizard explicitly skipped this key — even an explicit
|
|
4926
|
-
// `form.activate()` defers to the wizard's render-efficiency
|
|
4927
|
-
// skip-list on the server.
|
|
4928
|
-
...registry.ssr ? {
|
|
4929
|
-
ssrPrefetch: {
|
|
4930
|
-
enqueue: () => {
|
|
4931
|
-
registry.enqueuePrefetch(key);
|
|
4932
|
-
},
|
|
4933
|
-
shouldFire: () => registry.shouldPrefetch(key)
|
|
5534
|
+
function handlePatches(msg) {
|
|
5535
|
+
if (lifecycle !== "established") return;
|
|
5536
|
+
const safePatches = [];
|
|
5537
|
+
for (const p of msg.formPatches) {
|
|
5538
|
+
if (!Array.isArray(p.path)) continue;
|
|
5539
|
+
if (isPathLocallySuppressed(p.path)) continue;
|
|
5540
|
+
if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
|
|
5541
|
+
continue;
|
|
4934
5542
|
}
|
|
4935
|
-
|
|
4936
|
-
...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
|
|
4937
|
-
...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
|
|
4938
|
-
...configuration.shouldShowErrors !== void 0 ? { shouldShowErrors: configuration.shouldShowErrors } : {},
|
|
4939
|
-
...initialBlankPaths !== void 0 ? { initialBlankPaths } : {},
|
|
4940
|
-
...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {},
|
|
4941
|
-
...resolvedSegmentMatchesSensitive !== void 0 ? { segmentMatchesSensitive: resolvedSegmentMatchesSensitive } : {}
|
|
4942
|
-
};
|
|
4943
|
-
const state = createFormStore(createOptions);
|
|
4944
|
-
registry.forms.set(
|
|
4945
|
-
key,
|
|
4946
|
-
state
|
|
4947
|
-
);
|
|
4948
|
-
return state;
|
|
4949
|
-
}
|
|
4950
|
-
let anonCounter = 0;
|
|
4951
|
-
let formInstanceCounter = 0;
|
|
4952
|
-
const ambientProvideHistory = paths.__DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
4953
|
-
function recordAmbientProvide(ssr) {
|
|
4954
|
-
if (!paths.__DEV__ || ssr || ambientProvideHistory === null) return;
|
|
4955
|
-
const instance = vue.getCurrentInstance();
|
|
4956
|
-
if (instance === null) return;
|
|
4957
|
-
const instanceKey = instance;
|
|
4958
|
-
const entry = {
|
|
4959
|
-
source: paths.captureUserCallSite()
|
|
4960
|
-
};
|
|
4961
|
-
const existing = ambientProvideHistory.get(instanceKey);
|
|
4962
|
-
if (existing === void 0) {
|
|
4963
|
-
ambientProvideHistory.set(instanceKey, [entry]);
|
|
4964
|
-
return;
|
|
4965
|
-
}
|
|
4966
|
-
existing.push(entry);
|
|
4967
|
-
}
|
|
4968
|
-
function resolveFormKey(key) {
|
|
4969
|
-
if (key !== void 0 && key !== null && key !== "") {
|
|
4970
|
-
if (key.startsWith(RESERVED_KEY_PREFIX)) {
|
|
4971
|
-
throw new paths.ReservedFormKeyError(key);
|
|
5543
|
+
safePatches.push(p);
|
|
4972
5544
|
}
|
|
4973
|
-
|
|
5545
|
+
const safeBlankAdded = [];
|
|
5546
|
+
for (const k of msg.blankPathsAdded) {
|
|
5547
|
+
const segs = paths.canonicalizePath(k).segments;
|
|
5548
|
+
if (isPathLocallySuppressed(segs)) continue;
|
|
5549
|
+
safeBlankAdded.push(k);
|
|
5550
|
+
}
|
|
5551
|
+
const safeBlankRemoved = [];
|
|
5552
|
+
for (const k of msg.blankPathsRemoved) {
|
|
5553
|
+
const segs = paths.canonicalizePath(k).segments;
|
|
5554
|
+
if (isPathLocallySuppressed(segs)) continue;
|
|
5555
|
+
safeBlankRemoved.push(k);
|
|
5556
|
+
}
|
|
5557
|
+
if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
|
|
5558
|
+
return;
|
|
5559
|
+
}
|
|
5560
|
+
const candidate = applyPatchesForward(state.form.value, safePatches);
|
|
5561
|
+
try {
|
|
5562
|
+
options.validateForm(state.form.value);
|
|
5563
|
+
try {
|
|
5564
|
+
options.validateForm(candidate);
|
|
5565
|
+
} catch {
|
|
5566
|
+
return;
|
|
5567
|
+
}
|
|
5568
|
+
} catch {
|
|
5569
|
+
}
|
|
5570
|
+
const nextBlankPaths = new Set(state.blankPaths);
|
|
5571
|
+
for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
|
|
5572
|
+
for (const k of safeBlankAdded) nextBlankPaths.add(k);
|
|
5573
|
+
applyIncomingForm(candidate, [...nextBlankPaths]);
|
|
4974
5574
|
}
|
|
4975
|
-
|
|
4976
|
-
|
|
5575
|
+
function handleSnapshot(msg) {
|
|
5576
|
+
if (lifecycle !== "joining") return;
|
|
5577
|
+
if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return;
|
|
5578
|
+
if (snapshotTimeoutTimer !== null) {
|
|
5579
|
+
clearTimeout(snapshotTimeoutTimer);
|
|
5580
|
+
snapshotTimeoutTimer = null;
|
|
5581
|
+
}
|
|
5582
|
+
if (joinCollectionTimer !== null) {
|
|
5583
|
+
clearTimeout(joinCollectionTimer);
|
|
5584
|
+
joinCollectionTimer = null;
|
|
5585
|
+
}
|
|
5586
|
+
applyIncomingForm(msg.form, msg.blankPaths);
|
|
5587
|
+
lifecycle = "established";
|
|
5588
|
+
peerIds.clear();
|
|
4977
5589
|
}
|
|
4978
|
-
|
|
4979
|
-
}
|
|
4980
|
-
function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
|
|
4981
|
-
let existingFp;
|
|
4982
|
-
let incomingFp;
|
|
4983
|
-
try {
|
|
4984
|
-
existingFp = existing.fingerprint();
|
|
4985
|
-
incomingFp = incoming.fingerprint();
|
|
4986
|
-
} catch (error) {
|
|
4987
|
-
console.error(
|
|
4988
|
-
`[attaform] fingerprint() threw for key "${key}"; skipping mismatch check.`,
|
|
4989
|
-
error
|
|
4990
|
-
);
|
|
4991
|
-
return;
|
|
5590
|
+
function respondToHello() {
|
|
5591
|
+
safePost({ v: PROTOCOL_VERSION, kind: "announce", senderId });
|
|
4992
5592
|
}
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
`[attaform] useForm({ key: "${key}" }) passed a persist config but the first useForm({ key }) call didn't wire persistence; the new config is silently dropped. Pass persist on the first call, or remove persist here to make the inheritance explicit.`
|
|
5007
|
-
);
|
|
5008
|
-
return;
|
|
5593
|
+
function respondToSnapshotRequest(requesterId) {
|
|
5594
|
+
const now = Date.now();
|
|
5595
|
+
const last = lastSnapshotResponseAt.get(requesterId);
|
|
5596
|
+
if (last !== void 0 && now - last < SNAPSHOT_RESPONSE_MIN_INTERVAL_MS) return;
|
|
5597
|
+
lastSnapshotResponseAt.set(requesterId, now);
|
|
5598
|
+
const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath);
|
|
5599
|
+
safePost({
|
|
5600
|
+
v: PROTOCOL_VERSION,
|
|
5601
|
+
kind: "snapshot",
|
|
5602
|
+
senderId,
|
|
5603
|
+
form: scrubbedForm,
|
|
5604
|
+
blankPaths: [...state.blankPaths]
|
|
5605
|
+
});
|
|
5009
5606
|
}
|
|
5010
|
-
|
|
5011
|
-
console.warn(
|
|
5012
|
-
`[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
|
|
5013
|
-
wired: ${describePersist(wired.wiredConfig)}
|
|
5014
|
-
incoming: ${describePersist(incomingNormalized)}`
|
|
5015
|
-
);
|
|
5016
|
-
}
|
|
5017
|
-
function persistConfigsEquivalent(a, b) {
|
|
5018
|
-
if (a.storage !== b.storage) return false;
|
|
5019
|
-
if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
|
|
5020
|
-
if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
|
|
5021
|
-
return true;
|
|
5022
|
-
}
|
|
5023
|
-
function describePersist(config) {
|
|
5024
|
-
const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
|
|
5025
|
-
const parts = [`storage=${storage}`];
|
|
5026
|
-
if (config.key !== void 0) parts.push(`key=${config.key}`);
|
|
5027
|
-
if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
|
|
5028
|
-
return `{ ${parts.join(", ")} }`;
|
|
5029
|
-
}
|
|
5030
|
-
function wirePersistence(state, config) {
|
|
5031
|
-
const fingerprint = hashStableString(state.schema.fingerprint());
|
|
5032
|
-
const base = resolveStorageKeyBase(config, state.formKey);
|
|
5033
|
-
const key = `${base}:${fingerprint}`;
|
|
5034
|
-
const debounceMs = normalizeNumericOption({
|
|
5035
|
-
value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
|
|
5036
|
-
source: "useForm.persist.debounceMs",
|
|
5037
|
-
allowInfinity: false,
|
|
5038
|
-
min: 0,
|
|
5039
|
-
defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
|
|
5040
|
-
});
|
|
5041
|
-
const include = config.include ?? "form";
|
|
5042
|
-
const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
|
|
5043
|
-
const adapterPromise = getStorageAdapter(config.storage);
|
|
5044
|
-
let disposed = false;
|
|
5045
|
-
let inFlightFinalFlush = null;
|
|
5046
|
-
let pendingOptedInPaths = null;
|
|
5047
|
-
const writer = createDebouncedWriter(async () => {
|
|
5048
|
-
const optedInPaths = pendingOptedInPaths ?? new Set(state.persistOptIns.optedInPaths());
|
|
5049
|
-
pendingOptedInPaths = null;
|
|
5050
|
-
const adapter = await adapterPromise;
|
|
5607
|
+
channel.onmessage = (event) => {
|
|
5051
5608
|
if (disposed) return;
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
return;
|
|
5609
|
+
try {
|
|
5610
|
+
const data = event.data;
|
|
5611
|
+
if (!isValidSyncMessage(data)) return;
|
|
5612
|
+
const msg = data;
|
|
5613
|
+
if (msg.senderId === senderId) return;
|
|
5614
|
+
switch (msg.kind) {
|
|
5615
|
+
case "hello":
|
|
5616
|
+
if (lifecycle !== "established") return;
|
|
5617
|
+
respondToHello();
|
|
5618
|
+
break;
|
|
5619
|
+
case "announce":
|
|
5620
|
+
if (lifecycle === "joining") peerIds.add(msg.senderId);
|
|
5621
|
+
break;
|
|
5622
|
+
case "requestSnapshot":
|
|
5623
|
+
if (lifecycle !== "established") return;
|
|
5624
|
+
if (msg.targetId !== senderId) return;
|
|
5625
|
+
respondToSnapshotRequest(msg.senderId);
|
|
5626
|
+
break;
|
|
5627
|
+
case "snapshot":
|
|
5628
|
+
handleSnapshot(msg);
|
|
5629
|
+
break;
|
|
5630
|
+
case "patches":
|
|
5631
|
+
handlePatches(msg);
|
|
5632
|
+
break;
|
|
5633
|
+
}
|
|
5634
|
+
} catch {
|
|
5055
5635
|
}
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
const filteredSchemaErrors = filterErrorsByPaths(state.schemaErrors, optedInPaths);
|
|
5059
|
-
const filteredUserErrors = filterErrorsByPaths(state.userErrors, optedInPaths);
|
|
5060
|
-
const filteredTransientEmpty = /* @__PURE__ */ new Set();
|
|
5061
|
-
for (const tk of state.blankPaths) {
|
|
5062
|
-
if (optedInPaths.has(tk)) filteredTransientEmpty.add(tk);
|
|
5063
|
-
}
|
|
5064
|
-
const payload = buildPersistedPayload(
|
|
5065
|
-
filteredForm,
|
|
5066
|
-
include,
|
|
5067
|
-
filteredSchemaErrors,
|
|
5068
|
-
filteredUserErrors,
|
|
5069
|
-
filteredTransientEmpty
|
|
5070
|
-
);
|
|
5071
|
-
await adapter.setItem(key, payload);
|
|
5072
|
-
}, debounceMs);
|
|
5073
|
-
const unsubscribeChange = state.onFormChange((_next, meta) => {
|
|
5074
|
-
if (disposed || inFlightFinalFlush !== null) return;
|
|
5075
|
-
if (meta?.crossTab === true) return;
|
|
5076
|
-
if (meta?.persist !== true) return;
|
|
5077
|
-
pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
|
|
5078
|
-
writer.schedule();
|
|
5079
|
-
});
|
|
5080
|
-
const unsubscribeSuccess = clearOnSubmitSuccess ? state.onSubmitSuccess(() => {
|
|
5636
|
+
};
|
|
5637
|
+
function electLeaderAndRequest() {
|
|
5081
5638
|
if (disposed) return;
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5639
|
+
if (peerIds.size === 0) {
|
|
5640
|
+
lifecycle = "established";
|
|
5641
|
+
refreshPrior();
|
|
5642
|
+
return;
|
|
5643
|
+
}
|
|
5644
|
+
const sorted = [...peerIds].sort();
|
|
5645
|
+
const leaderId = sorted[0];
|
|
5646
|
+
peerIds.delete(leaderId);
|
|
5647
|
+
leaderAttempts++;
|
|
5648
|
+
safePost({
|
|
5649
|
+
v: PROTOCOL_VERSION,
|
|
5650
|
+
kind: "requestSnapshot",
|
|
5651
|
+
senderId,
|
|
5652
|
+
targetId: leaderId
|
|
5653
|
+
});
|
|
5654
|
+
snapshotTimeoutTimer = setTimeout(() => {
|
|
5655
|
+
snapshotTimeoutTimer = null;
|
|
5086
5656
|
if (disposed) return;
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
const adapter = await adapterPromise;
|
|
5092
|
-
if (disposed) return;
|
|
5093
|
-
void cleanupOrphanKeys(adapter, base, key);
|
|
5094
|
-
try {
|
|
5095
|
-
const raw = await adapter.getItem(key);
|
|
5096
|
-
const payload = readPersistedPayload(raw);
|
|
5097
|
-
if (payload === null) {
|
|
5098
|
-
if (raw !== null && raw !== void 0) {
|
|
5099
|
-
await adapter.removeItem(key);
|
|
5100
|
-
}
|
|
5657
|
+
if (lifecycle === "established") return;
|
|
5658
|
+
if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
|
|
5659
|
+
lifecycle = "established";
|
|
5660
|
+
refreshPrior();
|
|
5101
5661
|
return;
|
|
5102
5662
|
}
|
|
5663
|
+
electLeaderAndRequest();
|
|
5664
|
+
}, SNAPSHOT_TIMEOUT_MS);
|
|
5665
|
+
}
|
|
5666
|
+
function joinFlow() {
|
|
5667
|
+
safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
|
|
5668
|
+
joinCollectionTimer = setTimeout(() => {
|
|
5669
|
+
joinCollectionTimer = null;
|
|
5103
5670
|
if (disposed) return;
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5671
|
+
if (lifecycle === "established") return;
|
|
5672
|
+
electLeaderAndRequest();
|
|
5673
|
+
}, JOIN_COLLECTION_WINDOW_MS);
|
|
5674
|
+
}
|
|
5675
|
+
joinFlow();
|
|
5676
|
+
return {
|
|
5677
|
+
dispose: () => {
|
|
5678
|
+
if (disposed) return;
|
|
5679
|
+
disposed = true;
|
|
5680
|
+
if (joinCollectionTimer !== null) {
|
|
5681
|
+
clearTimeout(joinCollectionTimer);
|
|
5682
|
+
joinCollectionTimer = null;
|
|
5114
5683
|
}
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
state.originalBlankPaths.add(key2);
|
|
5684
|
+
if (snapshotTimeoutTimer !== null) {
|
|
5685
|
+
clearTimeout(snapshotTimeoutTimer);
|
|
5686
|
+
snapshotTimeoutTimer = null;
|
|
5119
5687
|
}
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
}
|
|
5125
|
-
if (payload.data.userErrors !== void 0) {
|
|
5126
|
-
const flat = payload.data.userErrors.flatMap(([, errs]) => errs);
|
|
5127
|
-
state.setAllUserErrors(flat);
|
|
5128
|
-
}
|
|
5688
|
+
unsubscribeChange();
|
|
5689
|
+
try {
|
|
5690
|
+
channel.close();
|
|
5691
|
+
} catch {
|
|
5129
5692
|
}
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
(
|
|
5152
|
-
|
|
5153
|
-
)
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5693
|
+
},
|
|
5694
|
+
lifecycle: () => lifecycle,
|
|
5695
|
+
senderId,
|
|
5696
|
+
channelName
|
|
5697
|
+
};
|
|
5698
|
+
}
|
|
5699
|
+
const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
|
|
5700
|
+
|
|
5701
|
+
const warned = /* @__PURE__ */ new Set();
|
|
5702
|
+
function warnOnceInsecureContext(feature) {
|
|
5703
|
+
if (!paths.__DEV__) return;
|
|
5704
|
+
if (warned.has(feature)) return;
|
|
5705
|
+
warned.add(feature);
|
|
5706
|
+
const message = featureMessage(feature);
|
|
5707
|
+
console.warn(`[attaform] ${message}`);
|
|
5708
|
+
}
|
|
5709
|
+
function featureMessage(feature) {
|
|
5710
|
+
switch (feature) {
|
|
5711
|
+
case "multiTab":
|
|
5712
|
+
return "Multi-tab sync requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is interceptable by network observers, so the sync module is disabled. Serve over HTTPS in production (or develop on `localhost`) to enable cross-tab synchronisation. Use `multiTab: false` on `useForm` to silence this warning.";
|
|
5713
|
+
case "persist:local":
|
|
5714
|
+
return "Built-in `persist: 'local'` storage requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is MITM-interceptable, so the persistence layer is disabled. Serve over HTTPS to enable localStorage persistence, or pass a custom storage adapter to opt out of the secure-context gate.";
|
|
5715
|
+
case "persist:session":
|
|
5716
|
+
return "Built-in `persist: 'session'` storage requires a secure context (HTTPS or localhost). Plain HTTP on a real hostname is MITM-interceptable, so the persistence layer is disabled. Serve over HTTPS to enable sessionStorage persistence, or pass a custom storage adapter to opt out of the secure-context gate.";
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
function isSecureContext() {
|
|
5720
|
+
return typeof window !== "undefined" && window.isSecureContext === true;
|
|
5721
|
+
}
|
|
5722
|
+
|
|
5723
|
+
function resolveTrichotomy(input) {
|
|
5724
|
+
if (typeof input === "function") {
|
|
5725
|
+
return { kind: "async", factory: input };
|
|
5726
|
+
}
|
|
5727
|
+
return { kind: "sync", value: input };
|
|
5728
|
+
}
|
|
5729
|
+
|
|
5730
|
+
function useAbstractForm(configuration, options) {
|
|
5731
|
+
if (configuration === void 0 || configuration === null || configuration.schema === void 0) {
|
|
5732
|
+
throw new paths.InvalidUseFormConfigError();
|
|
5733
|
+
}
|
|
5734
|
+
const key = resolveFormKey(configuration.key);
|
|
5735
|
+
const instance = vue.getCurrentInstance();
|
|
5736
|
+
if (instance !== null) paths.ensureAttaformInstalled(instance.appContext.app);
|
|
5737
|
+
const registry = options?.registry ?? paths.useRegistry();
|
|
5738
|
+
const resolvedDefaults = resolveTrichotomy(configuration.defaultValues);
|
|
5739
|
+
const materialisedDefaults = resolvedDefaults.kind === "sync" ? resolvedDefaults.value : void 0;
|
|
5740
|
+
const { defaultValues: _droppedDefaults, ...configWithoutDefaults } = configuration;
|
|
5741
|
+
const trichotomyOverride = materialisedDefaults === void 0 ? configWithoutDefaults : { ...configWithoutDefaults, defaultValues: materialisedDefaults };
|
|
5742
|
+
const merged = mergeWithDefaults(registry.defaults, trichotomyOverride);
|
|
5743
|
+
const maxRecursionDepth = normalizeNumericOption({
|
|
5744
|
+
value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
|
|
5745
|
+
source: "useForm.maxRecursionDepth",
|
|
5746
|
+
allowInfinity: true,
|
|
5747
|
+
min: 0,
|
|
5748
|
+
defaultValue: DEFAULT_MAX_RECURSION_DEPTH
|
|
5749
|
+
});
|
|
5750
|
+
const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
|
|
5751
|
+
if (configuration.persist !== void 0 && configuration.key === void 0) {
|
|
5752
|
+
throw new paths.AnonPersistError({
|
|
5753
|
+
cause: "no-key",
|
|
5754
|
+
schemaFields: extractSchemaFields(resolvedSchema),
|
|
5755
|
+
callSite: paths.captureUserCallSite()
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
const existing = registry.forms.get(key);
|
|
5759
|
+
if (paths.__DEV__ && existing !== void 0) {
|
|
5760
|
+
warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
|
|
5761
|
+
warnOnPersistDivergence(key, existing, configuration.persist);
|
|
5762
|
+
}
|
|
5763
|
+
const hadPendingHydration = registry.pendingHydration.has(key);
|
|
5764
|
+
const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
|
|
5765
|
+
if (existing !== void 0) ; else if (resolvedDefaults.kind === "sync") {
|
|
5766
|
+
state.defaultsResolved.value = true;
|
|
5767
|
+
}
|
|
5768
|
+
if (existing === void 0 && resolvedDefaults.kind === "async") {
|
|
5769
|
+
const factory = resolvedDefaults.factory;
|
|
5770
|
+
state.defaultValuesFactory.value = factory;
|
|
5771
|
+
if (hadPendingHydration) {
|
|
5772
|
+
state.hydrating.value = false;
|
|
5773
|
+
state.defaultsResolved.value = true;
|
|
5774
|
+
} else if (registry.ssr) {
|
|
5775
|
+
if (configuration.__ssrAccessed === true) {
|
|
5776
|
+
registry.enqueuePrefetch(key);
|
|
5158
5777
|
}
|
|
5778
|
+
vue.onServerPrefetch(() => {
|
|
5779
|
+
if (!registry.shouldPrefetch(key)) return;
|
|
5780
|
+
return state.activate();
|
|
5781
|
+
});
|
|
5159
5782
|
}
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5783
|
+
}
|
|
5784
|
+
if (vue.getCurrentScope() !== void 0) {
|
|
5785
|
+
const releaseConsumer = registry.trackConsumer(key);
|
|
5786
|
+
vue.onScopeDispose(releaseConsumer);
|
|
5787
|
+
}
|
|
5788
|
+
const persistDisabledByAnonRule = merged.persist !== void 0 && enforceAnonPersistRule(state.formKey, registry.ssr);
|
|
5789
|
+
if (existing === void 0 && !registry.ssr) {
|
|
5790
|
+
if (merged.persist !== void 0 && !persistDisabledByAnonRule) {
|
|
5791
|
+
const resolvedPersist = normalizePersistConfig(merged.persist);
|
|
5792
|
+
const storageKind = resolvedPersist.storage;
|
|
5793
|
+
const isBuiltinStorage = typeof storageKind === "string";
|
|
5794
|
+
const secureContextOk = !isBuiltinStorage || isSecureContext();
|
|
5795
|
+
if (!secureContextOk) {
|
|
5796
|
+
const feature = storageKind === "session" ? "persist:session" : "persist:local";
|
|
5797
|
+
warnOnceInsecureContext(feature);
|
|
5798
|
+
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
5799
|
+
} else {
|
|
5800
|
+
const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
|
|
5801
|
+
void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
|
|
5802
|
+
const persistenceModule = wirePersistence(state, resolvedPersist);
|
|
5803
|
+
state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
|
|
5804
|
+
state.registerDrain(() => persistenceModule.awaitPendingWrites());
|
|
5805
|
+
state.registerCleanup(() => persistenceModule.dispose());
|
|
5806
|
+
}
|
|
5178
5807
|
} else {
|
|
5179
|
-
|
|
5808
|
+
void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
|
|
5180
5809
|
}
|
|
5181
|
-
await adapter.setItem(
|
|
5182
|
-
key,
|
|
5183
|
-
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
5184
|
-
);
|
|
5185
5810
|
}
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
if (
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5811
|
+
if (existing === void 0 && merged.multiTab === true && configuration.key !== void 0 && !registry.ssr) {
|
|
5812
|
+
const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
|
|
5813
|
+
const secureContext = isSecureContext();
|
|
5814
|
+
if (hasBroadcastChannel && secureContext) {
|
|
5815
|
+
let channelName;
|
|
5816
|
+
try {
|
|
5817
|
+
channelName = `attaform:sync:${state.formKey}:${hashStableString(state.schema.fingerprint())}`;
|
|
5818
|
+
} catch {
|
|
5819
|
+
channelName = null;
|
|
5820
|
+
}
|
|
5821
|
+
if (channelName !== null) {
|
|
5822
|
+
const syncModule = createMultiTabSyncModule(state, channelName, {
|
|
5823
|
+
isSensitivePath: state.isSensitivePath,
|
|
5824
|
+
noSyncPaths: state.noSyncPaths,
|
|
5825
|
+
validateForm: (form) => {
|
|
5826
|
+
const result = state.schema.validateAtPath(form, void 0, { sync: true });
|
|
5827
|
+
if (result instanceof Promise) return;
|
|
5828
|
+
if (!result.success) {
|
|
5829
|
+
throw new Error("attaform multi-tab sync: post-apply schema validation failed");
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
});
|
|
5833
|
+
state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
|
|
5834
|
+
state.registerCleanup(() => syncModule.dispose());
|
|
5835
|
+
}
|
|
5836
|
+
} else if (hasBroadcastChannel && !secureContext) {
|
|
5837
|
+
warnOnceInsecureContext("multiTab");
|
|
5203
5838
|
}
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5839
|
+
}
|
|
5840
|
+
if (existing === void 0 && merged.history !== void 0) {
|
|
5841
|
+
const historyModule = createHistoryModule(state, merged.history);
|
|
5842
|
+
state.modules.set(HISTORY_MODULE_KEY, historyModule);
|
|
5843
|
+
state.registerCleanup(() => historyModule.dispose());
|
|
5844
|
+
}
|
|
5845
|
+
if (configuration.key === void 0) {
|
|
5846
|
+
recordAmbientProvide(registry.ssr);
|
|
5847
|
+
vue.provide(paths.kFormContext, state);
|
|
5848
|
+
}
|
|
5849
|
+
const formInstanceId = vue.getCurrentInstance() !== null ? vue.useId() : `atta:form-instance:${formInstanceCounter++}`;
|
|
5850
|
+
if (vue.getCurrentInstance() !== null) {
|
|
5851
|
+
vue.provide(paths.kFormInstanceId, formInstanceId);
|
|
5852
|
+
}
|
|
5853
|
+
const apiOptions = {};
|
|
5854
|
+
if (merged.onInvalidSubmit !== void 0) {
|
|
5855
|
+
apiOptions.onInvalidSubmit = merged.onInvalidSubmit;
|
|
5856
|
+
}
|
|
5857
|
+
const history = state.modules.get(HISTORY_MODULE_KEY);
|
|
5858
|
+
if (history !== void 0) {
|
|
5859
|
+
apiOptions.history = history;
|
|
5860
|
+
}
|
|
5861
|
+
if (merged.validateOn !== void 0) {
|
|
5862
|
+
apiOptions.validateOn = merged.validateOn;
|
|
5863
|
+
}
|
|
5864
|
+
const mergedDebounceMs = merged.debounceMs;
|
|
5865
|
+
if (mergedDebounceMs !== void 0) {
|
|
5866
|
+
apiOptions.debounceMs = mergedDebounceMs;
|
|
5867
|
+
}
|
|
5868
|
+
if (merged.getDisplayState !== void 0) {
|
|
5869
|
+
apiOptions.getDisplayState = merged.getDisplayState;
|
|
5870
|
+
}
|
|
5871
|
+
if (merged.coerce !== void 0) {
|
|
5872
|
+
apiOptions.coerce = merged.coerce;
|
|
5873
|
+
}
|
|
5874
|
+
if (merged.rememberVariants !== void 0) {
|
|
5875
|
+
apiOptions.rememberVariants = merged.rememberVariants;
|
|
5876
|
+
}
|
|
5877
|
+
if (merged.autoAria !== void 0) {
|
|
5878
|
+
apiOptions.autoAria = merged.autoAria;
|
|
5879
|
+
}
|
|
5880
|
+
return buildFormApi(
|
|
5881
|
+
state,
|
|
5882
|
+
formInstanceId,
|
|
5883
|
+
apiOptions
|
|
5884
|
+
);
|
|
5885
|
+
}
|
|
5886
|
+
function mergeWithDefaults(defaults, configuration) {
|
|
5887
|
+
const strict = configuration.strict ?? defaults.strict;
|
|
5888
|
+
const onInvalidSubmit = configuration.onInvalidSubmit ?? defaults.onInvalidSubmit;
|
|
5889
|
+
const history = configuration.history ?? defaults.history;
|
|
5890
|
+
const rememberVariants = configuration.rememberVariants ?? defaults.rememberVariants;
|
|
5891
|
+
const coerce = configuration.coerce ?? defaults.coerce;
|
|
5892
|
+
const validateOn = configuration.validateOn ?? defaults.validateOn;
|
|
5893
|
+
const debounceMs = configuration.debounceMs ?? defaults.debounceMs;
|
|
5894
|
+
const getDisplayState = configuration.getDisplayState ?? defaults.getDisplayState;
|
|
5895
|
+
const maxRecursionDepth = configuration.maxRecursionDepth ?? defaults.maxRecursionDepth;
|
|
5896
|
+
const sensitiveNames = configuration.sensitiveNames ?? defaults.sensitiveNames;
|
|
5897
|
+
const multiTab = configuration.multiTab ?? defaults.multiTab;
|
|
5898
|
+
const autoAria = configuration.autoAria ?? defaults.autoAria;
|
|
5899
|
+
return {
|
|
5900
|
+
...configuration,
|
|
5901
|
+
...strict === void 0 ? {} : { strict },
|
|
5902
|
+
...onInvalidSubmit === void 0 ? {} : { onInvalidSubmit },
|
|
5903
|
+
...history === void 0 ? {} : { history },
|
|
5904
|
+
...rememberVariants === void 0 ? {} : { rememberVariants },
|
|
5905
|
+
...coerce === void 0 ? {} : { coerce },
|
|
5906
|
+
...validateOn === void 0 ? {} : { validateOn },
|
|
5907
|
+
...debounceMs === void 0 ? {} : { debounceMs },
|
|
5908
|
+
...getDisplayState === void 0 ? {} : { getDisplayState },
|
|
5909
|
+
...maxRecursionDepth === void 0 ? {} : { maxRecursionDepth },
|
|
5910
|
+
...sensitiveNames === void 0 ? {} : { sensitiveNames },
|
|
5911
|
+
...multiTab === void 0 ? {} : { multiTab },
|
|
5912
|
+
...autoAria === void 0 ? {} : { autoAria }
|
|
5913
|
+
};
|
|
5914
|
+
}
|
|
5915
|
+
const HISTORY_MODULE_KEY = "history";
|
|
5916
|
+
function buildFreshState(key, schema, configuration, registry) {
|
|
5917
|
+
const pending = registry.pendingHydration.get(key);
|
|
5918
|
+
if (pending !== void 0) registry.pendingHydration.delete(key);
|
|
5919
|
+
const walked = walkUnsetSentinels(
|
|
5920
|
+
configuration.defaultValues,
|
|
5921
|
+
schema
|
|
5922
|
+
);
|
|
5923
|
+
let initialBlankPaths;
|
|
5924
|
+
if (pending === void 0) {
|
|
5925
|
+
initialBlankPaths = walked.paths;
|
|
5926
|
+
}
|
|
5927
|
+
const resolvedSensitiveNames = configuration.sensitiveNames;
|
|
5928
|
+
const resolvedIsSensitivePath = resolvedSensitiveNames === void 0 ? void 0 : paths.createIsSensitivePath(resolvedSensitiveNames);
|
|
5929
|
+
const createOptions = {
|
|
5930
|
+
formKey: key,
|
|
5931
|
+
schema,
|
|
5932
|
+
defaultValues: walked.cleanedValues,
|
|
5933
|
+
...configuration.strict !== void 0 ? { strict: configuration.strict } : {},
|
|
5934
|
+
hydration: pending,
|
|
5935
|
+
...configuration.validateOn !== void 0 ? { validateOn: configuration.validateOn } : {},
|
|
5936
|
+
...configuration.debounceMs !== void 0 ? { debounceMs: configuration.debounceMs } : {},
|
|
5937
|
+
ssr: registry.ssr,
|
|
5938
|
+
// Server-only: bind the SSR prefetch coordination handles. `enqueue`
|
|
5939
|
+
// records intent on every `state.activate()` so a wizard skip-list
|
|
5940
|
+
// override or a future transform mark has a consistent set to diff
|
|
5941
|
+
// against; `shouldFire` lets the activate path bail when the
|
|
5942
|
+
// wizard explicitly skipped this key — even an explicit
|
|
5943
|
+
// `form.activate()` defers to the wizard's render-efficiency
|
|
5944
|
+
// skip-list on the server.
|
|
5945
|
+
...registry.ssr ? {
|
|
5946
|
+
ssrPrefetch: {
|
|
5947
|
+
enqueue: () => {
|
|
5948
|
+
registry.enqueuePrefetch(key);
|
|
5949
|
+
},
|
|
5950
|
+
shouldFire: () => registry.shouldPrefetch(key)
|
|
5951
|
+
}
|
|
5952
|
+
} : {},
|
|
5953
|
+
...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
|
|
5954
|
+
...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
|
|
5955
|
+
...configuration.getDisplayState !== void 0 ? { getDisplayState: configuration.getDisplayState } : {},
|
|
5956
|
+
...initialBlankPaths !== void 0 ? { initialBlankPaths } : {},
|
|
5957
|
+
...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {}
|
|
5958
|
+
};
|
|
5959
|
+
const state = createFormStore(createOptions);
|
|
5960
|
+
registry.forms.set(
|
|
5961
|
+
key,
|
|
5962
|
+
state
|
|
5963
|
+
);
|
|
5964
|
+
return state;
|
|
5965
|
+
}
|
|
5966
|
+
let anonCounter = 0;
|
|
5967
|
+
let formInstanceCounter = 0;
|
|
5968
|
+
const ambientProvideHistory = paths.__DEV__ ? /* @__PURE__ */ new WeakMap() : null;
|
|
5969
|
+
function recordAmbientProvide(ssr) {
|
|
5970
|
+
if (!paths.__DEV__ || ssr || ambientProvideHistory === null) return;
|
|
5971
|
+
const instance = vue.getCurrentInstance();
|
|
5972
|
+
if (instance === null) return;
|
|
5973
|
+
const instanceKey = instance;
|
|
5974
|
+
const entry = {
|
|
5975
|
+
source: paths.captureUserCallSite()
|
|
5976
|
+
};
|
|
5977
|
+
const existing = ambientProvideHistory.get(instanceKey);
|
|
5978
|
+
if (existing === void 0) {
|
|
5979
|
+
ambientProvideHistory.set(instanceKey, [entry]);
|
|
5980
|
+
return;
|
|
5981
|
+
}
|
|
5982
|
+
existing.push(entry);
|
|
5983
|
+
}
|
|
5984
|
+
function resolveFormKey(key) {
|
|
5985
|
+
if (key !== void 0 && key !== null && key !== "") {
|
|
5986
|
+
if (key.startsWith(RESERVED_KEY_PREFIX)) {
|
|
5987
|
+
throw new paths.ReservedFormKeyError(key);
|
|
5216
5988
|
}
|
|
5217
|
-
|
|
5218
|
-
const userErrors = (existing.data.userErrors ?? []).filter(([k]) => k !== pathKey);
|
|
5219
|
-
const schemaMap = new Map(schemaErrors.map(([k, v]) => [k, [...v]]));
|
|
5220
|
-
const userMap = new Map(userErrors.map(([k, v]) => [k, [...v]]));
|
|
5221
|
-
await adapter.setItem(
|
|
5222
|
-
key,
|
|
5223
|
-
buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
|
|
5224
|
-
);
|
|
5989
|
+
return key;
|
|
5225
5990
|
}
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
if (disposed) return Promise.resolve();
|
|
5229
|
-
return writer.flush().catch(() => void 0);
|
|
5991
|
+
if (vue.getCurrentInstance() !== null) {
|
|
5992
|
+
return `${ANONYMOUS_FORM_KEY_PREFIX}${vue.useId()}`;
|
|
5230
5993
|
}
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5994
|
+
return `${ANONYMOUS_FORM_KEY_PREFIX}${anonCounter++}`;
|
|
5995
|
+
}
|
|
5996
|
+
function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
|
|
5997
|
+
let existingFp;
|
|
5998
|
+
let incomingFp;
|
|
5999
|
+
try {
|
|
6000
|
+
existingFp = existing.fingerprint();
|
|
6001
|
+
incomingFp = incoming.fingerprint();
|
|
6002
|
+
} catch (error) {
|
|
6003
|
+
console.error(
|
|
6004
|
+
`[attaform] fingerprint() threw for key "${key}"; skipping mismatch check.`,
|
|
6005
|
+
error
|
|
6006
|
+
);
|
|
6007
|
+
return;
|
|
5239
6008
|
}
|
|
5240
|
-
return
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
};
|
|
6009
|
+
if (existingFp === incomingFp) return;
|
|
6010
|
+
console.warn(
|
|
6011
|
+
`[attaform] useForm() calls with key "${key}" use different schemas; first wins, second is ignored. Use identical schemas or unique keys.
|
|
6012
|
+
existing: ${existingFp}
|
|
6013
|
+
incoming: ${incomingFp}`
|
|
6014
|
+
);
|
|
5247
6015
|
}
|
|
5248
|
-
function
|
|
5249
|
-
if (
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
6016
|
+
function warnOnPersistDivergence(key, existing, incomingPersist) {
|
|
6017
|
+
if (incomingPersist === void 0) return;
|
|
6018
|
+
const wired = existing.modules.get(PERSISTENCE_MODULE_KEY);
|
|
6019
|
+
const incomingNormalized = normalizePersistConfig(incomingPersist);
|
|
6020
|
+
if (wired === void 0) {
|
|
6021
|
+
console.warn(
|
|
6022
|
+
`[attaform] useForm({ key: "${key}" }) passed a persist config but the first useForm({ key }) call didn't wire persistence; the new config is silently dropped. Pass persist on the first call, or remove persist here to make the inheritance explicit.`
|
|
6023
|
+
);
|
|
6024
|
+
return;
|
|
6025
|
+
}
|
|
6026
|
+
if (persistConfigsEquivalent(wired.wiredConfig, incomingNormalized)) return;
|
|
6027
|
+
console.warn(
|
|
6028
|
+
`[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
|
|
6029
|
+
wired: ${describePersist(wired.wiredConfig)}
|
|
6030
|
+
incoming: ${describePersist(incomingNormalized)}`
|
|
6031
|
+
);
|
|
6032
|
+
}
|
|
6033
|
+
function persistConfigsEquivalent(a, b) {
|
|
6034
|
+
if (a.storage !== b.storage) return false;
|
|
6035
|
+
if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
|
|
6036
|
+
if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
|
|
6037
|
+
return true;
|
|
6038
|
+
}
|
|
6039
|
+
function describePersist(config) {
|
|
6040
|
+
const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
|
|
6041
|
+
const parts = [`storage=${storage}`];
|
|
6042
|
+
if (config.key !== void 0) parts.push(`key=${config.key}`);
|
|
6043
|
+
if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
|
|
6044
|
+
return `{ ${parts.join(", ")} }`;
|
|
5253
6045
|
}
|
|
5254
6046
|
const warnedAnonPersistKeys = /* @__PURE__ */ new Set();
|
|
5255
6047
|
function enforceAnonPersistRule(formKey, ssr) {
|
|
@@ -5267,33 +6059,6 @@ function enforceAnonPersistRule(formKey, ssr) {
|
|
|
5267
6059
|
}
|
|
5268
6060
|
return true;
|
|
5269
6061
|
}
|
|
5270
|
-
function collectPersistedLeafPaths(form) {
|
|
5271
|
-
const out = [];
|
|
5272
|
-
walk(form, []);
|
|
5273
|
-
return out;
|
|
5274
|
-
function walk(node, prefix) {
|
|
5275
|
-
if (Array.isArray(node)) {
|
|
5276
|
-
for (let i = 0; i < node.length; i++) {
|
|
5277
|
-
walk(node[i], [...prefix, i]);
|
|
5278
|
-
}
|
|
5279
|
-
return;
|
|
5280
|
-
}
|
|
5281
|
-
if (isPlainRecord(node)) {
|
|
5282
|
-
for (const key of Object.keys(node)) {
|
|
5283
|
-
walk(node[key], [...prefix, key]);
|
|
5284
|
-
}
|
|
5285
|
-
return;
|
|
5286
|
-
}
|
|
5287
|
-
if (prefix.length === 0) return;
|
|
5288
|
-
out.push(paths.canonicalizePath(prefix).key);
|
|
5289
|
-
}
|
|
5290
|
-
}
|
|
5291
|
-
function isDescendantPathKey(candidate, ancestor) {
|
|
5292
|
-
if (candidate.length <= ancestor.length) return false;
|
|
5293
|
-
if (!ancestor.endsWith("]")) return false;
|
|
5294
|
-
const childPrefix = `${ancestor.slice(0, -1)},`;
|
|
5295
|
-
return candidate.startsWith(childPrefix);
|
|
5296
|
-
}
|
|
5297
6062
|
|
|
5298
6063
|
let injectedInstanceCounter = 0;
|
|
5299
6064
|
function injectForm(input) {
|
|
@@ -5373,8 +6138,6 @@ function isLazyMarker(value) {
|
|
|
5373
6138
|
}
|
|
5374
6139
|
|
|
5375
6140
|
const NOOP_WIZARD_HISTORY = {
|
|
5376
|
-
push() {
|
|
5377
|
-
},
|
|
5378
6141
|
replace() {
|
|
5379
6142
|
},
|
|
5380
6143
|
read() {
|
|
@@ -5401,21 +6164,16 @@ function createWizardHistory(param) {
|
|
|
5401
6164
|
for (const subscriber of subscribers) subscriber(value);
|
|
5402
6165
|
}
|
|
5403
6166
|
window.addEventListener("popstate", handlePopstate);
|
|
5404
|
-
function
|
|
6167
|
+
function safeReplaceState(key) {
|
|
5405
6168
|
try {
|
|
5406
|
-
|
|
5407
|
-
fn.call(window.history, {}, "", buildUrl(key));
|
|
6169
|
+
window.history.replaceState({}, "", buildUrl(key));
|
|
5408
6170
|
} catch {
|
|
5409
6171
|
}
|
|
5410
6172
|
}
|
|
5411
6173
|
return {
|
|
5412
|
-
push(key) {
|
|
5413
|
-
if (disposed) return;
|
|
5414
|
-
safeWriteState(key, "push");
|
|
5415
|
-
},
|
|
5416
6174
|
replace(key) {
|
|
5417
6175
|
if (disposed) return;
|
|
5418
|
-
|
|
6176
|
+
safeReplaceState(key);
|
|
5419
6177
|
},
|
|
5420
6178
|
read() {
|
|
5421
6179
|
const url = new URL(window.location.href);
|
|
@@ -5474,68 +6232,16 @@ function buildWizardStatusesProxy(statuses) {
|
|
|
5474
6232
|
}
|
|
5475
6233
|
return result;
|
|
5476
6234
|
});
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
return computedEntry.value;
|
|
5488
|
-
},
|
|
5489
|
-
get(_, key) {
|
|
5490
|
-
if (typeof key === "symbol") {
|
|
5491
|
-
if (key === Symbol.toPrimitive) return proxyToPrimitive;
|
|
5492
|
-
return Reflect.get(target, key);
|
|
5493
|
-
}
|
|
5494
|
-
if (key === "toJSON") return () => snapshot.value;
|
|
5495
|
-
if (key === "toString") return proxyToString;
|
|
5496
|
-
if (key === "valueOf")
|
|
5497
|
-
return function() {
|
|
5498
|
-
return this;
|
|
5499
|
-
};
|
|
5500
|
-
const computedEntry = statuses[key];
|
|
5501
|
-
if (computedEntry === void 0) return void 0;
|
|
5502
|
-
return computedEntry.value;
|
|
5503
|
-
},
|
|
5504
|
-
has(_, key) {
|
|
5505
|
-
if (typeof key === "symbol") return Reflect.has(target, key);
|
|
5506
|
-
return Object.hasOwn(statuses, key);
|
|
5507
|
-
},
|
|
5508
|
-
ownKeys() {
|
|
5509
|
-
return Object.keys(statuses);
|
|
5510
|
-
},
|
|
5511
|
-
getOwnPropertyDescriptor(_, key) {
|
|
5512
|
-
if (typeof key === "symbol") return void 0;
|
|
5513
|
-
const computedEntry = statuses[key];
|
|
5514
|
-
if (computedEntry === void 0) return void 0;
|
|
5515
|
-
return {
|
|
5516
|
-
configurable: true,
|
|
5517
|
-
enumerable: true,
|
|
5518
|
-
writable: false,
|
|
5519
|
-
value: computedEntry.value
|
|
5520
|
-
};
|
|
5521
|
-
},
|
|
5522
|
-
set(_, key) {
|
|
5523
|
-
if (paths.__DEV__) {
|
|
5524
|
-
console.warn(
|
|
5525
|
-
`[attaform] wizard.statuses is read-only \u2014 write to "${String(key)}" was ignored. Statuses derive from each form's meta; mutate the underlying form instead.`
|
|
5526
|
-
);
|
|
5527
|
-
}
|
|
5528
|
-
return true;
|
|
5529
|
-
},
|
|
5530
|
-
deleteProperty(_, key) {
|
|
5531
|
-
if (paths.__DEV__) {
|
|
5532
|
-
console.warn(
|
|
5533
|
-
`[attaform] wizard.statuses is read-only \u2014 delete of "${String(key)}" was ignored.`
|
|
5534
|
-
);
|
|
5535
|
-
}
|
|
5536
|
-
return true;
|
|
5537
|
-
},
|
|
5538
|
-
defineProperty: () => true
|
|
6235
|
+
return buildCallableReadonlySnapshotProxy({
|
|
6236
|
+
surface: "wizard.statuses",
|
|
6237
|
+
snapshot: () => snapshot.value,
|
|
6238
|
+
resolveKey: (key) => statuses[key]?.value,
|
|
6239
|
+
// Single-key callable form. Strings stringify naturally; non-
|
|
6240
|
+
// string args coerce via `String(arg)` and miss the lookup, which
|
|
6241
|
+
// resolves to `undefined` (consistent with property-access).
|
|
6242
|
+
resolveCall: (arg) => statuses[String(arg)]?.value,
|
|
6243
|
+
ownKeys: () => Object.keys(statuses),
|
|
6244
|
+
hasKey: (key) => Object.hasOwn(statuses, key)
|
|
5539
6245
|
});
|
|
5540
6246
|
}
|
|
5541
6247
|
|
|
@@ -5552,6 +6258,12 @@ const NOOP_VALID_STATUS = {
|
|
|
5552
6258
|
submitted: false,
|
|
5553
6259
|
errorCount: 0
|
|
5554
6260
|
};
|
|
6261
|
+
function asStatusSource(form) {
|
|
6262
|
+
return form;
|
|
6263
|
+
}
|
|
6264
|
+
function asSubmissionSource(form) {
|
|
6265
|
+
return form;
|
|
6266
|
+
}
|
|
5555
6267
|
function useWizard(options) {
|
|
5556
6268
|
const rawSteps = Array.isArray(options.steps) ? options.steps : [];
|
|
5557
6269
|
if (rawSteps.length === 0 && paths.__DEV__) {
|
|
@@ -5639,10 +6351,12 @@ function useWizard(options) {
|
|
|
5639
6351
|
return { configurable: true, enumerable: true, writable: false, value: form };
|
|
5640
6352
|
}
|
|
5641
6353
|
});
|
|
5642
|
-
const slotCtx =
|
|
6354
|
+
const slotCtx = {
|
|
5643
6355
|
forms: slotForms,
|
|
5644
|
-
currentKey
|
|
5645
|
-
|
|
6356
|
+
get currentKey() {
|
|
6357
|
+
return activeKey.value === "" ? void 0 : activeKey.value;
|
|
6358
|
+
}
|
|
6359
|
+
};
|
|
5646
6360
|
function resolveSlot(slot, index, ctx) {
|
|
5647
6361
|
if (typeof slot === "string") {
|
|
5648
6362
|
return getOrBuildNoop(slot);
|
|
@@ -5665,12 +6379,6 @@ function useWizard(options) {
|
|
|
5665
6379
|
}
|
|
5666
6380
|
return result;
|
|
5667
6381
|
}
|
|
5668
|
-
const lazyCtx = {
|
|
5669
|
-
forms: slotForms,
|
|
5670
|
-
get currentKey() {
|
|
5671
|
-
return activeKey.value === "" ? void 0 : activeKey.value;
|
|
5672
|
-
}
|
|
5673
|
-
};
|
|
5674
6382
|
for (let i = 0; i < rawSteps.length; i++) {
|
|
5675
6383
|
const slot = rawSteps[i];
|
|
5676
6384
|
if (isLazyMarker(slot)) {
|
|
@@ -5680,18 +6388,17 @@ function useWizard(options) {
|
|
|
5680
6388
|
idx,
|
|
5681
6389
|
vue.computed(() => {
|
|
5682
6390
|
void lazyEpoch.value;
|
|
5683
|
-
return marker.resolve(
|
|
6391
|
+
return marker.resolve(slotCtx);
|
|
5684
6392
|
})
|
|
5685
6393
|
);
|
|
5686
6394
|
}
|
|
5687
6395
|
}
|
|
5688
6396
|
const compiledSteps = vue.computed(() => {
|
|
5689
|
-
const ctx = slotCtx.value;
|
|
5690
6397
|
const out = [];
|
|
5691
6398
|
const seen = /* @__PURE__ */ new Set();
|
|
5692
6399
|
for (let i = 0; i < rawSteps.length; i++) {
|
|
5693
6400
|
const slot = rawSteps[i];
|
|
5694
|
-
const form = resolveSlot(slot, i,
|
|
6401
|
+
const form = resolveSlot(slot, i, slotCtx);
|
|
5695
6402
|
if (form === void 0) continue;
|
|
5696
6403
|
if (seen.has(form.key)) {
|
|
5697
6404
|
if (paths.__DEV__) {
|
|
@@ -5717,9 +6424,12 @@ function useWizard(options) {
|
|
|
5717
6424
|
return -1;
|
|
5718
6425
|
});
|
|
5719
6426
|
const currentStep = vue.computed(() => {
|
|
5720
|
-
const
|
|
5721
|
-
|
|
5722
|
-
|
|
6427
|
+
const list = compiledSteps.value;
|
|
6428
|
+
const idx = activeIndex.value;
|
|
6429
|
+
if (idx >= 0 && idx < list.length) {
|
|
6430
|
+
return list[idx].key;
|
|
6431
|
+
}
|
|
6432
|
+
const first = list[0];
|
|
5723
6433
|
return first === void 0 ? void 0 : first.key;
|
|
5724
6434
|
});
|
|
5725
6435
|
const activeForm = vue.computed(() => {
|
|
@@ -5742,36 +6452,94 @@ function useWizard(options) {
|
|
|
5742
6452
|
for (const step of compiledSteps.value) out[step.key] = step.form;
|
|
5743
6453
|
return out;
|
|
5744
6454
|
});
|
|
5745
|
-
|
|
5746
|
-
const
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
6455
|
+
function isFormReady(key) {
|
|
6456
|
+
const store = registry.forms.get(key);
|
|
6457
|
+
return store?.defaultsResolved.value === true;
|
|
6458
|
+
}
|
|
6459
|
+
function toWizardAggregateError(err, fallbackKey) {
|
|
6460
|
+
const entry = {
|
|
6461
|
+
formKey: err.formKey ?? fallbackKey,
|
|
6462
|
+
path: err.path,
|
|
6463
|
+
message: err.message
|
|
6464
|
+
};
|
|
6465
|
+
if (err.code !== void 0) entry.code = err.code;
|
|
6466
|
+
return entry;
|
|
6467
|
+
}
|
|
6468
|
+
const valuesCache = /* @__PURE__ */ new Map();
|
|
6469
|
+
function valuesFor(form) {
|
|
6470
|
+
const cached = valuesCache.get(form.key);
|
|
6471
|
+
if (cached !== void 0) return cached;
|
|
6472
|
+
const source = asStatusSource(form);
|
|
6473
|
+
const computedValues = vue.computed(() => source.values);
|
|
6474
|
+
valuesCache.set(form.key, computedValues);
|
|
6475
|
+
return computedValues;
|
|
6476
|
+
}
|
|
6477
|
+
const errorsCache = /* @__PURE__ */ new Map();
|
|
6478
|
+
function errorsFor(form) {
|
|
6479
|
+
const cached = errorsCache.get(form.key);
|
|
6480
|
+
if (cached !== void 0) return cached;
|
|
6481
|
+
const source = asStatusSource(form);
|
|
6482
|
+
const computedErrors = vue.computed(() => {
|
|
6483
|
+
if (!isFormReady(form.key)) return [];
|
|
6484
|
+
const errors = source.meta?.errors ?? [];
|
|
6485
|
+
const list = [];
|
|
6486
|
+
for (const err of errors) list.push(toWizardAggregateError(err, form.key));
|
|
6487
|
+
return list;
|
|
6488
|
+
});
|
|
6489
|
+
errorsCache.set(form.key, computedErrors);
|
|
6490
|
+
return computedErrors;
|
|
6491
|
+
}
|
|
6492
|
+
const allValues = new Proxy({}, {
|
|
6493
|
+
get(_, key) {
|
|
6494
|
+
if (typeof key !== "string") return void 0;
|
|
6495
|
+
const form = formsRecord.value[key];
|
|
6496
|
+
if (form === void 0) return void 0;
|
|
6497
|
+
return valuesFor(form).value;
|
|
6498
|
+
},
|
|
6499
|
+
has(_, key) {
|
|
6500
|
+
if (typeof key !== "string") return false;
|
|
6501
|
+
return formsRecord.value[key] !== void 0;
|
|
6502
|
+
},
|
|
6503
|
+
ownKeys() {
|
|
6504
|
+
return Object.keys(formsRecord.value);
|
|
6505
|
+
},
|
|
6506
|
+
getOwnPropertyDescriptor(_, key) {
|
|
6507
|
+
if (typeof key !== "string") return void 0;
|
|
6508
|
+
const form = formsRecord.value[key];
|
|
6509
|
+
if (form === void 0) return void 0;
|
|
6510
|
+
return {
|
|
6511
|
+
configurable: true,
|
|
6512
|
+
enumerable: true,
|
|
6513
|
+
writable: false,
|
|
6514
|
+
value: valuesFor(form).value
|
|
6515
|
+
};
|
|
5750
6516
|
}
|
|
5751
|
-
return out;
|
|
5752
6517
|
});
|
|
5753
|
-
const allErrors =
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
const
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
6518
|
+
const allErrors = new Proxy({}, {
|
|
6519
|
+
get(_, key) {
|
|
6520
|
+
if (typeof key !== "string") return void 0;
|
|
6521
|
+
const form = formsRecord.value[key];
|
|
6522
|
+
if (form === void 0) return void 0;
|
|
6523
|
+
return errorsFor(form).value;
|
|
6524
|
+
},
|
|
6525
|
+
has(_, key) {
|
|
6526
|
+
if (typeof key !== "string") return false;
|
|
6527
|
+
return formsRecord.value[key] !== void 0;
|
|
6528
|
+
},
|
|
6529
|
+
ownKeys() {
|
|
6530
|
+
return Object.keys(formsRecord.value);
|
|
6531
|
+
},
|
|
6532
|
+
getOwnPropertyDescriptor(_, key) {
|
|
6533
|
+
if (typeof key !== "string") return void 0;
|
|
6534
|
+
const form = formsRecord.value[key];
|
|
6535
|
+
if (form === void 0) return void 0;
|
|
6536
|
+
return {
|
|
6537
|
+
configurable: true,
|
|
6538
|
+
enumerable: true,
|
|
6539
|
+
writable: false,
|
|
6540
|
+
value: errorsFor(form).value
|
|
6541
|
+
};
|
|
5773
6542
|
}
|
|
5774
|
-
return out;
|
|
5775
6543
|
});
|
|
5776
6544
|
const seedRef = vue.ref(void 0);
|
|
5777
6545
|
const seedInput = options.defaultStatuses;
|
|
@@ -5794,11 +6562,9 @@ function useWizard(options) {
|
|
|
5794
6562
|
function statusFor(form) {
|
|
5795
6563
|
const cached = statusCache.get(form.key);
|
|
5796
6564
|
if (cached !== void 0) return cached;
|
|
5797
|
-
const source = form;
|
|
6565
|
+
const source = asStatusSource(form);
|
|
5798
6566
|
const computedStatus = vue.computed(() => {
|
|
5799
|
-
|
|
5800
|
-
const resolved = store?.defaultsResolved.value === true;
|
|
5801
|
-
if (resolved) {
|
|
6567
|
+
if (isFormReady(form.key)) {
|
|
5802
6568
|
const meta = source.meta;
|
|
5803
6569
|
if (meta !== void 0 && meta !== null) {
|
|
5804
6570
|
return {
|
|
@@ -5941,7 +6707,7 @@ function useWizard(options) {
|
|
|
5941
6707
|
}
|
|
5942
6708
|
if (!registry.ssr) {
|
|
5943
6709
|
for (const step of compiledSteps.value) {
|
|
5944
|
-
const source = step.form;
|
|
6710
|
+
const source = asSubmissionSource(step.form);
|
|
5945
6711
|
if (typeof source.activate === "function") void source.activate();
|
|
5946
6712
|
}
|
|
5947
6713
|
}
|
|
@@ -5985,7 +6751,7 @@ function useWizard(options) {
|
|
|
5985
6751
|
const submissionAttempts = vue.ref(0);
|
|
5986
6752
|
const done = vue.ref(false);
|
|
5987
6753
|
function activateForm(form) {
|
|
5988
|
-
const source = form;
|
|
6754
|
+
const source = asSubmissionSource(form);
|
|
5989
6755
|
if (typeof source.activate === "function") {
|
|
5990
6756
|
void source.activate();
|
|
5991
6757
|
}
|
|
@@ -6085,7 +6851,7 @@ function useWizard(options) {
|
|
|
6085
6851
|
};
|
|
6086
6852
|
}
|
|
6087
6853
|
async function processOne(form) {
|
|
6088
|
-
const full = form;
|
|
6854
|
+
const full = asSubmissionSource(form);
|
|
6089
6855
|
let activationFailure;
|
|
6090
6856
|
try {
|
|
6091
6857
|
if (typeof full.activate === "function") await full.activate();
|
|
@@ -6117,15 +6883,7 @@ function useWizard(options) {
|
|
|
6117
6883
|
for (const step of compiledSteps.value) {
|
|
6118
6884
|
const processed = results.get(step.key);
|
|
6119
6885
|
if (processed === void 0 || processed.success === true) continue;
|
|
6120
|
-
for (const err of processed.errors)
|
|
6121
|
-
const entry = {
|
|
6122
|
-
formKey: err.formKey,
|
|
6123
|
-
path: err.path,
|
|
6124
|
-
message: err.message
|
|
6125
|
-
};
|
|
6126
|
-
if (err.code !== void 0) entry.code = err.code;
|
|
6127
|
-
out.push(entry);
|
|
6128
|
-
}
|
|
6886
|
+
for (const err of processed.errors) out.push(toWizardAggregateError(err, step.key));
|
|
6129
6887
|
}
|
|
6130
6888
|
return out;
|
|
6131
6889
|
}
|
|
@@ -6179,8 +6937,7 @@ function useWizard(options) {
|
|
|
6179
6937
|
if (processed !== void 0 && processed.success === true) {
|
|
6180
6938
|
valuesMap[step.key] = processed.data;
|
|
6181
6939
|
} else {
|
|
6182
|
-
|
|
6183
|
-
valuesMap[step.key] = source.values;
|
|
6940
|
+
valuesMap[step.key] = asStatusSource(step.form).values;
|
|
6184
6941
|
}
|
|
6185
6942
|
}
|
|
6186
6943
|
const ctx = buildSubmitContext(valuesMap, currentKey, final);
|
|
@@ -6201,8 +6958,11 @@ function useWizard(options) {
|
|
|
6201
6958
|
moveTo(firstFailedKey);
|
|
6202
6959
|
await vue.nextTick();
|
|
6203
6960
|
const failedForm = formsRecord.value[firstFailedKey];
|
|
6204
|
-
if (failedForm !== void 0
|
|
6205
|
-
failedForm
|
|
6961
|
+
if (failedForm !== void 0) {
|
|
6962
|
+
const failedSource = asSubmissionSource(failedForm);
|
|
6963
|
+
if (typeof failedSource.applyInvalidSubmitPolicy === "function") {
|
|
6964
|
+
failedSource.applyInvalidSubmitPolicy();
|
|
6965
|
+
}
|
|
6206
6966
|
}
|
|
6207
6967
|
}
|
|
6208
6968
|
}
|
|
@@ -6217,7 +6977,7 @@ function useWizard(options) {
|
|
|
6217
6977
|
done.value = false;
|
|
6218
6978
|
lazyEpoch.value += 1;
|
|
6219
6979
|
for (const step of compiledSteps.value) {
|
|
6220
|
-
const full = step.form;
|
|
6980
|
+
const full = asSubmissionSource(step.form);
|
|
6221
6981
|
if (typeof full.reset === "function") full.reset();
|
|
6222
6982
|
}
|
|
6223
6983
|
const firstStep = compiledSteps.value[0];
|
|
@@ -6267,12 +7027,8 @@ function useWizard(options) {
|
|
|
6267
7027
|
return count.value;
|
|
6268
7028
|
},
|
|
6269
7029
|
statuses,
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
},
|
|
6273
|
-
get allErrors() {
|
|
6274
|
-
return allErrors.value;
|
|
6275
|
-
},
|
|
7030
|
+
allValues,
|
|
7031
|
+
allErrors,
|
|
6276
7032
|
get progress() {
|
|
6277
7033
|
return progress.value;
|
|
6278
7034
|
},
|
|
@@ -6405,7 +7161,7 @@ function warnIfAmbientWizardProviderHadDuplicates() {
|
|
|
6405
7161
|
|
|
6406
7162
|
exports.AttaformErrorCode = AttaformErrorCode;
|
|
6407
7163
|
exports.defaultCoercionRules = defaultCoercionRules;
|
|
6408
|
-
exports.
|
|
7164
|
+
exports.defaultDisplayState = defaultDisplayState;
|
|
6409
7165
|
exports.defineCoercion = defineCoercion;
|
|
6410
7166
|
exports.getAtPath = getAtPath;
|
|
6411
7167
|
exports.humanize = humanize;
|
|
@@ -6420,4 +7176,4 @@ exports.slimKindOf = slimKindOf;
|
|
|
6420
7176
|
exports.unset = unset;
|
|
6421
7177
|
exports.useAbstractForm = useAbstractForm;
|
|
6422
7178
|
exports.useWizard = useWizard;
|
|
6423
|
-
//# sourceMappingURL=attaform.
|
|
7179
|
+
//# sourceMappingURL=attaform.DL4CQ-oW.cjs.map
|