attaform 0.27.2 → 0.27.4

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 (51) hide show
  1. package/dist/abstract.cjs +1 -1
  2. package/dist/abstract.d.cts +2 -2
  3. package/dist/abstract.d.mts +2 -2
  4. package/dist/abstract.d.ts +2 -2
  5. package/dist/abstract.mjs +1 -1
  6. package/dist/chunks/fingerprint2.cjs +1 -1
  7. package/dist/chunks/fingerprint2.mjs +1 -1
  8. package/dist/index.cjs +2 -2
  9. package/dist/index.d.cts +1 -1
  10. package/dist/index.d.mts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.mjs +2 -2
  13. package/dist/shared/{attaform.BF1mYHru.cjs → attaform.B3spfqIA.cjs} +3 -3
  14. package/dist/shared/{attaform.BF1mYHru.cjs.map → attaform.B3spfqIA.cjs.map} +1 -1
  15. package/dist/shared/{attaform.gyed1fGh.mjs → attaform.BrVzB14N.mjs} +3 -3
  16. package/dist/shared/{attaform.gyed1fGh.mjs.map → attaform.BrVzB14N.mjs.map} +1 -1
  17. package/dist/shared/{attaform.C1SJy524.cjs → attaform.C9arOC5p.cjs} +4 -4
  18. package/dist/shared/{attaform.C1SJy524.cjs.map → attaform.C9arOC5p.cjs.map} +1 -1
  19. package/dist/shared/{attaform.6IZBjA1Q.mjs → attaform.CC4KCx_q.mjs} +4 -4
  20. package/dist/shared/{attaform.6IZBjA1Q.mjs.map → attaform.CC4KCx_q.mjs.map} +1 -1
  21. package/dist/shared/{attaform.CIPwCQe8.cjs → attaform.CX2NHAFs.cjs} +2 -2
  22. package/dist/shared/{attaform.CIPwCQe8.cjs.map → attaform.CX2NHAFs.cjs.map} +1 -1
  23. package/dist/shared/{attaform.DIJTp_Kv.d.cts → attaform.Cjh6b6xU.d.cts} +64 -16
  24. package/dist/shared/{attaform.DGCijpZT.mjs → attaform.CuUx4jWI.mjs} +53 -30
  25. package/dist/shared/{attaform.CXHebpij.cjs.map → attaform.CuUx4jWI.mjs.map} +1 -1
  26. package/dist/shared/{attaform.B2d0FszH.d.ts → attaform.DKtXMaJB.d.ts} +64 -16
  27. package/dist/shared/{attaform.CvaPYOZu.cjs → attaform.DXwJVUNS.cjs} +3 -3
  28. package/dist/shared/{attaform.CvaPYOZu.cjs.map → attaform.DXwJVUNS.cjs.map} +1 -1
  29. package/dist/shared/{attaform.CXHebpij.cjs → attaform.Ib2seKUE.cjs} +53 -30
  30. package/dist/shared/{attaform.DGCijpZT.mjs.map → attaform.Ib2seKUE.cjs.map} +1 -1
  31. package/dist/shared/{attaform.BAlfXoAx.mjs → attaform.a1GnhjVk.mjs} +2 -2
  32. package/dist/shared/{attaform.BAlfXoAx.mjs.map → attaform.a1GnhjVk.mjs.map} +1 -1
  33. package/dist/shared/{attaform.Dcj-OS43.d.mts → attaform.gj8dtOlx.d.mts} +64 -16
  34. package/dist/shared/{attaform.yOE3udL1.mjs → attaform.shkNCMEz.mjs} +3 -3
  35. package/dist/shared/{attaform.yOE3udL1.mjs.map → attaform.shkNCMEz.mjs.map} +1 -1
  36. package/dist/zod-v3.cjs +2 -2
  37. package/dist/zod-v3.d.cts +1 -1
  38. package/dist/zod-v3.d.mts +1 -1
  39. package/dist/zod-v3.d.ts +1 -1
  40. package/dist/zod-v3.mjs +2 -2
  41. package/dist/zod-v4.cjs +2 -2
  42. package/dist/zod-v4.d.cts +2 -2
  43. package/dist/zod-v4.d.mts +2 -2
  44. package/dist/zod-v4.d.ts +2 -2
  45. package/dist/zod-v4.mjs +2 -2
  46. package/dist/zod.cjs +2 -2
  47. package/dist/zod.d.cts +1 -1
  48. package/dist/zod.d.mts +1 -1
  49. package/dist/zod.d.ts +1 -1
  50. package/dist/zod.mjs +2 -2
  51. package/package.json +1 -1
@@ -1026,13 +1026,24 @@ type AnyForm = {
1026
1026
  * `submissionAttempts > 0` is the "user has tried" signal.
1027
1027
  * - `errorCount` — `form.meta.errorCount`. Count of active validation
1028
1028
  * errors (zero when valid).
1029
- * - `locked`: `true` when this step sits after an uncleared `gate()`
1030
- * (see `gate`). Its member form is frozen (data writes no-op) and
1031
- * navigation to it is refused, so a stepper can render it as an
1032
- * unreachable gate. `false` when no gate seals it.
1029
+ * - `locked`: `true` when this step sits after an uncleared gate. Its
1030
+ * member form is frozen (data writes no-op) and navigation to it is
1031
+ * refused, so a stepper can render it as an unreachable step. `false`
1032
+ * when no gate seals it. Orthogonal to `gate`: the first uncleared
1033
+ * gate reads `locked: false` (you must reach it to clear it), while a
1034
+ * later gate sealed behind it reads both `gate: 'uncleared'` and
1035
+ * `locked: true`.
1036
+ * - `gate`: this step's own role as a hard prerequisite. `null` when the
1037
+ * step is not a `gate()`; `'uncleared'` while its member form is
1038
+ * unconfirmed (so it seals every later step); `'cleared'` once
1039
+ * confirmed by a clean member submit or seeded cleared via
1040
+ * `defaultStatuses`. Reflects the live compiled shape, so a conditional
1041
+ * `gate()` that a function slot drops resolves to `null`.
1033
1042
  *
1034
1043
  * Noop forms generated for string slots surface as default-valid
1035
- * (`{ valid: true, dirty: false, submitted: false, errorCount: 0, locked: false }`).
1044
+ * (`{ valid: true, dirty: false, submitted: false, errorCount: 0, locked: false, gate: null }`),
1045
+ * unless the string slot is a `gate('...')`, whose `gate` tracks the
1046
+ * in-session acknowledgment.
1036
1047
  */
1037
1048
  type FormStatus = {
1038
1049
  readonly valid: boolean;
@@ -1040,14 +1051,30 @@ type FormStatus = {
1040
1051
  readonly submitted: boolean;
1041
1052
  readonly errorCount: number;
1042
1053
  readonly locked: boolean;
1054
+ readonly gate: 'cleared' | 'uncleared' | null;
1043
1055
  };
1044
1056
  /**
1045
- * Seed shape accepted by `useWizard({ defaultStatuses })`. Mirrors
1046
- * `FormStatus` minus `locked`: the lock flag is computed live from the
1047
- * wizard's gates and overlaid on every status read, so a seed never
1048
- * carries it (a seeded value would be ignored).
1049
- */
1050
- type FormStatusSeed = Omit<FormStatus, 'locked'>;
1057
+ * Seed shape accepted by `useWizard({ defaultStatuses })`. Every field is
1058
+ * optional; an omitted meta field falls back to the pending sentinel on
1059
+ * read.
1060
+ *
1061
+ * `locked` is never accepted: it is purely live-derived (sealed behind an
1062
+ * earlier uncleared gate) and overlaid on every status read, so a seeded
1063
+ * value would be ignored.
1064
+ *
1065
+ * `gate` is a WRITE-only seed, not a status field echoed back on read. A
1066
+ * seed of `'cleared'` latches the gate's cleared state once at construction
1067
+ * (an SSR restore of a confirmed prerequisite); the live `gate` overlay
1068
+ * then owns the read. `'uncleared'` is the default and seeds nothing. Only
1069
+ * a step that compiles to a `gate()` honors it — other keys ignore it.
1070
+ */
1071
+ type FormStatusSeed = {
1072
+ readonly valid?: boolean;
1073
+ readonly dirty?: boolean;
1074
+ readonly submitted?: boolean;
1075
+ readonly errorCount?: number;
1076
+ readonly gate?: 'cleared' | 'uncleared';
1077
+ };
1051
1078
  /**
1052
1079
  * Flat error shape returned per form by `wizard.allErrors[key]`. Each
1053
1080
  * entry carries the formKey + path tuple so consumers can route to the
@@ -1302,12 +1329,20 @@ type WizardOptions = {
1302
1329
  * Status resolution priority per form:
1303
1330
  * 1. `store.defaultsResolved === true` → derive from `form.meta`
1304
1331
  * 2. else noop form → built-in always-valid status
1305
- * 3. else seed value for this key → frozen seed
1332
+ * 3. else seed value for this key → seed over the pending sentinel
1306
1333
  * 4. else → pending sentinel
1307
1334
  *
1308
- * The `locked` flag is never seeded: it is computed live from the
1309
- * wizard's gates and overlaid on every status read, so the seed shape
1310
- * is `FormStatus` minus `locked`.
1335
+ * `locked` is never seeded: it is computed live from the wizard's gates
1336
+ * and overlaid on every status read. `gate: 'cleared'` IS honored, but as
1337
+ * a one-shot seed of the gate's cleared latch at construction (an SSR
1338
+ * restore of a confirmed prerequisite), not as a status field echoed on
1339
+ * read — the live `gate` overlay owns the read. Only a step that compiles
1340
+ * to a `gate()` honors it. See `FormStatusSeed`.
1341
+ *
1342
+ * SSR note: the async factory resolves after hydration (client-only), so
1343
+ * a gate seeded through it clears post-hydration. For a gate that must
1344
+ * render open on the first server byte, use the plain or sync-factory
1345
+ * form (resolve server truth in the page's `setup` await).
1311
1346
  *
1312
1347
  * Unknown keys in the seed object dev-warn so a stale resume payload
1313
1348
  * surfaces at construction.
@@ -1547,7 +1582,19 @@ type WizardForms<S> = FormsRecordOf<S> & Readonly<Record<FormKey, AnyForm>>;
1547
1582
  * - `reset()` — zeros wizard lifecycle (`submissionAttempts`,
1548
1583
  * `visited`), resets every form, returns
1549
1584
  * `currentStep` to `steps[0].key`, and invokes
1550
- * `persist` with the cleared state.
1585
+ * `persist` with the cleared state. Re-applies the
1586
+ * `defaultStatuses` gate seed, so a reboot returns to
1587
+ * the seeded initial clearance, not fully sealed.
1588
+ * - `relock(key, commit)` — re-seal a cleared gate, contingent on
1589
+ * `commit`. Awaits `commit` (a server-side revoke) and
1590
+ * re-seals only if it resolves clean, so the gate reflects
1591
+ * server-confirmed truth the same way a clearing submit
1592
+ * does; a thrown `commit` leaves it as-is. Seal-only (never
1593
+ * opens a gate), so it cannot become the leading-signal
1594
+ * foot-gun `gate()` exists to prevent. `commit` is required
1595
+ * (pass `() => {}` for a deliberate client-only re-seal).
1596
+ * Resolves whether the gate ended up sealed; never rejects.
1597
+ * Resolves `false` (dev-warn) on a non-gate key.
1551
1598
  */
1552
1599
  type UseWizardReturnType<S extends ReadonlyArray<StepSlot> = ReadonlyArray<StepSlot>> = {
1553
1600
  readonly key: string;
@@ -1576,6 +1623,7 @@ type UseWizardReturnType<S extends ReadonlyArray<StepSlot> = ReadonlyArray<StepS
1576
1623
  readonly tryNext: () => Promise<boolean>;
1577
1624
  readonly handleSubmit: (onSubmit: WizardOnSubmit, onError?: WizardOnError) => (event?: Event) => Promise<void>;
1578
1625
  readonly reset: () => void;
1626
+ readonly relock: (key: FormKey, commit: () => void | Promise<void>) => Promise<boolean>;
1579
1627
  };
1580
1628
 
1581
1629
  /**
@@ -6346,14 +6346,16 @@ const PENDING_STATUS = {
6346
6346
  dirty: false,
6347
6347
  submitted: false,
6348
6348
  errorCount: 0,
6349
- locked: false
6349
+ locked: false,
6350
+ gate: null
6350
6351
  };
6351
6352
  const NOOP_VALID_STATUS = {
6352
6353
  valid: true,
6353
6354
  dirty: false,
6354
6355
  submitted: false,
6355
6356
  errorCount: 0,
6356
- locked: false
6357
+ locked: false,
6358
+ gate: null
6357
6359
  };
6358
6360
  const EMPTY_LOCK_SET = /* @__PURE__ */ new Set();
6359
6361
  function asStatusSource(form) {
@@ -6685,24 +6687,6 @@ function useWizard(options) {
6685
6687
  (slot) => isGateMarker(slot) || isLazyMarker(slot) || typeof slot === "function"
6686
6688
  );
6687
6689
  const clearedGates = reactive(/* @__PURE__ */ new Set());
6688
- const seededSampled = /* @__PURE__ */ new Set();
6689
- function isGateVerdictSettled(key) {
6690
- const store = registry.forms.get(key);
6691
- if (store === void 0) return false;
6692
- return store.defaultsResolved.value === true && store.firstValidationDone.value === true && store.activeValidations.value === 0;
6693
- }
6694
- function isGateFormValid(key) {
6695
- const form = formsRecord.value[key] ?? formsAccumulator.get(key);
6696
- if (form === void 0) return false;
6697
- return asStatusSource(form).meta?.valid ?? false;
6698
- }
6699
- function sampleSeededGate(key) {
6700
- if (seededSampled.has(key)) return;
6701
- if (noopForms.has(key)) return;
6702
- if (!isGateVerdictSettled(key)) return;
6703
- seededSampled.add(key);
6704
- if (isGateFormValid(key)) clearedGates.add(key);
6705
- }
6706
6690
  const gateSubs = /* @__PURE__ */ new Map();
6707
6691
  function reconcileGates() {
6708
6692
  const gateKeys = /* @__PURE__ */ new Set();
@@ -6719,7 +6703,6 @@ function useWizard(options) {
6719
6703
  );
6720
6704
  }
6721
6705
  }
6722
- sampleSeededGate(key);
6723
6706
  }
6724
6707
  for (const [key, unsub] of gateSubs) {
6725
6708
  if (!gateKeys.has(key)) {
@@ -6777,8 +6760,7 @@ function useWizard(options) {
6777
6760
  for (const step of compiledSteps.value) {
6778
6761
  if (!step.isGate) continue;
6779
6762
  const store = registry.forms.get(step.key);
6780
- const settled = store !== void 0 && isGateVerdictSettled(step.key);
6781
- parts.push(`${step.key}:${store !== void 0 ? 1 : 0}:${settled ? 1 : 0}`);
6763
+ parts.push(`${step.key}:${store !== void 0 ? 1 : 0}`);
6782
6764
  }
6783
6765
  return parts.join("|");
6784
6766
  },
@@ -6811,20 +6793,30 @@ function useWizard(options) {
6811
6793
  });
6812
6794
  }
6813
6795
  }
6796
+ function applyGateSeed(map) {
6797
+ if (map === void 0 || !mightGate) return;
6798
+ const gates = gatePositions.value;
6799
+ for (const key of Object.keys(map)) {
6800
+ if (map[key]?.gate === "cleared" && gates.includes(key)) clearedGates.add(key);
6801
+ }
6802
+ }
6814
6803
  const seedRef = ref(void 0);
6815
6804
  const seedInput = options.defaultStatuses;
6816
6805
  if (seedInput !== void 0) {
6817
6806
  const resolved = resolveTrichotomy(seedInput);
6818
6807
  if (resolved.kind === "sync") {
6819
6808
  seedRef.value = resolved.value;
6809
+ applyGateSeed(resolved.value);
6820
6810
  } else {
6821
6811
  const eager = resolved.factory();
6822
6812
  if (eager instanceof Promise) {
6823
6813
  void eager.then((value) => {
6824
6814
  seedRef.value = value;
6815
+ applyGateSeed(value);
6825
6816
  });
6826
6817
  } else {
6827
6818
  seedRef.value = eager;
6819
+ applyGateSeed(eager);
6828
6820
  }
6829
6821
  }
6830
6822
  }
@@ -6835,6 +6827,10 @@ function useWizard(options) {
6835
6827
  const source = asStatusSource(form);
6836
6828
  const computedStatus = computed(() => {
6837
6829
  const locked = navLockSet.value.has(form.key);
6830
+ let gate = null;
6831
+ if (gatePositions.value.includes(form.key)) {
6832
+ gate = clearedGates.has(form.key) ? "cleared" : "uncleared";
6833
+ }
6838
6834
  if (isFormReady(form.key)) {
6839
6835
  const meta = source.meta;
6840
6836
  if (meta !== void 0 && meta !== null) {
@@ -6843,16 +6839,17 @@ function useWizard(options) {
6843
6839
  dirty: meta.dirty,
6844
6840
  submitted: meta.submitted,
6845
6841
  errorCount: meta.errorCount,
6846
- locked
6842
+ locked,
6843
+ gate
6847
6844
  };
6848
6845
  }
6849
6846
  }
6850
6847
  if (noopForms.has(form.key)) {
6851
- return locked ? { ...NOOP_VALID_STATUS, locked: true } : NOOP_VALID_STATUS;
6848
+ return locked || gate !== null ? { ...NOOP_VALID_STATUS, locked, gate } : NOOP_VALID_STATUS;
6852
6849
  }
6853
6850
  const seed = seedRef.value?.[form.key];
6854
- if (seed !== void 0) return { ...seed, locked };
6855
- return locked ? { ...PENDING_STATUS, locked: true } : PENDING_STATUS;
6851
+ if (seed !== void 0) return { ...PENDING_STATUS, ...seed, locked, gate };
6852
+ return locked || gate !== null ? { ...PENDING_STATUS, locked, gate } : PENDING_STATUS;
6856
6853
  });
6857
6854
  statusCache.set(form.key, computedStatus);
6858
6855
  return computedStatus;
@@ -7343,14 +7340,16 @@ function useWizard(options) {
7343
7340
  submitError.value = null;
7344
7341
  if (mightGate) {
7345
7342
  clearedGates.clear();
7346
- seededSampled.clear();
7347
7343
  }
7348
7344
  lazyEpoch.value += 1;
7349
7345
  for (const step of compiledSteps.value) {
7350
7346
  const full = asSubmissionSource(step.form);
7351
7347
  if (typeof full.reset === "function") full.reset();
7352
7348
  }
7353
- if (mightGate) reconcileGates();
7349
+ if (mightGate) {
7350
+ reconcileGates();
7351
+ applyGateSeed(seedRef.value);
7352
+ }
7354
7353
  const firstStep = compiledSteps.value[0];
7355
7354
  if (firstStep !== void 0) {
7356
7355
  const landing = commitActiveKey(firstStep.key);
@@ -7361,6 +7360,29 @@ function useWizard(options) {
7361
7360
  }
7362
7361
  }
7363
7362
  }
7363
+ async function relock(key, commit) {
7364
+ if (!mightGate || !gatePositions.value.includes(key)) {
7365
+ if (__DEV__) {
7366
+ console.warn(
7367
+ `[attaform] useWizard.relock(${JSON.stringify(key)}): no gate at that key; nothing to re-lock.`
7368
+ );
7369
+ }
7370
+ return false;
7371
+ }
7372
+ try {
7373
+ await commit();
7374
+ } catch (err) {
7375
+ if (__DEV__) {
7376
+ console.warn(
7377
+ `[attaform] useWizard.relock(${JSON.stringify(key)}): commit threw, so the gate was not re-sealed.`,
7378
+ err
7379
+ );
7380
+ }
7381
+ return false;
7382
+ }
7383
+ clearedGates.delete(key);
7384
+ return true;
7385
+ }
7364
7386
  if (mightGate) {
7365
7387
  watchEffect(() => {
7366
7388
  const key = activeKey.value;
@@ -7386,6 +7408,7 @@ function useWizard(options) {
7386
7408
  tryNext,
7387
7409
  handleSubmit,
7388
7410
  reset,
7411
+ relock,
7389
7412
  get currentStep() {
7390
7413
  return currentStep.value;
7391
7414
  },
@@ -7566,4 +7589,4 @@ function escapeForInlineScript(json) {
7566
7589
  }
7567
7590
 
7568
7591
  export { AttaformErrorCode as A, DEFAULT_TIMINGS as D, defaultDisplayState as a, defineCoercion as b, injectWizard as c, defaultCoercionRules as d, escapeForInlineScript as e, isUnset as f, gate as g, useAbstractForm as h, injectForm as i, useRegister as j, useWizard as k, lazy as l, makeDefaultDisplayState as m, isPlainRecord as n, safeAssign as o, slimKindOf as p, humanize as q, getAtPath as r, safeOwnRead as s, setAtPath as t, unset as u };
7569
- //# sourceMappingURL=attaform.DGCijpZT.mjs.map
7592
+ //# sourceMappingURL=attaform.CuUx4jWI.mjs.map