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.
Files changed (111) hide show
  1. package/README.md +3 -0
  2. package/dist/chunks/devtools.cjs +1 -1
  3. package/dist/chunks/devtools.mjs +1 -1
  4. package/dist/chunks/indexeddb.cjs +1 -1
  5. package/dist/chunks/indexeddb.mjs +1 -1
  6. package/dist/chunks/local-storage.cjs +1 -1
  7. package/dist/chunks/local-storage.mjs +1 -1
  8. package/dist/chunks/session-storage.cjs +1 -1
  9. package/dist/chunks/session-storage.mjs +1 -1
  10. package/dist/index.cjs +4 -7
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +77 -110
  13. package/dist/index.d.mts +77 -110
  14. package/dist/index.d.ts +77 -110
  15. package/dist/index.mjs +5 -5
  16. package/dist/nuxt.d.cts +1 -1
  17. package/dist/nuxt.d.mts +1 -1
  18. package/dist/nuxt.d.ts +1 -1
  19. package/dist/runtime/components/AttaformDevtoolsPanel.vue +2 -2
  20. package/dist/runtime/components/DevtoolsValueTree.d.vue.ts +1 -3
  21. package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +1 -3
  22. package/dist/runtime/plugins/attaform.cjs +2 -2
  23. package/dist/runtime/plugins/attaform.mjs +2 -2
  24. package/dist/shared/{attaform.CDmaxrt2.mjs → attaform.BKozEdTr.mjs} +305 -178
  25. package/dist/shared/attaform.BKozEdTr.mjs.map +1 -0
  26. package/dist/shared/{attaform.Bubm_slq.cjs → attaform.BM6YD9kZ.cjs} +212 -269
  27. package/dist/shared/attaform.BM6YD9kZ.cjs.map +1 -0
  28. package/dist/shared/{attaform.5UhpSVFI.cjs → attaform.BPxsYtTe.cjs} +2 -26
  29. package/dist/shared/attaform.BPxsYtTe.cjs.map +1 -0
  30. package/dist/shared/{attaform.BqK_L4gK.cjs → attaform.BPy-4qRx.cjs} +305 -180
  31. package/dist/shared/attaform.BPy-4qRx.cjs.map +1 -0
  32. package/dist/shared/attaform.BWgAFnsj.mjs +770 -0
  33. package/dist/shared/attaform.BWgAFnsj.mjs.map +1 -0
  34. package/dist/shared/{attaform.CGX1CNpz.d.ts → attaform.Bh3ACtts.d.ts} +152 -111
  35. package/dist/shared/{attaform.CXpzmj38.mjs → attaform.BupwXkj_.mjs} +213 -270
  36. package/dist/shared/attaform.BupwXkj_.mjs.map +1 -0
  37. package/dist/shared/{attaform.Dlk1jMuv.cjs → attaform.CIn4bMsD.cjs} +263 -799
  38. package/dist/shared/attaform.CIn4bMsD.cjs.map +1 -0
  39. package/dist/shared/{attaform.CZ-XtZt_.mjs → attaform.CKFbKFb6.mjs} +2265 -1509
  40. package/dist/shared/attaform.CKFbKFb6.mjs.map +1 -0
  41. package/dist/shared/{attaform.CuN7ZhBy.d.cts → attaform.D5-1XGQU.d.cts} +152 -111
  42. package/dist/shared/{attaform.-1GQTX2T.mjs → attaform.DEBvCjeH.mjs} +257 -793
  43. package/dist/shared/attaform.DEBvCjeH.mjs.map +1 -0
  44. package/dist/shared/{attaform.II89Pcf4.cjs → attaform.DL4CQ-oW.cjs} +2270 -1514
  45. package/dist/shared/attaform.DL4CQ-oW.cjs.map +1 -0
  46. package/dist/shared/{attaform.FnEwjhvX.d.ts → attaform.DSD85fHb.d.cts} +1 -19
  47. package/dist/shared/{attaform.CRmmNAYp.d.cts → attaform.DSD85fHb.d.mts} +1 -19
  48. package/dist/shared/{attaform.D9wuTGu9.d.mts → attaform.DSD85fHb.d.ts} +1 -19
  49. package/dist/shared/{attaform.B7rzpK1U.d.cts → attaform.DkA5J8NW.d.cts} +1 -17
  50. package/dist/shared/{attaform.B7rzpK1U.d.mts → attaform.DkA5J8NW.d.mts} +1 -17
  51. package/dist/shared/{attaform.B7rzpK1U.d.ts → attaform.DkA5J8NW.d.ts} +1 -17
  52. package/dist/shared/{attaform.B957T6NU.d.ts → attaform.Dl5kDY-A.d.ts} +1 -1
  53. package/dist/shared/attaform.Dmb6itxC.cjs +781 -0
  54. package/dist/shared/attaform.Dmb6itxC.cjs.map +1 -0
  55. package/dist/shared/{attaform.M-RanbyV.d.mts → attaform.DoKXru-a.d.mts} +1 -1
  56. package/dist/shared/attaform.DvA-CJJW.mjs +1876 -0
  57. package/dist/shared/attaform.DvA-CJJW.mjs.map +1 -0
  58. package/dist/shared/{attaform.D1gzu2GL.d.mts → attaform.EMzJcQci.d.mts} +152 -111
  59. package/dist/shared/attaform.EZG6fOFb.mjs +35 -0
  60. package/dist/shared/attaform.EZG6fOFb.mjs.map +1 -0
  61. package/dist/shared/{attaform.XDjA7sRz.d.cts → attaform.GbDo_lJi.d.cts} +1 -1
  62. package/dist/shared/{attaform.Ca5_6Ky-.d.mts → attaform.SfhU0OEY.d.cts} +499 -116
  63. package/dist/shared/{attaform.Ca5_6Ky-.d.cts → attaform.SfhU0OEY.d.mts} +499 -116
  64. package/dist/shared/{attaform.Ca5_6Ky-.d.ts → attaform.SfhU0OEY.d.ts} +499 -116
  65. package/dist/shared/attaform.jgzuNZVC.cjs +1882 -0
  66. package/dist/shared/attaform.jgzuNZVC.cjs.map +1 -0
  67. package/dist/transforms.cjs +2 -2
  68. package/dist/transforms.d.cts +22 -13
  69. package/dist/transforms.d.mts +22 -13
  70. package/dist/transforms.d.ts +22 -13
  71. package/dist/transforms.mjs +1 -1
  72. package/dist/vite.cjs +8 -7
  73. package/dist/vite.cjs.map +1 -1
  74. package/dist/vite.mjs +8 -7
  75. package/dist/vite.mjs.map +1 -1
  76. package/dist/zod-v3.cjs +3 -3
  77. package/dist/zod-v3.d.cts +32 -6
  78. package/dist/zod-v3.d.mts +32 -6
  79. package/dist/zod-v3.d.ts +32 -6
  80. package/dist/zod-v3.mjs +3 -3
  81. package/dist/zod-v4.cjs +3 -3
  82. package/dist/zod-v4.d.cts +12 -8
  83. package/dist/zod-v4.d.mts +12 -8
  84. package/dist/zod-v4.d.ts +12 -8
  85. package/dist/zod-v4.mjs +3 -3
  86. package/dist/zod.cjs +8 -8
  87. package/dist/zod.cjs.map +1 -1
  88. package/dist/zod.d.cts +6 -6
  89. package/dist/zod.d.mts +6 -6
  90. package/dist/zod.d.ts +6 -6
  91. package/dist/zod.mjs +6 -6
  92. package/package.json +2 -2
  93. package/dist/shared/attaform.-1GQTX2T.mjs.map +0 -1
  94. package/dist/shared/attaform.5UhpSVFI.cjs.map +0 -1
  95. package/dist/shared/attaform.BqK_L4gK.cjs.map +0 -1
  96. package/dist/shared/attaform.Bubm_slq.cjs.map +0 -1
  97. package/dist/shared/attaform.C8CyvYa_.cjs +0 -36
  98. package/dist/shared/attaform.C8CyvYa_.cjs.map +0 -1
  99. package/dist/shared/attaform.CDmaxrt2.mjs.map +0 -1
  100. package/dist/shared/attaform.CXpzmj38.mjs.map +0 -1
  101. package/dist/shared/attaform.CZ-XtZt_.mjs.map +0 -1
  102. package/dist/shared/attaform.D13GMFgK.mjs +0 -32
  103. package/dist/shared/attaform.D13GMFgK.mjs.map +0 -1
  104. package/dist/shared/attaform.DUHru0OF.cjs +0 -1600
  105. package/dist/shared/attaform.DUHru0OF.cjs.map +0 -1
  106. package/dist/shared/attaform.Df0tU0Ut.mjs +0 -1594
  107. package/dist/shared/attaform.Df0tU0Ut.mjs.map +0 -1
  108. package/dist/shared/attaform.Dl161U6E.mjs +0 -57
  109. package/dist/shared/attaform.Dl161U6E.mjs.map +0 -1
  110. package/dist/shared/attaform.Dlk1jMuv.cjs.map +0 -1
  111. package/dist/shared/attaform.II89Pcf4.cjs.map +0 -1
@@ -45,14 +45,6 @@ class ReservedFormKeyError extends AttaformError {
45
45
  );
46
46
  }
47
47
  }
48
- class SensitivePersistFieldError extends AttaformError {
49
- constructor(path) {
50
- const display = Array.isArray(path) ? path.join(".") : String(path);
51
- super(
52
- `[attaform] Refusing to persist "${display}" \u2014 this path matches a sensitive-name pattern (password / cvv / ssn / token / etc.). Storing sensitive data in client-side storage is a compliance risk (HIPAA / PII / PCI-DSS / SOC2). Fix: persist this server-side, OR pass \`acknowledgeSensitive: true\` to register() (or form.persist()) if the client-side persistence is intentional.`
53
- );
54
- }
55
- }
56
48
  class AnonPersistError extends AttaformError {
57
49
  constructor(opts) {
58
50
  super(formatAnonPersistMessage(opts));
@@ -406,6 +398,197 @@ function warnNoParentRV(instance) {
406
398
  );
407
399
  }
408
400
 
401
+ const MANAGED_ARIA_ATTRS = [
402
+ "aria-invalid",
403
+ "aria-busy",
404
+ "aria-required",
405
+ "aria-describedby"
406
+ ];
407
+ const ariaLockKey = Symbol.for("attaform:aria-locks");
408
+ const ariaScopeKey = Symbol.for("attaform:aria-scope");
409
+ const EMPTY_ARIA_LOCKS = /* @__PURE__ */ new Set();
410
+ function mergeAriaLocks(el, vnode) {
411
+ let locks = el[ariaLockKey];
412
+ if (locks === void 0) {
413
+ locks = /* @__PURE__ */ new Set();
414
+ el[ariaLockKey] = locks;
415
+ }
416
+ const props = vnode.props;
417
+ if (props !== null) {
418
+ for (const attr of MANAGED_ARIA_ATTRS) {
419
+ if (attr in props) locks.add(attr);
420
+ }
421
+ }
422
+ return locks;
423
+ }
424
+ function setAriaAttr(el, attr, value) {
425
+ if (value === null) el.removeAttribute(attr);
426
+ else el.setAttribute(attr, value);
427
+ }
428
+ function resolveAriaValue(attr, rv, ds) {
429
+ switch (attr) {
430
+ case "aria-invalid":
431
+ return ds === "error" ? "true" : null;
432
+ case "aria-busy":
433
+ return ds === "pending" ? "true" : null;
434
+ case "aria-required":
435
+ return rv.isRequired === true ? "true" : null;
436
+ case "aria-describedby":
437
+ return ds === "error" && rv.aria?.errorId !== void 0 ? rv.aria.errorId : null;
438
+ default:
439
+ return null;
440
+ }
441
+ }
442
+ function applyAria(el, rv) {
443
+ if (rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) return;
444
+ const locks = el[ariaLockKey] ?? EMPTY_ARIA_LOCKS;
445
+ const ds = rv.ariaDisplayState.value;
446
+ for (const attr of MANAGED_ARIA_ATTRS) {
447
+ if (!locks.has(attr)) setAriaAttr(el, attr, resolveAriaValue(attr, rv, ds));
448
+ }
449
+ }
450
+ function setupAria(el, rv, vnode) {
451
+ if (rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) return;
452
+ mergeAriaLocks(el, vnode);
453
+ applyAria(el, rv);
454
+ const displayState = rv.ariaDisplayState;
455
+ const scope = vue.effectScope(true);
456
+ scope.run(() => {
457
+ vue.watch(displayState, () => applyAria(el, rv), { flush: "post" });
458
+ });
459
+ el[ariaScopeKey] = () => scope.stop();
460
+ }
461
+ function getSSRAriaProps(rv, vnode) {
462
+ if (rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) return void 0;
463
+ const props = vnode?.props ?? null;
464
+ const ds = rv.ariaDisplayState.value;
465
+ const out = {};
466
+ for (const attr of MANAGED_ARIA_ATTRS) {
467
+ if (props !== null && attr in props) continue;
468
+ const value = resolveAriaValue(attr, rv, ds);
469
+ if (value !== null) out[attr] = value;
470
+ }
471
+ return out;
472
+ }
473
+ function teardownAria(el) {
474
+ const stop = el[ariaScopeKey];
475
+ if (stop === void 0) return;
476
+ stop();
477
+ delete el[ariaScopeKey];
478
+ const locks = el[ariaLockKey] ?? EMPTY_ARIA_LOCKS;
479
+ for (const attr of MANAGED_ARIA_ATTRS) {
480
+ if (!locks.has(attr)) el.removeAttribute(attr);
481
+ }
482
+ delete el[ariaLockKey];
483
+ }
484
+
485
+ const listenersKey = Symbol.for("attaform:directive-listeners");
486
+ function addTrackedListener(el, event, handler, options) {
487
+ el.addEventListener(event, handler, options);
488
+ const carrier = el;
489
+ const bag = carrier[listenersKey] ?? [];
490
+ bag.push({ event, handler, options });
491
+ carrier[listenersKey] = bag;
492
+ }
493
+ function removeTrackedListeners(el) {
494
+ const carrier = el;
495
+ const bag = carrier[listenersKey];
496
+ if (bag === void 0) return;
497
+ for (const { event, handler, options } of bag) {
498
+ el.removeEventListener(event, handler, options);
499
+ }
500
+ delete carrier[listenersKey];
501
+ }
502
+ function noteInteraction(value) {
503
+ if (isRegisterValueLike(value)) value.markInteracted();
504
+ }
505
+ function isRegisterValueLike(val) {
506
+ return typeof val === "object" && val !== null && "markInteracted" in val && typeof val.markInteracted === "function";
507
+ }
508
+
509
+ function isBlankFileValue(value) {
510
+ if (value === null || value === void 0) return true;
511
+ if (Array.isArray(value) && value.length === 0) return true;
512
+ if (typeof FileList !== "undefined" && value instanceof FileList && value.length === 0)
513
+ return true;
514
+ return false;
515
+ }
516
+ function readFilesFromInput(el) {
517
+ const files = el.files;
518
+ if (el.multiple) {
519
+ return files === null ? [] : Array.from(files);
520
+ }
521
+ if (files === null || files.length === 0) return null;
522
+ return files.item(0);
523
+ }
524
+ const warnedPersistedFileForms = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
525
+ function maybeWarnPersistedFile(value) {
526
+ if (!__DEV__ || warnedPersistedFileForms === null) return;
527
+ if (value.persist !== true) return;
528
+ let warnedPaths = warnedPersistedFileForms.get(value.persistOptIns);
529
+ if (warnedPaths === void 0) {
530
+ warnedPaths = /* @__PURE__ */ new Set();
531
+ warnedPersistedFileForms.set(value.persistOptIns, warnedPaths);
532
+ }
533
+ if (warnedPaths.has(value.path)) return;
534
+ warnedPaths.add(value.path);
535
+ vue.warn(
536
+ `[attaform] register('${value.path}', { persist: true }) on <input type="file"> \u2014 files can't ride a refresh (browsers block programmatic writes to <input type="file">), so this path won't be saved. For long-lived flows, upload on selection and persist the resulting URL or ID in a sibling string field.`
537
+ );
538
+ }
539
+ const fileScopeKey = Symbol.for("attaform:file-scope");
540
+ const vRegisterFile = {
541
+ created(el, { value }) {
542
+ if (!isRegisterValue(value)) return;
543
+ const input = el;
544
+ value.registerElement(input);
545
+ maybeWarnPersistedFile(value);
546
+ const currentRaw = value.innerRef.value;
547
+ if (isBlankFileValue(currentRaw)) {
548
+ const blankShape = input.multiple ? [] : null;
549
+ value.setValueWithInternalPath(blankShape, { blank: true });
550
+ }
551
+ addTrackedListener(input, "change", () => {
552
+ noteInteraction(value);
553
+ const next = readFilesFromInput(input);
554
+ const blank = isBlankFileValue(next);
555
+ value.setValueWithInternalPath(next, blank ? { blank: true } : void 0);
556
+ });
557
+ const scope = vue.effectScope(true);
558
+ scope.run(() => {
559
+ vue.watch(
560
+ value.innerRef,
561
+ (next) => {
562
+ if (!isBlankFileValue(next)) return;
563
+ value.setValueWithInternalPath(next, { blank: true });
564
+ if (input.value !== "") input.value = "";
565
+ },
566
+ { flush: "post" }
567
+ );
568
+ });
569
+ input[fileScopeKey] = () => scope.stop();
570
+ },
571
+ beforeUpdate(el, { value }) {
572
+ if (!isRegisterValue(value)) return;
573
+ const input = el;
574
+ const currentRaw = value.innerRef.value;
575
+ if (isBlankFileValue(currentRaw)) {
576
+ value.setValueWithInternalPath(currentRaw, { blank: true });
577
+ if (input.value !== "") input.value = "";
578
+ }
579
+ },
580
+ beforeUnmount(el, { value }) {
581
+ removeTrackedListeners(el);
582
+ const stop = el[fileScopeKey];
583
+ if (stop !== void 0) {
584
+ stop();
585
+ delete el[fileScopeKey];
586
+ }
587
+ if (!isRegisterValue(value)) return;
588
+ value.deregisterElement(el);
589
+ }
590
+ };
591
+
409
592
  const idGenerator = /* @__PURE__ */ (() => {
410
593
  let counter = 0;
411
594
  return () => `el-${++counter}`;
@@ -590,22 +773,81 @@ function createIsSensitivePath(names = DEFAULT_SENSITIVE_NAMES) {
590
773
  return false;
591
774
  };
592
775
  }
593
- const defaultSegmentMatches = createSegmentMatchesSensitive();
594
776
  const defaultIsSensitivePath = createIsSensitivePath();
595
- function segmentMatchesSensitive(segment) {
596
- return defaultSegmentMatches(segment);
597
- }
598
777
  function isSensitivePath(path) {
599
778
  return defaultIsSensitivePath(path);
600
779
  }
601
- function enforceSensitiveCheck(path, acknowledged, isSensitive = defaultIsSensitivePath) {
602
- if (acknowledged) return;
603
- if (!isSensitive(path)) return;
604
- throw new SensitivePersistFieldError(path);
780
+ const warnedSensitivePersist = __DEV__ ? /* @__PURE__ */ new Set() : null;
781
+ function allowSensitivePersist(path, acknowledged, isSensitive = defaultIsSensitivePath) {
782
+ if (acknowledged) return true;
783
+ if (!isSensitive(path)) return true;
784
+ if (warnedSensitivePersist !== null) {
785
+ const display = Array.isArray(path) ? path.join(".") : String(path);
786
+ if (!warnedSensitivePersist.has(display)) {
787
+ warnedSensitivePersist.add(display);
788
+ console.warn(
789
+ `[attaform] Not persisting "${display}" \u2014 it matches a sensitive-name pattern (password / cvv / ssn / token / etc.) and was opted into persistence without \`acknowledgeSensitive: true\`. Storing sensitive data in client-side storage is a compliance risk (PII / PCI-DSS / HIPAA / SOC2). Persist it server-side, or pass \`acknowledgeSensitive: true\` to register() / form.persist() if this is intentional.`
790
+ );
791
+ }
792
+ }
793
+ return false;
605
794
  }
606
795
 
796
+ function syncPersistOptIn(el, value, oldValue, vnodeType) {
797
+ const wasOptedIn = isRegisterValue(oldValue) && oldValue.persist === true;
798
+ const isFileInput = el.tagName === "INPUT" && (vnodeType === "file" || el.type === "file");
799
+ const wantsOptIn = !isFileInput && isRegisterValue(value) && value.persist === true;
800
+ if (!wasOptedIn && !wantsOptIn) return;
801
+ const elementId = getOrAssignElementId(el);
802
+ if (wasOptedIn) {
803
+ const old = oldValue;
804
+ const samePathAndRegistry = wantsOptIn && value.path === old.path && value.persistOptIns === old.persistOptIns;
805
+ if (!samePathAndRegistry) {
806
+ old.persistOptIns.remove(elementId, old.path);
807
+ }
808
+ }
809
+ if (wantsOptIn) {
810
+ const v = value;
811
+ if (allowSensitivePersist(v.path, v.acknowledgeSensitive, v.isSensitivePath)) {
812
+ v.persistOptIns.add(elementId, v.path);
813
+ }
814
+ }
815
+ }
816
+ function syncMultiTabOptOut(value, oldValue) {
817
+ const wasOptedOut = isRegisterValue(oldValue) && oldValue.unmarkNoSync !== void 0;
818
+ const wantsOptOut = isRegisterValue(value) && value.markNoSync !== void 0;
819
+ if (!wasOptedOut && !wantsOptOut) return;
820
+ if (wasOptedOut) {
821
+ const old = oldValue;
822
+ const samePath = wantsOptOut && value.path === old.path;
823
+ if (!samePath) old.unmarkNoSync?.();
824
+ }
825
+ if (wantsOptOut) {
826
+ const v = value;
827
+ const samePathOld = wasOptedOut && oldValue.path === v.path;
828
+ if (!samePathOld) v.markNoSync?.();
829
+ }
830
+ }
831
+ function syncElementRegistration(el, value, oldValue) {
832
+ const wasRegistered = isRegisterValue(oldValue);
833
+ const isRegistered = isRegisterValue(value);
834
+ if (!wasRegistered && !isRegistered) return;
835
+ if (wasRegistered && isRegistered) {
836
+ const old = oldValue;
837
+ const next = value;
838
+ if (old.path === next.path && old.persistOptIns === next.persistOptIns) return;
839
+ }
840
+ if (wasRegistered) {
841
+ oldValue.deregisterElement(el);
842
+ }
843
+ if (isRegistered) {
844
+ value.registerElement(el);
845
+ }
846
+ }
847
+
848
+ const INTERACTIVE_TAG_NAMES = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
849
+
607
850
  const assignKey = Symbol.for("attaform:assign-key");
608
- const listenersKey = Symbol.for("attaform:directive-listeners");
609
851
  function isRegisterValue(val) {
610
852
  if (typeof val !== "object" || val === null) return false;
611
853
  if (!("innerRef" in val)) return false;
@@ -616,22 +858,6 @@ function isRegisterValue(val) {
616
858
  if (typeof val.setValueWithInternalPath !== "function") return false;
617
859
  return true;
618
860
  }
619
- function addEventListener(el, event, handler, options) {
620
- el.addEventListener(event, handler, options);
621
- const carrier = el;
622
- const bag = carrier[listenersKey] ?? [];
623
- bag.push({ event, handler, options });
624
- carrier[listenersKey] = bag;
625
- }
626
- function removeTrackedListeners(el) {
627
- const carrier = el;
628
- const bag = carrier[listenersKey];
629
- if (bag === void 0) return;
630
- for (const { event, handler, options } of bag) {
631
- el.removeEventListener(event, handler, options);
632
- }
633
- delete carrier[listenersKey];
634
- }
635
861
  function writeLastTypedForm(rv, next) {
636
862
  rv.lastTypedForm.value = next;
637
863
  }
@@ -644,7 +870,7 @@ function isDefaultAssigner(fn) {
644
870
  return typeof fn === "function" && fn[DEFAULT_ASSIGNER_TAG] === true;
645
871
  }
646
872
  function shouldBailListener(el) {
647
- if (SUPPORTED_TAGS.has(el.tagName)) return false;
873
+ if (INTERACTIVE_TAG_NAMES.has(el.tagName)) return false;
648
874
  return isDefaultAssigner(el[assignKey]);
649
875
  }
650
876
  function runTransforms(initial, registerValue) {
@@ -734,56 +960,6 @@ const getModelAssigner = (el, vnode, registerValue) => {
734
960
  defaultAssigner[DEFAULT_ASSIGNER_TAG] = true;
735
961
  return defaultAssigner;
736
962
  };
737
- function syncPersistOptIn(el, value, oldValue, vnodeType) {
738
- const wasOptedIn = isRegisterValue(oldValue) && oldValue.persist === true;
739
- const isFileInput = el.tagName === "INPUT" && (vnodeType === "file" || el.type === "file");
740
- const wantsOptIn = !isFileInput && isRegisterValue(value) && value.persist === true;
741
- if (!wasOptedIn && !wantsOptIn) return;
742
- const elementId = getOrAssignElementId(el);
743
- if (wasOptedIn) {
744
- const old = oldValue;
745
- const samePathAndRegistry = wantsOptIn && value.path === old.path && value.persistOptIns === old.persistOptIns;
746
- if (!samePathAndRegistry) {
747
- old.persistOptIns.remove(elementId, old.path);
748
- }
749
- }
750
- if (wantsOptIn) {
751
- const v = value;
752
- enforceSensitiveCheck(v.path, v.acknowledgeSensitive, v.isSensitivePath);
753
- v.persistOptIns.add(elementId, v.path);
754
- }
755
- }
756
- function syncMultiTabOptOut(value, oldValue) {
757
- const wasOptedOut = isRegisterValue(oldValue) && oldValue.unmarkNoSync !== void 0;
758
- const wantsOptOut = isRegisterValue(value) && value.markNoSync !== void 0;
759
- if (!wasOptedOut && !wantsOptOut) return;
760
- if (wasOptedOut) {
761
- const old = oldValue;
762
- const samePath = wantsOptOut && value.path === old.path;
763
- if (!samePath) old.unmarkNoSync?.();
764
- }
765
- if (wantsOptOut) {
766
- const v = value;
767
- const samePathOld = wasOptedOut && oldValue.path === v.path;
768
- if (!samePathOld) v.markNoSync?.();
769
- }
770
- }
771
- function syncElementRegistration(el, value, oldValue) {
772
- const wasRegistered = isRegisterValue(oldValue);
773
- const isRegistered = isRegisterValue(value);
774
- if (!wasRegistered && !isRegistered) return;
775
- if (wasRegistered && isRegistered) {
776
- const old = oldValue;
777
- const next = value;
778
- if (old.path === next.path && old.persistOptIns === next.persistOptIns) return;
779
- }
780
- if (wasRegistered) {
781
- oldValue.deregisterElement(el);
782
- }
783
- if (isRegistered) {
784
- value.registerElement(el);
785
- }
786
- }
787
963
  function onCompositionStart(e) {
788
964
  const target = e.target;
789
965
  if (!target) return;
@@ -825,10 +1001,11 @@ const vRegisterText = {
825
1001
  value.registerElement(el);
826
1002
  setAssignFunction(el, vnode, value);
827
1003
  }
828
- addEventListener(el, lazy === true ? "change" : "input", (e) => {
1004
+ addTrackedListener(el, lazy === true ? "change" : "input", (e) => {
829
1005
  if (shouldBailListener(el)) return;
830
1006
  const target = e.target;
831
1007
  if (target === null || target.composing) return;
1008
+ noteInteraction(value);
832
1009
  let domValue = el.value;
833
1010
  if (trim === true && lazy === true) {
834
1011
  domValue = domValue.trim();
@@ -880,7 +1057,7 @@ const vRegisterText = {
880
1057
  }
881
1058
  });
882
1059
  if (trim === true || castToNumber) {
883
- addEventListener(el, "change", () => {
1060
+ addTrackedListener(el, "change", () => {
884
1061
  if (shouldBailListener(el)) return;
885
1062
  let normalized = el.value;
886
1063
  if (trim === true) normalized = normalized.trim();
@@ -906,12 +1083,12 @@ const vRegisterText = {
906
1083
  });
907
1084
  }
908
1085
  if (lazy !== true) {
909
- addEventListener(el, "compositionstart", onCompositionStart);
910
- addEventListener(el, "compositionend", onCompositionEnd);
911
- addEventListener(el, "change", onCompositionEnd);
1086
+ addTrackedListener(el, "compositionstart", onCompositionStart);
1087
+ addTrackedListener(el, "compositionend", onCompositionEnd);
1088
+ addTrackedListener(el, "change", onCompositionEnd);
912
1089
  }
913
1090
  if (number === true && vnode.props?.["type"] !== "number") {
914
- addEventListener(el, "beforeinput", (e) => {
1091
+ addTrackedListener(el, "beforeinput", (e) => {
915
1092
  const ev = e;
916
1093
  if (ev.inputType !== "insertText" && ev.inputType !== "insertFromPaste" && ev.inputType !== "insertFromDrop") {
917
1094
  return;
@@ -958,8 +1135,9 @@ const vRegisterCheckbox = {
958
1135
  if (!isRegisterValue(value)) return;
959
1136
  value.registerElement(el);
960
1137
  setAssignFunction(el, vnode, value);
961
- addEventListener(el, "change", () => {
1138
+ addTrackedListener(el, "change", () => {
962
1139
  if (shouldBailListener(el)) return;
1140
+ noteInteraction(value);
963
1141
  const modelValue = value.innerRef.value ?? [];
964
1142
  const explicitValueRequired = true;
965
1143
  const rawElementValue = getValue(el, explicitValueRequired);
@@ -1055,8 +1233,9 @@ const vRegisterRadio = {
1055
1233
  if (!isRegisterValue(value)) return;
1056
1234
  value.registerElement(el);
1057
1235
  setAssignFunction(el, vnode, value);
1058
- addEventListener(el, "change", () => {
1236
+ addTrackedListener(el, "change", () => {
1059
1237
  if (shouldBailListener(el)) return;
1238
+ noteInteraction(value);
1060
1239
  el[assignKey]?.(getValue(el));
1061
1240
  if (isRegisterValue(value) && isDefaultAssigner(el[assignKey])) {
1062
1241
  const currentModel = value.innerRef.value;
@@ -1103,8 +1282,9 @@ const vRegisterSelect = {
1103
1282
  if (!isRegisterValue(value)) return;
1104
1283
  value.registerElement(el);
1105
1284
  const isSetModel = isSet(value.innerRef.value);
1106
- addEventListener(el, "change", () => {
1285
+ addTrackedListener(el, "change", () => {
1107
1286
  if (shouldBailListener(el)) return;
1287
+ noteInteraction(value);
1108
1288
  const selectedVal = Array.prototype.filter.call(el.options, (o) => o.selected).map((o) => number === true ? looseToNumber(getValue(o)) : getValue(o));
1109
1289
  const wrote = el[assignKey]?.(
1110
1290
  el.multiple ? isSetModel ? new Set(selectedVal) : selectedVal : selectedVal[0]
@@ -1217,14 +1397,14 @@ function getCheckboxValue(el, checked) {
1217
1397
  const key = checked ? "_trueValue" : "_falseValue";
1218
1398
  return key in el ? el[key] : checked;
1219
1399
  }
1220
- const SUPPORTED_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
1221
1400
  const warnedUnsupportedElements = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
1222
1401
  const vRegisterDynamic = {
1223
1402
  created(el, binding, vnode) {
1224
1403
  syncPersistOptIn(el, binding.value, void 0, vnode.props?.["type"]);
1225
1404
  syncMultiTabOptOut(binding.value, void 0);
1226
1405
  callModelHook(el, binding, vnode, null, "created");
1227
- if (__DEV__ && warnedUnsupportedElements !== null && !SUPPORTED_TAGS.has(el.tagName) && !warnedUnsupportedElements.has(el)) {
1406
+ if (isRegisterValue(binding.value)) setupAria(el, binding.value, vnode);
1407
+ if (__DEV__ && warnedUnsupportedElements !== null && !INTERACTIVE_TAG_NAMES.has(el.tagName) && !warnedUnsupportedElements.has(el)) {
1228
1408
  void vue.nextTick(() => {
1229
1409
  if (warnedUnsupportedElements.has(el)) return;
1230
1410
  const hasMarker = el[REGISTER_OWNER_MARKER] === true;
@@ -1247,12 +1427,28 @@ const vRegisterDynamic = {
1247
1427
  syncMultiTabOptOut(binding.value, binding.oldValue);
1248
1428
  syncElementRegistration(el, binding.value, binding.oldValue);
1249
1429
  callModelHook(el, binding, vnode, prevVNode, "beforeUpdate");
1430
+ const ariaEl = el;
1431
+ const value = binding.value;
1432
+ if (!isRegisterValue(value) || value.ariaEnabled !== true || value.ariaDisplayState === void 0) {
1433
+ teardownAria(ariaEl);
1434
+ } else {
1435
+ const old = binding.oldValue;
1436
+ const pathChanged = !isRegisterValue(old) || old.path !== value.path;
1437
+ if (pathChanged) {
1438
+ teardownAria(ariaEl);
1439
+ setupAria(ariaEl, value, vnode);
1440
+ } else {
1441
+ mergeAriaLocks(ariaEl, vnode);
1442
+ applyAria(ariaEl, value);
1443
+ }
1444
+ }
1250
1445
  },
1251
1446
  updated(el, binding, vnode, prevVNode) {
1252
1447
  callModelHook(el, binding, vnode, prevVNode, "updated");
1253
1448
  },
1254
1449
  beforeUnmount(el, { value }) {
1255
1450
  removeTrackedListeners(el);
1451
+ teardownAria(el);
1256
1452
  if (isRegisterValue(value)) {
1257
1453
  value.persistOptIns.removeAllFor(getOrAssignElementId(el));
1258
1454
  value.unmarkNoSync?.();
@@ -1262,87 +1458,18 @@ const vRegisterDynamic = {
1262
1458
  delete el.composing;
1263
1459
  delete el._assigning;
1264
1460
  delete el[assignKey];
1265
- }
1266
- };
1267
- function isBlankFileValue(value) {
1268
- if (value === null || value === void 0) return true;
1269
- if (Array.isArray(value) && value.length === 0) return true;
1270
- if (typeof FileList !== "undefined" && value instanceof FileList && value.length === 0)
1271
- return true;
1272
- return false;
1273
- }
1274
- function readFilesFromInput(el) {
1275
- const files = el.files;
1276
- if (el.multiple) {
1277
- return files === null ? [] : Array.from(files);
1278
- }
1279
- if (files === null || files.length === 0) return null;
1280
- return files.item(0);
1281
- }
1282
- const warnedPersistedFileForms = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
1283
- function maybeWarnPersistedFile(value) {
1284
- if (!__DEV__ || warnedPersistedFileForms === null) return;
1285
- if (value.persist !== true) return;
1286
- let warnedPaths = warnedPersistedFileForms.get(value.persistOptIns);
1287
- if (warnedPaths === void 0) {
1288
- warnedPaths = /* @__PURE__ */ new Set();
1289
- warnedPersistedFileForms.set(value.persistOptIns, warnedPaths);
1290
- }
1291
- if (warnedPaths.has(value.path)) return;
1292
- warnedPaths.add(value.path);
1293
- vue.warn(
1294
- `[attaform] register('${value.path}', { persist: true }) on <input type="file"> \u2014 files can't ride a refresh (browsers block programmatic writes to <input type="file">), so this path won't be saved. For long-lived flows, upload on selection and persist the resulting URL or ID in a sibling string field.`
1295
- );
1296
- }
1297
- const fileScopeKey = Symbol.for("attaform:file-scope");
1298
- const vRegisterFile = {
1299
- created(el, { value }) {
1300
- if (!isRegisterValue(value)) return;
1301
- const input = el;
1302
- value.registerElement(input);
1303
- maybeWarnPersistedFile(value);
1304
- const currentRaw = value.innerRef.value;
1305
- if (isBlankFileValue(currentRaw)) {
1306
- const blankShape = input.multiple ? [] : null;
1307
- value.setValueWithInternalPath(blankShape, { blank: true });
1308
- }
1309
- addEventListener(input, "change", () => {
1310
- const next = readFilesFromInput(input);
1311
- const blank = isBlankFileValue(next);
1312
- value.setValueWithInternalPath(next, blank ? { blank: true } : void 0);
1313
- });
1314
- const scope = vue.effectScope(true);
1315
- scope.run(() => {
1316
- vue.watch(
1317
- value.innerRef,
1318
- (next) => {
1319
- if (!isBlankFileValue(next)) return;
1320
- value.setValueWithInternalPath(next, { blank: true });
1321
- if (input.value !== "") input.value = "";
1322
- },
1323
- { flush: "post" }
1324
- );
1325
- });
1326
- input[fileScopeKey] = () => scope.stop();
1327
- },
1328
- beforeUpdate(el, { value }) {
1329
- if (!isRegisterValue(value)) return;
1330
- const input = el;
1331
- const currentRaw = value.innerRef.value;
1332
- if (isBlankFileValue(currentRaw)) {
1333
- value.setValueWithInternalPath(currentRaw, { blank: true });
1334
- if (input.value !== "") input.value = "";
1335
- }
1336
1461
  },
1337
- beforeUnmount(el, { value }) {
1338
- removeTrackedListeners(el);
1339
- const stop = el[fileScopeKey];
1340
- if (stop !== void 0) {
1341
- stop();
1342
- delete el[fileScopeKey];
1343
- }
1344
- if (!isRegisterValue(value)) return;
1345
- value.deregisterElement(el);
1462
+ // The lifecycle hooks above don't run on the server (Vue skips
1463
+ // directive lifecycle during SSR), so emit the same aria attributes
1464
+ // here from the SSR-time gated display state. Honors authored attrs
1465
+ // (vnode-level lockout) and the ariaEnabled gate, touches no DOM, and
1466
+ // shares `resolveAriaValue` with the client path. Ids are SSR-stable
1467
+ // (formInstanceId derives from Vue's useId), so a server-rendered
1468
+ // describedby matches the client after hydration.
1469
+ getSSRProps(binding, vnode) {
1470
+ const rv = binding.value;
1471
+ if (!isRegisterValue(rv)) return void 0;
1472
+ return getSSRAriaProps(rv, vnode ?? null);
1346
1473
  }
1347
1474
  };
1348
1475
  function resolveDynamicModel(tagName, type) {
@@ -1511,6 +1638,7 @@ exports.AttaformError = AttaformError;
1511
1638
  exports.DEFAULT_SENSITIVE_NAMES = DEFAULT_SENSITIVE_NAMES;
1512
1639
  exports.FORM_ERRORS_PATH = FORM_ERRORS_PATH;
1513
1640
  exports.FORM_ERRORS_PATH_KEY = FORM_ERRORS_PATH_KEY;
1641
+ exports.INTERACTIVE_TAG_NAMES = INTERACTIVE_TAG_NAMES;
1514
1642
  exports.InvalidPathError = InvalidPathError;
1515
1643
  exports.InvalidUseFormConfigError = InvalidUseFormConfigError;
1516
1644
  exports.OutsideSetupError = OutsideSetupError;
@@ -1518,9 +1646,9 @@ exports.ROOT_PATH = ROOT_PATH;
1518
1646
  exports.ROOT_PATH_KEY = ROOT_PATH_KEY;
1519
1647
  exports.RegistryNotInstalledError = RegistryNotInstalledError;
1520
1648
  exports.ReservedFormKeyError = ReservedFormKeyError;
1521
- exports.SensitivePersistFieldError = SensitivePersistFieldError;
1522
1649
  exports.SubmitErrorHandlerError = SubmitErrorHandlerError;
1523
1650
  exports.__DEV__ = __DEV__;
1651
+ exports.allowSensitivePersist = allowSensitivePersist;
1524
1652
  exports.assignKey = assignKey;
1525
1653
  exports.canonicalizePath = canonicalizePath;
1526
1654
  exports.captureUserCallSite = captureUserCallSite;
@@ -1529,8 +1657,6 @@ exports.createAttaform = createAttaform;
1529
1657
  exports.createIsSensitivePath = createIsSensitivePath;
1530
1658
  exports.createPersistOptInRegistry = createPersistOptInRegistry;
1531
1659
  exports.createRegistry = createRegistry;
1532
- exports.createSegmentMatchesSensitive = createSegmentMatchesSensitive;
1533
- exports.enforceSensitiveCheck = enforceSensitiveCheck;
1534
1660
  exports.ensureAttaformInstalled = ensureAttaformInstalled;
1535
1661
  exports.getRegistryFromApp = getRegistryFromApp;
1536
1662
  exports.isPathPrefix = isPathPrefix;
@@ -1543,9 +1669,8 @@ exports.kFormContext = kFormContext;
1543
1669
  exports.kFormInstanceId = kFormInstanceId;
1544
1670
  exports.parseDottedPath = parseDottedPath;
1545
1671
  exports.pathKeyToDotted = pathKeyToDotted;
1546
- exports.segmentMatchesSensitive = segmentMatchesSensitive;
1547
1672
  exports.segmentsForPathKey = segmentsForPathKey;
1548
1673
  exports.useRegister = useRegister;
1549
1674
  exports.useRegistry = useRegistry;
1550
1675
  exports.vRegister = vRegister;
1551
- //# sourceMappingURL=attaform.BqK_L4gK.cjs.map
1676
+ //# sourceMappingURL=attaform.BPy-4qRx.cjs.map