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
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const app = require('nuxt/app');
4
- const devtoolsShared = require('../../shared/attaform.5UhpSVFI.cjs');
5
- const paths = require('../../shared/attaform.BqK_L4gK.cjs');
4
+ const devtoolsShared = require('../../shared/attaform.BPxsYtTe.cjs');
5
+ const paths = require('../../shared/attaform.BPy-4qRx.cjs');
6
6
 
7
7
  var attaform_default = app.defineNuxtPlugin({
8
8
  // `enforce: 'pre'` makes the "we run before any component's setup" claim
@@ -1,6 +1,6 @@
1
1
  import { defineNuxtPlugin, useRuntimeConfig, useRoute } from 'nuxt/app';
2
- import { a as renderAttaformState, h as hydrateAttaformState, D as DEVTOOLS_WINDOW_KEY } from '../../shared/attaform.Dl161U6E.mjs';
3
- import { l as createAttaform, y as kAttaformWizardActiveStepResolver, s as getRegistryFromApp } from '../../shared/attaform.CDmaxrt2.mjs';
2
+ import { r as renderAttaformState, h as hydrateAttaformState, D as DEVTOOLS_WINDOW_KEY } from '../../shared/attaform.EZG6fOFb.mjs';
3
+ import { m as createAttaform, x as kAttaformWizardActiveStepResolver, r as getRegistryFromApp } from '../../shared/attaform.BKozEdTr.mjs';
4
4
 
5
5
  var attaform_default = defineNuxtPlugin({
6
6
  // `enforce: 'pre'` makes the "we run before any component's setup" claim
@@ -1,4 +1,4 @@
1
- import { shallowReactive, getCurrentInstance, inject, shallowRef, onBeforeMount, onBeforeUpdate, onMounted, nextTick, warn, isRef, effectScope, watch } from 'vue';
1
+ import { shallowReactive, getCurrentInstance, inject, shallowRef, onBeforeMount, onBeforeUpdate, onMounted, effectScope, watch, warn, nextTick, isRef } from 'vue';
2
2
 
3
3
  const __DEV__ = typeof process !== "undefined" && process.env["NODE_ENV"] !== "production";
4
4
 
@@ -43,14 +43,6 @@ class ReservedFormKeyError extends AttaformError {
43
43
  );
44
44
  }
45
45
  }
46
- class SensitivePersistFieldError extends AttaformError {
47
- constructor(path) {
48
- const display = Array.isArray(path) ? path.join(".") : String(path);
49
- super(
50
- `[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.`
51
- );
52
- }
53
- }
54
46
  class AnonPersistError extends AttaformError {
55
47
  constructor(opts) {
56
48
  super(formatAnonPersistMessage(opts));
@@ -404,6 +396,197 @@ function warnNoParentRV(instance) {
404
396
  );
405
397
  }
406
398
 
399
+ const MANAGED_ARIA_ATTRS = [
400
+ "aria-invalid",
401
+ "aria-busy",
402
+ "aria-required",
403
+ "aria-describedby"
404
+ ];
405
+ const ariaLockKey = Symbol.for("attaform:aria-locks");
406
+ const ariaScopeKey = Symbol.for("attaform:aria-scope");
407
+ const EMPTY_ARIA_LOCKS = /* @__PURE__ */ new Set();
408
+ function mergeAriaLocks(el, vnode) {
409
+ let locks = el[ariaLockKey];
410
+ if (locks === void 0) {
411
+ locks = /* @__PURE__ */ new Set();
412
+ el[ariaLockKey] = locks;
413
+ }
414
+ const props = vnode.props;
415
+ if (props !== null) {
416
+ for (const attr of MANAGED_ARIA_ATTRS) {
417
+ if (attr in props) locks.add(attr);
418
+ }
419
+ }
420
+ return locks;
421
+ }
422
+ function setAriaAttr(el, attr, value) {
423
+ if (value === null) el.removeAttribute(attr);
424
+ else el.setAttribute(attr, value);
425
+ }
426
+ function resolveAriaValue(attr, rv, ds) {
427
+ switch (attr) {
428
+ case "aria-invalid":
429
+ return ds === "error" ? "true" : null;
430
+ case "aria-busy":
431
+ return ds === "pending" ? "true" : null;
432
+ case "aria-required":
433
+ return rv.isRequired === true ? "true" : null;
434
+ case "aria-describedby":
435
+ return ds === "error" && rv.aria?.errorId !== void 0 ? rv.aria.errorId : null;
436
+ default:
437
+ return null;
438
+ }
439
+ }
440
+ function applyAria(el, rv) {
441
+ if (rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) return;
442
+ const locks = el[ariaLockKey] ?? EMPTY_ARIA_LOCKS;
443
+ const ds = rv.ariaDisplayState.value;
444
+ for (const attr of MANAGED_ARIA_ATTRS) {
445
+ if (!locks.has(attr)) setAriaAttr(el, attr, resolveAriaValue(attr, rv, ds));
446
+ }
447
+ }
448
+ function setupAria(el, rv, vnode) {
449
+ if (rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) return;
450
+ mergeAriaLocks(el, vnode);
451
+ applyAria(el, rv);
452
+ const displayState = rv.ariaDisplayState;
453
+ const scope = effectScope(true);
454
+ scope.run(() => {
455
+ watch(displayState, () => applyAria(el, rv), { flush: "post" });
456
+ });
457
+ el[ariaScopeKey] = () => scope.stop();
458
+ }
459
+ function getSSRAriaProps(rv, vnode) {
460
+ if (rv.ariaEnabled !== true || rv.ariaDisplayState === void 0) return void 0;
461
+ const props = vnode?.props ?? null;
462
+ const ds = rv.ariaDisplayState.value;
463
+ const out = {};
464
+ for (const attr of MANAGED_ARIA_ATTRS) {
465
+ if (props !== null && attr in props) continue;
466
+ const value = resolveAriaValue(attr, rv, ds);
467
+ if (value !== null) out[attr] = value;
468
+ }
469
+ return out;
470
+ }
471
+ function teardownAria(el) {
472
+ const stop = el[ariaScopeKey];
473
+ if (stop === void 0) return;
474
+ stop();
475
+ delete el[ariaScopeKey];
476
+ const locks = el[ariaLockKey] ?? EMPTY_ARIA_LOCKS;
477
+ for (const attr of MANAGED_ARIA_ATTRS) {
478
+ if (!locks.has(attr)) el.removeAttribute(attr);
479
+ }
480
+ delete el[ariaLockKey];
481
+ }
482
+
483
+ const listenersKey = Symbol.for("attaform:directive-listeners");
484
+ function addTrackedListener(el, event, handler, options) {
485
+ el.addEventListener(event, handler, options);
486
+ const carrier = el;
487
+ const bag = carrier[listenersKey] ?? [];
488
+ bag.push({ event, handler, options });
489
+ carrier[listenersKey] = bag;
490
+ }
491
+ function removeTrackedListeners(el) {
492
+ const carrier = el;
493
+ const bag = carrier[listenersKey];
494
+ if (bag === void 0) return;
495
+ for (const { event, handler, options } of bag) {
496
+ el.removeEventListener(event, handler, options);
497
+ }
498
+ delete carrier[listenersKey];
499
+ }
500
+ function noteInteraction(value) {
501
+ if (isRegisterValueLike(value)) value.markInteracted();
502
+ }
503
+ function isRegisterValueLike(val) {
504
+ return typeof val === "object" && val !== null && "markInteracted" in val && typeof val.markInteracted === "function";
505
+ }
506
+
507
+ function isBlankFileValue(value) {
508
+ if (value === null || value === void 0) return true;
509
+ if (Array.isArray(value) && value.length === 0) return true;
510
+ if (typeof FileList !== "undefined" && value instanceof FileList && value.length === 0)
511
+ return true;
512
+ return false;
513
+ }
514
+ function readFilesFromInput(el) {
515
+ const files = el.files;
516
+ if (el.multiple) {
517
+ return files === null ? [] : Array.from(files);
518
+ }
519
+ if (files === null || files.length === 0) return null;
520
+ return files.item(0);
521
+ }
522
+ const warnedPersistedFileForms = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
523
+ function maybeWarnPersistedFile(value) {
524
+ if (!__DEV__ || warnedPersistedFileForms === null) return;
525
+ if (value.persist !== true) return;
526
+ let warnedPaths = warnedPersistedFileForms.get(value.persistOptIns);
527
+ if (warnedPaths === void 0) {
528
+ warnedPaths = /* @__PURE__ */ new Set();
529
+ warnedPersistedFileForms.set(value.persistOptIns, warnedPaths);
530
+ }
531
+ if (warnedPaths.has(value.path)) return;
532
+ warnedPaths.add(value.path);
533
+ warn(
534
+ `[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.`
535
+ );
536
+ }
537
+ const fileScopeKey = Symbol.for("attaform:file-scope");
538
+ const vRegisterFile = {
539
+ created(el, { value }) {
540
+ if (!isRegisterValue(value)) return;
541
+ const input = el;
542
+ value.registerElement(input);
543
+ maybeWarnPersistedFile(value);
544
+ const currentRaw = value.innerRef.value;
545
+ if (isBlankFileValue(currentRaw)) {
546
+ const blankShape = input.multiple ? [] : null;
547
+ value.setValueWithInternalPath(blankShape, { blank: true });
548
+ }
549
+ addTrackedListener(input, "change", () => {
550
+ noteInteraction(value);
551
+ const next = readFilesFromInput(input);
552
+ const blank = isBlankFileValue(next);
553
+ value.setValueWithInternalPath(next, blank ? { blank: true } : void 0);
554
+ });
555
+ const scope = effectScope(true);
556
+ scope.run(() => {
557
+ watch(
558
+ value.innerRef,
559
+ (next) => {
560
+ if (!isBlankFileValue(next)) return;
561
+ value.setValueWithInternalPath(next, { blank: true });
562
+ if (input.value !== "") input.value = "";
563
+ },
564
+ { flush: "post" }
565
+ );
566
+ });
567
+ input[fileScopeKey] = () => scope.stop();
568
+ },
569
+ beforeUpdate(el, { value }) {
570
+ if (!isRegisterValue(value)) return;
571
+ const input = el;
572
+ const currentRaw = value.innerRef.value;
573
+ if (isBlankFileValue(currentRaw)) {
574
+ value.setValueWithInternalPath(currentRaw, { blank: true });
575
+ if (input.value !== "") input.value = "";
576
+ }
577
+ },
578
+ beforeUnmount(el, { value }) {
579
+ removeTrackedListeners(el);
580
+ const stop = el[fileScopeKey];
581
+ if (stop !== void 0) {
582
+ stop();
583
+ delete el[fileScopeKey];
584
+ }
585
+ if (!isRegisterValue(value)) return;
586
+ value.deregisterElement(el);
587
+ }
588
+ };
589
+
407
590
  const idGenerator = /* @__PURE__ */ (() => {
408
591
  let counter = 0;
409
592
  return () => `el-${++counter}`;
@@ -588,22 +771,81 @@ function createIsSensitivePath(names = DEFAULT_SENSITIVE_NAMES) {
588
771
  return false;
589
772
  };
590
773
  }
591
- const defaultSegmentMatches = createSegmentMatchesSensitive();
592
774
  const defaultIsSensitivePath = createIsSensitivePath();
593
- function segmentMatchesSensitive(segment) {
594
- return defaultSegmentMatches(segment);
595
- }
596
775
  function isSensitivePath(path) {
597
776
  return defaultIsSensitivePath(path);
598
777
  }
599
- function enforceSensitiveCheck(path, acknowledged, isSensitive = defaultIsSensitivePath) {
600
- if (acknowledged) return;
601
- if (!isSensitive(path)) return;
602
- throw new SensitivePersistFieldError(path);
778
+ const warnedSensitivePersist = __DEV__ ? /* @__PURE__ */ new Set() : null;
779
+ function allowSensitivePersist(path, acknowledged, isSensitive = defaultIsSensitivePath) {
780
+ if (acknowledged) return true;
781
+ if (!isSensitive(path)) return true;
782
+ if (warnedSensitivePersist !== null) {
783
+ const display = Array.isArray(path) ? path.join(".") : String(path);
784
+ if (!warnedSensitivePersist.has(display)) {
785
+ warnedSensitivePersist.add(display);
786
+ console.warn(
787
+ `[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.`
788
+ );
789
+ }
790
+ }
791
+ return false;
603
792
  }
604
793
 
794
+ function syncPersistOptIn(el, value, oldValue, vnodeType) {
795
+ const wasOptedIn = isRegisterValue(oldValue) && oldValue.persist === true;
796
+ const isFileInput = el.tagName === "INPUT" && (vnodeType === "file" || el.type === "file");
797
+ const wantsOptIn = !isFileInput && isRegisterValue(value) && value.persist === true;
798
+ if (!wasOptedIn && !wantsOptIn) return;
799
+ const elementId = getOrAssignElementId(el);
800
+ if (wasOptedIn) {
801
+ const old = oldValue;
802
+ const samePathAndRegistry = wantsOptIn && value.path === old.path && value.persistOptIns === old.persistOptIns;
803
+ if (!samePathAndRegistry) {
804
+ old.persistOptIns.remove(elementId, old.path);
805
+ }
806
+ }
807
+ if (wantsOptIn) {
808
+ const v = value;
809
+ if (allowSensitivePersist(v.path, v.acknowledgeSensitive, v.isSensitivePath)) {
810
+ v.persistOptIns.add(elementId, v.path);
811
+ }
812
+ }
813
+ }
814
+ function syncMultiTabOptOut(value, oldValue) {
815
+ const wasOptedOut = isRegisterValue(oldValue) && oldValue.unmarkNoSync !== void 0;
816
+ const wantsOptOut = isRegisterValue(value) && value.markNoSync !== void 0;
817
+ if (!wasOptedOut && !wantsOptOut) return;
818
+ if (wasOptedOut) {
819
+ const old = oldValue;
820
+ const samePath = wantsOptOut && value.path === old.path;
821
+ if (!samePath) old.unmarkNoSync?.();
822
+ }
823
+ if (wantsOptOut) {
824
+ const v = value;
825
+ const samePathOld = wasOptedOut && oldValue.path === v.path;
826
+ if (!samePathOld) v.markNoSync?.();
827
+ }
828
+ }
829
+ function syncElementRegistration(el, value, oldValue) {
830
+ const wasRegistered = isRegisterValue(oldValue);
831
+ const isRegistered = isRegisterValue(value);
832
+ if (!wasRegistered && !isRegistered) return;
833
+ if (wasRegistered && isRegistered) {
834
+ const old = oldValue;
835
+ const next = value;
836
+ if (old.path === next.path && old.persistOptIns === next.persistOptIns) return;
837
+ }
838
+ if (wasRegistered) {
839
+ oldValue.deregisterElement(el);
840
+ }
841
+ if (isRegistered) {
842
+ value.registerElement(el);
843
+ }
844
+ }
845
+
846
+ const INTERACTIVE_TAG_NAMES = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
847
+
605
848
  const assignKey = Symbol.for("attaform:assign-key");
606
- const listenersKey = Symbol.for("attaform:directive-listeners");
607
849
  function isRegisterValue(val) {
608
850
  if (typeof val !== "object" || val === null) return false;
609
851
  if (!("innerRef" in val)) return false;
@@ -614,22 +856,6 @@ function isRegisterValue(val) {
614
856
  if (typeof val.setValueWithInternalPath !== "function") return false;
615
857
  return true;
616
858
  }
617
- function addEventListener(el, event, handler, options) {
618
- el.addEventListener(event, handler, options);
619
- const carrier = el;
620
- const bag = carrier[listenersKey] ?? [];
621
- bag.push({ event, handler, options });
622
- carrier[listenersKey] = bag;
623
- }
624
- function removeTrackedListeners(el) {
625
- const carrier = el;
626
- const bag = carrier[listenersKey];
627
- if (bag === void 0) return;
628
- for (const { event, handler, options } of bag) {
629
- el.removeEventListener(event, handler, options);
630
- }
631
- delete carrier[listenersKey];
632
- }
633
859
  function writeLastTypedForm(rv, next) {
634
860
  rv.lastTypedForm.value = next;
635
861
  }
@@ -642,7 +868,7 @@ function isDefaultAssigner(fn) {
642
868
  return typeof fn === "function" && fn[DEFAULT_ASSIGNER_TAG] === true;
643
869
  }
644
870
  function shouldBailListener(el) {
645
- if (SUPPORTED_TAGS.has(el.tagName)) return false;
871
+ if (INTERACTIVE_TAG_NAMES.has(el.tagName)) return false;
646
872
  return isDefaultAssigner(el[assignKey]);
647
873
  }
648
874
  function runTransforms(initial, registerValue) {
@@ -732,56 +958,6 @@ const getModelAssigner = (el, vnode, registerValue) => {
732
958
  defaultAssigner[DEFAULT_ASSIGNER_TAG] = true;
733
959
  return defaultAssigner;
734
960
  };
735
- function syncPersistOptIn(el, value, oldValue, vnodeType) {
736
- const wasOptedIn = isRegisterValue(oldValue) && oldValue.persist === true;
737
- const isFileInput = el.tagName === "INPUT" && (vnodeType === "file" || el.type === "file");
738
- const wantsOptIn = !isFileInput && isRegisterValue(value) && value.persist === true;
739
- if (!wasOptedIn && !wantsOptIn) return;
740
- const elementId = getOrAssignElementId(el);
741
- if (wasOptedIn) {
742
- const old = oldValue;
743
- const samePathAndRegistry = wantsOptIn && value.path === old.path && value.persistOptIns === old.persistOptIns;
744
- if (!samePathAndRegistry) {
745
- old.persistOptIns.remove(elementId, old.path);
746
- }
747
- }
748
- if (wantsOptIn) {
749
- const v = value;
750
- enforceSensitiveCheck(v.path, v.acknowledgeSensitive, v.isSensitivePath);
751
- v.persistOptIns.add(elementId, v.path);
752
- }
753
- }
754
- function syncMultiTabOptOut(value, oldValue) {
755
- const wasOptedOut = isRegisterValue(oldValue) && oldValue.unmarkNoSync !== void 0;
756
- const wantsOptOut = isRegisterValue(value) && value.markNoSync !== void 0;
757
- if (!wasOptedOut && !wantsOptOut) return;
758
- if (wasOptedOut) {
759
- const old = oldValue;
760
- const samePath = wantsOptOut && value.path === old.path;
761
- if (!samePath) old.unmarkNoSync?.();
762
- }
763
- if (wantsOptOut) {
764
- const v = value;
765
- const samePathOld = wasOptedOut && oldValue.path === v.path;
766
- if (!samePathOld) v.markNoSync?.();
767
- }
768
- }
769
- function syncElementRegistration(el, value, oldValue) {
770
- const wasRegistered = isRegisterValue(oldValue);
771
- const isRegistered = isRegisterValue(value);
772
- if (!wasRegistered && !isRegistered) return;
773
- if (wasRegistered && isRegistered) {
774
- const old = oldValue;
775
- const next = value;
776
- if (old.path === next.path && old.persistOptIns === next.persistOptIns) return;
777
- }
778
- if (wasRegistered) {
779
- oldValue.deregisterElement(el);
780
- }
781
- if (isRegistered) {
782
- value.registerElement(el);
783
- }
784
- }
785
961
  function onCompositionStart(e) {
786
962
  const target = e.target;
787
963
  if (!target) return;
@@ -823,10 +999,11 @@ const vRegisterText = {
823
999
  value.registerElement(el);
824
1000
  setAssignFunction(el, vnode, value);
825
1001
  }
826
- addEventListener(el, lazy === true ? "change" : "input", (e) => {
1002
+ addTrackedListener(el, lazy === true ? "change" : "input", (e) => {
827
1003
  if (shouldBailListener(el)) return;
828
1004
  const target = e.target;
829
1005
  if (target === null || target.composing) return;
1006
+ noteInteraction(value);
830
1007
  let domValue = el.value;
831
1008
  if (trim === true && lazy === true) {
832
1009
  domValue = domValue.trim();
@@ -878,7 +1055,7 @@ const vRegisterText = {
878
1055
  }
879
1056
  });
880
1057
  if (trim === true || castToNumber) {
881
- addEventListener(el, "change", () => {
1058
+ addTrackedListener(el, "change", () => {
882
1059
  if (shouldBailListener(el)) return;
883
1060
  let normalized = el.value;
884
1061
  if (trim === true) normalized = normalized.trim();
@@ -904,12 +1081,12 @@ const vRegisterText = {
904
1081
  });
905
1082
  }
906
1083
  if (lazy !== true) {
907
- addEventListener(el, "compositionstart", onCompositionStart);
908
- addEventListener(el, "compositionend", onCompositionEnd);
909
- addEventListener(el, "change", onCompositionEnd);
1084
+ addTrackedListener(el, "compositionstart", onCompositionStart);
1085
+ addTrackedListener(el, "compositionend", onCompositionEnd);
1086
+ addTrackedListener(el, "change", onCompositionEnd);
910
1087
  }
911
1088
  if (number === true && vnode.props?.["type"] !== "number") {
912
- addEventListener(el, "beforeinput", (e) => {
1089
+ addTrackedListener(el, "beforeinput", (e) => {
913
1090
  const ev = e;
914
1091
  if (ev.inputType !== "insertText" && ev.inputType !== "insertFromPaste" && ev.inputType !== "insertFromDrop") {
915
1092
  return;
@@ -956,8 +1133,9 @@ const vRegisterCheckbox = {
956
1133
  if (!isRegisterValue(value)) return;
957
1134
  value.registerElement(el);
958
1135
  setAssignFunction(el, vnode, value);
959
- addEventListener(el, "change", () => {
1136
+ addTrackedListener(el, "change", () => {
960
1137
  if (shouldBailListener(el)) return;
1138
+ noteInteraction(value);
961
1139
  const modelValue = value.innerRef.value ?? [];
962
1140
  const explicitValueRequired = true;
963
1141
  const rawElementValue = getValue(el, explicitValueRequired);
@@ -1053,8 +1231,9 @@ const vRegisterRadio = {
1053
1231
  if (!isRegisterValue(value)) return;
1054
1232
  value.registerElement(el);
1055
1233
  setAssignFunction(el, vnode, value);
1056
- addEventListener(el, "change", () => {
1234
+ addTrackedListener(el, "change", () => {
1057
1235
  if (shouldBailListener(el)) return;
1236
+ noteInteraction(value);
1058
1237
  el[assignKey]?.(getValue(el));
1059
1238
  if (isRegisterValue(value) && isDefaultAssigner(el[assignKey])) {
1060
1239
  const currentModel = value.innerRef.value;
@@ -1101,8 +1280,9 @@ const vRegisterSelect = {
1101
1280
  if (!isRegisterValue(value)) return;
1102
1281
  value.registerElement(el);
1103
1282
  const isSetModel = isSet(value.innerRef.value);
1104
- addEventListener(el, "change", () => {
1283
+ addTrackedListener(el, "change", () => {
1105
1284
  if (shouldBailListener(el)) return;
1285
+ noteInteraction(value);
1106
1286
  const selectedVal = Array.prototype.filter.call(el.options, (o) => o.selected).map((o) => number === true ? looseToNumber(getValue(o)) : getValue(o));
1107
1287
  const wrote = el[assignKey]?.(
1108
1288
  el.multiple ? isSetModel ? new Set(selectedVal) : selectedVal : selectedVal[0]
@@ -1215,14 +1395,14 @@ function getCheckboxValue(el, checked) {
1215
1395
  const key = checked ? "_trueValue" : "_falseValue";
1216
1396
  return key in el ? el[key] : checked;
1217
1397
  }
1218
- const SUPPORTED_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
1219
1398
  const warnedUnsupportedElements = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
1220
1399
  const vRegisterDynamic = {
1221
1400
  created(el, binding, vnode) {
1222
1401
  syncPersistOptIn(el, binding.value, void 0, vnode.props?.["type"]);
1223
1402
  syncMultiTabOptOut(binding.value, void 0);
1224
1403
  callModelHook(el, binding, vnode, null, "created");
1225
- if (__DEV__ && warnedUnsupportedElements !== null && !SUPPORTED_TAGS.has(el.tagName) && !warnedUnsupportedElements.has(el)) {
1404
+ if (isRegisterValue(binding.value)) setupAria(el, binding.value, vnode);
1405
+ if (__DEV__ && warnedUnsupportedElements !== null && !INTERACTIVE_TAG_NAMES.has(el.tagName) && !warnedUnsupportedElements.has(el)) {
1226
1406
  void nextTick(() => {
1227
1407
  if (warnedUnsupportedElements.has(el)) return;
1228
1408
  const hasMarker = el[REGISTER_OWNER_MARKER] === true;
@@ -1245,12 +1425,28 @@ const vRegisterDynamic = {
1245
1425
  syncMultiTabOptOut(binding.value, binding.oldValue);
1246
1426
  syncElementRegistration(el, binding.value, binding.oldValue);
1247
1427
  callModelHook(el, binding, vnode, prevVNode, "beforeUpdate");
1428
+ const ariaEl = el;
1429
+ const value = binding.value;
1430
+ if (!isRegisterValue(value) || value.ariaEnabled !== true || value.ariaDisplayState === void 0) {
1431
+ teardownAria(ariaEl);
1432
+ } else {
1433
+ const old = binding.oldValue;
1434
+ const pathChanged = !isRegisterValue(old) || old.path !== value.path;
1435
+ if (pathChanged) {
1436
+ teardownAria(ariaEl);
1437
+ setupAria(ariaEl, value, vnode);
1438
+ } else {
1439
+ mergeAriaLocks(ariaEl, vnode);
1440
+ applyAria(ariaEl, value);
1441
+ }
1442
+ }
1248
1443
  },
1249
1444
  updated(el, binding, vnode, prevVNode) {
1250
1445
  callModelHook(el, binding, vnode, prevVNode, "updated");
1251
1446
  },
1252
1447
  beforeUnmount(el, { value }) {
1253
1448
  removeTrackedListeners(el);
1449
+ teardownAria(el);
1254
1450
  if (isRegisterValue(value)) {
1255
1451
  value.persistOptIns.removeAllFor(getOrAssignElementId(el));
1256
1452
  value.unmarkNoSync?.();
@@ -1260,87 +1456,18 @@ const vRegisterDynamic = {
1260
1456
  delete el.composing;
1261
1457
  delete el._assigning;
1262
1458
  delete el[assignKey];
1263
- }
1264
- };
1265
- function isBlankFileValue(value) {
1266
- if (value === null || value === void 0) return true;
1267
- if (Array.isArray(value) && value.length === 0) return true;
1268
- if (typeof FileList !== "undefined" && value instanceof FileList && value.length === 0)
1269
- return true;
1270
- return false;
1271
- }
1272
- function readFilesFromInput(el) {
1273
- const files = el.files;
1274
- if (el.multiple) {
1275
- return files === null ? [] : Array.from(files);
1276
- }
1277
- if (files === null || files.length === 0) return null;
1278
- return files.item(0);
1279
- }
1280
- const warnedPersistedFileForms = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
1281
- function maybeWarnPersistedFile(value) {
1282
- if (!__DEV__ || warnedPersistedFileForms === null) return;
1283
- if (value.persist !== true) return;
1284
- let warnedPaths = warnedPersistedFileForms.get(value.persistOptIns);
1285
- if (warnedPaths === void 0) {
1286
- warnedPaths = /* @__PURE__ */ new Set();
1287
- warnedPersistedFileForms.set(value.persistOptIns, warnedPaths);
1288
- }
1289
- if (warnedPaths.has(value.path)) return;
1290
- warnedPaths.add(value.path);
1291
- warn(
1292
- `[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.`
1293
- );
1294
- }
1295
- const fileScopeKey = Symbol.for("attaform:file-scope");
1296
- const vRegisterFile = {
1297
- created(el, { value }) {
1298
- if (!isRegisterValue(value)) return;
1299
- const input = el;
1300
- value.registerElement(input);
1301
- maybeWarnPersistedFile(value);
1302
- const currentRaw = value.innerRef.value;
1303
- if (isBlankFileValue(currentRaw)) {
1304
- const blankShape = input.multiple ? [] : null;
1305
- value.setValueWithInternalPath(blankShape, { blank: true });
1306
- }
1307
- addEventListener(input, "change", () => {
1308
- const next = readFilesFromInput(input);
1309
- const blank = isBlankFileValue(next);
1310
- value.setValueWithInternalPath(next, blank ? { blank: true } : void 0);
1311
- });
1312
- const scope = effectScope(true);
1313
- scope.run(() => {
1314
- watch(
1315
- value.innerRef,
1316
- (next) => {
1317
- if (!isBlankFileValue(next)) return;
1318
- value.setValueWithInternalPath(next, { blank: true });
1319
- if (input.value !== "") input.value = "";
1320
- },
1321
- { flush: "post" }
1322
- );
1323
- });
1324
- input[fileScopeKey] = () => scope.stop();
1325
- },
1326
- beforeUpdate(el, { value }) {
1327
- if (!isRegisterValue(value)) return;
1328
- const input = el;
1329
- const currentRaw = value.innerRef.value;
1330
- if (isBlankFileValue(currentRaw)) {
1331
- value.setValueWithInternalPath(currentRaw, { blank: true });
1332
- if (input.value !== "") input.value = "";
1333
- }
1334
1459
  },
1335
- beforeUnmount(el, { value }) {
1336
- removeTrackedListeners(el);
1337
- const stop = el[fileScopeKey];
1338
- if (stop !== void 0) {
1339
- stop();
1340
- delete el[fileScopeKey];
1341
- }
1342
- if (!isRegisterValue(value)) return;
1343
- value.deregisterElement(el);
1460
+ // The lifecycle hooks above don't run on the server (Vue skips
1461
+ // directive lifecycle during SSR), so emit the same aria attributes
1462
+ // here from the SSR-time gated display state. Honors authored attrs
1463
+ // (vnode-level lockout) and the ariaEnabled gate, touches no DOM, and
1464
+ // shares `resolveAriaValue` with the client path. Ids are SSR-stable
1465
+ // (formInstanceId derives from Vue's useId), so a server-rendered
1466
+ // describedby matches the client after hydration.
1467
+ getSSRProps(binding, vnode) {
1468
+ const rv = binding.value;
1469
+ if (!isRegisterValue(rv)) return void 0;
1470
+ return getSSRAriaProps(rv, vnode ?? null);
1344
1471
  }
1345
1472
  };
1346
1473
  function resolveDynamicModel(tagName, type) {
@@ -1504,5 +1631,5 @@ function isPathPrefix(prefix, path) {
1504
1631
  return true;
1505
1632
  }
1506
1633
 
1507
- export { AnonPersistError as A, kFormInstanceId as B, parseDottedPath as C, DEFAULT_SENSITIVE_NAMES as D, pathKeyToDotted as E, FORM_ERRORS_PATH as F, segmentMatchesSensitive as G, segmentsForPathKey as H, InvalidPathError as I, useRegister as J, useRegistry as K, vRegister as L, OutsideSetupError as O, ROOT_PATH as R, SensitivePersistFieldError as S, __DEV__ as _, AttaformError as a, FORM_ERRORS_PATH_KEY as b, InvalidUseFormConfigError as c, ROOT_PATH_KEY as d, RegistryNotInstalledError as e, ReservedFormKeyError as f, SubmitErrorHandlerError as g, assignKey as h, canonicalizePath as i, captureUserCallSite as j, coerceToPathKey as k, createAttaform as l, createIsSensitivePath as m, createPersistOptInRegistry as n, createRegistry as o, createSegmentMatchesSensitive as p, enforceSensitiveCheck as q, ensureAttaformInstalled as r, getRegistryFromApp as s, isPathPrefix as t, isRegisterValue as u, isSensitivePath as v, kAttaformAncestorWizard as w, kAttaformRegistry as x, kAttaformWizardActiveStepResolver as y, kFormContext as z };
1508
- //# sourceMappingURL=attaform.CDmaxrt2.mjs.map
1634
+ export { AnonPersistError as A, parseDottedPath as B, pathKeyToDotted as C, DEFAULT_SENSITIVE_NAMES as D, segmentsForPathKey as E, FORM_ERRORS_PATH as F, useRegister as G, useRegistry as H, INTERACTIVE_TAG_NAMES as I, vRegister as J, OutsideSetupError as O, ROOT_PATH as R, SubmitErrorHandlerError as S, __DEV__ as _, AttaformError as a, FORM_ERRORS_PATH_KEY as b, InvalidPathError as c, InvalidUseFormConfigError as d, ROOT_PATH_KEY as e, RegistryNotInstalledError as f, ReservedFormKeyError as g, allowSensitivePersist as h, assignKey as i, canonicalizePath as j, captureUserCallSite as k, coerceToPathKey as l, createAttaform as m, createIsSensitivePath as n, createPersistOptInRegistry as o, createRegistry as p, ensureAttaformInstalled as q, getRegistryFromApp as r, isPathPrefix as s, isRegisterValue as t, isSensitivePath as u, kAttaformAncestorWizard as v, kAttaformRegistry as w, kAttaformWizardActiveStepResolver as x, kFormContext as y, kFormInstanceId as z };
1635
+ //# sourceMappingURL=attaform.BKozEdTr.mjs.map