attaform 0.27.1 → 0.27.3

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 (89) hide show
  1. package/dist/abstract.cjs +3 -2
  2. package/dist/abstract.cjs.map +1 -1
  3. package/dist/abstract.d.cts +4 -4
  4. package/dist/abstract.d.mts +4 -4
  5. package/dist/abstract.d.ts +4 -4
  6. package/dist/abstract.mjs +2 -2
  7. package/dist/chunks/devtools.cjs +1 -1
  8. package/dist/chunks/devtools.mjs +1 -1
  9. package/dist/chunks/fingerprint2.cjs +1 -1
  10. package/dist/chunks/fingerprint2.mjs +1 -1
  11. package/dist/index.cjs +4 -3
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +5 -5
  14. package/dist/index.d.mts +5 -5
  15. package/dist/index.d.ts +5 -5
  16. package/dist/index.mjs +3 -3
  17. package/dist/nuxt.d.cts +1 -1
  18. package/dist/nuxt.d.mts +1 -1
  19. package/dist/nuxt.d.ts +1 -1
  20. package/dist/runtime/plugins/attaform.cjs +1 -1
  21. package/dist/runtime/plugins/attaform.mjs +1 -1
  22. package/dist/shared/{attaform.Co5LNRnv.cjs → attaform.BBaMFa-k.cjs} +31 -5
  23. package/dist/shared/attaform.BBaMFa-k.cjs.map +1 -0
  24. package/dist/shared/{attaform.C3WnCDXz.d.cts → attaform.BIPSHY5U.d.cts} +55 -1
  25. package/dist/shared/{attaform.C3WnCDXz.d.mts → attaform.BIPSHY5U.d.mts} +55 -1
  26. package/dist/shared/{attaform.C3WnCDXz.d.ts → attaform.BIPSHY5U.d.ts} +55 -1
  27. package/dist/shared/{attaform.DDAmSFnl.mjs → attaform.BKxpBJVF.mjs} +3 -3
  28. package/dist/shared/{attaform.DDAmSFnl.mjs.map → attaform.BKxpBJVF.mjs.map} +1 -1
  29. package/dist/shared/{attaform.C__F01CE.cjs → attaform.BOIpRdeG.cjs} +4 -4
  30. package/dist/shared/{attaform.C__F01CE.cjs.map → attaform.BOIpRdeG.cjs.map} +1 -1
  31. package/dist/shared/{attaform.D99pd7JG.d.cts → attaform.BjGedLx2.d.cts} +181 -24
  32. package/dist/shared/{attaform.8tFZu1Ub.cjs → attaform.Bn0IzhD_.cjs} +50 -2
  33. package/dist/shared/attaform.Bn0IzhD_.cjs.map +1 -0
  34. package/dist/shared/{attaform.CyQxTOdX.mjs → attaform.BqkFQ2B9.mjs} +332 -84
  35. package/dist/shared/attaform.BqkFQ2B9.mjs.map +1 -0
  36. package/dist/shared/{attaform.CC_y1_Ej.d.ts → attaform.C8zVmank.d.ts} +1 -1
  37. package/dist/shared/{attaform.CpXHK60W.d.cts → attaform.C925L0WA.d.mts} +1 -1
  38. package/dist/shared/{attaform.kBkYlKP_.d.mts → attaform.CEZP3i3I.d.mts} +181 -24
  39. package/dist/shared/{attaform.Da8EdI2t.mjs → attaform.CgsQZdy1.mjs} +31 -5
  40. package/dist/shared/attaform.CgsQZdy1.mjs.map +1 -0
  41. package/dist/shared/{attaform.CvVYOvZF.d.mts → attaform.CnvbLiIO.d.cts} +1 -1
  42. package/dist/shared/{attaform.BERt5YMU.mjs → attaform.Cs2Rbdlo.mjs} +50 -2
  43. package/dist/shared/attaform.Cs2Rbdlo.mjs.map +1 -0
  44. package/dist/shared/{attaform.BceJrhQ7.mjs → attaform.DDIn4fv-.mjs} +4 -4
  45. package/dist/shared/{attaform.BceJrhQ7.mjs.map → attaform.DDIn4fv-.mjs.map} +1 -1
  46. package/dist/shared/{attaform.DkD0DR7R.mjs → attaform.DHQjaGMP.mjs} +5 -5
  47. package/dist/shared/{attaform.DkD0DR7R.mjs.map → attaform.DHQjaGMP.mjs.map} +1 -1
  48. package/dist/shared/{attaform.DhVgG2jh.cjs → attaform.DeHCOlC8.cjs} +4 -4
  49. package/dist/shared/{attaform.DhVgG2jh.cjs.map → attaform.DeHCOlC8.cjs.map} +1 -1
  50. package/dist/shared/{attaform.DlSu08F5.mjs → attaform.DuVCclwv.mjs} +4 -4
  51. package/dist/shared/{attaform.DlSu08F5.mjs.map → attaform.DuVCclwv.mjs.map} +1 -1
  52. package/dist/shared/{attaform.K6Thc6By.cjs → attaform.Mg4g-J0z.cjs} +5 -5
  53. package/dist/shared/{attaform.K6Thc6By.cjs.map → attaform.Mg4g-J0z.cjs.map} +1 -1
  54. package/dist/shared/{attaform.Drsi43GG.cjs → attaform.dX58Auwf.cjs} +3 -3
  55. package/dist/shared/{attaform.Drsi43GG.cjs.map → attaform.dX58Auwf.cjs.map} +1 -1
  56. package/dist/shared/{attaform.CEXUlUkc.d.ts → attaform.gqOCvpSB.d.ts} +181 -24
  57. package/dist/shared/{attaform.DQT7_-FO.cjs → attaform.ij2W7VTw.cjs} +331 -82
  58. package/dist/shared/attaform.ij2W7VTw.cjs.map +1 -0
  59. package/dist/transforms.cjs +1 -1
  60. package/dist/transforms.mjs +1 -1
  61. package/dist/vite.cjs +3 -2
  62. package/dist/vite.cjs.map +1 -1
  63. package/dist/vite.mjs +3 -2
  64. package/dist/vite.mjs.map +1 -1
  65. package/dist/zod-v3.cjs +4 -3
  66. package/dist/zod-v3.cjs.map +1 -1
  67. package/dist/zod-v3.d.cts +3 -3
  68. package/dist/zod-v3.d.mts +3 -3
  69. package/dist/zod-v3.d.ts +3 -3
  70. package/dist/zod-v3.mjs +3 -3
  71. package/dist/zod-v4.cjs +4 -3
  72. package/dist/zod-v4.cjs.map +1 -1
  73. package/dist/zod-v4.d.cts +6 -6
  74. package/dist/zod-v4.d.mts +6 -6
  75. package/dist/zod-v4.d.ts +6 -6
  76. package/dist/zod-v4.mjs +3 -3
  77. package/dist/zod.cjs +4 -3
  78. package/dist/zod.cjs.map +1 -1
  79. package/dist/zod.d.cts +3 -3
  80. package/dist/zod.d.mts +3 -3
  81. package/dist/zod.d.ts +3 -3
  82. package/dist/zod.mjs +3 -3
  83. package/package.json +3 -2
  84. package/dist/shared/attaform.8tFZu1Ub.cjs.map +0 -1
  85. package/dist/shared/attaform.BERt5YMU.mjs.map +0 -1
  86. package/dist/shared/attaform.Co5LNRnv.cjs.map +0 -1
  87. package/dist/shared/attaform.CyQxTOdX.mjs.map +0 -1
  88. package/dist/shared/attaform.DQT7_-FO.cjs.map +0 -1
  89. package/dist/shared/attaform.Da8EdI2t.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const vue = require('vue');
4
- const devtoolsShared = require('./attaform.Co5LNRnv.cjs');
4
+ const devtoolsShared = require('./attaform.BBaMFa-k.cjs');
5
5
  const registerProtocol = require('./attaform.yUrz7C2z.cjs');
6
6
 
7
7
  function safeAssign(target, key, value) {
@@ -595,7 +595,17 @@ const AttaformErrorCode = {
595
595
  * keeps them; a bare `Error` or a non-Error throw defaults here at the
596
596
  * form-level path `[]`.
597
597
  */
598
- SubmitError: "atta:submit-error"
598
+ SubmitError: "atta:submit-error",
599
+ /**
600
+ * Synthesized by `wizard.handleSubmit` when the whole-wizard submit
601
+ * would otherwise succeed but a `gate()` step is still uncleared. A
602
+ * gate clears only on its member form's clean submit (confirmation), so
603
+ * a downstream step that happens to be valid-by-default must not let the
604
+ * wizard finish past an unconfirmed prerequisite. Emitted at the gate
605
+ * form's key (path `[]`) so the aggregate error pipeline routes focus to
606
+ * the gate, and `done` never latches.
607
+ */
608
+ GateNotCleared: "atta:gate-not-cleared"
599
609
  };
600
610
  function makeBlankRequiredError(segments, formKey) {
601
611
  return {
@@ -610,6 +620,7 @@ function isGateOpen(field, formMeta) {
610
620
  return formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
611
621
  }
612
622
  function computeVerdict(field, formMeta) {
623
+ if (field.disabled === true) return "idle";
613
624
  if (!isGateOpen(field, formMeta)) return "idle";
614
625
  const hasOwnError = field.errors.some(
615
626
  (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
@@ -817,6 +828,7 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
817
828
  ...computeFieldIdentity(formInstanceId, state.formKey, key),
818
829
  key: state.arrayElementKey(segments),
819
830
  blank: state.blankPaths.has(key),
831
+ disabled: state.effectiveDisabled.value,
820
832
  label,
821
833
  description: resolved.description,
822
834
  placeholder: resolved.placeholder,
@@ -1004,6 +1016,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
1004
1016
  ...computeFieldIdentity(formInstanceId, state.formKey, key),
1005
1017
  key: state.arrayElementKey(segments),
1006
1018
  blank,
1019
+ disabled: state.effectiveDisabled.value,
1007
1020
  label,
1008
1021
  description: resolved.description,
1009
1022
  placeholder: resolved.placeholder,
@@ -1058,7 +1071,8 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingS
1058
1071
  rollupApplies = true;
1059
1072
  }
1060
1073
  const submitValidating = rollupApplies && isRoot && state.submitting.value && state.activeValidations.value > 0;
1061
- const displayState = machine.display === "pending" || submitValidating ? "pending" : rollupApplies && revealedDescendantError ? "error" : machine.display;
1074
+ const resolvedDisplayState = machine.display === "pending" || submitValidating ? "pending" : rollupApplies && revealedDescendantError ? "error" : machine.display;
1075
+ const displayState = base.disabled === true ? "idle" : resolvedDisplayState;
1062
1076
  return {
1063
1077
  ...base,
1064
1078
  displayState,
@@ -1646,6 +1660,7 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1646
1660
  "aria",
1647
1661
  "key",
1648
1662
  "blank",
1663
+ "disabled",
1649
1664
  "label",
1650
1665
  "description",
1651
1666
  "placeholder",
@@ -2415,6 +2430,9 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2415
2430
  return Object.prototype.toString.call(raw);
2416
2431
  }
2417
2432
  });
2433
+ const hostModelValue = vue.computed(
2434
+ () => state.blankPaths.has(pathKey) ? void 0 : innerRef.value
2435
+ );
2418
2436
  const slimDefault = state.schema.getDefaultAtPath(segments);
2419
2437
  const slimTypes = state.schema.getSlimPrimitiveTypesAtPath(segments);
2420
2438
  const acceptsUndefined = slimTypes.has("undefined");
@@ -2434,19 +2452,18 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2434
2452
  const isRequired = state.schema.isRequiredAtPath(segments);
2435
2453
  const ariaEnabled = options?.autoAria ?? formAutoAria;
2436
2454
  const ariaDisplayState = getDisplayStateAt !== void 0 ? vue.computed(() => getDisplayStateAt(segments)) : void 0;
2455
+ const markBlank = () => state.setValueAtPath(segments, slimDefault, withInstanceMeta({ blank: true }));
2437
2456
  const internalRv = {
2438
2457
  innerRef,
2439
2458
  displayValue,
2459
+ hostModelValue,
2460
+ // Live form-freeze flag for the compile-time transforms' `:disabled`
2461
+ // bind (native attribute + component-host prop) and custom
2462
+ // `useRegister` integrations. Reads the form-level effective freeze,
2463
+ // so every binding on a disabled form reports `true`.
2464
+ disabled: vue.computed(() => state.effectiveDisabled.value),
2440
2465
  lastTypedForm,
2441
- markBlank: () => {
2442
- return state.setValueAtPath(
2443
- segments,
2444
- slimDefault,
2445
- withInstanceMeta({
2446
- blank: true
2447
- })
2448
- );
2449
- },
2466
+ markBlank,
2450
2467
  markInteracted: () => {
2451
2468
  state.markInteracted(segments);
2452
2469
  },
@@ -2464,7 +2481,8 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2464
2481
  },
2465
2482
  setValueFromHost: (value) => {
2466
2483
  state.markInteracted(segments);
2467
- const accepted = state.setValueAtPath(segments, value, withInstanceMeta(void 0));
2484
+ const isBlankSignal = value === "" && !acceptsString || value === null && !slimTypes.has("null") || value === void 0 && !acceptsUndefined;
2485
+ const accepted = isBlankSignal ? markBlank() : state.setValueAtPath(segments, value, withInstanceMeta(void 0));
2468
2486
  if (devtoolsShared.__DEV__) {
2469
2487
  const dedupeKey = `${formInstanceId}:${pathKey}`;
2470
2488
  const isWired = () => (state.elements.get(pathKey)?.elements.size ?? 0) > 0 || state.getFieldRecord(segments)?.connected === true;
@@ -2911,6 +2929,9 @@ function buildFormApi(state, formInstanceId, options = {}) {
2911
2929
  get blank() {
2912
2930
  return rootBase().blank;
2913
2931
  },
2932
+ get disabled() {
2933
+ return rootBase().disabled;
2934
+ },
2914
2935
  get label() {
2915
2936
  return rootBase().label;
2916
2937
  },
@@ -3229,6 +3250,9 @@ function buildFormApi(state, formInstanceId, options = {}) {
3229
3250
  get blank() {
3230
3251
  return rootFieldState.value.blank;
3231
3252
  },
3253
+ get disabled() {
3254
+ return rootFieldState.value.disabled;
3255
+ },
3232
3256
  get label() {
3233
3257
  return rootFieldState.value.label;
3234
3258
  },
@@ -4271,6 +4295,24 @@ function createFormStore(options) {
4271
4295
  const submitted = vue.ref(false);
4272
4296
  const submitError = vue.ref(null);
4273
4297
  const departAttempts = vue.ref(0);
4298
+ const externalLock = vue.ref(false);
4299
+ let warnedDisabledThrow = false;
4300
+ let warnedDisabledWrite = false;
4301
+ const effectiveDisabled = vue.computed(() => {
4302
+ let own = false;
4303
+ try {
4304
+ own = Boolean(vue.toValue(options.disabled));
4305
+ } catch (err) {
4306
+ if (devtoolsShared.__DEV__ && !warnedDisabledThrow) {
4307
+ warnedDisabledThrow = true;
4308
+ console.warn(
4309
+ `[attaform] useForm({ disabled }) getter threw for form "${String(formKey)}"; treating its config as enabled. Fix the getter to clear this warning.`,
4310
+ err
4311
+ );
4312
+ }
4313
+ }
4314
+ return own || externalLock.value;
4315
+ });
4274
4316
  const submissionGeneration = vue.ref(0);
4275
4317
  const activeValidations = vue.ref(0);
4276
4318
  const pathSnapshots = /* @__PURE__ */ new Map();
@@ -4564,6 +4606,15 @@ function createFormStore(options) {
4564
4606
  commitWritePatches(patches, meta);
4565
4607
  }
4566
4608
  function setValueAtPath(path, value, meta) {
4609
+ if (effectiveDisabled.value) {
4610
+ if (devtoolsShared.__DEV__ && !warnedDisabledWrite) {
4611
+ warnedDisabledWrite = true;
4612
+ console.warn(
4613
+ `[attaform] Ignored a write to a disabled form ("${String(formKey)}"). Value writes no-op while \`disabled\` resolves truthy. This warning fires once.`
4614
+ );
4615
+ }
4616
+ return false;
4617
+ }
4567
4618
  if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4568
4619
  for (const idx of freshElementIndices(meta.arrayOp)) {
4569
4620
  value[idx] = stripSymbolsDeep(value[idx]);
@@ -5127,6 +5178,7 @@ function createFormStore(options) {
5127
5178
  }
5128
5179
  }
5129
5180
  function markFocused(path, focused, meta) {
5181
+ if (effectiveDisabled.value) return;
5130
5182
  const { key } = devtoolsShared.canonicalizePath(path);
5131
5183
  const current = fields.get(key);
5132
5184
  touchFieldRecord(key, path, {
@@ -5174,6 +5226,7 @@ function createFormStore(options) {
5174
5226
  }
5175
5227
  }
5176
5228
  function markInteracted(path) {
5229
+ if (effectiveDisabled.value) return;
5177
5230
  const { key } = devtoolsShared.canonicalizePath(path);
5178
5231
  if (fields.get(key)?.interacted === true) return;
5179
5232
  touchFieldRecord(key, path, { interacted: true });
@@ -5490,6 +5543,8 @@ function createFormStore(options) {
5490
5543
  submitted,
5491
5544
  submitError,
5492
5545
  departAttempts,
5546
+ effectiveDisabled,
5547
+ externalLock,
5493
5548
  hydrating,
5494
5549
  hydrateError,
5495
5550
  defaultValuesFactory,
@@ -5877,6 +5932,7 @@ function mergeWithDefaults(defaults, configuration) {
5877
5932
  const onInvalidSubmit = configuration.onInvalidSubmit ?? defaults.onInvalidSubmit;
5878
5933
  const history = configuration.history ?? defaults.history;
5879
5934
  const rememberVariants = configuration.rememberVariants ?? defaults.rememberVariants;
5935
+ const disabled = configuration.disabled ?? defaults.disabled;
5880
5936
  const coerce = configuration.coerce ?? defaults.coerce;
5881
5937
  const validateOn = configuration.validateOn ?? defaults.validateOn;
5882
5938
  const debounceMs = configuration.debounceMs ?? defaults.debounceMs;
@@ -5889,6 +5945,7 @@ function mergeWithDefaults(defaults, configuration) {
5889
5945
  ...onInvalidSubmit === void 0 ? {} : { onInvalidSubmit },
5890
5946
  ...history === void 0 ? {} : { history },
5891
5947
  ...rememberVariants === void 0 ? {} : { rememberVariants },
5948
+ ...disabled === void 0 ? {} : { disabled },
5892
5949
  ...coerce === void 0 ? {} : { coerce },
5893
5950
  ...validateOn === void 0 ? {} : { validateOn },
5894
5951
  ...debounceMs === void 0 ? {} : { debounceMs },
@@ -5934,6 +5991,7 @@ function buildFreshState(key, schema, configuration, registry) {
5934
5991
  }
5935
5992
  } : {},
5936
5993
  ...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
5994
+ ...configuration.disabled !== void 0 ? { disabled: configuration.disabled } : {},
5937
5995
  ...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
5938
5996
  ...configuration.getDisplayState !== void 0 ? { getDisplayState: configuration.getDisplayState } : {},
5939
5997
  ...initialBlankPaths !== void 0 ? { initialBlankPaths } : {}
@@ -6158,6 +6216,14 @@ function isLazyMarker(value) {
6158
6216
  return typeof value === "object" && value !== null && LAZY_BRAND in value;
6159
6217
  }
6160
6218
 
6219
+ const GATE_BRAND = /* @__PURE__ */ Symbol.for("attaform/wizard-gate");
6220
+ function gate(step) {
6221
+ return { [GATE_BRAND]: true, inner: step };
6222
+ }
6223
+ function isGateMarker(value) {
6224
+ return typeof value === "object" && value !== null && GATE_BRAND in value;
6225
+ }
6226
+
6161
6227
  const NOOP_WIZARD_HISTORY = {
6162
6228
  push() {
6163
6229
  },
@@ -6281,14 +6347,19 @@ const PENDING_STATUS = {
6281
6347
  valid: false,
6282
6348
  dirty: false,
6283
6349
  submitted: false,
6284
- errorCount: 0
6350
+ errorCount: 0,
6351
+ locked: false,
6352
+ gate: null
6285
6353
  };
6286
6354
  const NOOP_VALID_STATUS = {
6287
6355
  valid: true,
6288
6356
  dirty: false,
6289
6357
  submitted: false,
6290
- errorCount: 0
6358
+ errorCount: 0,
6359
+ locked: false,
6360
+ gate: null
6291
6361
  };
6362
+ const EMPTY_LOCK_SET = /* @__PURE__ */ new Set();
6292
6363
  function asStatusSource(form) {
6293
6364
  return form;
6294
6365
  }
@@ -6355,7 +6426,7 @@ function useWizard(options) {
6355
6426
  }
6356
6427
  }
6357
6428
  const lazyEpoch = vue.ref(0);
6358
- const lazyComputeds = /* @__PURE__ */ new Map();
6429
+ const lazyComputeds = /* @__PURE__ */ new WeakMap();
6359
6430
  const activeKey = vue.ref("");
6360
6431
  const formsAccumulator = /* @__PURE__ */ new Map();
6361
6432
  for (const slot of rawSteps) {
@@ -6391,50 +6462,44 @@ function useWizard(options) {
6391
6462
  return activeKey.value === "" ? void 0 : activeKey.value;
6392
6463
  }
6393
6464
  };
6394
- function resolveSlot(slot, index, ctx) {
6395
- if (slot === void 0 || slot === null) return void 0;
6396
- if (typeof slot === "string") {
6397
- return getOrBuildNoop(slot);
6398
- }
6399
- if (isLazyMarker(slot)) {
6400
- const c = lazyComputeds.get(index);
6401
- return c === void 0 ? void 0 : resolveSlotResult(c.value);
6465
+ function lazyComputedFor(marker) {
6466
+ const cached = lazyComputeds.get(marker);
6467
+ if (cached !== void 0) return cached;
6468
+ const c = vue.computed(() => {
6469
+ void lazyEpoch.value;
6470
+ return marker.resolve(slotCtx);
6471
+ });
6472
+ lazyComputeds.set(marker, c);
6473
+ return c;
6474
+ }
6475
+ const MAX_SLOT_DEPTH = 32;
6476
+ function normalizeSlot(slot, ctx, gated, depth) {
6477
+ if (depth > MAX_SLOT_DEPTH) {
6478
+ if (devtoolsShared.__DEV__) {
6479
+ console.warn(
6480
+ `[attaform] useWizard: a step slot nested past ${MAX_SLOT_DEPTH} levels (gate / lazy / function); dropping it. Check for a resolver that returns itself.`
6481
+ );
6482
+ }
6483
+ return void 0;
6402
6484
  }
6485
+ if (slot === void 0 || slot === null) return void 0;
6486
+ if (typeof slot === "string") return { form: getOrBuildNoop(slot), gated };
6487
+ if (isGateMarker(slot)) return normalizeSlot(slot.inner, ctx, true, depth + 1);
6488
+ if (isLazyMarker(slot)) return normalizeSlot(lazyComputedFor(slot).value, ctx, gated, depth + 1);
6403
6489
  if (typeof slot === "function") {
6404
6490
  const result = slot(ctx);
6405
- return resolveSlotResult(result);
6491
+ return normalizeSlot(result, ctx, gated, depth + 1);
6406
6492
  }
6407
- if (isAnyForm(slot)) return slot;
6493
+ if (isAnyForm(slot)) return { form: slot, gated };
6408
6494
  return void 0;
6409
6495
  }
6410
- function resolveSlotResult(result) {
6411
- if (result === void 0 || result === null) return void 0;
6412
- if (typeof result === "string") {
6413
- return getOrBuildNoop(result);
6414
- }
6415
- return result;
6416
- }
6417
- for (let i = 0; i < rawSteps.length; i++) {
6418
- const slot = rawSteps[i];
6419
- if (isLazyMarker(slot)) {
6420
- const idx = i;
6421
- const marker = slot;
6422
- lazyComputeds.set(
6423
- idx,
6424
- vue.computed(() => {
6425
- void lazyEpoch.value;
6426
- return marker.resolve(slotCtx);
6427
- })
6428
- );
6429
- }
6430
- }
6431
6496
  const compiledSteps = vue.computed(() => {
6432
6497
  const out = [];
6433
6498
  const seen = /* @__PURE__ */ new Set();
6434
6499
  for (let i = 0; i < rawSteps.length; i++) {
6435
- const slot = rawSteps[i];
6436
- const form = resolveSlot(slot, i, slotCtx);
6437
- if (form === void 0) continue;
6500
+ const norm = normalizeSlot(rawSteps[i], slotCtx, false, 0);
6501
+ if (norm === void 0) continue;
6502
+ const { form, gated } = norm;
6438
6503
  if (seen.has(form.key)) {
6439
6504
  if (devtoolsShared.__DEV__) {
6440
6505
  console.warn(
@@ -6445,7 +6510,7 @@ function useWizard(options) {
6445
6510
  }
6446
6511
  seen.add(form.key);
6447
6512
  trackOnce(form);
6448
- out.push({ key: form.key, form });
6513
+ out.push({ key: form.key, form, isGate: gated });
6449
6514
  }
6450
6515
  return out;
6451
6516
  });
@@ -6481,12 +6546,12 @@ function useWizard(options) {
6481
6546
  if (key === "") return;
6482
6547
  if (list.some((step) => step.key === key)) return;
6483
6548
  if (list.length === 0) {
6484
- activeKey.value = "";
6549
+ commitActiveKey("");
6485
6550
  return;
6486
6551
  }
6487
6552
  const oldIndex = prevList.findIndex((step) => step.key === key);
6488
6553
  const slid = list[oldIndex < 0 ? 0 : Math.min(oldIndex, list.length - 1)];
6489
- if (slid !== void 0) activeKey.value = slid.key;
6554
+ if (slid !== void 0) commitActiveKey(slid.key);
6490
6555
  });
6491
6556
  const activeFormFacade = new Proxy({}, {
6492
6557
  get(_target, prop) {
@@ -6620,6 +6685,136 @@ function useWizard(options) {
6620
6685
  };
6621
6686
  }
6622
6687
  });
6688
+ const mightGate = rawSteps.some(
6689
+ (slot) => isGateMarker(slot) || isLazyMarker(slot) || typeof slot === "function"
6690
+ );
6691
+ const clearedGates = vue.reactive(/* @__PURE__ */ new Set());
6692
+ const seededSampled = /* @__PURE__ */ new Set();
6693
+ function isGateVerdictSettled(key) {
6694
+ const store = registry.forms.get(key);
6695
+ if (store === void 0) return false;
6696
+ return store.defaultsResolved.value === true && store.firstValidationDone.value === true && store.activeValidations.value === 0;
6697
+ }
6698
+ function isGateFormValid(key) {
6699
+ const form = formsRecord.value[key] ?? formsAccumulator.get(key);
6700
+ if (form === void 0) return false;
6701
+ return asStatusSource(form).meta?.valid ?? false;
6702
+ }
6703
+ function sampleSeededGate(key) {
6704
+ if (seededSampled.has(key)) return;
6705
+ if (noopForms.has(key)) return;
6706
+ if (!isGateVerdictSettled(key)) return;
6707
+ seededSampled.add(key);
6708
+ if (isGateFormValid(key)) clearedGates.add(key);
6709
+ }
6710
+ const gateSubs = /* @__PURE__ */ new Map();
6711
+ function reconcileGates() {
6712
+ const gateKeys = /* @__PURE__ */ new Set();
6713
+ for (const step of compiledSteps.value) if (step.isGate) gateKeys.add(step.key);
6714
+ for (const key of gateKeys) {
6715
+ if (!gateSubs.has(key)) {
6716
+ const store = registry.forms.get(key);
6717
+ if (store !== void 0) {
6718
+ gateSubs.set(
6719
+ key,
6720
+ store.onSubmitSuccess(() => {
6721
+ clearedGates.add(key);
6722
+ })
6723
+ );
6724
+ }
6725
+ }
6726
+ sampleSeededGate(key);
6727
+ }
6728
+ for (const [key, unsub] of gateSubs) {
6729
+ if (!gateKeys.has(key)) {
6730
+ unsub();
6731
+ gateSubs.delete(key);
6732
+ }
6733
+ }
6734
+ }
6735
+ const gatePositions = vue.computed(
6736
+ () => compiledSteps.value.filter((step) => step.isGate).map((step) => step.key)
6737
+ );
6738
+ const navLockSet = vue.computed(() => {
6739
+ const out = /* @__PURE__ */ new Set();
6740
+ let sealed = false;
6741
+ for (const step of compiledSteps.value) {
6742
+ if (sealed) {
6743
+ out.add(step.key);
6744
+ continue;
6745
+ }
6746
+ if (step.isGate && !clearedGates.has(step.key)) sealed = true;
6747
+ }
6748
+ return out.size === 0 ? EMPTY_LOCK_SET : out;
6749
+ });
6750
+ const freezeSet = vue.computed(() => {
6751
+ const out = new Set(navLockSet.value);
6752
+ for (const key of gatePositions.value) if (clearedGates.has(key)) out.add(key);
6753
+ return out.size === 0 ? EMPTY_LOCK_SET : out;
6754
+ });
6755
+ function lastReachableKey() {
6756
+ const list = compiledSteps.value;
6757
+ const locked = navLockSet.value;
6758
+ let last;
6759
+ for (const step of list) {
6760
+ if (locked.has(step.key)) break;
6761
+ last = step.key;
6762
+ }
6763
+ return last;
6764
+ }
6765
+ function resolveLandingKey(target) {
6766
+ if (!navLockSet.value.has(target)) return target;
6767
+ const redirect = lastReachableKey();
6768
+ if (redirect === void 0) return target;
6769
+ if (!isFormReady(redirect)) return target;
6770
+ return redirect;
6771
+ }
6772
+ function commitActiveKey(target) {
6773
+ const landing = target === "" ? "" : resolveLandingKey(target);
6774
+ if (activeKey.value !== landing) activeKey.value = landing;
6775
+ return landing;
6776
+ }
6777
+ if (mightGate) {
6778
+ vue.watch(
6779
+ () => {
6780
+ const parts = [];
6781
+ for (const step of compiledSteps.value) {
6782
+ if (!step.isGate) continue;
6783
+ const store = registry.forms.get(step.key);
6784
+ const settled = store !== void 0 && isGateVerdictSettled(step.key);
6785
+ parts.push(`${step.key}:${store !== void 0 ? 1 : 0}:${settled ? 1 : 0}`);
6786
+ }
6787
+ return parts.join("|");
6788
+ },
6789
+ reconcileGates,
6790
+ { immediate: true }
6791
+ );
6792
+ let previouslyFrozen = [];
6793
+ vue.watchEffect(() => {
6794
+ const frozen = freezeSet.value;
6795
+ for (const key of previouslyFrozen) {
6796
+ if (!frozen.has(key)) {
6797
+ const store = registry.forms.get(key);
6798
+ if (store !== void 0) store.externalLock.value = false;
6799
+ }
6800
+ }
6801
+ for (const key of frozen) {
6802
+ const store = registry.forms.get(key);
6803
+ if (store !== void 0) store.externalLock.value = true;
6804
+ }
6805
+ previouslyFrozen = [...frozen];
6806
+ });
6807
+ if (vue.getCurrentScope() !== void 0) {
6808
+ vue.onScopeDispose(() => {
6809
+ for (const key of previouslyFrozen) {
6810
+ const store = registry.forms.get(key);
6811
+ if (store !== void 0) store.externalLock.value = false;
6812
+ }
6813
+ for (const unsub of gateSubs.values()) unsub();
6814
+ gateSubs.clear();
6815
+ });
6816
+ }
6817
+ }
6623
6818
  const seedRef = vue.ref(void 0);
6624
6819
  const seedInput = options.defaultStatuses;
6625
6820
  if (seedInput !== void 0) {
@@ -6643,6 +6838,11 @@ function useWizard(options) {
6643
6838
  if (cached !== void 0) return cached;
6644
6839
  const source = asStatusSource(form);
6645
6840
  const computedStatus = vue.computed(() => {
6841
+ const locked = navLockSet.value.has(form.key);
6842
+ let gate = null;
6843
+ if (gatePositions.value.includes(form.key)) {
6844
+ gate = clearedGates.has(form.key) ? "cleared" : "uncleared";
6845
+ }
6646
6846
  if (isFormReady(form.key)) {
6647
6847
  const meta = source.meta;
6648
6848
  if (meta !== void 0 && meta !== null) {
@@ -6650,16 +6850,18 @@ function useWizard(options) {
6650
6850
  valid: meta.valid,
6651
6851
  dirty: meta.dirty,
6652
6852
  submitted: meta.submitted,
6653
- errorCount: meta.errorCount
6853
+ errorCount: meta.errorCount,
6854
+ locked,
6855
+ gate
6654
6856
  };
6655
6857
  }
6656
6858
  }
6657
- if (noopForms.has(form.key)) return NOOP_VALID_STATUS;
6658
- const seedMap = seedRef.value;
6659
- if (seedMap !== void 0 && Object.hasOwn(seedMap, form.key)) {
6660
- return seedMap[form.key];
6859
+ if (noopForms.has(form.key)) {
6860
+ return locked || gate !== null ? { ...NOOP_VALID_STATUS, locked, gate } : NOOP_VALID_STATUS;
6661
6861
  }
6662
- return PENDING_STATUS;
6862
+ const seed = seedRef.value?.[form.key];
6863
+ if (seed !== void 0) return { ...seed, locked, gate };
6864
+ return locked || gate !== null ? { ...PENDING_STATUS, locked, gate } : PENDING_STATUS;
6663
6865
  });
6664
6866
  statusCache.set(form.key, computedStatus);
6665
6867
  return computedStatus;
@@ -6775,8 +6977,8 @@ function useWizard(options) {
6775
6977
  initialKey = firstKey();
6776
6978
  }
6777
6979
  if (initialKey !== void 0) {
6778
- activeKey.value = initialKey;
6779
- visited.value = [initialKey];
6980
+ const landing = commitActiveKey(initialKey);
6981
+ visited.value = [landing];
6780
6982
  }
6781
6983
  if (registry.ssr) {
6782
6984
  for (const step of compiledSteps.value) {
@@ -6808,8 +7010,8 @@ function useWizard(options) {
6808
7010
  return;
6809
7011
  }
6810
7012
  if (step === activeKey.value) return;
6811
- activeKey.value = step;
6812
- if (!visited.value.includes(step)) visited.value.push(step);
7013
+ const landing = commitActiveKey(step);
7014
+ if (!visited.value.includes(landing)) visited.value.push(landing);
6813
7015
  }
6814
7016
  );
6815
7017
  }
@@ -6841,11 +7043,11 @@ function useWizard(options) {
6841
7043
  }
6842
7044
  function moveTo(key, options2) {
6843
7045
  if (activeKey.value === key) return;
6844
- activeKey.value = key;
6845
- if (!visited.value.includes(key)) visited.value.push(key);
7046
+ const landing = commitActiveKey(key);
7047
+ if (!visited.value.includes(landing)) visited.value.push(landing);
6846
7048
  const list = compiledSteps.value;
6847
7049
  for (const step of list) {
6848
- if (step.key === key) {
7050
+ if (step.key === landing) {
6849
7051
  activateForm(step.form);
6850
7052
  return;
6851
7053
  }
@@ -6855,6 +7057,14 @@ function useWizard(options) {
6855
7057
  const store = registry.forms.get(key);
6856
7058
  if (store !== void 0) store.departAttempts.value += 1;
6857
7059
  }
7060
+ function advanceOne() {
7061
+ const list = compiledSteps.value;
7062
+ const idx = activeIndex.value;
7063
+ if (idx < 0 || idx >= list.length - 1) return;
7064
+ recordDeparture(activeKey.value);
7065
+ const target = list[idx + 1];
7066
+ moveTo(target.key);
7067
+ }
6858
7068
  async function next() {
6859
7069
  if (submitting.value) {
6860
7070
  if (devtoolsShared.__DEV__) {
@@ -6880,9 +7090,12 @@ function useWizard(options) {
6880
7090
  }
6881
7091
  return;
6882
7092
  }
6883
- recordDeparture(activeKey.value);
6884
- const target = list[idx + 1];
6885
- moveTo(target.key);
7093
+ const active = list[idx];
7094
+ if (active !== void 0 && active.isGate) {
7095
+ await tryNext();
7096
+ return;
7097
+ }
7098
+ advanceOne();
6886
7099
  }
6887
7100
  async function tryNext() {
6888
7101
  if (submitting.value) {
@@ -6909,13 +7122,13 @@ function useWizard(options) {
6909
7122
  }
6910
7123
  const form = activeForm.value;
6911
7124
  if (form === void 0) return false;
6912
- let advanced = false;
7125
+ const before = activeKey.value;
7126
+ let ranClean = false;
6913
7127
  await asHandleSubmitSource(form).handleSubmit(() => {
6914
- const before = activeKey.value;
6915
- next();
6916
- advanced = activeKey.value !== before;
7128
+ ranClean = true;
6917
7129
  })();
6918
- return advanced;
7130
+ if (ranClean) advanceOne();
7131
+ return activeKey.value !== before;
6919
7132
  }
6920
7133
  function back() {
6921
7134
  if (submitting.value) {
@@ -6955,6 +7168,11 @@ function useWizard(options) {
6955
7168
  }
6956
7169
  return;
6957
7170
  }
7171
+ if (devtoolsShared.__DEV__ && navLockSet.value.has(key)) {
7172
+ console.warn(
7173
+ `[attaform] wizard.goTo("${key}"): that step sits behind an uncleared gate(); the navigation was refused. Clear the gate before navigating here.`
7174
+ );
7175
+ }
6958
7176
  if (key !== activeKey.value) recordDeparture(activeKey.value);
6959
7177
  moveTo(key);
6960
7178
  }
@@ -7014,6 +7232,20 @@ function useWizard(options) {
7014
7232
  }
7015
7233
  return out;
7016
7234
  }
7235
+ function collectUnclearedGateErrors() {
7236
+ const out = [];
7237
+ for (const step of compiledSteps.value) {
7238
+ if (step.isGate && !clearedGates.has(step.key)) {
7239
+ out.push({
7240
+ formKey: step.key,
7241
+ path: [],
7242
+ message: `Step "${step.key}" is a gate that has not been cleared. Submit it to continue.`,
7243
+ code: AttaformErrorCode.GateNotCleared
7244
+ });
7245
+ }
7246
+ }
7247
+ return out;
7248
+ }
7017
7249
  async function focusFirstWizardError(errors) {
7018
7250
  if (options.focusFirstError === false) return;
7019
7251
  const firstFailedKey = errors[0]?.formKey;
@@ -7070,7 +7302,9 @@ function useWizard(options) {
7070
7302
  }
7071
7303
  submissionAttempts.value += 1;
7072
7304
  const errors = collectErrors(results);
7073
- if (errors.length === 0) {
7305
+ const gateErrors = mightGate ? collectUnclearedGateErrors() : [];
7306
+ const blocking = gateErrors.length > 0 ? [...errors, ...gateErrors] : errors;
7307
+ if (blocking.length === 0) {
7074
7308
  const valuesMap = {};
7075
7309
  for (const step of list) {
7076
7310
  const processed = results.get(step.key);
@@ -7096,10 +7330,10 @@ function useWizard(options) {
7096
7330
  }
7097
7331
  done.value = true;
7098
7332
  } else {
7099
- await focusFirstWizardError(errors);
7333
+ await focusFirstWizardError(blocking);
7100
7334
  if (onError !== void 0) {
7101
7335
  try {
7102
- await onError(errors);
7336
+ await onError(blocking);
7103
7337
  } catch (cause) {
7104
7338
  throw new devtoolsShared.SubmitErrorHandlerError("User-provided onError threw", { cause });
7105
7339
  }
@@ -7116,21 +7350,35 @@ function useWizard(options) {
7116
7350
  submissionAttempts.value = 0;
7117
7351
  done.value = false;
7118
7352
  submitError.value = null;
7353
+ if (mightGate) {
7354
+ clearedGates.clear();
7355
+ seededSampled.clear();
7356
+ }
7119
7357
  lazyEpoch.value += 1;
7120
7358
  for (const step of compiledSteps.value) {
7121
7359
  const full = asSubmissionSource(step.form);
7122
7360
  if (typeof full.reset === "function") full.reset();
7123
7361
  }
7362
+ if (mightGate) reconcileGates();
7124
7363
  const firstStep = compiledSteps.value[0];
7125
7364
  if (firstStep !== void 0) {
7126
- activeKey.value = firstStep.key;
7127
- visited.value = [firstStep.key];
7365
+ const landing = commitActiveKey(firstStep.key);
7366
+ visited.value = [landing];
7128
7367
  if (persistCallback !== void 0) {
7129
- lastPersisted = firstStep.key;
7130
- persistCallback({ step: firstStep.key });
7368
+ lastPersisted = landing;
7369
+ persistCallback({ step: landing });
7131
7370
  }
7132
7371
  }
7133
7372
  }
7373
+ if (mightGate) {
7374
+ vue.watchEffect(() => {
7375
+ const key = activeKey.value;
7376
+ if (key === "" || !navLockSet.value.has(key)) return;
7377
+ const redirect = lastReachableKey();
7378
+ if (redirect === void 0 || redirect === key || !isFormReady(redirect)) return;
7379
+ moveTo(redirect);
7380
+ });
7381
+ }
7134
7382
  if (vue.getCurrentScope() !== void 0) {
7135
7383
  vue.onScopeDispose(() => {
7136
7384
  historyHandle.dispose();
@@ -7332,6 +7580,7 @@ exports.defaultCoercionRules = defaultCoercionRules;
7332
7580
  exports.defaultDisplayState = defaultDisplayState;
7333
7581
  exports.defineCoercion = defineCoercion;
7334
7582
  exports.escapeForInlineScript = escapeForInlineScript;
7583
+ exports.gate = gate;
7335
7584
  exports.getAtPath = getAtPath;
7336
7585
  exports.humanize = humanize;
7337
7586
  exports.injectForm = injectForm;
@@ -7348,4 +7597,4 @@ exports.unset = unset;
7348
7597
  exports.useAbstractForm = useAbstractForm;
7349
7598
  exports.useRegister = useRegister;
7350
7599
  exports.useWizard = useWizard;
7351
- //# sourceMappingURL=attaform.DQT7_-FO.cjs.map
7600
+ //# sourceMappingURL=attaform.ij2W7VTw.cjs.map