attaform 0.18.2 → 0.19.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 (71) 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 -4
  11. package/dist/index.d.cts +68 -75
  12. package/dist/index.d.mts +68 -75
  13. package/dist/index.d.ts +68 -75
  14. package/dist/index.mjs +5 -5
  15. package/dist/nuxt.d.cts +1 -1
  16. package/dist/nuxt.d.mts +1 -1
  17. package/dist/nuxt.d.ts +1 -1
  18. package/dist/runtime/plugins/attaform.cjs +2 -2
  19. package/dist/runtime/plugins/attaform.mjs +2 -2
  20. package/dist/shared/{attaform.CZ-XtZt_.mjs → attaform.BTi-PsHr.mjs} +544 -134
  21. package/dist/shared/attaform.BTi-PsHr.mjs.map +1 -0
  22. package/dist/shared/{attaform.CuN7ZhBy.d.cts → attaform.BTpuvGec.d.ts} +44 -11
  23. package/dist/shared/{attaform.BqK_L4gK.cjs → attaform.BqEfHpVB.cjs} +119 -1
  24. package/dist/shared/attaform.BqEfHpVB.cjs.map +1 -0
  25. package/dist/shared/{attaform.D1gzu2GL.d.mts → attaform.BtBmfLQN.d.mts} +44 -11
  26. package/dist/shared/{attaform.Ca5_6Ky-.d.cts → attaform.C0uGZQ4M.d.cts} +365 -86
  27. package/dist/shared/{attaform.Ca5_6Ky-.d.mts → attaform.C0uGZQ4M.d.mts} +365 -86
  28. package/dist/shared/{attaform.Ca5_6Ky-.d.ts → attaform.C0uGZQ4M.d.ts} +365 -86
  29. package/dist/shared/{attaform.II89Pcf4.cjs → attaform.C1msmO2v.cjs} +544 -134
  30. package/dist/shared/attaform.C1msmO2v.cjs.map +1 -0
  31. package/dist/shared/{attaform.CRmmNAYp.d.cts → attaform.CBjmobqk.d.cts} +1 -1
  32. package/dist/shared/{attaform.B957T6NU.d.ts → attaform.CJ-e9gYI.d.ts} +1 -1
  33. package/dist/shared/{attaform.XDjA7sRz.d.cts → attaform.CRNA0vrd.d.mts} +1 -1
  34. package/dist/shared/{attaform.Dl161U6E.mjs → attaform.Cghpuav8.mjs} +2 -2
  35. package/dist/shared/{attaform.Dl161U6E.mjs.map → attaform.Cghpuav8.mjs.map} +1 -1
  36. package/dist/shared/{attaform.Df0tU0Ut.mjs → attaform.CiMqJHDm.mjs} +3 -3
  37. package/dist/shared/{attaform.Df0tU0Ut.mjs.map → attaform.CiMqJHDm.mjs.map} +1 -1
  38. package/dist/shared/{attaform.5UhpSVFI.cjs → attaform.CoxJ8Qm8.cjs} +2 -2
  39. package/dist/shared/{attaform.5UhpSVFI.cjs.map → attaform.CoxJ8Qm8.cjs.map} +1 -1
  40. package/dist/shared/{attaform.CDmaxrt2.mjs → attaform.CrpjyXdO.mjs} +119 -1
  41. package/dist/shared/attaform.CrpjyXdO.mjs.map +1 -0
  42. package/dist/shared/{attaform.FnEwjhvX.d.ts → attaform.D4I63aBV.d.ts} +1 -1
  43. package/dist/shared/{attaform.D9wuTGu9.d.mts → attaform.DXYHL99q.d.mts} +1 -1
  44. package/dist/shared/{attaform.Dlk1jMuv.cjs → attaform.JBx8cfMA.cjs} +3 -3
  45. package/dist/shared/{attaform.Dlk1jMuv.cjs.map → attaform.JBx8cfMA.cjs.map} +1 -1
  46. package/dist/shared/{attaform.DUHru0OF.cjs → attaform.OznWyOPy.cjs} +3 -3
  47. package/dist/shared/{attaform.DUHru0OF.cjs.map → attaform.OznWyOPy.cjs.map} +1 -1
  48. package/dist/shared/{attaform.M-RanbyV.d.mts → attaform.QvygsFGh.d.cts} +1 -1
  49. package/dist/shared/{attaform.-1GQTX2T.mjs → attaform.a3uBo-gw.mjs} +3 -3
  50. package/dist/shared/{attaform.-1GQTX2T.mjs.map → attaform.a3uBo-gw.mjs.map} +1 -1
  51. package/dist/shared/{attaform.CGX1CNpz.d.ts → attaform.ePUcKxId.d.cts} +44 -11
  52. package/dist/zod-v3.cjs +3 -3
  53. package/dist/zod-v3.d.cts +4 -4
  54. package/dist/zod-v3.d.mts +4 -4
  55. package/dist/zod-v3.d.ts +4 -4
  56. package/dist/zod-v3.mjs +3 -3
  57. package/dist/zod-v4.cjs +3 -3
  58. package/dist/zod-v4.d.cts +4 -4
  59. package/dist/zod-v4.d.mts +4 -4
  60. package/dist/zod-v4.d.ts +4 -4
  61. package/dist/zod-v4.mjs +3 -3
  62. package/dist/zod.cjs +4 -4
  63. package/dist/zod.d.cts +6 -6
  64. package/dist/zod.d.mts +6 -6
  65. package/dist/zod.d.ts +6 -6
  66. package/dist/zod.mjs +5 -5
  67. package/package.json +2 -2
  68. package/dist/shared/attaform.BqK_L4gK.cjs.map +0 -1
  69. package/dist/shared/attaform.CDmaxrt2.mjs.map +0 -1
  70. package/dist/shared/attaform.CZ-XtZt_.mjs.map +0 -1
  71. package/dist/shared/attaform.II89Pcf4.cjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, reactive, watch, markRaw, toRaw, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, effectScope, nextTick } from 'vue';
2
- import { i as canonicalizePath, H as segmentsForPathKey, t as isPathPrefix, b as FORM_ERRORS_PATH_KEY, _ as __DEV__, E as pathKeyToDotted, g as SubmitErrorHandlerError, A as AnonPersistError, j as captureUserCallSite, R as ROOT_PATH, q as enforceSensitiveCheck, F as FORM_ERRORS_PATH, k as coerceToPathKey, G as segmentMatchesSensitive, v as isSensitivePath, n as createPersistOptInRegistry, c as InvalidUseFormConfigError, r as ensureAttaformInstalled, K as useRegistry, z as kFormContext, B as kFormInstanceId, f as ReservedFormKeyError, m as createIsSensitivePath, p as createSegmentMatchesSensitive, y as kAttaformWizardActiveStepResolver, w as kAttaformAncestorWizard } from './attaform.CDmaxrt2.mjs';
2
+ import { _ as __DEV__, i as canonicalizePath, H as segmentsForPathKey, t as isPathPrefix, b as FORM_ERRORS_PATH_KEY, E as pathKeyToDotted, g as SubmitErrorHandlerError, A as AnonPersistError, j as captureUserCallSite, d as ROOT_PATH_KEY, R as ROOT_PATH, q as enforceSensitiveCheck, F as FORM_ERRORS_PATH, k as coerceToPathKey, G as segmentMatchesSensitive, v as isSensitivePath, n as createPersistOptInRegistry, c as InvalidUseFormConfigError, r as ensureAttaformInstalled, K as useRegistry, z as kFormContext, B as kFormInstanceId, f as ReservedFormKeyError, m as createIsSensitivePath, p as createSegmentMatchesSensitive, y as kAttaformWizardActiveStepResolver, w as kAttaformAncestorWizard } from './attaform.CrpjyXdO.mjs';
3
3
 
4
4
  const NOT_FOUND = Symbol("NOT_FOUND");
5
5
  function descendStep(value, segment) {
@@ -434,6 +434,74 @@ function structuralSnapshot(value) {
434
434
  return out;
435
435
  }
436
436
 
437
+ const defaultDisplayState = (field, formMeta) => {
438
+ const gateOpen = formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
439
+ if (!gateOpen) return "idle";
440
+ if (field.validating === true) return "pending";
441
+ const hasOwnError = field.errors.some(
442
+ (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
443
+ );
444
+ if (hasOwnError) return "error";
445
+ if (field.valid === true && field.blank !== true && field.dirty === true) return "success";
446
+ return "idle";
447
+ };
448
+ function resolveGetDisplayState(config) {
449
+ return config ?? defaultDisplayState;
450
+ }
451
+
452
+ const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
453
+ const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
454
+ const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
455
+ const PERSISTENCE_KEY_PREFIX = "attaform:";
456
+ const RESERVED_KEY_PREFIX = "__atta:";
457
+ const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
458
+ const ANONYMOUS_WIZARD_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon-wizard:`;
459
+ const DEFAULT_MAX_RECURSION_DEPTH = 64;
460
+ function normalizeNumericOption(config) {
461
+ const { value, source, allowInfinity, min, defaultValue } = config;
462
+ if (allowInfinity && value === Infinity) return Infinity;
463
+ if (typeof value !== "number" || Number.isNaN(value) || value === Infinity || value === -Infinity) {
464
+ if (__DEV__) {
465
+ const acceptedDescription = allowInfinity ? "a non-negative integer or Infinity" : "a non-negative finite integer";
466
+ console.warn(
467
+ `[attaform] ${source} must be ${acceptedDescription}; got ${String(value)}. Falling back to ${String(defaultValue)}.`
468
+ );
469
+ }
470
+ return defaultValue;
471
+ }
472
+ return Math.max(min, Math.floor(value));
473
+ }
474
+
475
+ function hashStableString(input, seed = 0) {
476
+ let h1 = 3735928559 ^ seed;
477
+ let h2 = 1103547991 ^ seed;
478
+ for (let i = 0; i < input.length; i++) {
479
+ const ch = input.charCodeAt(i);
480
+ h1 = Math.imul(h1 ^ ch, 2654435761);
481
+ h2 = Math.imul(h2 ^ ch, 1597334677);
482
+ }
483
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
484
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
485
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
486
+ }
487
+
488
+ const ANON_STEM = "atta";
489
+ function readableFormKeyStem(formKey) {
490
+ if (formKey === "" || formKey.startsWith(ANONYMOUS_FORM_KEY_PREFIX)) return ANON_STEM;
491
+ const sanitized = formKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
492
+ return sanitized === "" ? ANON_STEM : sanitized;
493
+ }
494
+ function fieldIdToken(formInstanceId, pathKey) {
495
+ return hashStableString(`${formInstanceId}:${pathKey}`).slice(-7);
496
+ }
497
+ function computeFieldIdentity(formInstanceId, formKey, pathKey) {
498
+ const id = `${readableFormKeyStem(formKey)}-${fieldIdToken(formInstanceId, pathKey)}`;
499
+ return {
500
+ id,
501
+ aria: Object.freeze({ errorId: `${id}-error`, descriptionId: `${id}-description` })
502
+ };
503
+ }
504
+
437
505
  const EMPTY_RESOLVED_FIELD_META = Object.freeze({
438
506
  label: "",
439
507
  description: void 0,
@@ -467,21 +535,21 @@ function isUnderStubAncestor(state, segments) {
467
535
  }
468
536
  return false;
469
537
  }
470
- function buildFieldStateAccessor(state, getFormMetaBase, options) {
538
+ function buildFieldStateAccessor(state, formInstanceId, getFormMetaBase, options) {
471
539
  const cache = /* @__PURE__ */ new Map();
472
- const predicate = options?.shouldShowErrors;
540
+ const predicate = options?.getDisplayState;
473
541
  return function getFieldState(pathInput) {
474
542
  const { segments, key } = canonicalizePath(pathInput);
475
543
  const cached = cache.get(key);
476
544
  if (cached !== void 0) return cached;
477
545
  const c = computed(
478
- () => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, getFormMetaBase, predicate) : buildContainerFieldState(state, segments, key, getFormMetaBase, predicate)
546
+ () => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, predicate) : buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, predicate)
479
547
  );
480
548
  cache.set(key, c);
481
549
  return c;
482
550
  };
483
551
  }
484
- function buildLeafFieldStateBase(state, segments, key) {
552
+ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
485
553
  const record = state.fields.get(key);
486
554
  const value = state.getValueAtPath(segments);
487
555
  const original = state.originals.get(key)?.value;
@@ -511,6 +579,8 @@ function buildLeafFieldStateBase(state, segments, key) {
511
579
  focused: record?.focused ?? null,
512
580
  blurred: record?.blurred ?? null,
513
581
  touched: record?.touched ?? false,
582
+ interacted: record?.interacted ?? false,
583
+ blurredAfterInteraction: record?.blurredAfterInteraction ?? false,
514
584
  connected: record?.connected ?? false,
515
585
  element: firstElement,
516
586
  elements: elementsArr,
@@ -519,6 +589,8 @@ function buildLeafFieldStateBase(state, segments, key) {
519
589
  validating,
520
590
  valid,
521
591
  path: segments,
592
+ ...computeFieldIdentity(formInstanceId, state.formKey, key),
593
+ key: state.arrayElementKey(segments),
522
594
  blank: state.blankPaths.has(key),
523
595
  label,
524
596
  description: resolved.description,
@@ -526,11 +598,11 @@ function buildLeafFieldStateBase(state, segments, key) {
526
598
  meta: resolved.meta
527
599
  };
528
600
  }
529
- function buildLeafFieldState(state, segments, key, getFormMetaBase, shouldShowErrors) {
530
- const base = buildLeafFieldStateBase(state, segments, key);
531
- return decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors);
601
+ function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
602
+ const base = buildLeafFieldStateBase(state, segments, key, formInstanceId);
603
+ return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
532
604
  }
533
- function buildContainerFieldStateBase(state, segments, _key) {
605
+ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
534
606
  const formValue = state.form.value;
535
607
  const value = state.getValueAtPath(segments);
536
608
  const original = state.originals.get(canonicalizePath(segments).key)?.value;
@@ -540,6 +612,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
540
612
  let focused = false;
541
613
  let blurred = false;
542
614
  let touched = false;
615
+ let interacted = false;
616
+ let blurredAfterInteraction = false;
543
617
  let connected = false;
544
618
  let validating = false;
545
619
  let updatedAt = null;
@@ -558,6 +632,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
558
632
  if (leafRecord?.focused === true) focused = true;
559
633
  if (leafRecord?.blurred === true) blurred = true;
560
634
  if (leafRecord?.touched === true) touched = true;
635
+ if (leafRecord?.interacted === true) interacted = true;
636
+ if (leafRecord?.blurredAfterInteraction === true) blurredAfterInteraction = true;
561
637
  if (leafRecord?.connected === true) connected = true;
562
638
  if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) validating = true;
563
639
  if (state.pathHasAsyncValidation(entry.segments)) asyncPending = true;
@@ -566,6 +642,10 @@ function buildContainerFieldStateBase(state, segments, _key) {
566
642
  if (updatedAt === null || ts > updatedAt) updatedAt = ts;
567
643
  }
568
644
  }
645
+ if (!dirty && state.hasStructuralChangeUnder(segments)) {
646
+ pristine = false;
647
+ dirty = true;
648
+ }
569
649
  const errors = aggregateErrorsAt(state, segments);
570
650
  if (!asyncPending && state.pathHasAsyncValidation(segments)) asyncPending = true;
571
651
  const gated = asyncPending && !state.firstValidationDone.value;
@@ -581,6 +661,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
581
661
  focused,
582
662
  blurred,
583
663
  touched,
664
+ interacted,
665
+ blurredAfterInteraction,
584
666
  connected,
585
667
  element: null,
586
668
  elements: EMPTY_ELEMENTS,
@@ -589,6 +671,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
589
671
  validating,
590
672
  valid,
591
673
  path: segments,
674
+ ...computeFieldIdentity(formInstanceId, state.formKey, key),
675
+ key: state.arrayElementKey(segments),
592
676
  blank,
593
677
  label,
594
678
  description: resolved.description,
@@ -596,15 +680,29 @@ function buildContainerFieldStateBase(state, segments, _key) {
596
680
  meta: resolved.meta
597
681
  };
598
682
  }
599
- function buildContainerFieldState(state, segments, key, getFormMetaBase, shouldShowErrors) {
600
- const base = buildContainerFieldStateBase(state, segments);
601
- return decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors);
683
+ function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
684
+ const base = buildContainerFieldStateBase(state, segments, key, formInstanceId);
685
+ return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
602
686
  }
603
- function decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors) {
687
+ function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState) {
604
688
  const firstError = base.errors[0];
605
- const predicate = shouldShowErrors ?? state.shouldShowErrors;
606
- const showErrors = base.errors.length > 0 && predicate(base, getFormMetaBase());
607
- return { ...base, showErrors, firstError };
689
+ const predicate = getDisplayState ?? state.getDisplayState;
690
+ const formMeta = getFormMetaBase();
691
+ let displayState;
692
+ try {
693
+ displayState = predicate(base, formMeta);
694
+ } catch {
695
+ displayState = defaultDisplayState(base, formMeta);
696
+ }
697
+ return {
698
+ ...base,
699
+ displayState,
700
+ showErrors: displayState === "error",
701
+ showPending: displayState === "pending",
702
+ showSuccess: displayState === "success",
703
+ showIdle: displayState === "idle",
704
+ firstError
705
+ };
608
706
  }
609
707
  function aggregateErrorsAt(state, prefix) {
610
708
  const formValue = state.form.value;
@@ -986,19 +1084,19 @@ function buildFieldArrayApi(state) {
986
1084
  prepend(path, value) {
987
1085
  const next = readArray(path);
988
1086
  next.unshift(value);
989
- return writeArray(path, next, { kind: "shift-from", index: 0 });
1087
+ return writeArray(path, next, { kind: "insert", index: 0 });
990
1088
  },
991
1089
  insert(path, index, value) {
992
1090
  const next = readArray(path);
993
1091
  next.splice(index, 0, value);
994
1092
  const clampedIndex = Math.max(0, Math.min(index, next.length));
995
- return writeArray(path, next, { kind: "shift-from", index: clampedIndex });
1093
+ return writeArray(path, next, { kind: "insert", index: clampedIndex });
996
1094
  },
997
1095
  remove(path, index) {
998
1096
  const next = readArray(path);
999
1097
  if (index < 0 || index >= next.length) return false;
1000
1098
  next.splice(index, 1);
1001
- return writeArray(path, next, { kind: "shift-from", index });
1099
+ return writeArray(path, next, { kind: "remove", index });
1002
1100
  },
1003
1101
  swap(path, a, b) {
1004
1102
  const next = readArray(path);
@@ -1016,11 +1114,7 @@ function buildFieldArrayApi(state) {
1016
1114
  const [item] = next.splice(from, 1);
1017
1115
  const clampedTo = Math.max(0, Math.min(to, next.length));
1018
1116
  next.splice(clampedTo, 0, item);
1019
- return writeArray(path, next, {
1020
- kind: "shift-range",
1021
- fromIndex: Math.min(from, clampedTo),
1022
- toIndex: Math.max(from, clampedTo)
1023
- });
1117
+ return writeArray(path, next, { kind: "move", from, to: clampedTo });
1024
1118
  },
1025
1119
  replace(path, index, value) {
1026
1120
  const next = readArray(path);
@@ -1039,6 +1133,8 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1039
1133
  "focused",
1040
1134
  "blurred",
1041
1135
  "touched",
1136
+ "interacted",
1137
+ "blurredAfterInteraction",
1042
1138
  "connected",
1043
1139
  "element",
1044
1140
  "elements",
@@ -1046,20 +1142,28 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1046
1142
  "errors",
1047
1143
  "validating",
1048
1144
  "valid",
1145
+ "displayState",
1049
1146
  "showErrors",
1147
+ "showPending",
1148
+ "showSuccess",
1149
+ "showIdle",
1050
1150
  "firstError",
1051
1151
  "path",
1152
+ "id",
1153
+ "aria",
1154
+ "key",
1052
1155
  "blank",
1053
1156
  "label",
1054
1157
  "description",
1055
1158
  "placeholder",
1056
1159
  "meta"
1057
1160
  ]);
1058
- function buildFieldStateProxy(state, getFormMetaBase, options) {
1161
+ function buildFieldStateProxy(state, formInstanceId, getFormMetaBase, options) {
1059
1162
  const getFieldStateAt = buildFieldStateAccessor(
1060
1163
  state,
1164
+ formInstanceId,
1061
1165
  getFormMetaBase,
1062
- options?.shouldShowErrors !== void 0 ? { shouldShowErrors: options.shouldShowErrors } : void 0
1166
+ options?.getDisplayState !== void 0 ? { getDisplayState: options.getDisplayState } : void 0
1063
1167
  );
1064
1168
  const snapshotFieldStateAt = (path) => {
1065
1169
  const view = getFieldStateAt(path).value;
@@ -1166,29 +1270,6 @@ function walk$2(value, basePath, schema, snapshotFieldStateAt) {
1166
1270
  return result;
1167
1271
  }
1168
1272
 
1169
- const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
1170
- const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
1171
- const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
1172
- const PERSISTENCE_KEY_PREFIX = "attaform:";
1173
- const RESERVED_KEY_PREFIX = "__atta:";
1174
- const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
1175
- const ANONYMOUS_WIZARD_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon-wizard:`;
1176
- const DEFAULT_MAX_RECURSION_DEPTH = 64;
1177
- function normalizeNumericOption(config) {
1178
- const { value, source, allowInfinity, min, defaultValue } = config;
1179
- if (allowInfinity && value === Infinity) return Infinity;
1180
- if (typeof value !== "number" || Number.isNaN(value) || value === Infinity || value === -Infinity) {
1181
- if (__DEV__) {
1182
- const acceptedDescription = allowInfinity ? "a non-negative integer or Infinity" : "a non-negative finite integer";
1183
- console.warn(
1184
- `[attaform] ${source} must be ${acceptedDescription}; got ${String(value)}. Falling back to ${String(defaultValue)}.`
1185
- );
1186
- }
1187
- return defaultValue;
1188
- }
1189
- return Math.max(min, Math.floor(value));
1190
- }
1191
-
1192
1273
  const PERSISTENCE_MODULE_KEY = "persistence";
1193
1274
  async function getStorageAdapter(storage) {
1194
1275
  if (typeof storage === "object") return storage;
@@ -1994,6 +2075,8 @@ function detachFocusListeners(element) {
1994
2075
  function buildRegister(state, formInstanceId, instanceConfig) {
1995
2076
  const coerceIndex = instanceConfig?.coerce !== void 0 ? resolveCoercionIndex(instanceConfig.coerce) : state.coerceIndex;
1996
2077
  const instanceMeta = instanceConfig?.instanceMeta;
2078
+ const formAutoAria = instanceConfig?.autoAria ?? true;
2079
+ const getDisplayStateAt = instanceConfig?.getDisplayStateAt;
1997
2080
  const withInstanceMeta = (meta) => {
1998
2081
  if (instanceMeta === void 0) return meta;
1999
2082
  return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
@@ -2048,6 +2131,10 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2048
2131
  callSite: captureUserCallSite()
2049
2132
  });
2050
2133
  }
2134
+ const { aria } = computeFieldIdentity(formInstanceId, state.formKey, pathKey);
2135
+ const isRequired = state.schema.isRequiredAtPath(segments);
2136
+ const ariaEnabled = options?.autoAria ?? formAutoAria;
2137
+ const ariaDisplayState = getDisplayStateAt !== void 0 ? computed(() => getDisplayStateAt(segments)) : void 0;
2051
2138
  const internalRv = {
2052
2139
  innerRef,
2053
2140
  displayValue,
@@ -2062,6 +2149,9 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2062
2149
  })
2063
2150
  );
2064
2151
  },
2152
+ markInteracted: () => {
2153
+ state.markInteracted(segments);
2154
+ },
2065
2155
  registerElement: (element) => {
2066
2156
  if (!INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
2067
2157
  const added = state.registerElement(segments, element, formInstanceId);
@@ -2106,7 +2196,12 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2106
2196
  coerce,
2107
2197
  ...coerceElement !== void 0 ? { coerceElement } : {},
2108
2198
  acceptsUndefined,
2109
- acceptsString
2199
+ acceptsString,
2200
+ // --- Aria (internal; consumed by the directive) ---
2201
+ aria,
2202
+ isRequired,
2203
+ ariaEnabled,
2204
+ ...ariaDisplayState !== void 0 ? { ariaDisplayState } : {}
2110
2205
  };
2111
2206
  return shallowReadonly(internalRv);
2112
2207
  };
@@ -2350,15 +2445,34 @@ function buildFormApi(state, formInstanceId, options = {}) {
2350
2445
  if (instanceMeta === void 0) return meta;
2351
2446
  return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
2352
2447
  };
2353
- const registerConfig = {
2354
- ...instanceMeta !== void 0 ? { instanceMeta } : {},
2355
- ...options.coerce !== void 0 ? { coerce: options.coerce } : {}
2448
+ const getFormMetaBase = () => {
2449
+ const rootBase = buildContainerFieldStateBase(state, ROOT_PATH, ROOT_PATH_KEY, formInstanceId);
2450
+ return {
2451
+ ...rootBase,
2452
+ submitting: state.submitting.value,
2453
+ submissionAttempts: state.submissionAttempts.value,
2454
+ departAttempts: state.departAttempts.value,
2455
+ submitError: state.submitError.value,
2456
+ errorCount: rootBase.errors.length,
2457
+ submitted: state.submitted.value,
2458
+ instanceId: formInstanceId
2459
+ };
2356
2460
  };
2357
- const register = buildRegister(
2461
+ const fieldStateAccessorOptions = options.getDisplayState !== void 0 ? { getDisplayState: options.getDisplayState } : void 0;
2462
+ const getRootFieldStateAt = buildFieldStateAccessor(
2358
2463
  state,
2359
2464
  formInstanceId,
2360
- Object.keys(registerConfig).length > 0 ? registerConfig : void 0
2465
+ getFormMetaBase,
2466
+ fieldStateAccessorOptions
2361
2467
  );
2468
+ const getDisplayStateAt = (segments) => getRootFieldStateAt(segments).value.displayState;
2469
+ const registerConfig = {
2470
+ ...instanceMeta !== void 0 ? { instanceMeta } : {},
2471
+ ...options.coerce !== void 0 ? { coerce: options.coerce } : {},
2472
+ ...options.autoAria !== void 0 ? { autoAria: options.autoAria } : {},
2473
+ getDisplayStateAt
2474
+ };
2475
+ const register = buildRegister(state, formInstanceId, registerConfig);
2362
2476
  const processOptions = options.onInvalidSubmit !== void 0 ? { onInvalidSubmit: options.onInvalidSubmit } : {};
2363
2477
  const defaultInvalidSubmitPolicy = options.onInvalidSubmit ?? "focus-first-error";
2364
2478
  const {
@@ -2530,25 +2644,6 @@ function buildFormApi(state, formInstanceId, options = {}) {
2530
2644
  const metaErrors = computed(
2531
2645
  () => aggregateErrorsAt(state, [])
2532
2646
  );
2533
- const getFormMetaBase = () => {
2534
- const rootBase = buildContainerFieldStateBase(state, ROOT_PATH);
2535
- return {
2536
- ...rootBase,
2537
- submitting: state.submitting.value,
2538
- submissionAttempts: state.submissionAttempts.value,
2539
- departAttempts: state.departAttempts.value,
2540
- submitError: state.submitError.value,
2541
- errorCount: rootBase.errors.length,
2542
- submitted: state.submitted.value,
2543
- instanceId: formInstanceId
2544
- };
2545
- };
2546
- const fieldStateAccessorOptions = options.shouldShowErrors !== void 0 ? { shouldShowErrors: options.shouldShowErrors } : void 0;
2547
- const getRootFieldStateAt = buildFieldStateAccessor(
2548
- state,
2549
- getFormMetaBase,
2550
- fieldStateAccessorOptions
2551
- );
2552
2647
  const rootFieldState = getRootFieldStateAt([]);
2553
2648
  const formMeta = readonly(
2554
2649
  reactive({
@@ -2563,6 +2658,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
2563
2658
  focused: computed(() => rootFieldState.value.focused),
2564
2659
  blurred: computed(() => rootFieldState.value.blurred),
2565
2660
  touched: computed(() => rootFieldState.value.touched),
2661
+ interacted: computed(() => rootFieldState.value.interacted),
2662
+ blurredAfterInteraction: computed(() => rootFieldState.value.blurredAfterInteraction),
2566
2663
  connected: computed(() => rootFieldState.value.connected),
2567
2664
  element: computed(() => rootFieldState.value.element),
2568
2665
  elements: computed(() => rootFieldState.value.elements),
@@ -2584,14 +2681,21 @@ function buildFormApi(state, formInstanceId, options = {}) {
2584
2681
  // keep the explicit form-level computation for the gate.
2585
2682
  valid,
2586
2683
  errors: metaErrors,
2587
- // `showErrors` / `firstError` flow through the same root
2588
- // field-state computed as the rest of the FieldState surface,
2589
- // so `form.meta.showErrors` matches `form.fields().showErrors`
2590
- // exactly — the predicate runs once at the root and the result
2591
- // is shared.
2684
+ // `displayState` / the `show*` booleans / `firstError` flow
2685
+ // through the same root field-state computed as the rest of the
2686
+ // FieldState surface, so `form.meta.displayState` matches
2687
+ // `form.fields().displayState` exactly — the predicate runs once
2688
+ // at the root and the result is shared.
2689
+ displayState: computed(() => rootFieldState.value.displayState),
2592
2690
  showErrors: computed(() => rootFieldState.value.showErrors),
2691
+ showPending: computed(() => rootFieldState.value.showPending),
2692
+ showSuccess: computed(() => rootFieldState.value.showSuccess),
2693
+ showIdle: computed(() => rootFieldState.value.showIdle),
2593
2694
  firstError: computed(() => rootFieldState.value.firstError),
2594
2695
  path: computed(() => rootFieldState.value.path),
2696
+ id: computed(() => rootFieldState.value.id),
2697
+ aria: computed(() => rootFieldState.value.aria),
2698
+ key: computed(() => rootFieldState.value.key),
2595
2699
  blank: computed(() => rootFieldState.value.blank),
2596
2700
  label: computed(() => rootFieldState.value.label),
2597
2701
  description: computed(() => rootFieldState.value.description),
@@ -2707,13 +2811,41 @@ function buildFormApi(state, formInstanceId, options = {}) {
2707
2811
  return Object.freeze(view);
2708
2812
  });
2709
2813
  const valuesProxy = buildValuesProxy(state.form);
2710
- const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase, fieldStateAccessorOptions);
2814
+ const fieldStateProxy = buildFieldStateProxy(
2815
+ state,
2816
+ formInstanceId,
2817
+ getFormMetaBase,
2818
+ fieldStateAccessorOptions
2819
+ );
2711
2820
  function gated(fn) {
2712
2821
  return ((...args) => {
2713
2822
  void state.activate();
2714
2823
  return fn(...args);
2715
2824
  });
2716
2825
  }
2826
+ const callTerminal = fieldStateProxy;
2827
+ const EMPTY_FIELD_LIST = Object.freeze([]);
2828
+ function list(path) {
2829
+ const { segments } = canonicalizePath(path);
2830
+ const value = state.getValueAtPath(segments);
2831
+ if (!Array.isArray(value)) return EMPTY_FIELD_LIST;
2832
+ const out = new Array(value.length);
2833
+ for (let i = 0; i < value.length; i += 1) out[i] = callTerminal(`${path}.${i}`);
2834
+ return Object.freeze(out);
2835
+ }
2836
+ const EMPTY_FIELD_RECORD = Object.freeze({});
2837
+ function record(path) {
2838
+ const { segments } = canonicalizePath(path);
2839
+ const value = state.getValueAtPath(segments);
2840
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2841
+ return EMPTY_FIELD_RECORD;
2842
+ }
2843
+ const out = {};
2844
+ for (const key of Object.keys(value)) {
2845
+ out[key] = callTerminal(`${path}.${key}`);
2846
+ }
2847
+ return Object.freeze(out);
2848
+ }
2717
2849
  return {
2718
2850
  handleSubmit: gated(handleSubmit),
2719
2851
  // Callable readonly Proxies (`values`, `fields`, `errors`) and the
@@ -2795,6 +2927,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
2795
2927
  swap: gated(fieldArrays.swap),
2796
2928
  move: gated(fieldArrays.move),
2797
2929
  replace: gated(fieldArrays.replace),
2930
+ list: gated(list),
2931
+ record: gated(record),
2798
2932
  get blankPaths() {
2799
2933
  void state.activate();
2800
2934
  return blankPathsView;
@@ -2802,28 +2936,186 @@ function buildFormApi(state, formInstanceId, options = {}) {
2802
2936
  };
2803
2937
  }
2804
2938
 
2805
- const defaultShouldShowErrors = (field, formMeta) => {
2806
- const hasOwnError = field.errors.some(
2807
- (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
2808
- );
2809
- if (!hasOwnError) return false;
2810
- if (field.validating === true) return false;
2811
- if (formMeta.submissionAttempts > 0) return true;
2812
- return field.touched === true && field.focused !== true;
2813
- };
2814
- const SHOW_ALWAYS = () => true;
2815
- const SHOW_NEVER = () => false;
2816
- function resolveShouldShowErrors(config) {
2817
- if (config === void 0) return defaultShouldShowErrors;
2818
- if (config === true) return SHOW_ALWAYS;
2819
- if (config === false) return SHOW_NEVER;
2820
- return config;
2939
+ function createArrayIdentity(getArrayLength) {
2940
+ const tokens = /* @__PURE__ */ new Map();
2941
+ const baselines = /* @__PURE__ */ new Map();
2942
+ let counter = 0;
2943
+ const allocate = () => `k${(counter++).toString(36)}`;
2944
+ function ensure(arrayKey, expectedLen) {
2945
+ let ids = tokens.get(arrayKey);
2946
+ const firstTrack = ids === void 0;
2947
+ if (ids === void 0) {
2948
+ ids = [];
2949
+ tokens.set(arrayKey, ids);
2950
+ }
2951
+ while (ids.length < expectedLen) ids.push(allocate());
2952
+ if (ids.length > expectedLen) ids.length = expectedLen;
2953
+ if (firstTrack) baselines.set(arrayKey, [...ids]);
2954
+ return ids;
2955
+ }
2956
+ function orderPristineForKey(arrayKey) {
2957
+ const baseline = baselines.get(arrayKey);
2958
+ const current = tokens.get(arrayKey);
2959
+ if (baseline === void 0 || current === void 0) return true;
2960
+ if (baseline.length !== current.length) return false;
2961
+ for (let i = 0; i < current.length; i++) {
2962
+ if (current[i] !== baseline[i]) return false;
2963
+ }
2964
+ return true;
2965
+ }
2966
+ return {
2967
+ tokenAt(arraySegs, index) {
2968
+ const len = getArrayLength(arraySegs);
2969
+ if (index < 0 || index >= len) return "";
2970
+ const ids = ensure(canonicalizePath(arraySegs).key, len);
2971
+ return ids[index] ?? "";
2972
+ },
2973
+ applyOp(arraySegs, op) {
2974
+ const arrayKey = canonicalizePath(arraySegs).key;
2975
+ const postLen = getArrayLength(arraySegs);
2976
+ const preLen = op.kind === "insert" ? postLen - 1 : op.kind === "remove" ? postLen + 1 : postLen;
2977
+ const ids = ensure(arrayKey, Math.max(0, preLen));
2978
+ switch (op.kind) {
2979
+ case "insert":
2980
+ ids.splice(op.index, 0, allocate());
2981
+ return;
2982
+ case "remove":
2983
+ ids.splice(op.index, 1);
2984
+ return;
2985
+ case "move": {
2986
+ const [moved] = ids.splice(op.from, 1);
2987
+ ids.splice(op.to, 0, moved ?? allocate());
2988
+ return;
2989
+ }
2990
+ case "swap": {
2991
+ const tmp = ids[op.a] ?? allocate();
2992
+ ids[op.a] = ids[op.b] ?? allocate();
2993
+ ids[op.b] = tmp;
2994
+ return;
2995
+ }
2996
+ case "replace-at":
2997
+ ids[op.index] = allocate();
2998
+ return;
2999
+ }
3000
+ },
3001
+ realign(arraySegs) {
3002
+ ensure(canonicalizePath(arraySegs).key, getArrayLength(arraySegs));
3003
+ },
3004
+ hasStructuralChangeUnder(prefix) {
3005
+ for (const arrayKey of tokens.keys()) {
3006
+ if (orderPristineForKey(arrayKey)) continue;
3007
+ const segs = segmentsForPathKey(arrayKey);
3008
+ if (segs === null) continue;
3009
+ if (isPathPrefix(prefix, segs)) return true;
3010
+ }
3011
+ return false;
3012
+ },
3013
+ rebaselineAll() {
3014
+ for (const arrayKey of [...tokens.keys()]) {
3015
+ const segs = segmentsForPathKey(arrayKey);
3016
+ if (segs === null) continue;
3017
+ const ids = ensure(arrayKey, getArrayLength(segs));
3018
+ baselines.set(arrayKey, [...ids]);
3019
+ }
3020
+ }
3021
+ };
3022
+ }
3023
+
3024
+ function remapForOp(op, oldLen) {
3025
+ const moved = /* @__PURE__ */ new Map();
3026
+ const vacated = /* @__PURE__ */ new Set();
3027
+ const fresh = /* @__PURE__ */ new Set();
3028
+ switch (op.kind) {
3029
+ case "insert":
3030
+ for (let i = op.index; i < oldLen; i++) moved.set(i, i + 1);
3031
+ fresh.add(op.index);
3032
+ return { moved, vacated, fresh };
3033
+ case "remove":
3034
+ vacated.add(op.index);
3035
+ for (let i = op.index + 1; i < oldLen; i++) moved.set(i, i - 1);
3036
+ return { moved, vacated, fresh };
3037
+ case "move":
3038
+ if (op.from !== op.to) {
3039
+ moved.set(op.from, op.to);
3040
+ if (op.from < op.to) {
3041
+ for (let i = op.from + 1; i <= op.to; i++) moved.set(i, i - 1);
3042
+ } else {
3043
+ for (let i = op.to; i < op.from; i++) moved.set(i, i + 1);
3044
+ }
3045
+ }
3046
+ return { moved, vacated, fresh };
3047
+ case "swap":
3048
+ if (op.a !== op.b) {
3049
+ moved.set(op.a, op.b);
3050
+ moved.set(op.b, op.a);
3051
+ }
3052
+ return { moved, vacated, fresh };
3053
+ case "replace-at":
3054
+ vacated.add(op.index);
3055
+ fresh.add(op.index);
3056
+ return { moved, vacated, fresh };
3057
+ }
3058
+ }
3059
+ function changedIndices(remap) {
3060
+ const changed = new Set(remap.vacated);
3061
+ for (const [from, to] of remap.moved) {
3062
+ changed.add(from);
3063
+ changed.add(to);
3064
+ }
3065
+ for (const index of remap.fresh) changed.add(index);
3066
+ return changed;
3067
+ }
3068
+ function elementIndexUnder(arrayPath, key, idxPos) {
3069
+ const segments = segmentsForPathKey(key);
3070
+ if (segments === null) return null;
3071
+ if (!isPathPrefix(arrayPath, segments)) return null;
3072
+ if (segments.length <= idxPos) return null;
3073
+ const index = segments[idxPos];
3074
+ return typeof index === "number" ? index : null;
3075
+ }
3076
+ function migrateMapSubtree(map, arrayPath, remap, rewriteValue) {
3077
+ const idxPos = arrayPath.length;
3078
+ const snapshots = [];
3079
+ for (const [key, value] of map) {
3080
+ const index = elementIndexUnder(arrayPath, key, idxPos);
3081
+ if (index === null) continue;
3082
+ if (!remap.moved.has(index) && !remap.vacated.has(index)) continue;
3083
+ snapshots.push({ segments: [...segmentsForPathKey(key)], index, value });
3084
+ }
3085
+ if (snapshots.length === 0) return;
3086
+ for (const snap of snapshots) map.delete(canonicalizePath(snap.segments).key);
3087
+ for (const snap of snapshots) {
3088
+ const target = remap.moved.get(snap.index);
3089
+ if (target === void 0) continue;
3090
+ const relocated = snap.segments.slice();
3091
+ relocated[idxPos] = target;
3092
+ map.set(canonicalizePath(relocated).key, rewriteValue(snap.value, relocated));
3093
+ }
3094
+ }
3095
+ function migrateSetSubtree(set, arrayPath, remap) {
3096
+ const idxPos = arrayPath.length;
3097
+ const snapshots = [];
3098
+ for (const key of set) {
3099
+ const index = elementIndexUnder(arrayPath, key, idxPos);
3100
+ if (index === null) continue;
3101
+ if (!remap.moved.has(index) && !remap.vacated.has(index)) continue;
3102
+ snapshots.push({ segments: [...segmentsForPathKey(key)], index });
3103
+ }
3104
+ if (snapshots.length === 0) return;
3105
+ for (const snap of snapshots) set.delete(canonicalizePath(snap.segments).key);
3106
+ for (const snap of snapshots) {
3107
+ const target = remap.moved.get(snap.index);
3108
+ if (target === void 0) continue;
3109
+ const relocated = snap.segments.slice();
3110
+ relocated[idxPos] = target;
3111
+ set.add(canonicalizePath(relocated).key);
3112
+ }
2821
3113
  }
2822
3114
 
2823
3115
  function isHydratedFieldRecord(value) {
2824
3116
  if (typeof value !== "object" || value === null) return false;
2825
3117
  const r = value;
2826
- 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";
3118
+ 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";
2827
3119
  }
2828
3120
  function isHydratedValidationErrorArray(value) {
2829
3121
  if (!Array.isArray(value)) return false;
@@ -3010,9 +3302,7 @@ function createFormStore(options) {
3010
3302
  noSyncPathCounts.set(path, current - 1);
3011
3303
  }
3012
3304
  const coerceIndex = resolveCoercionIndex(options.coerce);
3013
- const resolvedShouldShowErrors = resolveShouldShowErrors(
3014
- options.shouldShowErrors
3015
- );
3305
+ const resolvedGetDisplayState = resolveGetDisplayState(options.getDisplayState);
3016
3306
  const resolvedIsSensitivePath = options.isSensitivePath ?? isSensitivePath;
3017
3307
  const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ?? segmentMatchesSensitive;
3018
3308
  const cleanupHooks = [];
@@ -3052,6 +3342,16 @@ function createFormStore(options) {
3052
3342
  warn: true
3053
3343
  });
3054
3344
  const form = ref(stubbedInitialData);
3345
+ const arrayIdentity = createArrayIdentity((arraySegs) => {
3346
+ const v = getAtPath(form.value, arraySegs);
3347
+ return Array.isArray(v) ? v.length : 0;
3348
+ });
3349
+ function arrayElementKey(path) {
3350
+ if (path.length === 0) return "";
3351
+ const last = path[path.length - 1];
3352
+ if (typeof last === "number") return arrayIdentity.tokenAt(path.slice(0, -1), last);
3353
+ return "";
3354
+ }
3055
3355
  const fields = reactive(/* @__PURE__ */ new Map());
3056
3356
  const elements = reactive(/* @__PURE__ */ new Map());
3057
3357
  const elementToFormInstance = /* @__PURE__ */ new WeakMap();
@@ -3088,12 +3388,16 @@ function createFormStore(options) {
3088
3388
  }
3089
3389
  function applyArrayOpToMemory(arrayPath, op) {
3090
3390
  switch (op.kind) {
3091
- case "shift-from":
3391
+ case "insert":
3392
+ case "remove":
3092
3393
  clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.index);
3093
3394
  return;
3094
- case "shift-range":
3095
- clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.fromIndex && i <= op.toIndex);
3395
+ case "move": {
3396
+ const lo = Math.min(op.from, op.to);
3397
+ const hi = Math.max(op.from, op.to);
3398
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= lo && i <= hi);
3096
3399
  return;
3400
+ }
3097
3401
  case "swap":
3098
3402
  clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
3099
3403
  return;
@@ -3102,6 +3406,68 @@ function createFormStore(options) {
3102
3406
  return;
3103
3407
  }
3104
3408
  }
3409
+ function migrateArrayElementState(arrayPath, remap) {
3410
+ if (remap.moved.size === 0 && remap.vacated.size === 0) return;
3411
+ migrateMapSubtree(fields, arrayPath, remap, (record, segments) => ({
3412
+ ...record,
3413
+ path: segments
3414
+ }));
3415
+ migrateMapSubtree(
3416
+ userErrors,
3417
+ arrayPath,
3418
+ remap,
3419
+ (errors, segments) => errors.map((error) => ({ ...error, path: [...segments] }))
3420
+ );
3421
+ migrateMapSubtree(originals, arrayPath, remap, (record, segments) => ({
3422
+ segments,
3423
+ value: record.value
3424
+ }));
3425
+ migrateSetSubtree(blankPaths, arrayPath, remap);
3426
+ migrateSetSubtree(originalBlankPaths, arrayPath, remap);
3427
+ }
3428
+ function seedFreshElement(arrayPath, freshIndex) {
3429
+ const elementPath = [...arrayPath, freshIndex];
3430
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3431
+ diffAndApply(void 0, getAtPath(form.value, elementPath), elementPath, (patch) => {
3432
+ if (patch.kind !== "added") return;
3433
+ const { key } = canonicalizePath(patch.path);
3434
+ if (!originals.has(key)) originals.set(key, { segments: patch.path, value: void 0 });
3435
+ touchFieldRecord(key, patch.path, { updatedAt: now });
3436
+ });
3437
+ }
3438
+ function dropSchemaErrorsAtChangedIndices(arrayPath, remap) {
3439
+ const changed = changedIndices(remap);
3440
+ if (changed.size === 0) return;
3441
+ const idxPos = arrayPath.length;
3442
+ for (const key of [...schemaErrors.keys()]) {
3443
+ const segs = segmentsForPathKey(key);
3444
+ if (segs === null) continue;
3445
+ if (!isPathPrefix(arrayPath, segs)) continue;
3446
+ if (segs.length <= idxPos) continue;
3447
+ const idx = segs[idxPos];
3448
+ if (typeof idx === "number" && changed.has(idx)) schemaErrors.delete(key);
3449
+ }
3450
+ }
3451
+ function abortValidationAtVacatedIndices(arrayPath, remap) {
3452
+ if (remap.vacated.size === 0) return;
3453
+ const idxPos = arrayPath.length;
3454
+ for (const [key, entry] of [...fieldValidationState]) {
3455
+ const segs = segmentsForPathKey(key);
3456
+ if (segs === null) continue;
3457
+ if (!isPathPrefix(arrayPath, segs)) continue;
3458
+ if (segs.length <= idxPos) continue;
3459
+ const idx = segs[idxPos];
3460
+ if (typeof idx !== "number" || !remap.vacated.has(idx)) continue;
3461
+ if (entry.timer !== null) {
3462
+ clearTimeout(entry.timer);
3463
+ } else if (!entry.settled) {
3464
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
3465
+ decFieldValidation(key);
3466
+ }
3467
+ entry.controller.abort();
3468
+ fieldValidationState.delete(key);
3469
+ }
3470
+ }
3105
3471
  const pathOrdinals = /* @__PURE__ */ new Map();
3106
3472
  let nextOrdinal = 0;
3107
3473
  function ensurePathOrdinal(key) {
@@ -3139,6 +3505,7 @@ function createFormStore(options) {
3139
3505
  const departAttempts = ref(0);
3140
3506
  const submissionGeneration = ref(0);
3141
3507
  const activeValidations = ref(0);
3508
+ let lastValidatedSnapshot = null;
3142
3509
  const hydrating = ref(false);
3143
3510
  const hydrateError = ref(null);
3144
3511
  const defaultValuesFactory = ref(void 0);
@@ -3209,7 +3576,9 @@ function createFormStore(options) {
3209
3576
  connected: false,
3210
3577
  focused: null,
3211
3578
  blurred: null,
3212
- touched: false
3579
+ touched: false,
3580
+ interacted: false,
3581
+ blurredAfterInteraction: false
3213
3582
  });
3214
3583
  });
3215
3584
  if (strict && !schemaResponse.success) {
@@ -3238,7 +3607,12 @@ function createFormStore(options) {
3238
3607
  blurred: patch.blurred !== void 0 ? patch.blurred : current?.blurred ?? null,
3239
3608
  // touched is plain `boolean`; `??` is equivalent to the explicit
3240
3609
  // guard here because `false` is not nullish.
3241
- touched: patch.touched ?? current?.touched ?? false
3610
+ touched: patch.touched ?? current?.touched ?? false,
3611
+ // interacted is sticky-true; a merge patch only ever sets it, so
3612
+ // `??` preserves the current bit. It flips back to false solely
3613
+ // through the reset paths, which reconstruct the record outright.
3614
+ interacted: patch.interacted ?? current?.interacted ?? false,
3615
+ blurredAfterInteraction: patch.blurredAfterInteraction ?? current?.blurredAfterInteraction ?? false
3242
3616
  });
3243
3617
  }
3244
3618
  function applyFormReplacement(next, meta) {
@@ -3384,12 +3758,20 @@ function createFormStore(options) {
3384
3758
  }
3385
3759
  return true;
3386
3760
  }
3761
+ const oldArrayLength = Array.isArray(currentValue) ? currentValue.length : 0;
3387
3762
  const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
3388
3763
  applyFormReplacement(nextForm, meta);
3389
3764
  if (meta?.arrayOp !== void 0) {
3765
+ const remap = remapForOp(meta.arrayOp, oldArrayLength);
3766
+ migrateArrayElementState(path, remap);
3767
+ for (const freshIndex of remap.fresh) seedFreshElement(path, freshIndex);
3768
+ dropSchemaErrorsAtChangedIndices(path, remap);
3769
+ abortValidationAtVacatedIndices(path, remap);
3390
3770
  applyArrayOpToMemory(path, meta.arrayOp);
3771
+ arrayIdentity.applyOp(path, meta.arrayOp);
3391
3772
  } else if (Array.isArray(value) && Array.isArray(currentValue)) {
3392
3773
  clearVariantMemoryUnderPath(path);
3774
+ arrayIdentity.realign(path);
3393
3775
  }
3394
3776
  const effectiveModeAfterWrite = meta?.instance?.validateOn ?? fieldValidationMode;
3395
3777
  if (effectiveModeAfterWrite === "change") {
@@ -3499,6 +3881,9 @@ function createFormStore(options) {
3499
3881
  const run = () => {
3500
3882
  fresh.timer = null;
3501
3883
  if (controller.signal.aborted) return;
3884
+ if (effectiveMode === "blur") {
3885
+ lastValidatedSnapshot = { value: structuralSnapshot(form.value) };
3886
+ }
3502
3887
  let activeIncremented = false;
3503
3888
  try {
3504
3889
  activeValidations.value += 1;
@@ -3741,25 +4126,47 @@ function createFormStore(options) {
3741
4126
  }
3742
4127
  function markFocused(path, focused, meta) {
3743
4128
  const { key } = canonicalizePath(path);
4129
+ const current = fields.get(key);
3744
4130
  touchFieldRecord(key, path, {
3745
4131
  focused,
3746
4132
  blurred: !focused,
3747
4133
  // `touched` flips to true on blur and stays true thereafter; while
3748
4134
  // a field is currently focused we keep whatever value it held.
3749
- touched: focused ? fields.get(key)?.touched ?? false : true
4135
+ touched: focused ? current?.touched ?? false : true,
4136
+ // `blurredAfterInteraction` flips true on the first blur that lands
4137
+ // after a value edit and stays true. A tab-through blur before any
4138
+ // edit leaves it false (`interacted` is still false at that blur),
4139
+ // which is what keeps a clean tab-through from arming the gate.
4140
+ blurredAfterInteraction: !focused && current?.interacted === true ? true : current?.blurredAfterInteraction ?? false
3750
4141
  });
3751
4142
  const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
3752
4143
  if (!focused && focusMode === "blur") {
3753
- scheduleFieldValidation(path, true, {
3754
- ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3755
- ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3756
- });
4144
+ const firstInteractiveBlur = current?.interacted === true && current.blurredAfterInteraction !== true;
4145
+ const snapshot = lastValidatedSnapshot;
4146
+ let changed = true;
4147
+ if (!firstInteractiveBlur && snapshot !== null) {
4148
+ changed = false;
4149
+ diffAndApply(snapshot.value, form.value, [], () => {
4150
+ changed = true;
4151
+ });
4152
+ }
4153
+ if (changed) {
4154
+ scheduleFieldValidation(path, true, {
4155
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
4156
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
4157
+ });
4158
+ }
3757
4159
  }
3758
4160
  }
3759
4161
  function markTouched(path) {
3760
4162
  const { key } = canonicalizePath(path);
3761
4163
  touchFieldRecord(key, path, { touched: true });
3762
4164
  }
4165
+ function markInteracted(path) {
4166
+ const { key } = canonicalizePath(path);
4167
+ if (fields.get(key)?.interacted === true) return;
4168
+ touchFieldRecord(key, path, { interacted: true });
4169
+ }
3763
4170
  function touchAtPath(segments) {
3764
4171
  const formValue = form.value;
3765
4172
  let touchedAny = false;
@@ -3870,6 +4277,7 @@ function createFormStore(options) {
3870
4277
  const next = resetResponse.data;
3871
4278
  rebuildAuthoredPaths(resetSource, next);
3872
4279
  applyFormReplacement(next);
4280
+ arrayIdentity.rebaselineAll();
3873
4281
  originals.clear();
3874
4282
  diffAndApply({}, next, [], (patch) => {
3875
4283
  if (patch.kind !== "added") return;
@@ -3913,7 +4321,9 @@ function createFormStore(options) {
3913
4321
  connected: record.connected,
3914
4322
  focused: record.focused,
3915
4323
  blurred: record.blurred,
3916
- touched: false
4324
+ touched: false,
4325
+ interacted: false,
4326
+ blurredAfterInteraction: false
3917
4327
  });
3918
4328
  }
3919
4329
  submissionGeneration.value += 1;
@@ -3997,7 +4407,9 @@ function createFormStore(options) {
3997
4407
  connected: record.connected,
3998
4408
  focused: record.focused,
3999
4409
  blurred: record.blurred,
4000
- touched: false
4410
+ touched: false,
4411
+ interacted: false,
4412
+ blurredAfterInteraction: false
4001
4413
  });
4002
4414
  }
4003
4415
  function isPathPrefix(prefix, candidate) {
@@ -4014,6 +4426,9 @@ function createFormStore(options) {
4014
4426
  if (entry === void 0) return true;
4015
4427
  return Object.is(getAtPath(form.value, segments), entry.value);
4016
4428
  }
4429
+ function hasStructuralChangeUnder(path) {
4430
+ return arrayIdentity.hasStructuralChangeUnder(path);
4431
+ }
4017
4432
  function getFieldRecord(path) {
4018
4433
  const { key } = canonicalizePath(path);
4019
4434
  return fields.get(key);
@@ -4057,7 +4472,7 @@ function createFormStore(options) {
4057
4472
  originals,
4058
4473
  schema,
4059
4474
  ssr,
4060
- shouldShowErrors: resolvedShouldShowErrors,
4475
+ getDisplayState: resolvedGetDisplayState,
4061
4476
  submitting,
4062
4477
  activeSubmissions,
4063
4478
  submissionAttempts,
@@ -4080,6 +4495,7 @@ function createFormStore(options) {
4080
4495
  applyFormReplacement,
4081
4496
  setValueAtPath,
4082
4497
  getValueAtPath,
4498
+ arrayElementKey,
4083
4499
  reset,
4084
4500
  resetField,
4085
4501
  clear,
@@ -4096,9 +4512,11 @@ function createFormStore(options) {
4096
4512
  deregisterElement,
4097
4513
  markFocused,
4098
4514
  markTouched,
4515
+ markInteracted,
4099
4516
  touchAtPath,
4100
4517
  markConnectedOptimistically,
4101
4518
  isPristineAtPath,
4519
+ hasStructuralChangeUnder,
4102
4520
  getFieldRecord,
4103
4521
  getOriginalAtPath,
4104
4522
  getFirstErrorElement,
@@ -4315,19 +4733,6 @@ function createHistoryModule(state, config) {
4315
4733
  };
4316
4734
  }
4317
4735
 
4318
- function hashStableString(input, seed = 0) {
4319
- let h1 = 3735928559 ^ seed;
4320
- let h2 = 1103547991 ^ seed;
4321
- for (let i = 0; i < input.length; i++) {
4322
- const ch = input.charCodeAt(i);
4323
- h1 = Math.imul(h1 ^ ch, 2654435761);
4324
- h2 = Math.imul(h2 ^ ch, 1597334677);
4325
- }
4326
- h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
4327
- h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
4328
- return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
4329
- }
4330
-
4331
4736
  const PROTOCOL_VERSION = 1;
4332
4737
  const JOIN_COLLECTION_WINDOW_MS = 50;
4333
4738
  const SNAPSHOT_TIMEOUT_MS = 200;
@@ -4850,8 +5255,8 @@ function useAbstractForm(configuration, options) {
4850
5255
  if (mergedDebounceMs !== void 0) {
4851
5256
  apiOptions.debounceMs = mergedDebounceMs;
4852
5257
  }
4853
- if (merged.shouldShowErrors !== void 0) {
4854
- apiOptions.shouldShowErrors = resolveShouldShowErrors(merged.shouldShowErrors);
5258
+ if (merged.getDisplayState !== void 0) {
5259
+ apiOptions.getDisplayState = merged.getDisplayState;
4855
5260
  }
4856
5261
  if (merged.coerce !== void 0) {
4857
5262
  apiOptions.coerce = merged.coerce;
@@ -4859,6 +5264,9 @@ function useAbstractForm(configuration, options) {
4859
5264
  if (merged.rememberVariants !== void 0) {
4860
5265
  apiOptions.rememberVariants = merged.rememberVariants;
4861
5266
  }
5267
+ if (merged.autoAria !== void 0) {
5268
+ apiOptions.autoAria = merged.autoAria;
5269
+ }
4862
5270
  return buildFormApi(
4863
5271
  state,
4864
5272
  formInstanceId,
@@ -4873,10 +5281,11 @@ function mergeWithDefaults(defaults, configuration) {
4873
5281
  const coerce = configuration.coerce ?? defaults.coerce;
4874
5282
  const validateOn = configuration.validateOn ?? defaults.validateOn;
4875
5283
  const debounceMs = configuration.debounceMs ?? defaults.debounceMs;
4876
- const shouldShowErrors = configuration.shouldShowErrors ?? defaults.shouldShowErrors;
5284
+ const getDisplayState = configuration.getDisplayState ?? defaults.getDisplayState;
4877
5285
  const maxRecursionDepth = configuration.maxRecursionDepth ?? defaults.maxRecursionDepth;
4878
5286
  const sensitiveNames = configuration.sensitiveNames ?? defaults.sensitiveNames;
4879
5287
  const multiTab = configuration.multiTab ?? defaults.multiTab;
5288
+ const autoAria = configuration.autoAria ?? defaults.autoAria;
4880
5289
  return {
4881
5290
  ...configuration,
4882
5291
  ...strict === void 0 ? {} : { strict },
@@ -4886,10 +5295,11 @@ function mergeWithDefaults(defaults, configuration) {
4886
5295
  ...coerce === void 0 ? {} : { coerce },
4887
5296
  ...validateOn === void 0 ? {} : { validateOn },
4888
5297
  ...debounceMs === void 0 ? {} : { debounceMs },
4889
- ...shouldShowErrors === void 0 ? {} : { shouldShowErrors },
5298
+ ...getDisplayState === void 0 ? {} : { getDisplayState },
4890
5299
  ...maxRecursionDepth === void 0 ? {} : { maxRecursionDepth },
4891
5300
  ...sensitiveNames === void 0 ? {} : { sensitiveNames },
4892
- ...multiTab === void 0 ? {} : { multiTab }
5301
+ ...multiTab === void 0 ? {} : { multiTab },
5302
+ ...autoAria === void 0 ? {} : { autoAria }
4893
5303
  };
4894
5304
  }
4895
5305
  const HISTORY_MODULE_KEY = "history";
@@ -4933,7 +5343,7 @@ function buildFreshState(key, schema, configuration, registry) {
4933
5343
  } : {},
4934
5344
  ...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
4935
5345
  ...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
4936
- ...configuration.shouldShowErrors !== void 0 ? { shouldShowErrors: configuration.shouldShowErrors } : {},
5346
+ ...configuration.getDisplayState !== void 0 ? { getDisplayState: configuration.getDisplayState } : {},
4937
5347
  ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {},
4938
5348
  ...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {},
4939
5349
  ...resolvedSegmentMatchesSensitive !== void 0 ? { segmentMatchesSensitive: resolvedSegmentMatchesSensitive } : {}
@@ -6401,5 +6811,5 @@ function warnIfAmbientWizardProviderHadDuplicates() {
6401
6811
  }
6402
6812
  }
6403
6813
 
6404
- export { AttaformErrorCode as A, defaultShouldShowErrors as a, defineCoercion as b, injectWizard as c, defaultCoercionRules as d, isPlainRecord as e, isUnset as f, getAtPath as g, humanize as h, injectForm as i, slimKindOf as j, useAbstractForm as k, lazy as l, useWizard as m, normalizeNumericOption as n, setAtPath as s, unset as u };
6405
- //# sourceMappingURL=attaform.CZ-XtZt_.mjs.map
6814
+ export { AttaformErrorCode as A, defaultDisplayState as a, defineCoercion as b, injectWizard as c, defaultCoercionRules as d, isPlainRecord as e, isUnset as f, getAtPath as g, humanize as h, injectForm as i, slimKindOf as j, useAbstractForm as k, lazy as l, useWizard as m, normalizeNumericOption as n, setAtPath as s, unset as u };
6815
+ //# sourceMappingURL=attaform.BTi-PsHr.mjs.map