attaform 0.24.4 → 0.26.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 (92) hide show
  1. package/dist/chunks/devtools.cjs +1 -1
  2. package/dist/chunks/devtools.mjs +1 -1
  3. package/dist/chunks/fingerprint2.cjs +1 -1
  4. package/dist/chunks/fingerprint2.mjs +1 -1
  5. package/dist/index.cjs +4 -4
  6. package/dist/index.d.cts +5 -5
  7. package/dist/index.d.mts +5 -5
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.mjs +4 -4
  10. package/dist/nuxt.d.cts +1 -1
  11. package/dist/nuxt.d.mts +1 -1
  12. package/dist/nuxt.d.ts +1 -1
  13. package/dist/runtime/plugins/attaform.cjs +2 -2
  14. package/dist/runtime/plugins/attaform.mjs +2 -2
  15. package/dist/shared/{attaform.CO0e7YVY.d.cts → attaform.55hFR_cl.d.ts} +3 -0
  16. package/dist/shared/{attaform.Ib7oSpxj.d.cts → attaform.B71rfNL5.d.mts} +1 -1
  17. package/dist/shared/{attaform.DTn1J0Eq.d.mts → attaform.BFfY7HRI.d.cts} +1 -1
  18. package/dist/shared/{attaform.CAVK56f8.cjs → attaform.BPl-ECRT.cjs} +49 -2
  19. package/dist/shared/attaform.BPl-ECRT.cjs.map +1 -0
  20. package/dist/shared/{attaform.CBMI57RP.cjs → attaform.BQflyVOL.cjs} +3 -3
  21. package/dist/shared/{attaform.CBMI57RP.cjs.map → attaform.BQflyVOL.cjs.map} +1 -1
  22. package/dist/shared/{attaform.DWKcw1ud.cjs → attaform.BRzKBj2g.cjs} +101 -4
  23. package/dist/shared/attaform.BRzKBj2g.cjs.map +1 -0
  24. package/dist/shared/{attaform.EeVCVnoH.d.ts → attaform.Be5otUVw.d.ts} +1 -1
  25. package/dist/shared/{attaform.iJ4y3sHU.mjs → attaform.BoYqnIPI.mjs} +223 -81
  26. package/dist/shared/attaform.BoYqnIPI.mjs.map +1 -0
  27. package/dist/shared/{attaform.B1N47eDM.d.ts → attaform.CEY_WLRJ.d.mts} +127 -56
  28. package/dist/shared/{attaform.CwqTCeTZ.mjs → attaform.CSXDW2tO.mjs} +4 -4
  29. package/dist/shared/{attaform.CwqTCeTZ.mjs.map → attaform.CSXDW2tO.mjs.map} +1 -1
  30. package/dist/shared/{attaform.lcTiMoma.mjs → attaform.CXVmZtFq.mjs} +3 -3
  31. package/dist/shared/{attaform.lcTiMoma.mjs.map → attaform.CXVmZtFq.mjs.map} +1 -1
  32. package/dist/shared/{attaform.CE-KJZ0j.cjs → attaform.Cs0UZ7q6.cjs} +4 -4
  33. package/dist/shared/{attaform.CE-KJZ0j.cjs.map → attaform.Cs0UZ7q6.cjs.map} +1 -1
  34. package/dist/shared/{attaform.AkX9wzfY.cjs → attaform.D51IwDtP.cjs} +2 -2
  35. package/dist/shared/{attaform.AkX9wzfY.cjs.map → attaform.D51IwDtP.cjs.map} +1 -1
  36. package/dist/shared/{attaform.QFrCtJBO.d.mts → attaform.DBNjHgNj.d.cts} +86 -17
  37. package/dist/shared/{attaform.QFrCtJBO.d.cts → attaform.DBNjHgNj.d.mts} +86 -17
  38. package/dist/shared/{attaform.QFrCtJBO.d.ts → attaform.DBNjHgNj.d.ts} +86 -17
  39. package/dist/shared/{attaform.DwsfkfSY.mjs → attaform.DGhIix5D.mjs} +48 -3
  40. package/dist/shared/attaform.DGhIix5D.mjs.map +1 -0
  41. package/dist/shared/{attaform.CO0e7YVY.d.mts → attaform.DJRz_bzM.d.cts} +3 -0
  42. package/dist/shared/{attaform.ChY7tIrn.mjs → attaform.DSOIz08n.mjs} +4 -4
  43. package/dist/shared/{attaform.ChY7tIrn.mjs.map → attaform.DSOIz08n.mjs.map} +1 -1
  44. package/dist/shared/{attaform.Buedl_UL.cjs → attaform.Du9RZp8m.cjs} +223 -81
  45. package/dist/shared/attaform.Du9RZp8m.cjs.map +1 -0
  46. package/dist/shared/{attaform.CO0e7YVY.d.ts → attaform.JPsaqjLl.d.mts} +3 -0
  47. package/dist/shared/{attaform.CqW4gF_Z.d.cts → attaform.MgaYKlPP.d.cts} +127 -56
  48. package/dist/shared/{attaform.CZpUm-2l.mjs → attaform.NqYEzRcM.mjs} +2 -2
  49. package/dist/shared/{attaform.CZpUm-2l.mjs.map → attaform.NqYEzRcM.mjs.map} +1 -1
  50. package/dist/shared/{attaform.WvcckZMD.mjs → attaform.Vp2-CWZB.mjs} +3 -2
  51. package/dist/shared/attaform.Vp2-CWZB.mjs.map +1 -0
  52. package/dist/shared/{attaform.CjdepGnw.cjs → attaform.ZXZ6dH72.cjs} +3 -1
  53. package/dist/shared/attaform.ZXZ6dH72.cjs.map +1 -0
  54. package/dist/shared/{attaform.f0l5ydy1.d.mts → attaform.ceYeVkjn.d.ts} +127 -56
  55. package/dist/shared/{attaform.QwLSdxuk.mjs → attaform.k9V0py0s.mjs} +101 -5
  56. package/dist/shared/attaform.k9V0py0s.mjs.map +1 -0
  57. package/dist/shared/{attaform.Bnm0FQaH.cjs → attaform.tZ0YKisf.cjs} +4 -4
  58. package/dist/shared/{attaform.Bnm0FQaH.cjs.map → attaform.tZ0YKisf.cjs.map} +1 -1
  59. package/dist/transforms.cjs +2 -1
  60. package/dist/transforms.cjs.map +1 -1
  61. package/dist/transforms.d.cts +13 -1
  62. package/dist/transforms.d.mts +13 -1
  63. package/dist/transforms.d.ts +13 -1
  64. package/dist/transforms.mjs +1 -1
  65. package/dist/vite.cjs +2 -1
  66. package/dist/vite.cjs.map +1 -1
  67. package/dist/vite.mjs +2 -1
  68. package/dist/vite.mjs.map +1 -1
  69. package/dist/zod-v3.cjs +2 -2
  70. package/dist/zod-v3.d.cts +3 -3
  71. package/dist/zod-v3.d.mts +3 -3
  72. package/dist/zod-v3.d.ts +3 -3
  73. package/dist/zod-v3.mjs +2 -2
  74. package/dist/zod-v4.cjs +2 -2
  75. package/dist/zod-v4.d.cts +6 -6
  76. package/dist/zod-v4.d.mts +6 -6
  77. package/dist/zod-v4.d.ts +6 -6
  78. package/dist/zod-v4.mjs +2 -2
  79. package/dist/zod.cjs +5 -5
  80. package/dist/zod.d.cts +5 -5
  81. package/dist/zod.d.mts +5 -5
  82. package/dist/zod.d.ts +5 -5
  83. package/dist/zod.mjs +5 -5
  84. package/package.json +1 -1
  85. package/dist/shared/attaform.Buedl_UL.cjs.map +0 -1
  86. package/dist/shared/attaform.CAVK56f8.cjs.map +0 -1
  87. package/dist/shared/attaform.CjdepGnw.cjs.map +0 -1
  88. package/dist/shared/attaform.DWKcw1ud.cjs.map +0 -1
  89. package/dist/shared/attaform.DwsfkfSY.mjs.map +0 -1
  90. package/dist/shared/attaform.QwLSdxuk.mjs.map +0 -1
  91. package/dist/shared/attaform.WvcckZMD.mjs.map +0 -1
  92. package/dist/shared/attaform.iJ4y3sHU.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, nextTick, warn, readonly, toRaw, reactive, watch, markRaw, triggerRef, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, onBeforeMount, onBeforeUpdate, onMounted, effectScope } from 'vue';
2
- import { n as pathsEqual, j as isPathPrefix, _ as __DEV__, f as canonicalizePath, s as segmentsForPathKey, o as keyForSegments, b as ROOT_PATH_KEY, S as SubmitErrorHandlerError, t as toError, q as INTERACTIVE_TAG_NAMES, R as ROOT_PATH, r as coerceToPathKey, a as InvalidUseFormConfigError, w as ensureAttaformInstalled, u as useRegistry, x as kFormContext, y as kFormInstanceId, d as ReservedFormKeyError, l as kAttaformWizardActiveStepResolver, z as kAttaformAncestorWizard } from './attaform.DwsfkfSY.mjs';
3
- import { R as REGISTER_OWNER_MARKER, V as V_REGISTER_MARKER } from './attaform.WvcckZMD.mjs';
2
+ import { n as pathsEqual, j as isPathPrefix, _ as __DEV__, f as canonicalizePath, s as segmentsForPathKey, o as keyForSegments, b as ROOT_PATH_KEY, S as SubmitErrorHandlerError, t as toError, q as normalizeErrorInput, r as INTERACTIVE_TAG_NAMES, R as ROOT_PATH, w as normalizeErrorInputs, x as coerceToPathKey, a as InvalidUseFormConfigError, y as ensureAttaformInstalled, u as useRegistry, z as kFormContext, B as kFormInstanceId, d as ReservedFormKeyError, l as kAttaformWizardActiveStepResolver, C as kAttaformAncestorWizard } from './attaform.DGhIix5D.mjs';
3
+ import { R as REGISTER_OWNER_MARKER, b as V_REGISTER_MARKER } from './attaform.Vp2-CWZB.mjs';
4
4
 
5
5
  function safeAssign(target, key, value) {
6
6
  if (key === "__proto__") {
@@ -582,7 +582,18 @@ const AttaformErrorCode = {
582
582
  * produced `ValidationError` carrying a stable, branchable identifier.
583
583
  * Override it per error by passing your own `code` (`api:…`, `auth:…`).
584
584
  */
585
- UserError: "atta:user-error"
585
+ UserError: "atta:user-error",
586
+ /**
587
+ * Stamped on a `ValidationError` synthesized from a throw or rejection
588
+ * out of a `handleSubmit` `onSubmit` callback. The raw error also lands
589
+ * on `form.meta.submitError` (a raw-`Error` inspection channel); this
590
+ * code marks the copy piped into the user-error layer so the failure
591
+ * also surfaces on `form.errors` / `meta.ownErrors` / `firstOwnError`
592
+ * where the UI already reads. A throw carrying its own `path` / `code`
593
+ * keeps them; a bare `Error` or a non-Error throw defaults here at the
594
+ * form-level path `[]`.
595
+ */
596
+ SubmitError: "atta:submit-error"
586
597
  };
587
598
  function makeBlankRequiredError(segments, formKey) {
588
599
  return {
@@ -790,6 +801,11 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
790
801
  elements: elementsArr,
791
802
  updatedAt: record?.updatedAt ?? null,
792
803
  errors,
804
+ // A leaf has no descendants, so its own-bucket errors ARE its subtree
805
+ // errors. Reuse the same array reference: referential `ownErrors ===
806
+ // errors`, and no second read of the whole-form `derivedBlankErrors`
807
+ // map (preserves the P3 blank-reactivity optimization above).
808
+ ownErrors: errors,
793
809
  validating,
794
810
  valid,
795
811
  transforming,
@@ -968,6 +984,12 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
968
984
  elements: EMPTY_ELEMENTS,
969
985
  updatedAt,
970
986
  errors,
987
+ // The container's OWN bucket only (this exact path), excluding every
988
+ // descendant that `errors` rolls up: a cross-field `.refine()` here,
989
+ // or `setErrors` pinned at this node. The same raw union the leaf
990
+ // builds from the three stores; the container already depends on all
991
+ // three via `aggregateErrorsAt`, so there's no new reactivity cost.
992
+ ownErrors: state.getErrorsForPath(segments),
971
993
  validating,
972
994
  valid,
973
995
  transforming,
@@ -1006,6 +1028,7 @@ function buildContainerFieldState(state, segments, key, formInstanceId, getFormM
1006
1028
  }
1007
1029
  function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingSince, transformingSince, revealedDescendantError, isRoot, getDisplayState) {
1008
1030
  const firstError = base.errors[0];
1031
+ const firstOwnError = base.ownErrors[0];
1009
1032
  const predicate = getDisplayState ?? state.getDisplayState;
1010
1033
  const formMeta = getFormMetaBase();
1011
1034
  const ctx = {
@@ -1041,7 +1064,8 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingS
1041
1064
  showPending: displayState === "pending",
1042
1065
  showSuccess: displayState === "success",
1043
1066
  showIdle: displayState === "idle",
1044
- firstError
1067
+ firstError,
1068
+ firstOwnError
1045
1069
  };
1046
1070
  }
1047
1071
  function aggregateErrorsAt(state, prefix) {
@@ -1410,17 +1434,15 @@ function buildErrorsProxy(state) {
1410
1434
  // shared `aggregateErrorsAt` helper that `form.meta.errors` and
1411
1435
  // `form.fields(path).errors` also use, so the surfaces never drift.
1412
1436
  //
1413
- // The root is the one carve-out. An EXPLICIT root path
1414
- // (`errors([])`) returns ONLY the global `[]` bucket (root
1415
- // `.refine()`, hydration failures, `setErrors`) via
1416
- // `getErrorsForPath`, giving consumers a dedicated channel for
1417
- // global messages undiluted by field errors. The no-arg `errors()`
1418
- // instead resolves the FULL aggregate through `resolveRootCall`
1419
- // below (identical to `meta.errors`).
1420
- resolveCallTarget: (path) => path.length === 0 ? state.getErrorsForPath([]) : aggregateErrorsAt(state, path),
1437
+ // The root is uniform with every other path: `errors([])` is the
1438
+ // subtree aggregate at `[]`, which is the whole form, identical to
1439
+ // the no-arg `errors()` and to `meta.errors`. The root `[]` bucket
1440
+ // alone (root `.refine()`, hydration failures, path-less
1441
+ // `setErrors`) is read through `meta.ownErrors` / `meta.firstOwnError`,
1442
+ // the dedicated own-bucket accessor, not a call-form carve-out.
1443
+ resolveCallTarget: (path) => aggregateErrorsAt(state, path),
1421
1444
  // No-arg `errors()` = the whole-form aggregate, matching
1422
- // `meta.errors`. Distinct from `errors([])` (global bucket only);
1423
- // see `resolveCallTarget`.
1445
+ // `meta.errors` and `errors([])`. All three are the same aggregate.
1424
1446
  resolveRootCall: () => aggregateErrorsAt(state, []),
1425
1447
  // Enumeration unions the live form-data keys at this path with the
1426
1448
  // first-child segments drawn from every error store. Without the
@@ -1604,6 +1626,7 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1604
1626
  "elements",
1605
1627
  "updatedAt",
1606
1628
  "errors",
1629
+ "ownErrors",
1607
1630
  "validating",
1608
1631
  "valid",
1609
1632
  "transforming",
@@ -1615,6 +1638,7 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1615
1638
  "showSuccess",
1616
1639
  "showIdle",
1617
1640
  "firstError",
1641
+ "firstOwnError",
1618
1642
  "path",
1619
1643
  "id",
1620
1644
  "aria",
@@ -1756,6 +1780,32 @@ function walk$1(value, basePath, schema, snapshotFieldStateAt) {
1756
1780
  }
1757
1781
 
1758
1782
  const warnedNoScopeStores = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
1783
+ function isErrorInputLike(value) {
1784
+ if (value instanceof Error) return true;
1785
+ if (typeof value !== "object" || value === null) return false;
1786
+ const shape = value;
1787
+ return typeof shape.message === "string" && shape.message.length > 0 || Array.isArray(shape.path);
1788
+ }
1789
+ function deriveSubmitErrors(err, formKey) {
1790
+ if (Array.isArray(err) && err.length > 0 && err.every(isErrorInputLike)) {
1791
+ return {
1792
+ entries: err.map(
1793
+ (item) => normalizeErrorInput(item, void 0, formKey, AttaformErrorCode.SubmitError)
1794
+ ),
1795
+ messageless: false
1796
+ };
1797
+ }
1798
+ if (isErrorInputLike(err)) {
1799
+ return {
1800
+ entries: [normalizeErrorInput(err, void 0, formKey, AttaformErrorCode.SubmitError)],
1801
+ messageless: false
1802
+ };
1803
+ }
1804
+ return {
1805
+ entries: [normalizeErrorInput({}, void 0, formKey, AttaformErrorCode.SubmitError)],
1806
+ messageless: true
1807
+ };
1808
+ }
1759
1809
  function buildProcessForm(state, formInstanceId, options = {}) {
1760
1810
  const invalidPolicy = options.onInvalidSubmit ?? "focus-first-error";
1761
1811
  function validate(pathInput) {
@@ -1937,13 +1987,6 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1937
1987
  if (state.submissionGeneration.value === genAtEntry) {
1938
1988
  applyInvalidSubmitPolicy(state, formInstanceId, invalidPolicy);
1939
1989
  }
1940
- if (onError !== void 0) {
1941
- try {
1942
- await onError(Array.from(state.userErrors.values()).flat());
1943
- } catch (cause) {
1944
- throw new SubmitErrorHandlerError("User-provided onError threw", { cause });
1945
- }
1946
- }
1947
1990
  return;
1948
1991
  }
1949
1992
  if (state.submissionGeneration.value === genAtEntry) {
@@ -1953,6 +1996,25 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1953
1996
  } catch (err) {
1954
1997
  if (state.submissionGeneration.value === genAtEntry) {
1955
1998
  state.submitError.value = toError(err);
1999
+ if (!(err instanceof SubmitErrorHandlerError)) {
2000
+ const { entries, messageless } = deriveSubmitErrors(err, state.formKey);
2001
+ const byPath = /* @__PURE__ */ new Map();
2002
+ for (const entry of entries) {
2003
+ const { key } = canonicalizePath(entry.path);
2004
+ const bucket = byPath.get(key);
2005
+ if (bucket === void 0) byPath.set(key, { segments: entry.path, entries: [entry] });
2006
+ else bucket.entries.push(entry);
2007
+ }
2008
+ for (const { segments, entries: bucket } of byPath.values()) {
2009
+ state.setUserErrorsForPath(segments, bucket);
2010
+ }
2011
+ applyInvalidSubmitPolicy(state, formInstanceId, invalidPolicy);
2012
+ if (__DEV__ && messageless) {
2013
+ console.warn(
2014
+ "[attaform] handleSubmit callback threw a non-Error value; throw an Error or a ValidationError ({ message, path? }) so the failure surfaces with a usable message."
2015
+ );
2016
+ }
2017
+ }
1956
2018
  }
1957
2019
  } finally {
1958
2020
  if (!validationSettled) {
@@ -2018,11 +2080,11 @@ function applyInvalidSubmitPolicy(state, formInstanceId, policy) {
2018
2080
  return;
2019
2081
  }
2020
2082
  if (policy === "focus-first-error") {
2021
- target.element.focus();
2083
+ target.element.focus({ focusVisible: true });
2022
2084
  return;
2023
2085
  }
2024
2086
  target.element.scrollIntoView();
2025
- target.element.focus({ preventScroll: true });
2087
+ target.element.focus({ preventScroll: true, focusVisible: true });
2026
2088
  }
2027
2089
 
2028
2090
  const warnedRejections = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
@@ -2814,6 +2876,9 @@ function buildFormApi(state, formInstanceId, options = {}) {
2814
2876
  get errors() {
2815
2877
  return rootBase().errors;
2816
2878
  },
2879
+ get ownErrors() {
2880
+ return rootBase().ownErrors;
2881
+ },
2817
2882
  get validating() {
2818
2883
  return rootBase().validating;
2819
2884
  },
@@ -2977,28 +3042,6 @@ function buildFormApi(state, formInstanceId, options = {}) {
2977
3042
  return true;
2978
3043
  }
2979
3044
  const errorsProxy = buildErrorsProxy(state);
2980
- function normalizeErrorInput(item, scope) {
2981
- if (item instanceof Error) {
2982
- return {
2983
- message: item.message.length > 0 ? item.message : "Unknown error",
2984
- path: scope !== void 0 ? [...scope] : [],
2985
- formKey: state.formKey,
2986
- code: AttaformErrorCode.UserError
2987
- };
2988
- }
2989
- const entry = {
2990
- message: typeof item.message === "string" && item.message.length > 0 ? item.message : "Unknown error",
2991
- path: scope !== void 0 ? [...scope] : Array.isArray(item.path) ? [...item.path] : [],
2992
- formKey: state.formKey,
2993
- code: typeof item.code === "string" && item.code.length > 0 ? item.code : AttaformErrorCode.UserError
2994
- };
2995
- if (item.data !== void 0) entry.data = item.data;
2996
- return entry;
2997
- }
2998
- function normalizeErrorInputs(input, scope) {
2999
- const items = Array.isArray(input) ? input : [input];
3000
- return items.map((item) => normalizeErrorInput(item, scope));
3001
- }
3002
3045
  function flattenUserErrors() {
3003
3046
  const all = [];
3004
3047
  for (const errs of state.userErrors.values()) all.push(...errs);
@@ -3010,12 +3053,17 @@ function buildFormApi(state, formInstanceId, options = {}) {
3010
3053
  const { segments, key } = canonicalizePath(arg1);
3011
3054
  const input2 = arg2;
3012
3055
  const resolved2 = typeof input2 === "function" ? input2((state.userErrors.get(key) ?? []).slice()) : input2;
3013
- state.setUserErrorsForPath(segments, normalizeErrorInputs(resolved2, segments));
3056
+ state.setUserErrorsForPath(
3057
+ segments,
3058
+ normalizeErrorInputs(resolved2, segments, state.formKey, AttaformErrorCode.UserError)
3059
+ );
3014
3060
  return;
3015
3061
  }
3016
3062
  const input = arg1;
3017
3063
  const resolved = typeof input === "function" ? input(flattenUserErrors()) : input;
3018
- state.setAllUserErrors(normalizeErrorInputs(resolved, void 0));
3064
+ state.setAllUserErrors(
3065
+ normalizeErrorInputs(resolved, void 0, state.formKey, AttaformErrorCode.UserError)
3066
+ );
3019
3067
  }
3020
3068
  function clearErrors(path) {
3021
3069
  if (path === void 0) {
@@ -3110,6 +3158,17 @@ function buildFormApi(state, formInstanceId, options = {}) {
3110
3158
  // keep the explicit form-level computation for the gate.
3111
3159
  valid,
3112
3160
  errors: metaErrors,
3161
+ // The root `[]` bucket alone: form-level errors from a root
3162
+ // `.refine()` or a path-less `setErrors`, excluding every field error
3163
+ // that `errors` (the whole-form aggregate) rolls up. This is the
3164
+ // banner accessor. Mirrors `rootFieldState` rather than taking a
3165
+ // dedicated computed: `getFieldState([])` routes a container root
3166
+ // through `getErrorsForPath([])` and a primitive root through its own
3167
+ // bucket, and both equal the root bucket, so the root field state is
3168
+ // already the single source of truth.
3169
+ get ownErrors() {
3170
+ return rootFieldState.value.ownErrors;
3171
+ },
3113
3172
  // Whole-form transforming mirrors the global `activeTransforms`
3114
3173
  // counter ORed with any per-leaf transform in flight (the root
3115
3174
  // rollup), exactly as `validating` composes its lifecycle and
@@ -3148,6 +3207,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
3148
3207
  get firstError() {
3149
3208
  return rootFieldState.value.firstError;
3150
3209
  },
3210
+ // `ownErrors[0]`: the form's first top-level error. Pair it with
3211
+ // `ownErrors` for the form-level banner.
3212
+ get firstOwnError() {
3213
+ return rootFieldState.value.firstOwnError;
3214
+ },
3151
3215
  get path() {
3152
3216
  return rootFieldState.value.path;
3153
3217
  },
@@ -3227,7 +3291,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
3227
3291
  const focusFirstError = (options2) => {
3228
3292
  const target = state.getFirstErrorElement(formInstanceId);
3229
3293
  if (target === null) return false;
3230
- target.element.focus(options2);
3294
+ target.element.focus({ focusVisible: true, ...options2 });
3231
3295
  return true;
3232
3296
  };
3233
3297
  const scrollToFirstError = (options2) => {
@@ -5364,10 +5428,7 @@ function createFormStore(options) {
5364
5428
  if (elementToFormInstance.get(entry.element) !== formInstanceId) continue;
5365
5429
  if (!entry.element.isConnected) continue;
5366
5430
  if (entry.element.offsetParent === null) continue;
5367
- const { key } = canonicalizePath(entry.path);
5368
- const hasSchemaErr = (schemaErrors.get(key)?.length ?? 0) > 0;
5369
- const hasUserErr = (userErrors.get(key)?.length ?? 0) > 0;
5370
- if (!hasSchemaErr && !hasUserErr) continue;
5431
+ if (getErrorsForPath(entry.path).length === 0) continue;
5371
5432
  return { path: entry.path, element: entry.element };
5372
5433
  }
5373
5434
  return null;
@@ -5947,7 +6008,7 @@ function resolveState(key, registry) {
5947
6008
  if (key !== void 0) {
5948
6009
  const stored = registry.forms.get(key);
5949
6010
  if (stored === void 0) {
5950
- warnMiss$1(`no form registered for key '${key}'`, registry.ssr);
6011
+ warnMiss$1(`no form registered for key '${key}'`, registry.ssr, registry.forms);
5951
6012
  return null;
5952
6013
  }
5953
6014
  return stored;
@@ -5957,12 +6018,22 @@ function resolveState(key, registry) {
5957
6018
  warnIfAmbientProviderHadDuplicates();
5958
6019
  return ambient;
5959
6020
  }
5960
- function warnMiss$1(detail, ssr) {
6021
+ function availableKeysHint$1(forms) {
6022
+ const addressable = [...forms.keys()].filter((key) => !key.startsWith(RESERVED_KEY_PREFIX));
6023
+ if (addressable.length === 0) return void 0;
6024
+ return `Registered keys: ${addressable.map((key) => `'${key}'`).join(", ")}.`;
6025
+ }
6026
+ function warnMiss$1(detail, ssr, forms) {
5961
6027
  if (!__DEV__ || ssr) return;
5962
6028
  const frame = captureUserCallSite();
5963
- console.warn(
5964
- `[attaform] injectForm: ${detail}. Returning null.` + (frame !== void 0 ? ` ${frame}` : "")
6029
+ const parts = [`[attaform] injectForm: ${detail}. Returning null.`];
6030
+ const keys = availableKeysHint$1(forms);
6031
+ if (keys !== void 0) parts.push(keys);
6032
+ parts.push(
6033
+ `A form created by a child or sibling component is not registered until that component's own setup runs, which happens after this point. Lift its useForm({ key }) call to a common ancestor, or read the form after mount.`
5965
6034
  );
6035
+ if (frame !== void 0) parts.push(frame);
6036
+ console.warn(parts.join(" "));
5966
6037
  }
5967
6038
  function warnIfAmbientProviderHadDuplicates() {
5968
6039
  if (!__DEV__ || ambientProvideHistory === null) return;
@@ -6222,6 +6293,9 @@ function asStatusSource(form) {
6222
6293
  function asSubmissionSource(form) {
6223
6294
  return form;
6224
6295
  }
6296
+ function asHandleSubmitSource(form) {
6297
+ return form;
6298
+ }
6225
6299
  function useWizard(options) {
6226
6300
  const rawSteps = Array.isArray(options.steps) ? options.steps : [];
6227
6301
  if (rawSteps.length === 0 && __DEV__) {
@@ -6316,6 +6390,7 @@ function useWizard(options) {
6316
6390
  }
6317
6391
  };
6318
6392
  function resolveSlot(slot, index, ctx) {
6393
+ if (slot === void 0 || slot === null) return void 0;
6319
6394
  if (typeof slot === "string") {
6320
6395
  return getOrBuildNoop(slot);
6321
6396
  }
@@ -6331,7 +6406,7 @@ function useWizard(options) {
6331
6406
  return void 0;
6332
6407
  }
6333
6408
  function resolveSlotResult(result) {
6334
- if (result === void 0) return void 0;
6409
+ if (result === void 0 || result === null) return void 0;
6335
6410
  if (typeof result === "string") {
6336
6411
  return getOrBuildNoop(result);
6337
6412
  }
@@ -6399,6 +6474,50 @@ function useWizard(options) {
6399
6474
  const first = list[0];
6400
6475
  return first === void 0 ? void 0 : first.form;
6401
6476
  });
6477
+ watch(compiledSteps, (list, prevList) => {
6478
+ const key = activeKey.value;
6479
+ if (key === "") return;
6480
+ if (list.some((step) => step.key === key)) return;
6481
+ if (list.length === 0) {
6482
+ activeKey.value = "";
6483
+ return;
6484
+ }
6485
+ const oldIndex = prevList.findIndex((step) => step.key === key);
6486
+ const slid = list[oldIndex < 0 ? 0 : Math.min(oldIndex, list.length - 1)];
6487
+ if (slid !== void 0) activeKey.value = slid.key;
6488
+ });
6489
+ const activeFormFacade = new Proxy({}, {
6490
+ get(_target, prop) {
6491
+ const form = activeForm.value;
6492
+ if (form === void 0) return void 0;
6493
+ if (prop === "handleSubmit") {
6494
+ const lateBound = (onValid, onInvalid) => {
6495
+ return (event) => {
6496
+ const current = activeForm.value;
6497
+ if (current === void 0) return Promise.resolve();
6498
+ return asHandleSubmitSource(current).handleSubmit(onValid, onInvalid)(event);
6499
+ };
6500
+ };
6501
+ return lateBound;
6502
+ }
6503
+ return Reflect.get(form, prop, form);
6504
+ },
6505
+ has(_target, prop) {
6506
+ const form = activeForm.value;
6507
+ return form === void 0 ? false : Reflect.has(form, prop);
6508
+ },
6509
+ ownKeys(_target) {
6510
+ const form = activeForm.value;
6511
+ return form === void 0 ? [] : Reflect.ownKeys(form);
6512
+ },
6513
+ getOwnPropertyDescriptor(_target, prop) {
6514
+ const form = activeForm.value;
6515
+ if (form === void 0) return void 0;
6516
+ const descriptor = Reflect.getOwnPropertyDescriptor(form, prop);
6517
+ if (descriptor === void 0) return void 0;
6518
+ return { ...descriptor, configurable: true };
6519
+ }
6520
+ });
6402
6521
  const isFinalStep = computed(() => {
6403
6522
  const list = compiledSteps.value;
6404
6523
  const idx = activeIndex.value;
@@ -6763,6 +6882,39 @@ function useWizard(options) {
6763
6882
  const target = list[idx + 1];
6764
6883
  moveTo(target.key);
6765
6884
  }
6885
+ async function tryNext() {
6886
+ if (submitting.value) {
6887
+ if (__DEV__) {
6888
+ console.warn(`[attaform] wizard.tryNext(): blocked while a submit is in flight.`);
6889
+ }
6890
+ return false;
6891
+ }
6892
+ const list = compiledSteps.value;
6893
+ if (list.length === 0) {
6894
+ if (__DEV__) {
6895
+ console.warn(`[attaform] wizard.tryNext(): wizard has no compiled steps; no-op.`);
6896
+ }
6897
+ return false;
6898
+ }
6899
+ const idx = activeIndex.value;
6900
+ if (idx < 0 || idx >= list.length - 1) {
6901
+ if (__DEV__) {
6902
+ console.warn(
6903
+ `[attaform] wizard.tryNext(): already on the final step ("${activeKey.value}"). Use wizard.handleSubmit() to submit.`
6904
+ );
6905
+ }
6906
+ return false;
6907
+ }
6908
+ const form = activeForm.value;
6909
+ if (form === void 0) return false;
6910
+ let advanced = false;
6911
+ await asHandleSubmitSource(form).handleSubmit(() => {
6912
+ const before = activeKey.value;
6913
+ next();
6914
+ advanced = activeKey.value !== before;
6915
+ })();
6916
+ return advanced;
6917
+ }
6766
6918
  function back() {
6767
6919
  if (submitting.value) {
6768
6920
  if (__DEV__) {
@@ -6899,20 +7051,13 @@ function useWizard(options) {
6899
7051
  const final = isFinalStep.value;
6900
7052
  const list = compiledSteps.value;
6901
7053
  const results = /* @__PURE__ */ new Map();
6902
- if (final) {
6903
- await Promise.all(
6904
- list.map(async (step) => {
6905
- registry.forms.get(step.key)?.clearUserErrors();
6906
- const result = await processOne(step.form);
6907
- results.set(step.key, result);
6908
- })
6909
- );
6910
- } else {
6911
- const active = activeForm.value;
6912
- registry.forms.get(active.key)?.clearUserErrors();
6913
- const result = await processOne(active);
6914
- results.set(active.key, result);
6915
- }
7054
+ await Promise.all(
7055
+ list.map(async (step) => {
7056
+ registry.forms.get(step.key)?.clearUserErrors();
7057
+ const result = await processOne(step.form);
7058
+ results.set(step.key, result);
7059
+ })
7060
+ );
6916
7061
  for (const key of results.keys()) {
6917
7062
  const store = registry.forms.get(key);
6918
7063
  if (store !== void 0) {
@@ -6947,14 +7092,7 @@ function useWizard(options) {
6947
7092
  }
6948
7093
  return;
6949
7094
  }
6950
- if (final) {
6951
- done.value = true;
6952
- } else {
6953
- recordDeparture(currentKey);
6954
- const idx = activeIndex.value;
6955
- const target = list[idx + 1];
6956
- if (target !== void 0) moveTo(target.key);
6957
- }
7095
+ done.value = true;
6958
7096
  } else {
6959
7097
  await focusFirstWizardError(errors);
6960
7098
  if (onError !== void 0) {
@@ -7004,13 +7142,14 @@ function useWizard(options) {
7004
7142
  next,
7005
7143
  back,
7006
7144
  goTo,
7145
+ tryNext,
7007
7146
  handleSubmit,
7008
7147
  reset,
7009
7148
  get currentStep() {
7010
7149
  return currentStep.value;
7011
7150
  },
7012
7151
  get activeForm() {
7013
- return activeForm.value;
7152
+ return activeForm.value === void 0 ? void 0 : activeFormFacade;
7014
7153
  },
7015
7154
  get activeIndex() {
7016
7155
  return activeIndex.value;
@@ -7142,6 +7281,9 @@ function warnMiss(detail, ssr, hint) {
7142
7281
  const frame = captureUserCallSite();
7143
7282
  const parts = [`[attaform] injectWizard: ${detail}. Returning null.`];
7144
7283
  if (hint !== void 0) parts.push(hint);
7284
+ parts.push(
7285
+ `A wizard created by a child or sibling component is not registered until that component's own setup runs, which happens after this point. Lift its useWizard({ key }) call to a common ancestor, or read the wizard after mount.`
7286
+ );
7145
7287
  if (frame !== void 0) parts.push(frame);
7146
7288
  console.warn(parts.join(" "));
7147
7289
  }
@@ -7164,4 +7306,4 @@ function warnIfAmbientWizardProviderHadDuplicates() {
7164
7306
  }
7165
7307
 
7166
7308
  export { AttaformErrorCode as A, DEFAULT_TIMINGS as D, injectWizard as a, isUnset as b, useRegister as c, useWizard as d, defaultCoercionRules as e, defaultDisplayState as f, defineCoercion as g, useAbstractForm as h, injectForm as i, isPlainRecord as j, safeAssign as k, lazy as l, makeDefaultDisplayState as m, slimKindOf as n, humanize as o, getAtPath as p, setAtPath as q, safeOwnRead as s, unset as u };
7167
- //# sourceMappingURL=attaform.iJ4y3sHU.mjs.map
7309
+ //# sourceMappingURL=attaform.BoYqnIPI.mjs.map