attaform 0.23.0 → 0.24.1

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 (99) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/devtools.cjs +1 -1
  3. package/dist/chunks/devtools.mjs +1 -1
  4. package/dist/chunks/fingerprint2.cjs +1 -1
  5. package/dist/chunks/fingerprint2.mjs +1 -1
  6. package/dist/index.cjs +5 -150
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +6 -132
  9. package/dist/index.d.mts +6 -132
  10. package/dist/index.d.ts +6 -132
  11. package/dist/index.mjs +5 -150
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/nuxt.d.cts +1 -1
  14. package/dist/nuxt.d.mts +1 -1
  15. package/dist/nuxt.d.ts +1 -1
  16. package/dist/runtime/components/AttaformDevtoolsPanel.vue +2 -2
  17. package/dist/runtime/plugins/attaform.cjs +2 -2
  18. package/dist/runtime/plugins/attaform.mjs +2 -2
  19. package/dist/shared/{attaform.Dx9-QQE2.mjs → attaform.0-00cYGw.mjs} +2 -2
  20. package/dist/shared/{attaform.Dx9-QQE2.mjs.map → attaform.0-00cYGw.mjs.map} +1 -1
  21. package/dist/shared/{attaform.D52oJiYC.d.cts → attaform.4zesozTg.d.mts} +38 -6
  22. package/dist/shared/{attaform.Db4E4IW6.cjs → attaform.B7UdTs_o.cjs} +79 -79
  23. package/dist/shared/attaform.B7UdTs_o.cjs.map +1 -0
  24. package/dist/shared/{attaform.BibT5AS_.cjs → attaform.BOi6n2Pn.cjs} +2 -2
  25. package/dist/shared/{attaform.BibT5AS_.cjs.map → attaform.BOi6n2Pn.cjs.map} +1 -1
  26. package/dist/shared/{attaform.DCkSNnPr.d.mts → attaform.Bk7vnQhG.d.cts} +38 -6
  27. package/dist/shared/{attaform.BGMRvckW.d.ts → attaform.Bq6Copxn.d.cts} +41 -25
  28. package/dist/shared/{attaform.DuPneYR0.d.cts → attaform.BrFPMFgi.d.ts} +41 -25
  29. package/dist/shared/{attaform.alpG7rT7.mjs → attaform.BunnTiTw.mjs} +4 -4
  30. package/dist/shared/attaform.BunnTiTw.mjs.map +1 -0
  31. package/dist/shared/{attaform.DrY8srOp.d.mts → attaform.BwAcpoRw.d.mts} +41 -25
  32. package/dist/shared/{attaform.CaYj3ZfY.cjs → attaform.C-1W0T1n.cjs} +6 -4
  33. package/dist/shared/attaform.C-1W0T1n.cjs.map +1 -0
  34. package/dist/shared/{attaform.Dd1Kmmaj.mjs → attaform.C-tQKknW.mjs} +6 -4
  35. package/dist/shared/attaform.C-tQKknW.mjs.map +1 -0
  36. package/dist/shared/{attaform.BhI9Icek.mjs → attaform.C0au8oXd.mjs} +36 -31
  37. package/dist/shared/attaform.C0au8oXd.mjs.map +1 -0
  38. package/dist/shared/attaform.CjdepGnw.cjs +27 -0
  39. package/dist/shared/attaform.CjdepGnw.cjs.map +1 -0
  40. package/dist/shared/{attaform.DbyTD8N2.cjs → attaform.Cn6JoG9o.cjs} +8 -6
  41. package/dist/shared/attaform.Cn6JoG9o.cjs.map +1 -0
  42. package/dist/shared/{attaform.Cmb_LCie.cjs → attaform.CrrIaHM8.cjs} +4 -4
  43. package/dist/shared/attaform.CrrIaHM8.cjs.map +1 -0
  44. package/dist/shared/{attaform.BJnNK75Y.d.mts → attaform.DBhrKb2j.d.cts} +206 -168
  45. package/dist/shared/{attaform.BJnNK75Y.d.ts → attaform.DBhrKb2j.d.mts} +206 -168
  46. package/dist/shared/{attaform.BJnNK75Y.d.cts → attaform.DBhrKb2j.d.ts} +206 -168
  47. package/dist/shared/{attaform.DsQkXE3o.cjs → attaform.DRQjF16I.cjs} +248 -195
  48. package/dist/shared/attaform.DRQjF16I.cjs.map +1 -0
  49. package/dist/shared/attaform.DdjDqTah.d.cts +56 -0
  50. package/dist/shared/attaform.DdjDqTah.d.mts +56 -0
  51. package/dist/shared/attaform.DdjDqTah.d.ts +56 -0
  52. package/dist/shared/{attaform.WEwfXcHq.d.ts → attaform.Df4xXKbE.d.ts} +38 -6
  53. package/dist/shared/{attaform.BFWb6hDk.mjs → attaform.DhXl0Kdr.mjs} +7 -1
  54. package/dist/shared/attaform.DhXl0Kdr.mjs.map +1 -0
  55. package/dist/shared/{attaform.CR6wGvNu.cjs → attaform.DwLw3Kzv.cjs} +7 -1
  56. package/dist/shared/attaform.DwLw3Kzv.cjs.map +1 -0
  57. package/dist/shared/{attaform.CzVta5o2.mjs → attaform.FY5r1BpA.mjs} +8 -6
  58. package/dist/shared/attaform.FY5r1BpA.mjs.map +1 -0
  59. package/dist/shared/{attaform.CtJOd7ox.mjs → attaform.NWrEGrNo.mjs} +247 -193
  60. package/dist/shared/attaform.NWrEGrNo.mjs.map +1 -0
  61. package/dist/shared/attaform.WvcckZMD.mjs +21 -0
  62. package/dist/shared/attaform.WvcckZMD.mjs.map +1 -0
  63. package/dist/transforms.cjs +1 -1
  64. package/dist/transforms.mjs +1 -1
  65. package/dist/vite.cjs +1 -1
  66. package/dist/vite.mjs +1 -1
  67. package/dist/zod-v3.cjs +2 -2
  68. package/dist/zod-v3.d.cts +12 -11
  69. package/dist/zod-v3.d.mts +12 -11
  70. package/dist/zod-v3.d.ts +12 -11
  71. package/dist/zod-v3.mjs +2 -2
  72. package/dist/zod-v4.cjs +2 -2
  73. package/dist/zod-v4.d.cts +6 -5
  74. package/dist/zod-v4.d.mts +6 -5
  75. package/dist/zod-v4.d.ts +6 -5
  76. package/dist/zod-v4.mjs +2 -2
  77. package/dist/zod.cjs +5 -5
  78. package/dist/zod.cjs.map +1 -1
  79. package/dist/zod.d.cts +21 -52
  80. package/dist/zod.d.mts +21 -52
  81. package/dist/zod.d.ts +21 -52
  82. package/dist/zod.mjs +5 -5
  83. package/dist/zod.mjs.map +1 -1
  84. package/package.json +1 -1
  85. package/dist/shared/attaform.BFWb6hDk.mjs.map +0 -1
  86. package/dist/shared/attaform.BhI9Icek.mjs.map +0 -1
  87. package/dist/shared/attaform.CR6wGvNu.cjs.map +0 -1
  88. package/dist/shared/attaform.CaYj3ZfY.cjs.map +0 -1
  89. package/dist/shared/attaform.Cmb_LCie.cjs.map +0 -1
  90. package/dist/shared/attaform.CtJOd7ox.mjs.map +0 -1
  91. package/dist/shared/attaform.CzVta5o2.mjs.map +0 -1
  92. package/dist/shared/attaform.Db4E4IW6.cjs.map +0 -1
  93. package/dist/shared/attaform.DbyTD8N2.cjs.map +0 -1
  94. package/dist/shared/attaform.Dd1Kmmaj.mjs.map +0 -1
  95. package/dist/shared/attaform.DsQkXE3o.cjs.map +0 -1
  96. package/dist/shared/attaform.alpG7rT7.mjs.map +0 -1
  97. package/dist/shared/attaform.nf83TIR5.d.cts +0 -35
  98. package/dist/shared/attaform.nf83TIR5.d.mts +0 -35
  99. package/dist/shared/attaform.nf83TIR5.d.ts +0 -35
@@ -1,5 +1,6 @@
1
1
  import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, toRaw, reactive, watch, markRaw, triggerRef, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, onBeforeMount, onBeforeUpdate, onMounted, effectScope, nextTick } from 'vue';
2
- import { o as pathsEqual, j as isPathPrefix, _ as __DEV__, a as canonicalizePath, s as segmentsForPathKey, F as FORM_ERRORS_PATH_KEY, q as keyForSegments, S as SubmitErrorHandlerError, t as toError, r as INTERACTIVE_TAG_NAMES, R as ROOT_PATH, w as FORM_ERRORS_PATH, d as ROOT_PATH_KEY, x as coerceToPathKey, b as InvalidUseFormConfigError, y as ensureAttaformInstalled, u as useRegistry, z as kFormContext, B as kFormInstanceId, f as ReservedFormKeyError, C as REGISTER_OWNER_MARKER, V as V_REGISTER_MARKER, k as kAttaformWizardActiveStepResolver, D as kAttaformAncestorWizard } from './attaform.BhI9Icek.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 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.C0au8oXd.mjs';
3
+ import { R as REGISTER_OWNER_MARKER, V as V_REGISTER_MARKER } from './attaform.WvcckZMD.mjs';
3
4
 
4
5
  function safeAssign(target, key, value) {
5
6
  if (key === "__proto__") {
@@ -540,6 +541,58 @@ function structuralSnapshot(value) {
540
541
  return out;
541
542
  }
542
543
 
544
+ const AttaformErrorCode = {
545
+ /** A required field is in the blank set — user hasn't supplied a value. */
546
+ NoValueSupplied: "atta:no-value-supplied",
547
+ /** The schema adapter's `validateAtPath` threw synchronously. */
548
+ AdapterThrew: "atta:adapter-threw",
549
+ /**
550
+ * User code inside a `z.preprocess`, `.refine`, or `.transform`
551
+ * threw (sync or async). The adapter caught the throw and surfaced
552
+ * it as a `ValidationError` at the field path so the form's normal
553
+ * error pipeline handles it instead of leaking as an unhandled
554
+ * rejection or routing through `submitError`.
555
+ */
556
+ ValidatorThrew: "atta:validator-threw",
557
+ /**
558
+ * A function-form `defaultValues` factory threw or its promise
559
+ * rejected. The runtime captures the raw error on `form.hydrateError`
560
+ * and ALSO surfaces a form-level `ValidationError` (path `[]`) so
561
+ * the standard error pipeline carries the signal. Critical for the
562
+ * SSR round-trip: `hydrateError` itself does not ride the wire
563
+ * payload, but `schemaErrors` does, so the client sees the failure
564
+ * after rehydration without an extra channel.
565
+ */
566
+ HydrationFailed: "atta:hydration-failed",
567
+ /** The supplied path didn't resolve to any node in the schema. */
568
+ PathNotFound: "atta:path-not-found",
569
+ /**
570
+ * A walked form's `activate()` (async `defaultValues` factory) threw
571
+ * during `wizard.handleSubmit`'s path walk. Surfaced as a synthetic
572
+ * `ValidationError` at the form-level path (`[]`) so the wizard's
573
+ * aggregate error pipeline can carry the failure alongside ordinary
574
+ * validation errors. The raw factory error remains on
575
+ * `form.hydrateError` for retry UX.
576
+ */
577
+ ActivationFailed: "atta:activation-failed",
578
+ /**
579
+ * Default code stamped on a manual error set through `form.setErrors`
580
+ * when the caller omits an explicit `code`. The `setErrors` input is
581
+ * lenient (`code` optional), so this is the fallback that keeps every
582
+ * produced `ValidationError` carrying a stable, branchable identifier.
583
+ * Override it per error by passing your own `code` (`api:…`, `auth:…`).
584
+ */
585
+ UserError: "atta:user-error"
586
+ };
587
+ function makeBlankRequiredError(segments, formKey) {
588
+ return {
589
+ message: "No value supplied",
590
+ path: [...segments],
591
+ formKey,
592
+ code: AttaformErrorCode.NoValueSupplied
593
+ };
594
+ }
595
+
543
596
  function isGateOpen(field, formMeta) {
544
597
  return formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
545
598
  }
@@ -595,50 +648,6 @@ function resolveGetDisplayState(config) {
595
648
  return config ?? defaultDisplayState;
596
649
  }
597
650
 
598
- const AttaformErrorCode = {
599
- /** A required field is in the blank set — user hasn't supplied a value. */
600
- NoValueSupplied: "atta:no-value-supplied",
601
- /** The schema adapter's `validateAtPath` threw synchronously. */
602
- AdapterThrew: "atta:adapter-threw",
603
- /**
604
- * User code inside a `z.preprocess`, `.refine`, or `.transform`
605
- * threw (sync or async). The adapter caught the throw and surfaced
606
- * it as a `ValidationError` at the field path so the form's normal
607
- * error pipeline handles it instead of leaking as an unhandled
608
- * rejection or routing through `submitError`.
609
- */
610
- ValidatorThrew: "atta:validator-threw",
611
- /**
612
- * A function-form `defaultValues` factory threw or its promise
613
- * rejected. The runtime captures the raw error on `form.hydrateError`
614
- * and ALSO surfaces a form-level `ValidationError` (path `[]`) so
615
- * the standard error pipeline carries the signal. Critical for the
616
- * SSR round-trip: `hydrateError` itself does not ride the wire
617
- * payload, but `schemaErrors` does, so the client sees the failure
618
- * after rehydration without an extra channel.
619
- */
620
- HydrationFailed: "atta:hydration-failed",
621
- /** The supplied path didn't resolve to any node in the schema. */
622
- PathNotFound: "atta:path-not-found",
623
- /**
624
- * A walked form's `activate()` (async `defaultValues` factory) threw
625
- * during `wizard.handleSubmit`'s path walk. Surfaced as a synthetic
626
- * `ValidationError` at the form-level path (`[]`) so the wizard's
627
- * aggregate error pipeline can carry the failure alongside ordinary
628
- * validation errors. The raw factory error remains on
629
- * `form.hydrateError` for retry UX.
630
- */
631
- ActivationFailed: "atta:activation-failed"
632
- };
633
- function makeBlankRequiredError(segments, formKey) {
634
- return {
635
- message: "No value supplied",
636
- path: [...segments],
637
- formKey,
638
- code: AttaformErrorCode.NoValueSupplied
639
- };
640
- }
641
-
642
651
  const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
643
652
  const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
644
653
  const RESERVED_KEY_PREFIX = "__atta:";
@@ -1031,7 +1040,7 @@ function aggregateErrorsAt(state, prefix) {
1031
1040
  const segs = segmentsForPathKey(pathKey);
1032
1041
  if (segs === null) continue;
1033
1042
  if (!isPathPrefix(prefix, segs)) continue;
1034
- if (pathKey === FORM_ERRORS_PATH_KEY) ; else if (segs.length > 0 && !hasAtPath(formValue, segs)) continue;
1043
+ if (segs.length > 0 && !hasAtPath(formValue, segs)) continue;
1035
1044
  const ordinal = state.ensurePathOrdinal(pathKey);
1036
1045
  const existing = buckets.get(ordinal);
1037
1046
  if (existing === void 0) buckets.set(ordinal, [...list]);
@@ -1150,7 +1159,12 @@ function buildSurfaceProxy(opts) {
1150
1159
  const proxy = new Proxy(target, {
1151
1160
  apply(_, __, args) {
1152
1161
  const arg = args[0];
1153
- if (arg === void 0) return opts.resolveCallTarget(segments);
1162
+ if (arg === void 0) {
1163
+ if (segments.length === 0 && opts.resolveRootCall !== void 0) {
1164
+ return opts.resolveRootCall();
1165
+ }
1166
+ return opts.resolveCallTarget(segments);
1167
+ }
1154
1168
  const { segments: argSegs } = canonicalizePath(arg);
1155
1169
  return opts.resolveCallTarget(argSegs);
1156
1170
  },
@@ -1361,8 +1375,7 @@ function buildErrorsProxy(state) {
1361
1375
  return merged;
1362
1376
  }
1363
1377
  const { key } = canonicalizePath(path);
1364
- const isFormLevel = key === FORM_ERRORS_PATH_KEY;
1365
- const active = isFormLevel || hasAtPath(state.form.value, path);
1378
+ const active = hasAtPath(state.form.value, path);
1366
1379
  collectAtKey(key, active, merged);
1367
1380
  return merged;
1368
1381
  },
@@ -1370,37 +1383,42 @@ function buildErrorsProxy(state) {
1370
1383
  // undefined) IS the terminal.
1371
1384
  materializeContainer: (segments) => materializeErrors(state, segments),
1372
1385
  // Any path ending in `''` is a meaningful terminal at the proxy
1373
- // layer: at root it's the form-level bucket; at depth >= 1 it's
1374
- // the container-self sentinel that surfaces cross-field refines
1375
- // and container-targeted marks. `resolveLeaf` translates `[...,
1376
- // '']` lookups to the parent container path before querying the
1377
- // stores. When a schema legitimately owns a `''` field, the
1378
- // literal leaf and any container-self errors share the slot
1379
- // (errors concatenate) — vanishingly rare, accepted as the
1380
- // ergonomic cost of one unified sentinel convention at every
1381
- // depth.
1386
+ // layer. A bare `['']` is the literal root `''` field; at depth
1387
+ // >= 1 a trailing `''` is the container-self sentinel that surfaces
1388
+ // cross-field refines and container-targeted marks (`resolveLeaf`
1389
+ // translates `[..., '']` to the parent container path before
1390
+ // querying the stores). When a schema legitimately owns a `''`
1391
+ // field, the literal leaf and any container-self errors share the
1392
+ // slot (errors concatenate) — vanishingly rare, accepted as the
1393
+ // ergonomic cost of one unified sentinel convention at depth >= 1.
1382
1394
  isTerminalAt: (segs) => segs.length >= 1 && segs[segs.length - 1] === "",
1383
1395
  // Call-form aggregates: `form.errors(path)` returns a single
1384
- // `ValidationError[]` for any depth (leaf or container) same
1396
+ // `ValidationError[]` for any depth (leaf or container) via the
1385
1397
  // shared `aggregateErrorsAt` helper that `form.meta.errors` and
1386
- // `form.fields(path).errors` use, so the three surfaces never
1387
- // drift. Empty results return `undefined`, matching the leaf
1388
- // proxy's pre-existing semantic (`form.errors.email === undefined`
1389
- // when valid) so consumer code that branches on truthiness keeps
1390
- // working the call-form just extends that semantic to
1391
- // containers and dynamic paths.
1392
- resolveCallTarget: (path) => aggregateErrorsAt(state, path),
1398
+ // `form.fields(path).errors` also use, so the surfaces never drift.
1399
+ //
1400
+ // The root is the one carve-out. An EXPLICIT root path
1401
+ // (`errors([])`) returns ONLY the global `[]` bucket (root
1402
+ // `.refine()`, hydration failures, `setErrors`) via
1403
+ // `getErrorsForPath`, giving consumers a dedicated channel for
1404
+ // global messages undiluted by field errors. The no-arg `errors()`
1405
+ // instead resolves the FULL aggregate through `resolveRootCall`
1406
+ // below (identical to `meta.errors`).
1407
+ resolveCallTarget: (path) => path.length === 0 ? state.getErrorsForPath([]) : aggregateErrorsAt(state, path),
1408
+ // No-arg `errors()` = the whole-form aggregate, matching
1409
+ // `meta.errors`. Distinct from `errors([])` (global bucket only);
1410
+ // see `resolveCallTarget`.
1411
+ resolveRootCall: () => aggregateErrorsAt(state, []),
1393
1412
  // Enumeration unions the live form-data keys at this path with the
1394
1413
  // first-child segments drawn from every error store. Without the
1395
1414
  // union, `Object.keys(form.errors)` / `{...form.errors}` /
1396
- // `v-for="(errs, k) in form.errors"` silently dropped two
1397
- // important error classes that the dot / call / JSON.stringify
1398
- // surfaces already exposed:
1399
- //
1400
- // - **Form-level** errors at the synthetic `['']` path (set via
1401
- // `setFormErrors` or root cross-field refines).
1402
- // - **Server-only** errors at a key the schema doesn't know
1403
- // about (`['ghost']`, `['address', 'ghost']`).
1415
+ // `v-for="(errs, k) in form.errors"` would silently drop
1416
+ // **server-only** errors at a key the schema doesn't know about
1417
+ // (`['ghost']`, `['address', 'ghost']`) that the dot / call /
1418
+ // JSON.stringify surfaces already expose. Global errors at the root
1419
+ // `[]` are NOT a child key and never enumerate here (read them via
1420
+ // `errors([])` / `meta.errors`); a literal `''` field enumerates
1421
+ // under the key `''` like any other field.
1404
1422
  //
1405
1423
  // The union closes that gap so `ownKeys` agrees with the rest of
1406
1424
  // the surface. Active-path filter mirrors `resolveLeaf`:
@@ -1422,10 +1440,6 @@ function errorAwareContainerKeys(state, segments) {
1422
1440
  const walk = (store, applyActivePathFilter) => {
1423
1441
  for (const [pathKey, errors] of store) {
1424
1442
  if (errors.length === 0) continue;
1425
- if (pathKey === FORM_ERRORS_PATH_KEY) {
1426
- if (segments.length === 0) keys.add("");
1427
- continue;
1428
- }
1429
1443
  const decoded = segmentsForPathKey(pathKey);
1430
1444
  if (decoded === null) continue;
1431
1445
  if (decoded.length <= segments.length) continue;
@@ -1448,31 +1462,25 @@ function materializeErrors(state, containerSegments) {
1448
1462
  if (errors.length === 0) continue;
1449
1463
  const fullPath = segmentsForPathKey(pathKey);
1450
1464
  if (fullPath === null) continue;
1451
- const isSyntheticFormLevel = fullPath.length === 1 && fullPath[0] === "";
1452
- if (!isSyntheticFormLevel) {
1453
- if (fullPath.length < containerSegments.length) continue;
1454
- for (let i = 0; i < containerSegments.length; i++) {
1455
- if (fullPath[i] !== containerSegments[i]) continue entries;
1456
- }
1457
- } else if (containerSegments.length !== 0) {
1465
+ if (fullPath.length === 0) {
1466
+ if (containerSegments.length === 0) placeAt(tree, [ROOT_PATH_KEY], errors);
1458
1467
  continue;
1459
1468
  }
1460
- if (applyActivePathFilter && !isSyntheticFormLevel && !hasAtPath(state.form.value, fullPath))
1461
- continue;
1469
+ if (fullPath.length < containerSegments.length) continue;
1470
+ for (let i = 0; i < containerSegments.length; i++) {
1471
+ if (fullPath[i] !== containerSegments[i]) continue entries;
1472
+ }
1473
+ if (applyActivePathFilter && !hasAtPath(state.form.value, fullPath)) continue;
1474
+ const relativePath = fullPath.slice(containerSegments.length);
1462
1475
  let placePath;
1463
- if (isSyntheticFormLevel) {
1476
+ if (relativePath.length === 0) {
1464
1477
  placePath = [""];
1478
+ } else if (state.schema.isLeafAtPath(fullPath)) {
1479
+ placePath = relativePath;
1480
+ } else if (state.schema.getSlimPrimitiveTypesAtPath(fullPath).size > 0) {
1481
+ placePath = [...relativePath, ""];
1465
1482
  } else {
1466
- const relativePath = fullPath.slice(containerSegments.length);
1467
- if (relativePath.length === 0) {
1468
- placePath = [""];
1469
- } else if (state.schema.isLeafAtPath(fullPath)) {
1470
- placePath = relativePath;
1471
- } else if (state.schema.getSlimPrimitiveTypesAtPath(fullPath).size > 0) {
1472
- placePath = [...relativePath, ""];
1473
- } else {
1474
- placePath = relativePath;
1475
- }
1483
+ placePath = relativePath;
1476
1484
  }
1477
1485
  placeAt(tree, placePath, errors);
1478
1486
  }
@@ -1912,6 +1920,19 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1912
1920
  state.clearSchemaErrors();
1913
1921
  }
1914
1922
  await onSubmit(merged.data);
1923
+ if (state.userErrors.size > 0) {
1924
+ if (state.submissionGeneration.value === genAtEntry) {
1925
+ applyInvalidSubmitPolicy(state, formInstanceId, invalidPolicy);
1926
+ }
1927
+ if (onError !== void 0) {
1928
+ try {
1929
+ await onError(Array.from(state.userErrors.values()).flat());
1930
+ } catch (cause) {
1931
+ throw new SubmitErrorHandlerError("User-provided onError threw", { cause });
1932
+ }
1933
+ }
1934
+ return;
1935
+ }
1915
1936
  if (state.submissionGeneration.value === genAtEntry) {
1916
1937
  state.submitted.value = true;
1917
1938
  }
@@ -2906,62 +2927,56 @@ function buildFormApi(state, formInstanceId, options = {}) {
2906
2927
  return true;
2907
2928
  }
2908
2929
  const errorsProxy = buildErrorsProxy(state);
2909
- function filterToOwnFormKey(errors, op) {
2910
- const own = [];
2911
- let dropped = 0;
2912
- for (const e of errors) {
2913
- if (e.formKey === state.formKey) own.push(e);
2914
- else dropped++;
2915
- }
2916
- if (__DEV__ && dropped > 0) {
2917
- console.warn(
2918
- `[attaform] ${op}: dropped ${dropped} error(s) with non-matching formKey (this form's key is "${String(state.formKey)}"). Errors are scoped to the form that produced them \u2014 pass them to the matching form instance.`
2919
- );
2930
+ function normalizeErrorInput(item, scope) {
2931
+ if (item instanceof Error) {
2932
+ return {
2933
+ message: item.message.length > 0 ? item.message : "Unknown error",
2934
+ path: scope !== void 0 ? [...scope] : [],
2935
+ formKey: state.formKey,
2936
+ code: AttaformErrorCode.UserError
2937
+ };
2920
2938
  }
2921
- return own;
2939
+ const entry = {
2940
+ message: typeof item.message === "string" && item.message.length > 0 ? item.message : "Unknown error",
2941
+ path: scope !== void 0 ? [...scope] : Array.isArray(item.path) ? [...item.path] : [],
2942
+ formKey: state.formKey,
2943
+ code: typeof item.code === "string" && item.code.length > 0 ? item.code : AttaformErrorCode.UserError
2944
+ };
2945
+ if (item.data !== void 0) entry.data = item.data;
2946
+ return entry;
2922
2947
  }
2923
- function setFieldErrors(errors) {
2924
- const preserved = state.userErrors.get(FORM_ERRORS_PATH_KEY);
2925
- state.setAllUserErrors(filterToOwnFormKey(errors, "setFieldErrors"));
2926
- if (preserved !== void 0 && preserved.length > 0) {
2927
- state.userErrors.set(FORM_ERRORS_PATH_KEY, preserved);
2948
+ function normalizeErrorInputs(input, scope) {
2949
+ const items = Array.isArray(input) ? input : [input];
2950
+ return items.map((item) => normalizeErrorInput(item, scope));
2951
+ }
2952
+ function flattenUserErrors() {
2953
+ const all = [];
2954
+ for (const errs of state.userErrors.values()) all.push(...errs);
2955
+ return all;
2956
+ }
2957
+ function setErrors(arg1, arg2) {
2958
+ const isScoped = arguments.length >= 2 && (typeof arg1 === "string" || Array.isArray(arg1));
2959
+ if (isScoped) {
2960
+ const { segments, key } = canonicalizePath(arg1);
2961
+ const input2 = arg2;
2962
+ const resolved2 = typeof input2 === "function" ? input2((state.userErrors.get(key) ?? []).slice()) : input2;
2963
+ state.setUserErrorsForPath(segments, normalizeErrorInputs(resolved2, segments));
2964
+ return;
2928
2965
  }
2966
+ const input = arg1;
2967
+ const resolved = typeof input === "function" ? input(flattenUserErrors()) : input;
2968
+ state.setAllUserErrors(normalizeErrorInputs(resolved, void 0));
2929
2969
  }
2930
- function addFieldErrors(errors) {
2931
- state.addUserErrors(filterToOwnFormKey(errors, "addFieldErrors"));
2932
- }
2933
- function clearFieldErrors(path) {
2970
+ function clearErrors(path) {
2934
2971
  if (path === void 0) {
2935
- const preserved = state.userErrors.get(FORM_ERRORS_PATH_KEY);
2936
2972
  state.clearSchemaErrors();
2937
2973
  state.clearUserErrors();
2938
- if (preserved !== void 0 && preserved.length > 0) {
2939
- state.userErrors.set(FORM_ERRORS_PATH_KEY, preserved);
2940
- }
2941
2974
  return;
2942
2975
  }
2943
2976
  const segments = canonicalizePath(path).segments;
2944
2977
  state.clearSchemaErrors(segments);
2945
2978
  state.clearUserErrors(segments);
2946
2979
  }
2947
- function setFormErrors(errors) {
2948
- if (errors.length === 0) {
2949
- state.userErrors.delete(FORM_ERRORS_PATH_KEY);
2950
- return;
2951
- }
2952
- state.userErrors.set(
2953
- FORM_ERRORS_PATH_KEY,
2954
- errors.map((e) => ({
2955
- path: [...FORM_ERRORS_PATH],
2956
- message: e.message,
2957
- formKey: state.formKey,
2958
- code: e.code ?? "atta:form-error"
2959
- }))
2960
- );
2961
- }
2962
- function clearFormErrors() {
2963
- state.userErrors.delete(FORM_ERRORS_PATH_KEY);
2964
- }
2965
2980
  const submitting = computed(() => state.submitting.value);
2966
2981
  const submissionAttempts = computed(() => state.submissionAttempts.value);
2967
2982
  const submitted = computed(() => state.submitted.value);
@@ -3228,14 +3243,14 @@ function buildFormApi(state, formInstanceId, options = {}) {
3228
3243
  }
3229
3244
  const EMPTY_FIELD_RECORD = Object.freeze({});
3230
3245
  function record(path) {
3231
- const { segments } = canonicalizePath(path);
3246
+ const segments = path === void 0 ? [] : canonicalizePath(path).segments;
3232
3247
  const value = state.getValueAtPath(segments);
3233
3248
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
3234
3249
  return EMPTY_FIELD_RECORD;
3235
3250
  }
3236
3251
  const out = {};
3237
3252
  for (const key of Object.keys(value)) {
3238
- safeAssign(out, key, callTerminal(`${path}.${key}`));
3253
+ safeAssign(out, key, callTerminal(segments.length === 0 ? key : `${path}.${key}`));
3239
3254
  }
3240
3255
  return Object.freeze(out);
3241
3256
  }
@@ -3292,11 +3307,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
3292
3307
  return errorsProxy;
3293
3308
  },
3294
3309
  toRef: gated(pathToRef),
3295
- setFieldErrors: gated(setFieldErrors),
3296
- addFieldErrors: gated(addFieldErrors),
3297
- clearFieldErrors: gated(clearFieldErrors),
3298
- setFormErrors: gated(setFormErrors),
3299
- clearFormErrors: gated(clearFormErrors),
3310
+ setErrors: gated(setErrors),
3311
+ clearErrors: gated(clearErrors),
3300
3312
  get meta() {
3301
3313
  void state.activate();
3302
3314
  return formMeta;
@@ -4814,25 +4826,14 @@ function createFormStore(options) {
4814
4826
  function getValueAtPath(path) {
4815
4827
  return getAtPath(form.value, path);
4816
4828
  }
4817
- function rerouteFormLevelEntry(err) {
4818
- if (err.path.length === 0) {
4819
- return { ...err, path: [...FORM_ERRORS_PATH] };
4820
- }
4821
- return err;
4822
- }
4823
- function pathKeyForEntry(err) {
4824
- if (err.path.length === 0) return FORM_ERRORS_PATH_KEY;
4825
- return canonicalizePath(err.path).key;
4826
- }
4827
4829
  function appendErrorsTo(map, entries) {
4828
4830
  for (const raw of entries) {
4829
- const err = rerouteFormLevelEntry(raw);
4830
- const key = pathKeyForEntry(err);
4831
+ const { key } = canonicalizePath(raw.path);
4831
4832
  const current = map.get(key);
4832
4833
  if (current === void 0) {
4833
- map.set(key, [err]);
4834
+ map.set(key, [raw]);
4834
4835
  } else {
4835
- map.set(key, [...current, err]);
4836
+ map.set(key, [...current, raw]);
4836
4837
  }
4837
4838
  }
4838
4839
  }
@@ -4857,14 +4858,13 @@ function createFormStore(options) {
4857
4858
  schemaErrors.set(key, [...entries]);
4858
4859
  }
4859
4860
  function applySchemaErrorsForSubtree(path, entries) {
4860
- const parentKey = path.length === 0 ? FORM_ERRORS_PATH_KEY : canonicalizePath(path).key;
4861
+ const parentKey = canonicalizePath(path).key;
4861
4862
  const grouped = /* @__PURE__ */ new Map();
4862
4863
  for (const raw of entries) {
4863
- const err = rerouteFormLevelEntry(raw);
4864
- const key = pathKeyForEntry(err);
4864
+ const { key } = canonicalizePath(raw.path);
4865
4865
  const list = grouped.get(key);
4866
- if (list === void 0) grouped.set(key, [err]);
4867
- else list.push(err);
4866
+ if (list === void 0) grouped.set(key, [raw]);
4867
+ else list.push(raw);
4868
4868
  }
4869
4869
  if (!grouped.has(parentKey)) schemaErrors.delete(parentKey);
4870
4870
  for (const existingKey of [...schemaErrors.keys()]) {
@@ -4886,8 +4886,13 @@ function createFormStore(options) {
4886
4886
  function setAllUserErrors(entries) {
4887
4887
  replaceErrorsIn(userErrors, entries);
4888
4888
  }
4889
- function addUserErrors(entries) {
4890
- appendErrorsTo(userErrors, entries);
4889
+ function setUserErrorsForPath(path, entries) {
4890
+ const { key } = canonicalizePath(path);
4891
+ if (entries.length === 0) {
4892
+ userErrors.delete(key);
4893
+ return;
4894
+ }
4895
+ userErrors.set(key, [...entries]);
4891
4896
  }
4892
4897
  function clearUserErrors(path) {
4893
4898
  clearErrorsIn(userErrors, path);
@@ -5081,25 +5086,25 @@ function createFormStore(options) {
5081
5086
  }
5082
5087
  }
5083
5088
  function clearHydrationFailedEntry() {
5084
- const existing = schemaErrors.get(FORM_ERRORS_PATH_KEY);
5089
+ const existing = schemaErrors.get(ROOT_PATH_KEY);
5085
5090
  if (existing === void 0) return;
5086
5091
  const filtered = existing.filter((e) => e.code !== AttaformErrorCode.HydrationFailed);
5087
5092
  if (filtered.length === 0) {
5088
- schemaErrors.delete(FORM_ERRORS_PATH_KEY);
5093
+ schemaErrors.delete(ROOT_PATH_KEY);
5089
5094
  } else {
5090
- schemaErrors.set(FORM_ERRORS_PATH_KEY, filtered);
5095
+ schemaErrors.set(ROOT_PATH_KEY, filtered);
5091
5096
  }
5092
5097
  }
5093
5098
  function appendHydrationFailedEntry(error) {
5094
5099
  const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Hydration failed";
5095
5100
  const entry = {
5096
5101
  message,
5097
- path: [...FORM_ERRORS_PATH],
5102
+ path: [...ROOT_PATH],
5098
5103
  formKey,
5099
5104
  code: AttaformErrorCode.HydrationFailed
5100
5105
  };
5101
- const existing = schemaErrors.get(FORM_ERRORS_PATH_KEY) ?? [];
5102
- schemaErrors.set(FORM_ERRORS_PATH_KEY, [...existing, entry]);
5106
+ const existing = schemaErrors.get(ROOT_PATH_KEY) ?? [];
5107
+ schemaErrors.set(ROOT_PATH_KEY, [...existing, entry]);
5103
5108
  return entry;
5104
5109
  }
5105
5110
  function reset(nextDefaultValues) {
@@ -5332,7 +5337,7 @@ function createFormStore(options) {
5332
5337
  clearSchemaErrors,
5333
5338
  applySchemaErrorsForSubtree,
5334
5339
  setAllUserErrors,
5335
- addUserErrors,
5340
+ setUserErrorsForPath,
5336
5341
  clearUserErrors,
5337
5342
  getErrorsForPath,
5338
5343
  ensurePathOrdinal,
@@ -5409,12 +5414,38 @@ function captureErrorEntries(map) {
5409
5414
  for (const [k, v] of map) out.push([k, [...v]]);
5410
5415
  return out;
5411
5416
  }
5417
+ function dataEqual(a, b) {
5418
+ if (a === b) return true;
5419
+ if (a === null || b === null || a === void 0 || b === void 0) return false;
5420
+ if (typeof a !== typeof b) return false;
5421
+ if (Array.isArray(a)) {
5422
+ if (!Array.isArray(b) || a.length !== b.length) return false;
5423
+ for (let i = 0; i < a.length; i++) {
5424
+ if (!dataEqual(a[i], b[i])) return false;
5425
+ }
5426
+ return true;
5427
+ }
5428
+ if (Array.isArray(b)) return false;
5429
+ if (typeof a === "object") {
5430
+ const ao = a;
5431
+ const bo = b;
5432
+ const keys = Object.keys(ao);
5433
+ if (keys.length !== Object.keys(bo).length) return false;
5434
+ for (const k of keys) {
5435
+ if (!Object.prototype.hasOwnProperty.call(bo, k)) return false;
5436
+ if (!dataEqual(ao[k], bo[k])) return false;
5437
+ }
5438
+ return true;
5439
+ }
5440
+ return false;
5441
+ }
5412
5442
  function errorFieldsEqual(av, bvi) {
5413
5443
  if (av === bvi) return true;
5414
5444
  if (av.message !== bvi.message) return false;
5415
5445
  if (av.code !== bvi.code) return false;
5416
5446
  if (av.formKey !== bvi.formKey) return false;
5417
- return av.path === bvi.path || pathsEqual(av.path, bvi.path);
5447
+ if (av.path !== bvi.path && !pathsEqual(av.path, bvi.path)) return false;
5448
+ return dataEqual(av.data, bvi.data);
5418
5449
  }
5419
5450
  function errorsEqual(a, b) {
5420
5451
  if (a.length !== b.length) return false;
@@ -6719,6 +6750,30 @@ function useWizard(options) {
6719
6750
  }
6720
6751
  return out;
6721
6752
  }
6753
+ function collectCallbackErrors(keys) {
6754
+ const out = [];
6755
+ for (const key of keys) {
6756
+ const store = registry.forms.get(key);
6757
+ if (store === void 0) continue;
6758
+ for (const errs of store.userErrors.values()) {
6759
+ for (const err of errs) out.push(toWizardAggregateError(err, key));
6760
+ }
6761
+ }
6762
+ return out;
6763
+ }
6764
+ async function focusFirstWizardError(errors) {
6765
+ if (options.focusFirstError === false) return;
6766
+ const firstFailedKey = errors[0]?.formKey;
6767
+ if (firstFailedKey === void 0 || !isCompiledKey(firstFailedKey)) return;
6768
+ moveTo(firstFailedKey);
6769
+ await nextTick();
6770
+ const failedForm = formsRecord.value[firstFailedKey];
6771
+ if (failedForm === void 0) return;
6772
+ const failedSource = asSubmissionSource(failedForm);
6773
+ if (typeof failedSource.applyInvalidSubmitPolicy === "function") {
6774
+ failedSource.applyInvalidSubmitPolicy();
6775
+ }
6776
+ }
6722
6777
  function handleSubmit(onSubmit, onError) {
6723
6778
  return async function submitHandler(event) {
6724
6779
  if (event !== void 0 && typeof event.preventDefault === "function") {
@@ -6781,6 +6836,18 @@ function useWizard(options) {
6781
6836
  }
6782
6837
  const ctx = buildSubmitContext(valuesMap, currentKey, final);
6783
6838
  await onSubmit(ctx);
6839
+ const callbackErrors = collectCallbackErrors(results.keys());
6840
+ if (callbackErrors.length > 0) {
6841
+ await focusFirstWizardError(callbackErrors);
6842
+ if (onError !== void 0) {
6843
+ try {
6844
+ await onError(callbackErrors);
6845
+ } catch (cause) {
6846
+ throw new SubmitErrorHandlerError("User-provided onError threw", { cause });
6847
+ }
6848
+ }
6849
+ return;
6850
+ }
6784
6851
  if (final) {
6785
6852
  done.value = true;
6786
6853
  } else {
@@ -6790,20 +6857,7 @@ function useWizard(options) {
6790
6857
  if (target !== void 0) moveTo(target.key);
6791
6858
  }
6792
6859
  } else {
6793
- if (options.focusFirstError !== false) {
6794
- const firstFailedKey = errors[0]?.formKey;
6795
- if (firstFailedKey !== void 0 && isCompiledKey(firstFailedKey)) {
6796
- moveTo(firstFailedKey);
6797
- await nextTick();
6798
- const failedForm = formsRecord.value[firstFailedKey];
6799
- if (failedForm !== void 0) {
6800
- const failedSource = asSubmissionSource(failedForm);
6801
- if (typeof failedSource.applyInvalidSubmitPolicy === "function") {
6802
- failedSource.applyInvalidSubmitPolicy();
6803
- }
6804
- }
6805
- }
6806
- }
6860
+ await focusFirstWizardError(errors);
6807
6861
  if (onError !== void 0) {
6808
6862
  try {
6809
6863
  await onError(errors);
@@ -7010,5 +7064,5 @@ function warnIfAmbientWizardProviderHadDuplicates() {
7010
7064
  }
7011
7065
  }
7012
7066
 
7013
- 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, normalizeNumericOption as n, slimKindOf as o, humanize as p, getAtPath as q, setAtPath as r, safeOwnRead as s, unset as u };
7014
- //# sourceMappingURL=attaform.CtJOd7ox.mjs.map
7067
+ 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 };
7068
+ //# sourceMappingURL=attaform.NWrEGrNo.mjs.map