attaform 0.18.1 → 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.DsC3rZHG.mjs → attaform.BTi-PsHr.mjs} +544 -134
  21. package/dist/shared/attaform.BTi-PsHr.mjs.map +1 -0
  22. package/dist/shared/{attaform.iTqxvl-P.d.mts → attaform.BTpuvGec.d.ts} +46 -13
  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.DK9aj0N8.d.ts → attaform.BtBmfLQN.d.mts} +46 -13
  26. package/dist/shared/{attaform.Dj9mwbaV.d.mts → attaform.C0uGZQ4M.d.cts} +365 -86
  27. package/dist/shared/{attaform.Dj9mwbaV.d.ts → attaform.C0uGZQ4M.d.mts} +365 -86
  28. package/dist/shared/{attaform.Dj9mwbaV.d.cts → 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.tts_OM7j.d.cts → attaform.CBjmobqk.d.cts} +1 -1
  32. package/dist/shared/{attaform.2b7M2mww.d.mts → attaform.CJ-e9gYI.d.ts} +1 -1
  33. package/dist/shared/{attaform.tsNFcEW7.d.ts → attaform.CRNA0vrd.d.mts} +1 -1
  34. package/dist/shared/{attaform.BDdFdjeX.mjs → attaform.Cghpuav8.mjs} +3 -3
  35. package/dist/shared/{attaform.BDdFdjeX.mjs.map → attaform.Cghpuav8.mjs.map} +1 -1
  36. package/dist/shared/{attaform.CtNUB9nf.mjs → attaform.CiMqJHDm.mjs} +3 -3
  37. package/dist/shared/{attaform.CtNUB9nf.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.Xhg0AYNa.mjs → attaform.CrpjyXdO.mjs} +120 -2
  41. package/dist/shared/attaform.CrpjyXdO.mjs.map +1 -0
  42. package/dist/shared/{attaform.DF8wo-ry.d.ts → attaform.D4I63aBV.d.ts} +1 -1
  43. package/dist/shared/{attaform.DVLB6CAn.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.M33WKVV4.d.cts → attaform.QvygsFGh.d.cts} +1 -1
  49. package/dist/shared/{attaform.Xt0A3QUd.mjs → attaform.a3uBo-gw.mjs} +3 -3
  50. package/dist/shared/{attaform.Xt0A3QUd.mjs.map → attaform.a3uBo-gw.mjs.map} +1 -1
  51. package/dist/shared/{attaform.DoSuaKMd.d.cts → attaform.ePUcKxId.d.cts} +46 -13
  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 +5 -5
  68. package/dist/shared/attaform.BqK_L4gK.cjs.map +0 -1
  69. package/dist/shared/attaform.DsC3rZHG.mjs.map +0 -1
  70. package/dist/shared/attaform.II89Pcf4.cjs.map +0 -1
  71. package/dist/shared/attaform.Xhg0AYNa.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const vue = require('vue');
4
- const paths = require('./attaform.BqK_L4gK.cjs');
4
+ const paths = require('./attaform.BqEfHpVB.cjs');
5
5
 
6
6
  const NOT_FOUND = Symbol("NOT_FOUND");
7
7
  function descendStep(value, segment) {
@@ -436,6 +436,74 @@ function structuralSnapshot(value) {
436
436
  return out;
437
437
  }
438
438
 
439
+ const defaultDisplayState = (field, formMeta) => {
440
+ const gateOpen = formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
441
+ if (!gateOpen) return "idle";
442
+ if (field.validating === true) return "pending";
443
+ const hasOwnError = field.errors.some(
444
+ (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
445
+ );
446
+ if (hasOwnError) return "error";
447
+ if (field.valid === true && field.blank !== true && field.dirty === true) return "success";
448
+ return "idle";
449
+ };
450
+ function resolveGetDisplayState(config) {
451
+ return config ?? defaultDisplayState;
452
+ }
453
+
454
+ const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
455
+ const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
456
+ const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
457
+ const PERSISTENCE_KEY_PREFIX = "attaform:";
458
+ const RESERVED_KEY_PREFIX = "__atta:";
459
+ const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
460
+ const ANONYMOUS_WIZARD_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon-wizard:`;
461
+ const DEFAULT_MAX_RECURSION_DEPTH = 64;
462
+ function normalizeNumericOption(config) {
463
+ const { value, source, allowInfinity, min, defaultValue } = config;
464
+ if (allowInfinity && value === Infinity) return Infinity;
465
+ if (typeof value !== "number" || Number.isNaN(value) || value === Infinity || value === -Infinity) {
466
+ if (paths.__DEV__) {
467
+ const acceptedDescription = allowInfinity ? "a non-negative integer or Infinity" : "a non-negative finite integer";
468
+ console.warn(
469
+ `[attaform] ${source} must be ${acceptedDescription}; got ${String(value)}. Falling back to ${String(defaultValue)}.`
470
+ );
471
+ }
472
+ return defaultValue;
473
+ }
474
+ return Math.max(min, Math.floor(value));
475
+ }
476
+
477
+ function hashStableString(input, seed = 0) {
478
+ let h1 = 3735928559 ^ seed;
479
+ let h2 = 1103547991 ^ seed;
480
+ for (let i = 0; i < input.length; i++) {
481
+ const ch = input.charCodeAt(i);
482
+ h1 = Math.imul(h1 ^ ch, 2654435761);
483
+ h2 = Math.imul(h2 ^ ch, 1597334677);
484
+ }
485
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
486
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
487
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
488
+ }
489
+
490
+ const ANON_STEM = "atta";
491
+ function readableFormKeyStem(formKey) {
492
+ if (formKey === "" || formKey.startsWith(ANONYMOUS_FORM_KEY_PREFIX)) return ANON_STEM;
493
+ const sanitized = formKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
494
+ return sanitized === "" ? ANON_STEM : sanitized;
495
+ }
496
+ function fieldIdToken(formInstanceId, pathKey) {
497
+ return hashStableString(`${formInstanceId}:${pathKey}`).slice(-7);
498
+ }
499
+ function computeFieldIdentity(formInstanceId, formKey, pathKey) {
500
+ const id = `${readableFormKeyStem(formKey)}-${fieldIdToken(formInstanceId, pathKey)}`;
501
+ return {
502
+ id,
503
+ aria: Object.freeze({ errorId: `${id}-error`, descriptionId: `${id}-description` })
504
+ };
505
+ }
506
+
439
507
  const EMPTY_RESOLVED_FIELD_META = Object.freeze({
440
508
  label: "",
441
509
  description: void 0,
@@ -469,21 +537,21 @@ function isUnderStubAncestor(state, segments) {
469
537
  }
470
538
  return false;
471
539
  }
472
- function buildFieldStateAccessor(state, getFormMetaBase, options) {
540
+ function buildFieldStateAccessor(state, formInstanceId, getFormMetaBase, options) {
473
541
  const cache = /* @__PURE__ */ new Map();
474
- const predicate = options?.shouldShowErrors;
542
+ const predicate = options?.getDisplayState;
475
543
  return function getFieldState(pathInput) {
476
544
  const { segments, key } = paths.canonicalizePath(pathInput);
477
545
  const cached = cache.get(key);
478
546
  if (cached !== void 0) return cached;
479
547
  const c = vue.computed(
480
- () => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, getFormMetaBase, predicate) : buildContainerFieldState(state, segments, key, getFormMetaBase, predicate)
548
+ () => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, predicate) : buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, predicate)
481
549
  );
482
550
  cache.set(key, c);
483
551
  return c;
484
552
  };
485
553
  }
486
- function buildLeafFieldStateBase(state, segments, key) {
554
+ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
487
555
  const record = state.fields.get(key);
488
556
  const value = state.getValueAtPath(segments);
489
557
  const original = state.originals.get(key)?.value;
@@ -513,6 +581,8 @@ function buildLeafFieldStateBase(state, segments, key) {
513
581
  focused: record?.focused ?? null,
514
582
  blurred: record?.blurred ?? null,
515
583
  touched: record?.touched ?? false,
584
+ interacted: record?.interacted ?? false,
585
+ blurredAfterInteraction: record?.blurredAfterInteraction ?? false,
516
586
  connected: record?.connected ?? false,
517
587
  element: firstElement,
518
588
  elements: elementsArr,
@@ -521,6 +591,8 @@ function buildLeafFieldStateBase(state, segments, key) {
521
591
  validating,
522
592
  valid,
523
593
  path: segments,
594
+ ...computeFieldIdentity(formInstanceId, state.formKey, key),
595
+ key: state.arrayElementKey(segments),
524
596
  blank: state.blankPaths.has(key),
525
597
  label,
526
598
  description: resolved.description,
@@ -528,11 +600,11 @@ function buildLeafFieldStateBase(state, segments, key) {
528
600
  meta: resolved.meta
529
601
  };
530
602
  }
531
- function buildLeafFieldState(state, segments, key, getFormMetaBase, shouldShowErrors) {
532
- const base = buildLeafFieldStateBase(state, segments, key);
533
- return decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors);
603
+ function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
604
+ const base = buildLeafFieldStateBase(state, segments, key, formInstanceId);
605
+ return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
534
606
  }
535
- function buildContainerFieldStateBase(state, segments, _key) {
607
+ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
536
608
  const formValue = state.form.value;
537
609
  const value = state.getValueAtPath(segments);
538
610
  const original = state.originals.get(paths.canonicalizePath(segments).key)?.value;
@@ -542,6 +614,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
542
614
  let focused = false;
543
615
  let blurred = false;
544
616
  let touched = false;
617
+ let interacted = false;
618
+ let blurredAfterInteraction = false;
545
619
  let connected = false;
546
620
  let validating = false;
547
621
  let updatedAt = null;
@@ -560,6 +634,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
560
634
  if (leafRecord?.focused === true) focused = true;
561
635
  if (leafRecord?.blurred === true) blurred = true;
562
636
  if (leafRecord?.touched === true) touched = true;
637
+ if (leafRecord?.interacted === true) interacted = true;
638
+ if (leafRecord?.blurredAfterInteraction === true) blurredAfterInteraction = true;
563
639
  if (leafRecord?.connected === true) connected = true;
564
640
  if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) validating = true;
565
641
  if (state.pathHasAsyncValidation(entry.segments)) asyncPending = true;
@@ -568,6 +644,10 @@ function buildContainerFieldStateBase(state, segments, _key) {
568
644
  if (updatedAt === null || ts > updatedAt) updatedAt = ts;
569
645
  }
570
646
  }
647
+ if (!dirty && state.hasStructuralChangeUnder(segments)) {
648
+ pristine = false;
649
+ dirty = true;
650
+ }
571
651
  const errors = aggregateErrorsAt(state, segments);
572
652
  if (!asyncPending && state.pathHasAsyncValidation(segments)) asyncPending = true;
573
653
  const gated = asyncPending && !state.firstValidationDone.value;
@@ -583,6 +663,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
583
663
  focused,
584
664
  blurred,
585
665
  touched,
666
+ interacted,
667
+ blurredAfterInteraction,
586
668
  connected,
587
669
  element: null,
588
670
  elements: EMPTY_ELEMENTS,
@@ -591,6 +673,8 @@ function buildContainerFieldStateBase(state, segments, _key) {
591
673
  validating,
592
674
  valid,
593
675
  path: segments,
676
+ ...computeFieldIdentity(formInstanceId, state.formKey, key),
677
+ key: state.arrayElementKey(segments),
594
678
  blank,
595
679
  label,
596
680
  description: resolved.description,
@@ -598,15 +682,29 @@ function buildContainerFieldStateBase(state, segments, _key) {
598
682
  meta: resolved.meta
599
683
  };
600
684
  }
601
- function buildContainerFieldState(state, segments, key, getFormMetaBase, shouldShowErrors) {
602
- const base = buildContainerFieldStateBase(state, segments);
603
- return decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors);
685
+ function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
686
+ const base = buildContainerFieldStateBase(state, segments, key, formInstanceId);
687
+ return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
604
688
  }
605
- function decorateWithDerivedProps(base, state, getFormMetaBase, shouldShowErrors) {
689
+ function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState) {
606
690
  const firstError = base.errors[0];
607
- const predicate = shouldShowErrors ?? state.shouldShowErrors;
608
- const showErrors = base.errors.length > 0 && predicate(base, getFormMetaBase());
609
- return { ...base, showErrors, firstError };
691
+ const predicate = getDisplayState ?? state.getDisplayState;
692
+ const formMeta = getFormMetaBase();
693
+ let displayState;
694
+ try {
695
+ displayState = predicate(base, formMeta);
696
+ } catch {
697
+ displayState = defaultDisplayState(base, formMeta);
698
+ }
699
+ return {
700
+ ...base,
701
+ displayState,
702
+ showErrors: displayState === "error",
703
+ showPending: displayState === "pending",
704
+ showSuccess: displayState === "success",
705
+ showIdle: displayState === "idle",
706
+ firstError
707
+ };
610
708
  }
611
709
  function aggregateErrorsAt(state, prefix) {
612
710
  const formValue = state.form.value;
@@ -988,19 +1086,19 @@ function buildFieldArrayApi(state) {
988
1086
  prepend(path, value) {
989
1087
  const next = readArray(path);
990
1088
  next.unshift(value);
991
- return writeArray(path, next, { kind: "shift-from", index: 0 });
1089
+ return writeArray(path, next, { kind: "insert", index: 0 });
992
1090
  },
993
1091
  insert(path, index, value) {
994
1092
  const next = readArray(path);
995
1093
  next.splice(index, 0, value);
996
1094
  const clampedIndex = Math.max(0, Math.min(index, next.length));
997
- return writeArray(path, next, { kind: "shift-from", index: clampedIndex });
1095
+ return writeArray(path, next, { kind: "insert", index: clampedIndex });
998
1096
  },
999
1097
  remove(path, index) {
1000
1098
  const next = readArray(path);
1001
1099
  if (index < 0 || index >= next.length) return false;
1002
1100
  next.splice(index, 1);
1003
- return writeArray(path, next, { kind: "shift-from", index });
1101
+ return writeArray(path, next, { kind: "remove", index });
1004
1102
  },
1005
1103
  swap(path, a, b) {
1006
1104
  const next = readArray(path);
@@ -1018,11 +1116,7 @@ function buildFieldArrayApi(state) {
1018
1116
  const [item] = next.splice(from, 1);
1019
1117
  const clampedTo = Math.max(0, Math.min(to, next.length));
1020
1118
  next.splice(clampedTo, 0, item);
1021
- return writeArray(path, next, {
1022
- kind: "shift-range",
1023
- fromIndex: Math.min(from, clampedTo),
1024
- toIndex: Math.max(from, clampedTo)
1025
- });
1119
+ return writeArray(path, next, { kind: "move", from, to: clampedTo });
1026
1120
  },
1027
1121
  replace(path, index, value) {
1028
1122
  const next = readArray(path);
@@ -1041,6 +1135,8 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1041
1135
  "focused",
1042
1136
  "blurred",
1043
1137
  "touched",
1138
+ "interacted",
1139
+ "blurredAfterInteraction",
1044
1140
  "connected",
1045
1141
  "element",
1046
1142
  "elements",
@@ -1048,20 +1144,28 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1048
1144
  "errors",
1049
1145
  "validating",
1050
1146
  "valid",
1147
+ "displayState",
1051
1148
  "showErrors",
1149
+ "showPending",
1150
+ "showSuccess",
1151
+ "showIdle",
1052
1152
  "firstError",
1053
1153
  "path",
1154
+ "id",
1155
+ "aria",
1156
+ "key",
1054
1157
  "blank",
1055
1158
  "label",
1056
1159
  "description",
1057
1160
  "placeholder",
1058
1161
  "meta"
1059
1162
  ]);
1060
- function buildFieldStateProxy(state, getFormMetaBase, options) {
1163
+ function buildFieldStateProxy(state, formInstanceId, getFormMetaBase, options) {
1061
1164
  const getFieldStateAt = buildFieldStateAccessor(
1062
1165
  state,
1166
+ formInstanceId,
1063
1167
  getFormMetaBase,
1064
- options?.shouldShowErrors !== void 0 ? { shouldShowErrors: options.shouldShowErrors } : void 0
1168
+ options?.getDisplayState !== void 0 ? { getDisplayState: options.getDisplayState } : void 0
1065
1169
  );
1066
1170
  const snapshotFieldStateAt = (path) => {
1067
1171
  const view = getFieldStateAt(path).value;
@@ -1168,29 +1272,6 @@ function walk$2(value, basePath, schema, snapshotFieldStateAt) {
1168
1272
  return result;
1169
1273
  }
1170
1274
 
1171
- const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
1172
- const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
1173
- const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
1174
- const PERSISTENCE_KEY_PREFIX = "attaform:";
1175
- const RESERVED_KEY_PREFIX = "__atta:";
1176
- const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
1177
- const ANONYMOUS_WIZARD_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon-wizard:`;
1178
- const DEFAULT_MAX_RECURSION_DEPTH = 64;
1179
- function normalizeNumericOption(config) {
1180
- const { value, source, allowInfinity, min, defaultValue } = config;
1181
- if (allowInfinity && value === Infinity) return Infinity;
1182
- if (typeof value !== "number" || Number.isNaN(value) || value === Infinity || value === -Infinity) {
1183
- if (paths.__DEV__) {
1184
- const acceptedDescription = allowInfinity ? "a non-negative integer or Infinity" : "a non-negative finite integer";
1185
- console.warn(
1186
- `[attaform] ${source} must be ${acceptedDescription}; got ${String(value)}. Falling back to ${String(defaultValue)}.`
1187
- );
1188
- }
1189
- return defaultValue;
1190
- }
1191
- return Math.max(min, Math.floor(value));
1192
- }
1193
-
1194
1275
  const PERSISTENCE_MODULE_KEY = "persistence";
1195
1276
  async function getStorageAdapter(storage) {
1196
1277
  if (typeof storage === "object") return storage;
@@ -1996,6 +2077,8 @@ function detachFocusListeners(element) {
1996
2077
  function buildRegister(state, formInstanceId, instanceConfig) {
1997
2078
  const coerceIndex = instanceConfig?.coerce !== void 0 ? resolveCoercionIndex(instanceConfig.coerce) : state.coerceIndex;
1998
2079
  const instanceMeta = instanceConfig?.instanceMeta;
2080
+ const formAutoAria = instanceConfig?.autoAria ?? true;
2081
+ const getDisplayStateAt = instanceConfig?.getDisplayStateAt;
1999
2082
  const withInstanceMeta = (meta) => {
2000
2083
  if (instanceMeta === void 0) return meta;
2001
2084
  return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
@@ -2050,6 +2133,10 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2050
2133
  callSite: paths.captureUserCallSite()
2051
2134
  });
2052
2135
  }
2136
+ const { aria } = computeFieldIdentity(formInstanceId, state.formKey, pathKey);
2137
+ const isRequired = state.schema.isRequiredAtPath(segments);
2138
+ const ariaEnabled = options?.autoAria ?? formAutoAria;
2139
+ const ariaDisplayState = getDisplayStateAt !== void 0 ? vue.computed(() => getDisplayStateAt(segments)) : void 0;
2053
2140
  const internalRv = {
2054
2141
  innerRef,
2055
2142
  displayValue,
@@ -2064,6 +2151,9 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2064
2151
  })
2065
2152
  );
2066
2153
  },
2154
+ markInteracted: () => {
2155
+ state.markInteracted(segments);
2156
+ },
2067
2157
  registerElement: (element) => {
2068
2158
  if (!INTERACTIVE_TAG_NAMES.has(element.tagName)) return;
2069
2159
  const added = state.registerElement(segments, element, formInstanceId);
@@ -2108,7 +2198,12 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2108
2198
  coerce,
2109
2199
  ...coerceElement !== void 0 ? { coerceElement } : {},
2110
2200
  acceptsUndefined,
2111
- acceptsString
2201
+ acceptsString,
2202
+ // --- Aria (internal; consumed by the directive) ---
2203
+ aria,
2204
+ isRequired,
2205
+ ariaEnabled,
2206
+ ...ariaDisplayState !== void 0 ? { ariaDisplayState } : {}
2112
2207
  };
2113
2208
  return vue.shallowReadonly(internalRv);
2114
2209
  };
@@ -2352,15 +2447,34 @@ function buildFormApi(state, formInstanceId, options = {}) {
2352
2447
  if (instanceMeta === void 0) return meta;
2353
2448
  return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
2354
2449
  };
2355
- const registerConfig = {
2356
- ...instanceMeta !== void 0 ? { instanceMeta } : {},
2357
- ...options.coerce !== void 0 ? { coerce: options.coerce } : {}
2450
+ const getFormMetaBase = () => {
2451
+ const rootBase = buildContainerFieldStateBase(state, paths.ROOT_PATH, paths.ROOT_PATH_KEY, formInstanceId);
2452
+ return {
2453
+ ...rootBase,
2454
+ submitting: state.submitting.value,
2455
+ submissionAttempts: state.submissionAttempts.value,
2456
+ departAttempts: state.departAttempts.value,
2457
+ submitError: state.submitError.value,
2458
+ errorCount: rootBase.errors.length,
2459
+ submitted: state.submitted.value,
2460
+ instanceId: formInstanceId
2461
+ };
2358
2462
  };
2359
- const register = buildRegister(
2463
+ const fieldStateAccessorOptions = options.getDisplayState !== void 0 ? { getDisplayState: options.getDisplayState } : void 0;
2464
+ const getRootFieldStateAt = buildFieldStateAccessor(
2360
2465
  state,
2361
2466
  formInstanceId,
2362
- Object.keys(registerConfig).length > 0 ? registerConfig : void 0
2467
+ getFormMetaBase,
2468
+ fieldStateAccessorOptions
2363
2469
  );
2470
+ const getDisplayStateAt = (segments) => getRootFieldStateAt(segments).value.displayState;
2471
+ const registerConfig = {
2472
+ ...instanceMeta !== void 0 ? { instanceMeta } : {},
2473
+ ...options.coerce !== void 0 ? { coerce: options.coerce } : {},
2474
+ ...options.autoAria !== void 0 ? { autoAria: options.autoAria } : {},
2475
+ getDisplayStateAt
2476
+ };
2477
+ const register = buildRegister(state, formInstanceId, registerConfig);
2364
2478
  const processOptions = options.onInvalidSubmit !== void 0 ? { onInvalidSubmit: options.onInvalidSubmit } : {};
2365
2479
  const defaultInvalidSubmitPolicy = options.onInvalidSubmit ?? "focus-first-error";
2366
2480
  const {
@@ -2532,25 +2646,6 @@ function buildFormApi(state, formInstanceId, options = {}) {
2532
2646
  const metaErrors = vue.computed(
2533
2647
  () => aggregateErrorsAt(state, [])
2534
2648
  );
2535
- const getFormMetaBase = () => {
2536
- const rootBase = buildContainerFieldStateBase(state, paths.ROOT_PATH);
2537
- return {
2538
- ...rootBase,
2539
- submitting: state.submitting.value,
2540
- submissionAttempts: state.submissionAttempts.value,
2541
- departAttempts: state.departAttempts.value,
2542
- submitError: state.submitError.value,
2543
- errorCount: rootBase.errors.length,
2544
- submitted: state.submitted.value,
2545
- instanceId: formInstanceId
2546
- };
2547
- };
2548
- const fieldStateAccessorOptions = options.shouldShowErrors !== void 0 ? { shouldShowErrors: options.shouldShowErrors } : void 0;
2549
- const getRootFieldStateAt = buildFieldStateAccessor(
2550
- state,
2551
- getFormMetaBase,
2552
- fieldStateAccessorOptions
2553
- );
2554
2649
  const rootFieldState = getRootFieldStateAt([]);
2555
2650
  const formMeta = vue.readonly(
2556
2651
  vue.reactive({
@@ -2565,6 +2660,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
2565
2660
  focused: vue.computed(() => rootFieldState.value.focused),
2566
2661
  blurred: vue.computed(() => rootFieldState.value.blurred),
2567
2662
  touched: vue.computed(() => rootFieldState.value.touched),
2663
+ interacted: vue.computed(() => rootFieldState.value.interacted),
2664
+ blurredAfterInteraction: vue.computed(() => rootFieldState.value.blurredAfterInteraction),
2568
2665
  connected: vue.computed(() => rootFieldState.value.connected),
2569
2666
  element: vue.computed(() => rootFieldState.value.element),
2570
2667
  elements: vue.computed(() => rootFieldState.value.elements),
@@ -2586,14 +2683,21 @@ function buildFormApi(state, formInstanceId, options = {}) {
2586
2683
  // keep the explicit form-level computation for the gate.
2587
2684
  valid,
2588
2685
  errors: metaErrors,
2589
- // `showErrors` / `firstError` flow through the same root
2590
- // field-state computed as the rest of the FieldState surface,
2591
- // so `form.meta.showErrors` matches `form.fields().showErrors`
2592
- // exactly — the predicate runs once at the root and the result
2593
- // is shared.
2686
+ // `displayState` / the `show*` booleans / `firstError` flow
2687
+ // through the same root field-state computed as the rest of the
2688
+ // FieldState surface, so `form.meta.displayState` matches
2689
+ // `form.fields().displayState` exactly — the predicate runs once
2690
+ // at the root and the result is shared.
2691
+ displayState: vue.computed(() => rootFieldState.value.displayState),
2594
2692
  showErrors: vue.computed(() => rootFieldState.value.showErrors),
2693
+ showPending: vue.computed(() => rootFieldState.value.showPending),
2694
+ showSuccess: vue.computed(() => rootFieldState.value.showSuccess),
2695
+ showIdle: vue.computed(() => rootFieldState.value.showIdle),
2595
2696
  firstError: vue.computed(() => rootFieldState.value.firstError),
2596
2697
  path: vue.computed(() => rootFieldState.value.path),
2698
+ id: vue.computed(() => rootFieldState.value.id),
2699
+ aria: vue.computed(() => rootFieldState.value.aria),
2700
+ key: vue.computed(() => rootFieldState.value.key),
2597
2701
  blank: vue.computed(() => rootFieldState.value.blank),
2598
2702
  label: vue.computed(() => rootFieldState.value.label),
2599
2703
  description: vue.computed(() => rootFieldState.value.description),
@@ -2709,13 +2813,41 @@ function buildFormApi(state, formInstanceId, options = {}) {
2709
2813
  return Object.freeze(view);
2710
2814
  });
2711
2815
  const valuesProxy = buildValuesProxy(state.form);
2712
- const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase, fieldStateAccessorOptions);
2816
+ const fieldStateProxy = buildFieldStateProxy(
2817
+ state,
2818
+ formInstanceId,
2819
+ getFormMetaBase,
2820
+ fieldStateAccessorOptions
2821
+ );
2713
2822
  function gated(fn) {
2714
2823
  return ((...args) => {
2715
2824
  void state.activate();
2716
2825
  return fn(...args);
2717
2826
  });
2718
2827
  }
2828
+ const callTerminal = fieldStateProxy;
2829
+ const EMPTY_FIELD_LIST = Object.freeze([]);
2830
+ function list(path) {
2831
+ const { segments } = paths.canonicalizePath(path);
2832
+ const value = state.getValueAtPath(segments);
2833
+ if (!Array.isArray(value)) return EMPTY_FIELD_LIST;
2834
+ const out = new Array(value.length);
2835
+ for (let i = 0; i < value.length; i += 1) out[i] = callTerminal(`${path}.${i}`);
2836
+ return Object.freeze(out);
2837
+ }
2838
+ const EMPTY_FIELD_RECORD = Object.freeze({});
2839
+ function record(path) {
2840
+ const { segments } = paths.canonicalizePath(path);
2841
+ const value = state.getValueAtPath(segments);
2842
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2843
+ return EMPTY_FIELD_RECORD;
2844
+ }
2845
+ const out = {};
2846
+ for (const key of Object.keys(value)) {
2847
+ out[key] = callTerminal(`${path}.${key}`);
2848
+ }
2849
+ return Object.freeze(out);
2850
+ }
2719
2851
  return {
2720
2852
  handleSubmit: gated(handleSubmit),
2721
2853
  // Callable readonly Proxies (`values`, `fields`, `errors`) and the
@@ -2797,6 +2929,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
2797
2929
  swap: gated(fieldArrays.swap),
2798
2930
  move: gated(fieldArrays.move),
2799
2931
  replace: gated(fieldArrays.replace),
2932
+ list: gated(list),
2933
+ record: gated(record),
2800
2934
  get blankPaths() {
2801
2935
  void state.activate();
2802
2936
  return blankPathsView;
@@ -2804,28 +2938,186 @@ function buildFormApi(state, formInstanceId, options = {}) {
2804
2938
  };
2805
2939
  }
2806
2940
 
2807
- const defaultShouldShowErrors = (field, formMeta) => {
2808
- const hasOwnError = field.errors.some(
2809
- (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
2810
- );
2811
- if (!hasOwnError) return false;
2812
- if (field.validating === true) return false;
2813
- if (formMeta.submissionAttempts > 0) return true;
2814
- return field.touched === true && field.focused !== true;
2815
- };
2816
- const SHOW_ALWAYS = () => true;
2817
- const SHOW_NEVER = () => false;
2818
- function resolveShouldShowErrors(config) {
2819
- if (config === void 0) return defaultShouldShowErrors;
2820
- if (config === true) return SHOW_ALWAYS;
2821
- if (config === false) return SHOW_NEVER;
2822
- return config;
2941
+ function createArrayIdentity(getArrayLength) {
2942
+ const tokens = /* @__PURE__ */ new Map();
2943
+ const baselines = /* @__PURE__ */ new Map();
2944
+ let counter = 0;
2945
+ const allocate = () => `k${(counter++).toString(36)}`;
2946
+ function ensure(arrayKey, expectedLen) {
2947
+ let ids = tokens.get(arrayKey);
2948
+ const firstTrack = ids === void 0;
2949
+ if (ids === void 0) {
2950
+ ids = [];
2951
+ tokens.set(arrayKey, ids);
2952
+ }
2953
+ while (ids.length < expectedLen) ids.push(allocate());
2954
+ if (ids.length > expectedLen) ids.length = expectedLen;
2955
+ if (firstTrack) baselines.set(arrayKey, [...ids]);
2956
+ return ids;
2957
+ }
2958
+ function orderPristineForKey(arrayKey) {
2959
+ const baseline = baselines.get(arrayKey);
2960
+ const current = tokens.get(arrayKey);
2961
+ if (baseline === void 0 || current === void 0) return true;
2962
+ if (baseline.length !== current.length) return false;
2963
+ for (let i = 0; i < current.length; i++) {
2964
+ if (current[i] !== baseline[i]) return false;
2965
+ }
2966
+ return true;
2967
+ }
2968
+ return {
2969
+ tokenAt(arraySegs, index) {
2970
+ const len = getArrayLength(arraySegs);
2971
+ if (index < 0 || index >= len) return "";
2972
+ const ids = ensure(paths.canonicalizePath(arraySegs).key, len);
2973
+ return ids[index] ?? "";
2974
+ },
2975
+ applyOp(arraySegs, op) {
2976
+ const arrayKey = paths.canonicalizePath(arraySegs).key;
2977
+ const postLen = getArrayLength(arraySegs);
2978
+ const preLen = op.kind === "insert" ? postLen - 1 : op.kind === "remove" ? postLen + 1 : postLen;
2979
+ const ids = ensure(arrayKey, Math.max(0, preLen));
2980
+ switch (op.kind) {
2981
+ case "insert":
2982
+ ids.splice(op.index, 0, allocate());
2983
+ return;
2984
+ case "remove":
2985
+ ids.splice(op.index, 1);
2986
+ return;
2987
+ case "move": {
2988
+ const [moved] = ids.splice(op.from, 1);
2989
+ ids.splice(op.to, 0, moved ?? allocate());
2990
+ return;
2991
+ }
2992
+ case "swap": {
2993
+ const tmp = ids[op.a] ?? allocate();
2994
+ ids[op.a] = ids[op.b] ?? allocate();
2995
+ ids[op.b] = tmp;
2996
+ return;
2997
+ }
2998
+ case "replace-at":
2999
+ ids[op.index] = allocate();
3000
+ return;
3001
+ }
3002
+ },
3003
+ realign(arraySegs) {
3004
+ ensure(paths.canonicalizePath(arraySegs).key, getArrayLength(arraySegs));
3005
+ },
3006
+ hasStructuralChangeUnder(prefix) {
3007
+ for (const arrayKey of tokens.keys()) {
3008
+ if (orderPristineForKey(arrayKey)) continue;
3009
+ const segs = paths.segmentsForPathKey(arrayKey);
3010
+ if (segs === null) continue;
3011
+ if (paths.isPathPrefix(prefix, segs)) return true;
3012
+ }
3013
+ return false;
3014
+ },
3015
+ rebaselineAll() {
3016
+ for (const arrayKey of [...tokens.keys()]) {
3017
+ const segs = paths.segmentsForPathKey(arrayKey);
3018
+ if (segs === null) continue;
3019
+ const ids = ensure(arrayKey, getArrayLength(segs));
3020
+ baselines.set(arrayKey, [...ids]);
3021
+ }
3022
+ }
3023
+ };
3024
+ }
3025
+
3026
+ function remapForOp(op, oldLen) {
3027
+ const moved = /* @__PURE__ */ new Map();
3028
+ const vacated = /* @__PURE__ */ new Set();
3029
+ const fresh = /* @__PURE__ */ new Set();
3030
+ switch (op.kind) {
3031
+ case "insert":
3032
+ for (let i = op.index; i < oldLen; i++) moved.set(i, i + 1);
3033
+ fresh.add(op.index);
3034
+ return { moved, vacated, fresh };
3035
+ case "remove":
3036
+ vacated.add(op.index);
3037
+ for (let i = op.index + 1; i < oldLen; i++) moved.set(i, i - 1);
3038
+ return { moved, vacated, fresh };
3039
+ case "move":
3040
+ if (op.from !== op.to) {
3041
+ moved.set(op.from, op.to);
3042
+ if (op.from < op.to) {
3043
+ for (let i = op.from + 1; i <= op.to; i++) moved.set(i, i - 1);
3044
+ } else {
3045
+ for (let i = op.to; i < op.from; i++) moved.set(i, i + 1);
3046
+ }
3047
+ }
3048
+ return { moved, vacated, fresh };
3049
+ case "swap":
3050
+ if (op.a !== op.b) {
3051
+ moved.set(op.a, op.b);
3052
+ moved.set(op.b, op.a);
3053
+ }
3054
+ return { moved, vacated, fresh };
3055
+ case "replace-at":
3056
+ vacated.add(op.index);
3057
+ fresh.add(op.index);
3058
+ return { moved, vacated, fresh };
3059
+ }
3060
+ }
3061
+ function changedIndices(remap) {
3062
+ const changed = new Set(remap.vacated);
3063
+ for (const [from, to] of remap.moved) {
3064
+ changed.add(from);
3065
+ changed.add(to);
3066
+ }
3067
+ for (const index of remap.fresh) changed.add(index);
3068
+ return changed;
3069
+ }
3070
+ function elementIndexUnder(arrayPath, key, idxPos) {
3071
+ const segments = paths.segmentsForPathKey(key);
3072
+ if (segments === null) return null;
3073
+ if (!paths.isPathPrefix(arrayPath, segments)) return null;
3074
+ if (segments.length <= idxPos) return null;
3075
+ const index = segments[idxPos];
3076
+ return typeof index === "number" ? index : null;
3077
+ }
3078
+ function migrateMapSubtree(map, arrayPath, remap, rewriteValue) {
3079
+ const idxPos = arrayPath.length;
3080
+ const snapshots = [];
3081
+ for (const [key, value] of map) {
3082
+ const index = elementIndexUnder(arrayPath, key, idxPos);
3083
+ if (index === null) continue;
3084
+ if (!remap.moved.has(index) && !remap.vacated.has(index)) continue;
3085
+ snapshots.push({ segments: [...paths.segmentsForPathKey(key)], index, value });
3086
+ }
3087
+ if (snapshots.length === 0) return;
3088
+ for (const snap of snapshots) map.delete(paths.canonicalizePath(snap.segments).key);
3089
+ for (const snap of snapshots) {
3090
+ const target = remap.moved.get(snap.index);
3091
+ if (target === void 0) continue;
3092
+ const relocated = snap.segments.slice();
3093
+ relocated[idxPos] = target;
3094
+ map.set(paths.canonicalizePath(relocated).key, rewriteValue(snap.value, relocated));
3095
+ }
3096
+ }
3097
+ function migrateSetSubtree(set, arrayPath, remap) {
3098
+ const idxPos = arrayPath.length;
3099
+ const snapshots = [];
3100
+ for (const key of set) {
3101
+ const index = elementIndexUnder(arrayPath, key, idxPos);
3102
+ if (index === null) continue;
3103
+ if (!remap.moved.has(index) && !remap.vacated.has(index)) continue;
3104
+ snapshots.push({ segments: [...paths.segmentsForPathKey(key)], index });
3105
+ }
3106
+ if (snapshots.length === 0) return;
3107
+ for (const snap of snapshots) set.delete(paths.canonicalizePath(snap.segments).key);
3108
+ for (const snap of snapshots) {
3109
+ const target = remap.moved.get(snap.index);
3110
+ if (target === void 0) continue;
3111
+ const relocated = snap.segments.slice();
3112
+ relocated[idxPos] = target;
3113
+ set.add(paths.canonicalizePath(relocated).key);
3114
+ }
2823
3115
  }
2824
3116
 
2825
3117
  function isHydratedFieldRecord(value) {
2826
3118
  if (typeof value !== "object" || value === null) return false;
2827
3119
  const r = value;
2828
- return Array.isArray(r.path) && (typeof r.updatedAt === "string" || r.updatedAt === null) && typeof r.connected === "boolean" && (typeof r.focused === "boolean" || r.focused === null) && (typeof r.blurred === "boolean" || r.blurred === null) && typeof r.touched === "boolean";
3120
+ 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";
2829
3121
  }
2830
3122
  function isHydratedValidationErrorArray(value) {
2831
3123
  if (!Array.isArray(value)) return false;
@@ -3012,9 +3304,7 @@ function createFormStore(options) {
3012
3304
  noSyncPathCounts.set(path, current - 1);
3013
3305
  }
3014
3306
  const coerceIndex = resolveCoercionIndex(options.coerce);
3015
- const resolvedShouldShowErrors = resolveShouldShowErrors(
3016
- options.shouldShowErrors
3017
- );
3307
+ const resolvedGetDisplayState = resolveGetDisplayState(options.getDisplayState);
3018
3308
  const resolvedIsSensitivePath = options.isSensitivePath ?? paths.isSensitivePath;
3019
3309
  const resolvedSegmentMatchesSensitive = options.segmentMatchesSensitive ?? paths.segmentMatchesSensitive;
3020
3310
  const cleanupHooks = [];
@@ -3054,6 +3344,16 @@ function createFormStore(options) {
3054
3344
  warn: true
3055
3345
  });
3056
3346
  const form = vue.ref(stubbedInitialData);
3347
+ const arrayIdentity = createArrayIdentity((arraySegs) => {
3348
+ const v = getAtPath(form.value, arraySegs);
3349
+ return Array.isArray(v) ? v.length : 0;
3350
+ });
3351
+ function arrayElementKey(path) {
3352
+ if (path.length === 0) return "";
3353
+ const last = path[path.length - 1];
3354
+ if (typeof last === "number") return arrayIdentity.tokenAt(path.slice(0, -1), last);
3355
+ return "";
3356
+ }
3057
3357
  const fields = vue.reactive(/* @__PURE__ */ new Map());
3058
3358
  const elements = vue.reactive(/* @__PURE__ */ new Map());
3059
3359
  const elementToFormInstance = /* @__PURE__ */ new WeakMap();
@@ -3090,12 +3390,16 @@ function createFormStore(options) {
3090
3390
  }
3091
3391
  function applyArrayOpToMemory(arrayPath, op) {
3092
3392
  switch (op.kind) {
3093
- case "shift-from":
3393
+ case "insert":
3394
+ case "remove":
3094
3395
  clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.index);
3095
3396
  return;
3096
- case "shift-range":
3097
- clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= op.fromIndex && i <= op.toIndex);
3397
+ case "move": {
3398
+ const lo = Math.min(op.from, op.to);
3399
+ const hi = Math.max(op.from, op.to);
3400
+ clearVariantMemoryAtArrayIndices(arrayPath, (i) => i >= lo && i <= hi);
3098
3401
  return;
3402
+ }
3099
3403
  case "swap":
3100
3404
  clearVariantMemoryAtArrayIndices(arrayPath, (i) => i === op.a || i === op.b);
3101
3405
  return;
@@ -3104,6 +3408,68 @@ function createFormStore(options) {
3104
3408
  return;
3105
3409
  }
3106
3410
  }
3411
+ function migrateArrayElementState(arrayPath, remap) {
3412
+ if (remap.moved.size === 0 && remap.vacated.size === 0) return;
3413
+ migrateMapSubtree(fields, arrayPath, remap, (record, segments) => ({
3414
+ ...record,
3415
+ path: segments
3416
+ }));
3417
+ migrateMapSubtree(
3418
+ userErrors,
3419
+ arrayPath,
3420
+ remap,
3421
+ (errors, segments) => errors.map((error) => ({ ...error, path: [...segments] }))
3422
+ );
3423
+ migrateMapSubtree(originals, arrayPath, remap, (record, segments) => ({
3424
+ segments,
3425
+ value: record.value
3426
+ }));
3427
+ migrateSetSubtree(blankPaths, arrayPath, remap);
3428
+ migrateSetSubtree(originalBlankPaths, arrayPath, remap);
3429
+ }
3430
+ function seedFreshElement(arrayPath, freshIndex) {
3431
+ const elementPath = [...arrayPath, freshIndex];
3432
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3433
+ diffAndApply(void 0, getAtPath(form.value, elementPath), elementPath, (patch) => {
3434
+ if (patch.kind !== "added") return;
3435
+ const { key } = paths.canonicalizePath(patch.path);
3436
+ if (!originals.has(key)) originals.set(key, { segments: patch.path, value: void 0 });
3437
+ touchFieldRecord(key, patch.path, { updatedAt: now });
3438
+ });
3439
+ }
3440
+ function dropSchemaErrorsAtChangedIndices(arrayPath, remap) {
3441
+ const changed = changedIndices(remap);
3442
+ if (changed.size === 0) return;
3443
+ const idxPos = arrayPath.length;
3444
+ for (const key of [...schemaErrors.keys()]) {
3445
+ const segs = paths.segmentsForPathKey(key);
3446
+ if (segs === null) continue;
3447
+ if (!isPathPrefix(arrayPath, segs)) continue;
3448
+ if (segs.length <= idxPos) continue;
3449
+ const idx = segs[idxPos];
3450
+ if (typeof idx === "number" && changed.has(idx)) schemaErrors.delete(key);
3451
+ }
3452
+ }
3453
+ function abortValidationAtVacatedIndices(arrayPath, remap) {
3454
+ if (remap.vacated.size === 0) return;
3455
+ const idxPos = arrayPath.length;
3456
+ for (const [key, entry] of [...fieldValidationState]) {
3457
+ const segs = paths.segmentsForPathKey(key);
3458
+ if (segs === null) continue;
3459
+ if (!isPathPrefix(arrayPath, segs)) continue;
3460
+ if (segs.length <= idxPos) continue;
3461
+ const idx = segs[idxPos];
3462
+ if (typeof idx !== "number" || !remap.vacated.has(idx)) continue;
3463
+ if (entry.timer !== null) {
3464
+ clearTimeout(entry.timer);
3465
+ } else if (!entry.settled) {
3466
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
3467
+ decFieldValidation(key);
3468
+ }
3469
+ entry.controller.abort();
3470
+ fieldValidationState.delete(key);
3471
+ }
3472
+ }
3107
3473
  const pathOrdinals = /* @__PURE__ */ new Map();
3108
3474
  let nextOrdinal = 0;
3109
3475
  function ensurePathOrdinal(key) {
@@ -3141,6 +3507,7 @@ function createFormStore(options) {
3141
3507
  const departAttempts = vue.ref(0);
3142
3508
  const submissionGeneration = vue.ref(0);
3143
3509
  const activeValidations = vue.ref(0);
3510
+ let lastValidatedSnapshot = null;
3144
3511
  const hydrating = vue.ref(false);
3145
3512
  const hydrateError = vue.ref(null);
3146
3513
  const defaultValuesFactory = vue.ref(void 0);
@@ -3211,7 +3578,9 @@ function createFormStore(options) {
3211
3578
  connected: false,
3212
3579
  focused: null,
3213
3580
  blurred: null,
3214
- touched: false
3581
+ touched: false,
3582
+ interacted: false,
3583
+ blurredAfterInteraction: false
3215
3584
  });
3216
3585
  });
3217
3586
  if (strict && !schemaResponse.success) {
@@ -3240,7 +3609,12 @@ function createFormStore(options) {
3240
3609
  blurred: patch.blurred !== void 0 ? patch.blurred : current?.blurred ?? null,
3241
3610
  // touched is plain `boolean`; `??` is equivalent to the explicit
3242
3611
  // guard here because `false` is not nullish.
3243
- touched: patch.touched ?? current?.touched ?? false
3612
+ touched: patch.touched ?? current?.touched ?? false,
3613
+ // interacted is sticky-true; a merge patch only ever sets it, so
3614
+ // `??` preserves the current bit. It flips back to false solely
3615
+ // through the reset paths, which reconstruct the record outright.
3616
+ interacted: patch.interacted ?? current?.interacted ?? false,
3617
+ blurredAfterInteraction: patch.blurredAfterInteraction ?? current?.blurredAfterInteraction ?? false
3244
3618
  });
3245
3619
  }
3246
3620
  function applyFormReplacement(next, meta) {
@@ -3386,12 +3760,20 @@ function createFormStore(options) {
3386
3760
  }
3387
3761
  return true;
3388
3762
  }
3763
+ const oldArrayLength = Array.isArray(currentValue) ? currentValue.length : 0;
3389
3764
  const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
3390
3765
  applyFormReplacement(nextForm, meta);
3391
3766
  if (meta?.arrayOp !== void 0) {
3767
+ const remap = remapForOp(meta.arrayOp, oldArrayLength);
3768
+ migrateArrayElementState(path, remap);
3769
+ for (const freshIndex of remap.fresh) seedFreshElement(path, freshIndex);
3770
+ dropSchemaErrorsAtChangedIndices(path, remap);
3771
+ abortValidationAtVacatedIndices(path, remap);
3392
3772
  applyArrayOpToMemory(path, meta.arrayOp);
3773
+ arrayIdentity.applyOp(path, meta.arrayOp);
3393
3774
  } else if (Array.isArray(value) && Array.isArray(currentValue)) {
3394
3775
  clearVariantMemoryUnderPath(path);
3776
+ arrayIdentity.realign(path);
3395
3777
  }
3396
3778
  const effectiveModeAfterWrite = meta?.instance?.validateOn ?? fieldValidationMode;
3397
3779
  if (effectiveModeAfterWrite === "change") {
@@ -3501,6 +3883,9 @@ function createFormStore(options) {
3501
3883
  const run = () => {
3502
3884
  fresh.timer = null;
3503
3885
  if (controller.signal.aborted) return;
3886
+ if (effectiveMode === "blur") {
3887
+ lastValidatedSnapshot = { value: structuralSnapshot(form.value) };
3888
+ }
3504
3889
  let activeIncremented = false;
3505
3890
  try {
3506
3891
  activeValidations.value += 1;
@@ -3743,25 +4128,47 @@ function createFormStore(options) {
3743
4128
  }
3744
4129
  function markFocused(path, focused, meta) {
3745
4130
  const { key } = paths.canonicalizePath(path);
4131
+ const current = fields.get(key);
3746
4132
  touchFieldRecord(key, path, {
3747
4133
  focused,
3748
4134
  blurred: !focused,
3749
4135
  // `touched` flips to true on blur and stays true thereafter; while
3750
4136
  // a field is currently focused we keep whatever value it held.
3751
- touched: focused ? fields.get(key)?.touched ?? false : true
4137
+ touched: focused ? current?.touched ?? false : true,
4138
+ // `blurredAfterInteraction` flips true on the first blur that lands
4139
+ // after a value edit and stays true. A tab-through blur before any
4140
+ // edit leaves it false (`interacted` is still false at that blur),
4141
+ // which is what keeps a clean tab-through from arming the gate.
4142
+ blurredAfterInteraction: !focused && current?.interacted === true ? true : current?.blurredAfterInteraction ?? false
3752
4143
  });
3753
4144
  const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
3754
4145
  if (!focused && focusMode === "blur") {
3755
- scheduleFieldValidation(path, true, {
3756
- ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3757
- ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3758
- });
4146
+ const firstInteractiveBlur = current?.interacted === true && current.blurredAfterInteraction !== true;
4147
+ const snapshot = lastValidatedSnapshot;
4148
+ let changed = true;
4149
+ if (!firstInteractiveBlur && snapshot !== null) {
4150
+ changed = false;
4151
+ diffAndApply(snapshot.value, form.value, [], () => {
4152
+ changed = true;
4153
+ });
4154
+ }
4155
+ if (changed) {
4156
+ scheduleFieldValidation(path, true, {
4157
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
4158
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
4159
+ });
4160
+ }
3759
4161
  }
3760
4162
  }
3761
4163
  function markTouched(path) {
3762
4164
  const { key } = paths.canonicalizePath(path);
3763
4165
  touchFieldRecord(key, path, { touched: true });
3764
4166
  }
4167
+ function markInteracted(path) {
4168
+ const { key } = paths.canonicalizePath(path);
4169
+ if (fields.get(key)?.interacted === true) return;
4170
+ touchFieldRecord(key, path, { interacted: true });
4171
+ }
3765
4172
  function touchAtPath(segments) {
3766
4173
  const formValue = form.value;
3767
4174
  let touchedAny = false;
@@ -3872,6 +4279,7 @@ function createFormStore(options) {
3872
4279
  const next = resetResponse.data;
3873
4280
  rebuildAuthoredPaths(resetSource, next);
3874
4281
  applyFormReplacement(next);
4282
+ arrayIdentity.rebaselineAll();
3875
4283
  originals.clear();
3876
4284
  diffAndApply({}, next, [], (patch) => {
3877
4285
  if (patch.kind !== "added") return;
@@ -3915,7 +4323,9 @@ function createFormStore(options) {
3915
4323
  connected: record.connected,
3916
4324
  focused: record.focused,
3917
4325
  blurred: record.blurred,
3918
- touched: false
4326
+ touched: false,
4327
+ interacted: false,
4328
+ blurredAfterInteraction: false
3919
4329
  });
3920
4330
  }
3921
4331
  submissionGeneration.value += 1;
@@ -3999,7 +4409,9 @@ function createFormStore(options) {
3999
4409
  connected: record.connected,
4000
4410
  focused: record.focused,
4001
4411
  blurred: record.blurred,
4002
- touched: false
4412
+ touched: false,
4413
+ interacted: false,
4414
+ blurredAfterInteraction: false
4003
4415
  });
4004
4416
  }
4005
4417
  function isPathPrefix(prefix, candidate) {
@@ -4016,6 +4428,9 @@ function createFormStore(options) {
4016
4428
  if (entry === void 0) return true;
4017
4429
  return Object.is(getAtPath(form.value, segments), entry.value);
4018
4430
  }
4431
+ function hasStructuralChangeUnder(path) {
4432
+ return arrayIdentity.hasStructuralChangeUnder(path);
4433
+ }
4019
4434
  function getFieldRecord(path) {
4020
4435
  const { key } = paths.canonicalizePath(path);
4021
4436
  return fields.get(key);
@@ -4059,7 +4474,7 @@ function createFormStore(options) {
4059
4474
  originals,
4060
4475
  schema,
4061
4476
  ssr,
4062
- shouldShowErrors: resolvedShouldShowErrors,
4477
+ getDisplayState: resolvedGetDisplayState,
4063
4478
  submitting,
4064
4479
  activeSubmissions,
4065
4480
  submissionAttempts,
@@ -4082,6 +4497,7 @@ function createFormStore(options) {
4082
4497
  applyFormReplacement,
4083
4498
  setValueAtPath,
4084
4499
  getValueAtPath,
4500
+ arrayElementKey,
4085
4501
  reset,
4086
4502
  resetField,
4087
4503
  clear,
@@ -4098,9 +4514,11 @@ function createFormStore(options) {
4098
4514
  deregisterElement,
4099
4515
  markFocused,
4100
4516
  markTouched,
4517
+ markInteracted,
4101
4518
  touchAtPath,
4102
4519
  markConnectedOptimistically,
4103
4520
  isPristineAtPath,
4521
+ hasStructuralChangeUnder,
4104
4522
  getFieldRecord,
4105
4523
  getOriginalAtPath,
4106
4524
  getFirstErrorElement,
@@ -4317,19 +4735,6 @@ function createHistoryModule(state, config) {
4317
4735
  };
4318
4736
  }
4319
4737
 
4320
- function hashStableString(input, seed = 0) {
4321
- let h1 = 3735928559 ^ seed;
4322
- let h2 = 1103547991 ^ seed;
4323
- for (let i = 0; i < input.length; i++) {
4324
- const ch = input.charCodeAt(i);
4325
- h1 = Math.imul(h1 ^ ch, 2654435761);
4326
- h2 = Math.imul(h2 ^ ch, 1597334677);
4327
- }
4328
- h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
4329
- h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
4330
- return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, "0");
4331
- }
4332
-
4333
4738
  const PROTOCOL_VERSION = 1;
4334
4739
  const JOIN_COLLECTION_WINDOW_MS = 50;
4335
4740
  const SNAPSHOT_TIMEOUT_MS = 200;
@@ -4852,8 +5257,8 @@ function useAbstractForm(configuration, options) {
4852
5257
  if (mergedDebounceMs !== void 0) {
4853
5258
  apiOptions.debounceMs = mergedDebounceMs;
4854
5259
  }
4855
- if (merged.shouldShowErrors !== void 0) {
4856
- apiOptions.shouldShowErrors = resolveShouldShowErrors(merged.shouldShowErrors);
5260
+ if (merged.getDisplayState !== void 0) {
5261
+ apiOptions.getDisplayState = merged.getDisplayState;
4857
5262
  }
4858
5263
  if (merged.coerce !== void 0) {
4859
5264
  apiOptions.coerce = merged.coerce;
@@ -4861,6 +5266,9 @@ function useAbstractForm(configuration, options) {
4861
5266
  if (merged.rememberVariants !== void 0) {
4862
5267
  apiOptions.rememberVariants = merged.rememberVariants;
4863
5268
  }
5269
+ if (merged.autoAria !== void 0) {
5270
+ apiOptions.autoAria = merged.autoAria;
5271
+ }
4864
5272
  return buildFormApi(
4865
5273
  state,
4866
5274
  formInstanceId,
@@ -4875,10 +5283,11 @@ function mergeWithDefaults(defaults, configuration) {
4875
5283
  const coerce = configuration.coerce ?? defaults.coerce;
4876
5284
  const validateOn = configuration.validateOn ?? defaults.validateOn;
4877
5285
  const debounceMs = configuration.debounceMs ?? defaults.debounceMs;
4878
- const shouldShowErrors = configuration.shouldShowErrors ?? defaults.shouldShowErrors;
5286
+ const getDisplayState = configuration.getDisplayState ?? defaults.getDisplayState;
4879
5287
  const maxRecursionDepth = configuration.maxRecursionDepth ?? defaults.maxRecursionDepth;
4880
5288
  const sensitiveNames = configuration.sensitiveNames ?? defaults.sensitiveNames;
4881
5289
  const multiTab = configuration.multiTab ?? defaults.multiTab;
5290
+ const autoAria = configuration.autoAria ?? defaults.autoAria;
4882
5291
  return {
4883
5292
  ...configuration,
4884
5293
  ...strict === void 0 ? {} : { strict },
@@ -4888,10 +5297,11 @@ function mergeWithDefaults(defaults, configuration) {
4888
5297
  ...coerce === void 0 ? {} : { coerce },
4889
5298
  ...validateOn === void 0 ? {} : { validateOn },
4890
5299
  ...debounceMs === void 0 ? {} : { debounceMs },
4891
- ...shouldShowErrors === void 0 ? {} : { shouldShowErrors },
5300
+ ...getDisplayState === void 0 ? {} : { getDisplayState },
4892
5301
  ...maxRecursionDepth === void 0 ? {} : { maxRecursionDepth },
4893
5302
  ...sensitiveNames === void 0 ? {} : { sensitiveNames },
4894
- ...multiTab === void 0 ? {} : { multiTab }
5303
+ ...multiTab === void 0 ? {} : { multiTab },
5304
+ ...autoAria === void 0 ? {} : { autoAria }
4895
5305
  };
4896
5306
  }
4897
5307
  const HISTORY_MODULE_KEY = "history";
@@ -4935,7 +5345,7 @@ function buildFreshState(key, schema, configuration, registry) {
4935
5345
  } : {},
4936
5346
  ...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
4937
5347
  ...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
4938
- ...configuration.shouldShowErrors !== void 0 ? { shouldShowErrors: configuration.shouldShowErrors } : {},
5348
+ ...configuration.getDisplayState !== void 0 ? { getDisplayState: configuration.getDisplayState } : {},
4939
5349
  ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {},
4940
5350
  ...resolvedIsSensitivePath !== void 0 ? { isSensitivePath: resolvedIsSensitivePath } : {},
4941
5351
  ...resolvedSegmentMatchesSensitive !== void 0 ? { segmentMatchesSensitive: resolvedSegmentMatchesSensitive } : {}
@@ -6405,7 +6815,7 @@ function warnIfAmbientWizardProviderHadDuplicates() {
6405
6815
 
6406
6816
  exports.AttaformErrorCode = AttaformErrorCode;
6407
6817
  exports.defaultCoercionRules = defaultCoercionRules;
6408
- exports.defaultShouldShowErrors = defaultShouldShowErrors;
6818
+ exports.defaultDisplayState = defaultDisplayState;
6409
6819
  exports.defineCoercion = defineCoercion;
6410
6820
  exports.getAtPath = getAtPath;
6411
6821
  exports.humanize = humanize;
@@ -6420,4 +6830,4 @@ exports.slimKindOf = slimKindOf;
6420
6830
  exports.unset = unset;
6421
6831
  exports.useAbstractForm = useAbstractForm;
6422
6832
  exports.useWizard = useWizard;
6423
- //# sourceMappingURL=attaform.II89Pcf4.cjs.map
6833
+ //# sourceMappingURL=attaform.C1msmO2v.cjs.map