attaform 0.29.0 → 0.30.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 (59) hide show
  1. package/dist/abstract.d.mts +5 -5
  2. package/dist/abstract.mjs +4 -4
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/dev-key-collision-warnings.mjs +47 -8
  5. package/dist/dev/chunks/devtools.mjs +1 -1
  6. package/dist/dev/directive.mjs +1 -1
  7. package/dist/dev/history.mjs +1 -2
  8. package/dist/dev/index.mjs +5 -5
  9. package/dist/dev/runtime/plugins/attaform.mjs +3 -3
  10. package/dist/dev/shared/{attaform-dev.BO-71R54.mjs → attaform-dev.BK6Ge2qR.mjs} +34 -188
  11. package/dist/dev/shared/{attaform-dev.A-TpJW0B.mjs → attaform-dev.BQc5QV3Y.mjs} +1 -19
  12. package/dist/dev/shared/{attaform-dev.DrzObAZo.mjs → attaform-dev.BcNtyOmf.mjs} +4 -4
  13. package/dist/dev/shared/{attaform-dev.ButTkwHd.mjs → attaform-dev.BcYT6tFh.mjs} +7 -8
  14. package/dist/dev/shared/{attaform-dev.BgM6FdtA.mjs → attaform-dev.BisFstxh.mjs} +91 -59
  15. package/dist/dev/shared/{attaform-dev.DHuydLAC.mjs → attaform-dev.CQ2rJiAX.mjs} +560 -631
  16. package/dist/dev/shared/{attaform-dev.D8aoKb89.mjs → attaform-dev.D6CLvaQT.mjs} +8 -8
  17. package/dist/dev/shared/{attaform-dev.q6UutkCx.mjs → attaform-dev.DTrHonpc.mjs} +69 -120
  18. package/dist/dev/shared/{attaform-dev.DD5m2W51.mjs → attaform-dev.DUZckyV8.mjs} +1 -3
  19. package/dist/dev/zod-v3.mjs +6 -6
  20. package/dist/dev/zod-v4.mjs +6 -6
  21. package/dist/dev/zod.mjs +5 -5
  22. package/dist/directive.d.mts +2 -2
  23. package/dist/directive.mjs +1 -1
  24. package/dist/history.d.mts +1 -1
  25. package/dist/history.mjs +1 -2
  26. package/dist/index.d.mts +6 -6
  27. package/dist/index.mjs +5 -5
  28. package/dist/nuxt.d.mts +2 -11
  29. package/dist/nuxt.mjs +0 -1
  30. package/dist/runtime/plugins/attaform.mjs +3 -3
  31. package/dist/shared/{attaform.DffRL5pP.mjs → attaform.B3resMQ1.mjs} +8 -8
  32. package/dist/shared/{attaform.Cvao8bW5.mjs → attaform.BRDDQeHV.mjs} +6 -6
  33. package/dist/shared/{attaform.C2MVyuxC.mjs → attaform.BXneBxEj.mjs} +34 -188
  34. package/dist/shared/{attaform.BndjUUYX.mjs → attaform.ByWP6kSx.mjs} +4 -4
  35. package/dist/shared/{attaform.D1fnTifF.mjs → attaform.CJ8DpRGj.mjs} +69 -120
  36. package/dist/shared/{attaform.B0dKL8ST.mjs → attaform.CUfrMK7t.mjs} +91 -59
  37. package/dist/shared/{attaform.CZYt_5N_.mjs → attaform.CdZIEf7_.mjs} +1 -19
  38. package/dist/shared/{attaform.axHXVQO7.d.mts → attaform.Clz0ArIi.d.mts} +1 -1
  39. package/dist/shared/{attaform.CZMAYwSK.mjs → attaform.DNJOWjQw.mjs} +559 -588
  40. package/dist/shared/{attaform.CIWLMY-R.d.mts → attaform.Rc9u4Vfw.d.mts} +1 -1
  41. package/dist/shared/{attaform.DDtqHYv1.d.mts → attaform.UGyOK83T.d.mts} +115 -214
  42. package/dist/shared/{attaform.D0x-qSXm.mjs → attaform.WiGdIqgx.mjs} +1 -3
  43. package/dist/shared/{attaform.CsoeRi3P.d.mts → attaform.bKLzMU0o.d.mts} +99 -493
  44. package/dist/vite.mjs +5 -0
  45. package/dist/zod-v3.d.mts +4 -4
  46. package/dist/zod-v3.mjs +6 -6
  47. package/dist/zod-v4.d.mts +9 -9
  48. package/dist/zod-v4.mjs +6 -6
  49. package/dist/zod.d.mts +4 -4
  50. package/dist/zod.mjs +5 -5
  51. package/package.json +3 -2
  52. package/skills/attaform/SKILL.md +1 -1
  53. package/skills/attaform/references/custom-components.md +1 -1
  54. package/dist/chunks/fingerprint.mjs +0 -183
  55. package/dist/chunks/fingerprint2.mjs +0 -159
  56. package/dist/dev/chunks/fingerprint.mjs +0 -183
  57. package/dist/dev/chunks/fingerprint2.mjs +0 -159
  58. package/dist/dev/shared/attaform-dev.BxfsuwBR.mjs +0 -44
  59. package/dist/shared/attaform.BxfsuwBR.mjs +0 -44
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { G as GenericForm, s as FlatPath, B as NestedType, F as FormKey, U as UseFormConfiguration, A as AbstractSchema, av as AcceptableDefaults, ah as ValidateOnConfig, a as UseFormReturnType } from './attaform.CsoeRi3P.mjs';
2
+ import { G as GenericForm, l as FlatPath, s as NestedType, F as FormKey, U as UseFormConfiguration, A as AbstractSchema, aq as AcceptableDefaults, ac as ValidateOnConfig, a as UseFormReturnType } from './attaform.bKLzMU0o.mjs';
3
3
 
4
4
  /**
5
5
  * The Zod v4 schema roots `useForm` accepts. A fixed-shape
@@ -1,4 +1,4 @@
1
- import { G as GenericForm, F as FormKey, a as UseFormReturnType, a7 as RegisterValue, V as PathKey, k as DisplayCtx, w as GetDisplayState, l as DisplayMachine, ac as SlimPrimitiveKind, C as CoercionEntry, e as CoercionRegistry, aw as FieldRecord, ax as ErrorCell, ai as ValidationError, ay as OriginalsRecord, A as AbstractSchema, T as Path, am as WriteMeta, h as DeepPartial, an as WriteShape, az as AttaformDomBinding, aA as TransformAbortHolder, ag as ValidateOn, d as AttaformDefaults } from './attaform.CsoeRi3P.mjs';
1
+ import { G as GenericForm, F as FormKey, a as UseFormReturnType, _ as RegisterValue, v as Path, w as PathKey, ar as DisplayCtx, as as GetDisplayState, at as DisplayMachine, au as FieldRecord, av as ErrorCell, ad as ValidationError, aw as OriginalsRecord, A as AbstractSchema, ah as WriteMeta, d as DeepPartial, ai as WriteShape, ax as AttaformDomBinding, ay as TransformAbortHolder } from './attaform.bKLzMU0o.mjs';
2
2
  import { Ref, ComputedRef, ShallowRef, App, InjectionKey, Plugin } from 'vue';
3
3
 
4
4
  /**
@@ -208,9 +208,26 @@ type UseRegisterOptions = {
208
208
  };
209
209
  declare function useRegister<V = unknown>(options?: UseRegisterOptions): UseRegisterReturn<V> | undefined;
210
210
 
211
+ type DynamicPathSweep = {
212
+ /**
213
+ * Note that a cache has just stored an entry for `segments` under
214
+ * `key`. Paths a schema shape bounds are ignored, so only the
215
+ * candidates the sweep could ever drop are walked.
216
+ */
217
+ readonly track: (segments: Path, key: PathKey) => void;
218
+ /**
219
+ * Register a cache's own eviction. It receives the canonical
220
+ * `PathKey` of a path the form no longer has, and deletes whatever it
221
+ * files under that path. A cache keying by more than the path (a
222
+ * shape sigil, say) deletes every variant; a key it never held is a
223
+ * no-op.
224
+ */
225
+ readonly onEvict: (evict: (key: PathKey) => void) => void;
226
+ };
227
+
211
228
  /**
212
- * Per-form display engine: owns the clock and the timers that the pure
213
- * `getDisplayState` reducer policy needs, so the reducer itself stays a
229
+ * Per-form display engine: owns the clock and the timers the display
230
+ * reducer's timing policy needs, so the reducer itself stays a
214
231
  * deterministic `(prev, ctx) => next` function.
215
232
  *
216
233
  * It keeps a `Map` of the machines that are still *active* (a spinner is
@@ -229,15 +246,15 @@ declare function useRegister<V = unknown>(options?: UseRegisterOptions): UseRegi
229
246
  * retained set is bounded by the rendered non-idle fields; none of them
230
247
  * arm a timer, so retention costs memory, never CPU.
231
248
  *
232
- * Untrusted reducer: `getDisplayState` is consumer-overridable, so the
233
- * engine treats the returned `reviewAt` as untrusted. A non-finite deadline
234
- * (NaN / ±Infinity from a custom predicate's bad arithmetic) is ignored
235
- * rather than handed to `setTimeout`, where it coerces to 0 and spins; an
236
- * over-large finite deadline is clamped below the 32-bit `setTimeout`
237
- * overflow; and the timer refuses to re-arm for the exact deadline it just
238
- * fired, so a predicate re-emitting a fixed or past `reviewAt` can't drive an
239
- * infinite fire loop. None of these arise from the library default, which
240
- * always advances its deadline or drops it.
249
+ * `setTimeout` invariants on the returned `reviewAt`, enforced here rather
250
+ * than trusted from the reducer. A non-finite deadline is ignored rather
251
+ * than handed to `setTimeout`, where it coerces to 0 and spins; an over-large
252
+ * finite deadline is clamped below the 32-bit `setTimeout` overflow; and the
253
+ * timer refuses to re-arm for a deadline it just fired that has also already
254
+ * passed. The last of those is not belt-and-braces: the library reducer's own
255
+ * min-visible hold re-emits ONE fixed instant for the length of the window,
256
+ * which is exactly the shape that stranded a field on `'pending'` forever (see
257
+ * `rearm`).
241
258
  *
242
259
  * Background tabs: `setTimeout` is throttled to >= 1s while a tab is hidden,
243
260
  * so a min-visible hold can overshoot. A `visibilitychange` listener bumps
@@ -271,56 +288,64 @@ type DisplayEngine = {
271
288
  };
272
289
 
273
290
  /**
274
- * Schema-driven coercion of user-typed DOM values at the v-register
275
- * directive layer. When the slim schema declares a numeric or
276
- * boolean type at a path, the directive coerces incoming string
277
- * values (`'25'` → `25`, `'true'` → `true`) before the slim-primitive
278
- * gate sees the write — making the schema authoritative for storage
279
- * shape and freeing consumers from sprinkling `.number` modifiers
280
- * across templates.
281
- *
282
- * Coercion is consumer-extensible: a `CoercionRegistry` is just an
283
- * `Array<CoercionEntry>` keyed at config time by `(input, output)`
284
- * `SlimPrimitiveKind` literals. The library ships
285
- * `defaultCoercionRules` (string→number, string→boolean) and
286
- * `defineCoercion` for type-narrowed authoring; consumers spread the
287
- * defaults to extend or supply their own array to replace.
288
- *
289
- * Coercion applies ONLY to user-typed DOM values flowing through
290
- * the directive's assigner. Programmatic writes (`form.setValue`,
291
- * `setValueWithInternalPath`) bypass coercion — they're authoritative
292
- * writes whose strict typing is on the caller. This mirrors the
293
- * `transforms` pipeline's user-input-only contract.
291
+ * The error stores, indexed by path prefix.
292
+ *
293
+ * `aggregateErrorsAt(state, prefix)` answers "every active error at or
294
+ * under this path", and it used to answer it by walking every entry in
295
+ * all three error stores, resolving each key back to segments and
296
+ * testing the prefix. That is fine for one call. A table asks for one
297
+ * per row: `form.list('rows')` on 400 rows with 800 errors did 320,000
298
+ * of those tests, and a keystroke in any single row re-did all of them,
299
+ * costing a quarter of a second per character typed.
300
+ *
301
+ * The cost is structural, not incidental: the work is O(rows x errors)
302
+ * where the answer is O(errors). So the keys are collected ONCE per
303
+ * change to the stores, resolved to segments once, and sorted, and a
304
+ * query binary-searches its own window instead of scanning.
305
+ *
306
+ * ## Why a sorted key array is the right index
307
+ *
308
+ * A `PathKey` is `JSON.stringify(segments)`, so a path's descendants
309
+ * all begin with the parent's key minus its closing bracket, plus a
310
+ * comma: every key under `["rows",0]` begins `["rows",0,`. Call that
311
+ * the marker. Two facts make a sorted array enough:
312
+ *
313
+ * - every descendant key is >= the marker, since it begins with it;
314
+ * - every descendant key is <= the parent's own key, since the two
315
+ * agree up to the last character, where `,` (0x2C) sorts before
316
+ * `]` (0x5D).
317
+ *
318
+ * So `[marker, parentKey]` is a contiguous window containing every
319
+ * descendant and the parent itself. It is a SUPERSET, not an exact
320
+ * answer, and the caller still applies `isPathPrefix` to what comes
321
+ * back: the window narrows the scan, it does not decide membership.
322
+ * That split is deliberate. Membership stays where it already was, so
323
+ * the encoding argument above cannot quietly become the definition of
324
+ * a prefix.
325
+ *
326
+ * The root prefix has no marker (`[]` minus its bracket plus a comma is
327
+ * `[,`, which nothing begins with), and the root matches everything
328
+ * anyway, so it is answered by handing back the whole index.
294
329
  */
295
330
 
296
- /**
297
- * Type-narrowing helper for authoring entries. At runtime it's
298
- * identity; at compile time it preserves the `input` / `output`
299
- * literal types so `transform`'s parameter is narrowed to the
300
- * runtime type instead of widening to `SlimRuntimeOf<SlimPrimitiveKind>`.
301
- *
302
- * Without this helper, authoring `{ input: 'string', output:
303
- * 'number', transform: (s) => ... }` against the broader
304
- * `CoercionEntry` widens `s` to `string | number | boolean | ...`,
305
- * forcing a cast in every transform body. `defineCoercion` is the
306
- * opaque-free idiom.
307
- */
308
- declare function defineCoercion<I extends SlimPrimitiveKind, O extends SlimPrimitiveKind>(entry: CoercionEntry<I, O>): CoercionEntry<I, O>;
309
- /**
310
- * Internal index built from a `CoercionRegistry` at config-resolve
311
- * time. Keyed by `${input}->${output}` for O(1) per-keystroke
312
- * dispatch. The authoring shape (array, ergonomic, type-narrowing-
313
- * friendly) and the dispatch shape (Map, fast) decouple cleanly.
314
- */
315
- type CoercionIndex = ReadonlyMap<`${SlimPrimitiveKind}->${SlimPrimitiveKind}`, CoercionEntry>;
316
- /**
317
- * The library's built-in registry. Two cells: string→number and
318
- * string→boolean. Re-exported so consumers can spread it when
319
- * supplying a custom registry that extends defaults.
320
- */
321
- declare const defaultCoercionRules: CoercionRegistry;
331
+ /** One indexed path: its key, and the segments that key decodes to. */
332
+ type ErrorPathEntry = {
333
+ readonly key: PathKey;
334
+ readonly segments: Path;
335
+ };
322
336
 
323
337
  type FormStore<F extends GenericForm, G extends GenericForm = F> = {
338
+ /**
339
+ * The form's single liveness sweep over every per-path cache. Owned by
340
+ * the store rather than by `buildFormApi` because the store's own
341
+ * per-path maps (`fields`, `originals`, `authoredPaths`,
342
+ * `fieldValidationState`) are the largest thing it evicts, and they
343
+ * exist whether or not a form API was ever built around them. Read
344
+ * surfaces register their own evictions into it, so one subscription
345
+ * and one liveness walk serve all of them. See
346
+ * `dynamic-path-sweep.ts`.
347
+ */
348
+ readonly pathSweep: DynamicPathSweep;
324
349
  readonly formKey: FormKey;
325
350
  readonly form: Ref<F>;
326
351
  readonly fields: Map<PathKey, FieldRecord>;
@@ -359,6 +384,22 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
359
384
  * sentinel — see `docs/validation/blank.md`.
360
385
  */
361
386
  readonly derivedBlankErrors: ComputedRef<ReadonlyMap<PathKey, ValidationError[]>>;
387
+ /**
388
+ * Every path carrying an error at or under `prefix`, sorted by key.
389
+ * `aggregateErrorsAt` takes its candidates from here instead of
390
+ * re-scanning all three error stores per call, which is what turns a
391
+ * table of N rows from O(N x errors) back into O(errors). See
392
+ * `error-path-index.ts`.
393
+ *
394
+ * Reading this rather than the form-global index is also what keeps
395
+ * containers isolated from each other. The index is one value for the
396
+ * whole form, so any path gaining or losing an error gives it a new
397
+ * identity; a container reading it directly woke on every other
398
+ * container's errors. Each prefix gets its own memoised `computed`
399
+ * that holds its previous array when its own window is unchanged, so
400
+ * the global change stops there. See `errorWindowAt` in the store.
401
+ */
402
+ readonly errorWindowAt: (prefix: Path, prefixKey: PathKey) => readonly ErrorPathEntry[];
362
403
  readonly originals: Map<PathKey, OriginalsRecord>;
363
404
  /**
364
405
  * Reactive set of paths whose displayed state should be EMPTY even
@@ -408,18 +449,9 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
408
449
  * the eventual directive lifecycle remains the source of truth.
409
450
  */
410
451
  readonly ssr: boolean;
411
- /**
412
- * Resolved `getDisplayState` predicate driving `field.displayState`,
413
- * the `show*` booleans, and their `form.meta` rollups. Resolved once
414
- * at construction via `resolveGetDisplayState(options.getDisplayState)`;
415
- * `undefined` config falls through to `defaultDisplayState`. The
416
- * field-state computeds read the resolved function directly on every
417
- * read.
418
- */
419
- readonly getDisplayState: GetDisplayState;
420
452
  /**
421
453
  * Per-form display engine: owns the clock and the single timer the timed
422
- * `getDisplayState` reducer policy needs, keeping the reducer itself a
454
+ * display-reducer policy needs, keeping the reducer itself a
423
455
  * pure `(prev, ctx) => next` function. The field-state computeds route
424
456
  * every `displayState` read through `displayEngine.resolve(...)`, which
425
457
  * threads the path's previous machine, persists or evicts the result, and
@@ -570,26 +602,10 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
570
602
  * run yet — so without the gate, frame 1 paints the form as
571
603
  * "valid" before the real verdict arrives a tick later.
572
604
  *
573
- * Initialized to `!strict`: non-strict consumers opt out of the
574
- * validation pipeline by design, so locking them on
575
- * `firstValidationDone === false` would defeat the opt-out.
576
605
  * Reset is left untouched — the post-reset validation flips it
577
606
  * back true on completion, same as the construction-time path.
578
607
  */
579
608
  readonly firstValidationDone: Ref<boolean>;
580
- /**
581
- * `true` when the sub-schema rooted at `path` (or any of its
582
- * descendants) declares async work — composes
583
- * `schema.getSchemasAtPath(path)` with each candidate's
584
- * `needsAsyncValidation()`, memoised per canonical path key for
585
- * the lifetime of the FormStore. Used by `meta.valid` /
586
- * `field.valid` to skip the `firstValidationDone` gate on subtrees
587
- * that are fully synchronous: their verdict resolves at construction
588
- * (or on the next per-field run) without waiting on a microtask, so
589
- * honouring the form-wide gate would just play dumb about a known
590
- * answer.
591
- */
592
- pathHasAsyncValidation(path: Path): boolean;
593
609
  /**
594
610
  * Precomputed-key shortcut for `pathHasAsyncValidation`. The
595
611
  * canonical key is required and must correspond to `segments`; the
@@ -809,17 +825,6 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
809
825
  * source there).
810
826
  */
811
827
  markConnectedOptimistically(path: Path): void;
812
- /**
813
- * Leaf-only pristine check. `originals` is populated via
814
- * `diffAndApply`'s `added` patches, which fire only on primitive
815
- * leaves — a container path (e.g. `['profile']`) that isn't in
816
- * `originals` returns `true` here even when a descendant is dirty.
817
- * Callers that need container semantics should either loop over
818
- * leaves or walk `originals` manually. The public `getFieldState`
819
- * surface is typed to accept leaf paths only, so in practice this
820
- * isn't exposed to consumers.
821
- */
822
- isPristineAtPath(path: Path): boolean;
823
828
  /**
824
829
  * Precomputed-key shortcut for `isPristineAtPath`. The canonical
825
830
  * key is required and must correspond to `segments`; the helper
@@ -846,7 +851,6 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
846
851
  */
847
852
  hasRemovedSubtreeUnder(path: Path): boolean;
848
853
  getFieldRecord(path: Path): FieldRecord | undefined;
849
- getOriginalAtPath(path: Path): unknown;
850
854
  /**
851
855
  * Cancel every in-flight field-level validation run — clears timers
852
856
  * for debounced 'change' runs that haven't fired, latches `aborted`
@@ -871,12 +875,6 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
871
875
  endTransform(key: PathKey, token: number): void;
872
876
  /** Record a per-field normalization failure at `key` (`field.transformError`). */
873
877
  setTransformError(key: PathKey, err: Error): void;
874
- /**
875
- * Abort + release every in-flight async-transform run (all paths) and
876
- * clear `transformErrors`. Mirrors `cancelFieldValidation`; called by
877
- * `reset()` and store teardown.
878
- */
879
- cancelTransforms(): void;
880
878
  /**
881
879
  * Path-scoped counterpart to `cancelTransforms`: abort + release only
882
880
  * the runs at-or-under `prefix`, clearing their `transformError`.
@@ -907,10 +905,7 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
907
905
  * store's captured `debounceMs`. Used so sibling instances sharing a
908
906
  * FormStore can each validate on their own cadence.
909
907
  */
910
- scheduleFieldValidation(path: Path, immediate: boolean, override?: {
911
- readonly mode?: ValidateOn;
912
- readonly debounceMs?: number;
913
- }): void;
908
+ scheduleFieldValidation(path: Path, immediate: boolean, instance?: WriteMeta['instance']): void;
914
909
  /**
915
910
  * Subscribe to every `applyFormReplacement`. Fires synchronously
916
911
  * after `form.value` has been swapped to `next` and all field /
@@ -957,14 +952,12 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
957
952
  */
958
953
  readonly modules: Map<string, unknown>;
959
954
  /**
960
- * Resolved schema-coercion index — the merged config from
961
- * `createAttaform({ defaults: { coerce } })` ∪ `useForm({ coerce })`,
962
- * keyed by `${input}->${output}` for O(1) per-keystroke dispatch.
963
- * Empty Map when coercion is disabled. Read at `register()` time
964
- * by `buildCoerceFn` to bake the per-path coerce closure on
965
- * `RegisterValue.coerce`.
955
+ * Whether schema-driven coercion runs for this form — `false` only
956
+ * when the consumer passed `useForm({ coerce: false })`. Read at
957
+ * `register()` time by `buildCoerceFn` to bake the per-path coerce
958
+ * closure on `RegisterValue.coerce`.
966
959
  */
967
- readonly coerceIndex: CoercionIndex;
960
+ readonly coerceEnabled: boolean;
968
961
  /**
969
962
  * Tear down non-reactive resources owned by this FormStore. Invoked
970
963
  * by the registry when the last consumer unmounts. Cancels pending
@@ -1354,8 +1347,8 @@ type WizardOptions = {
1354
1347
  /**
1355
1348
  * When `wizard.handleSubmit` finds errors, automatically focus the
1356
1349
  * first failing form: jump to its step and invoke its
1357
- * `applyInvalidSubmitPolicy()` (focus / scroll per the form's own
1358
- * `onInvalidSubmit` configuration). Default `true`; pass `false` to
1350
+ * `applyInvalidSubmitPolicy()` (which honors that form's own
1351
+ * `focusOnInvalidSubmit` choice). Default `true`; pass `false` to
1359
1352
  * keep the active step where the user left it and handle navigation
1360
1353
  * manually in the `onError` callback.
1361
1354
  */
@@ -1730,8 +1723,6 @@ type AttaformRegistry = {
1730
1723
  readonly pendingHydration: PendingHydration;
1731
1724
  /** `true` while running on the server during SSR; `false` on the client. */
1732
1725
  readonly ssr: boolean;
1733
- /** App-level defaults applied to every `useForm` call. */
1734
- readonly defaults: AttaformDefaults;
1735
1726
  /**
1736
1727
  * Track a consumer of `key`. Returns a dispose function — call it
1737
1728
  * when the consumer unmounts. The form is evicted automatically
@@ -1787,13 +1778,7 @@ declare module 'vue' {
1787
1778
  }
1788
1779
  }
1789
1780
  /** Options for `createRegistry`. */
1790
- type CreateRegistryOptions = SSRDetectOptions & {
1791
- /**
1792
- * App-level defaults applied to every `useForm` call. Per-form
1793
- * options always win. Omitted is equivalent to `{}`.
1794
- */
1795
- defaults?: AttaformDefaults;
1796
- };
1781
+ type CreateRegistryOptions = SSRDetectOptions;
1797
1782
  /**
1798
1783
  * Create a fresh `AttaformRegistry`. `createAttaform()` calls
1799
1784
  * this internally — most consumers never need to call it directly.
@@ -2099,34 +2084,20 @@ type AttaformPluginOptions = SSRDetectOptions & {
2099
2084
  * fails silently. Pass `false` to skip the integration even in dev.
2100
2085
  */
2101
2086
  devtools?: boolean;
2102
- /**
2103
- * App-level defaults applied to every `useForm` call in this app.
2104
- * Per-form options always win. See `AttaformDefaults` for
2105
- * the supported option set and the merge rules.
2106
- *
2107
- * ```ts
2108
- * app.use(
2109
- * createAttaform({
2110
- * defaults: { debounceMs: 100 },
2111
- * })
2112
- * )
2113
- * ```
2114
- */
2115
- defaults?: AttaformDefaults;
2116
2087
  };
2117
2088
  /**
2118
2089
  * Create the Vue plugin that installs the form library on a Vue
2119
2090
  * application. Required only when you want app-wide options
2120
- * (`defaults`, `devtools: false`, `ssr: true`) — for the default
2121
- * setup, `useForm` / `injectForm` / `useRegister` lazy-install the
2122
- * registry on first use.
2091
+ * (`devtools: false`, `ssr: true`) — for the default setup,
2092
+ * `useForm` / `injectForm` / `useRegister` lazy-install the registry
2093
+ * on first use.
2123
2094
  *
2124
2095
  * ```ts
2125
2096
  * import { createApp } from 'vue'
2126
2097
  * import { createAttaform } from 'attaform'
2127
2098
  *
2128
2099
  * createApp(App)
2129
- * .use(createAttaform({ defaults: { debounceMs: 100 } }))
2100
+ * .use(createAttaform({ devtools: false }))
2130
2101
  * .mount('#app')
2131
2102
  * ```
2132
2103
  *
@@ -2391,75 +2362,5 @@ declare class ReservedFormKeyError extends AttaformError {
2391
2362
  constructor(key: string);
2392
2363
  }
2393
2364
 
2394
- /**
2395
- * Anti-flash timing for the library-default display reducer.
2396
- *
2397
- * - `showDelay` — how long in-flight work (a validation run or an async
2398
- * register transform) may run before its spinner is allowed to show. Work
2399
- * that settles inside this window never reveals `'pending'` at all, so a
2400
- * fast (often synchronous) check does not flash a spinner on every keystroke.
2401
- * - `minVisible` — once shown, the minimum time the spinner stays up. Work
2402
- * that lands just past `showDelay` is held here so the spinner does not
2403
- * itself flash on and immediately off.
2404
- *
2405
- * Both are milliseconds.
2406
- */
2407
- type DisplayTimings = {
2408
- readonly showDelay: number;
2409
- readonly minVisible: number;
2410
- };
2411
- /**
2412
- * Library-default anti-flash timings. `showDelay: 120` cleanly swallows
2413
- * synchronous, microtask-resolved, and tiny-async work (validation runs and
2414
- * async register transforms alike) so none of them flash a spinner;
2415
- * `minVisible: 120` keeps a shown spinner snappy once one does land.
2416
- * Retune via {@link makeDefaultDisplayState} without touching the engine.
2417
- */
2418
- declare const DEFAULT_TIMINGS: DisplayTimings;
2419
- /**
2420
- * Build a default `getDisplayState` reducer with custom anti-flash timing.
2421
- * Power users who want a tighter or looser spinner than {@link DEFAULT_TIMINGS}
2422
- * pass their own `{ showDelay, minVisible }`:
2423
- *
2424
- * ```ts
2425
- * import { makeDefaultDisplayState } from 'attaform'
2426
- *
2427
- * useForm({
2428
- * schema,
2429
- * getDisplayState: makeDefaultDisplayState({ showDelay: 50, minVisible: 200 }),
2430
- * })
2431
- * ```
2432
- *
2433
- * The returned reducer is pure: the engine injects `now` and threads the
2434
- * previous machine, so the same `(prev, ctx)` always yields the same next
2435
- * machine. It shapes only the display projection — `errors`, `valid`,
2436
- * `validating`, and the underlying validation run exactly as before.
2437
- */
2438
- declare function makeDefaultDisplayState({ showDelay, minVisible, }: DisplayTimings): GetDisplayState;
2439
- /**
2440
- * Library-default `getDisplayState` reducer. Resolves every path's
2441
- * `field.displayState` — and thus `field.show*` and the `form.meta`
2442
- * rollups — whenever the consumer has not configured an override at the
2443
- * per-form or plugin level. Built from {@link DEFAULT_TIMINGS}; publicly
2444
- * re-exported so an override can compose with it (a layered reducer that
2445
- * special-cases a subtree but otherwise defers picks up future
2446
- * refinements for free):
2447
- *
2448
- * ```ts
2449
- * import { defaultDisplayState } from 'attaform'
2450
- *
2451
- * useForm({
2452
- * schema,
2453
- * getDisplayState: (prev, ctx) => {
2454
- * const next = defaultDisplayState(prev, ctx)
2455
- * return next.display === 'success' && ctx.field.path[0] === 'username'
2456
- * ? { display: 'idle' }
2457
- * : next
2458
- * },
2459
- * })
2460
- * ```
2461
- */
2462
- declare const defaultDisplayState: GetDisplayState;
2463
-
2464
- export { makeDefaultDisplayState as $, assignKey as B, DEFAULT_TIMINGS as D, createAttaform as E, createRegistry as H, defaultCoercionRules as J, defaultDisplayState as K, defineCoercion as M, escapeForInlineScript as N, OutsideSetupError as O, gate as P, getRegistryFromApp as Q, RegistryNotInstalledError as R, hydrateAttaformState as T, injectForm as V, injectWizard as X, isRegisterValue as Y, kAttaformRegistry as Z, lazy as _, renderAttaformState as a0, useRegister as a1, useRegistry as a2, useWizard as a3, AttaformError as c, AttaformErrorCode as d, DEVTOOLS_WINDOW_KEY as f, InvalidPathError as i, InvalidUseFormConfigError as j, ReservedFormKeyError as k, SubmitErrorHandlerError as n };
2465
- export type { AttaformRegistry as A, CompiledStep as C, FormStatus as F, GateMarker as G, InjectWizardInput as I, LazyMarker as L, SerializedAttaformState as S, UseRegisterOptions as U, WizardAggregateError as W, AnyForm as a, AttaformDevtoolsBridge as b, AttaformPluginOptions as e, DisplayTimings as g, FormStatusSeed as h, SerializedFormData as l, StepSlot as m, UseRegisterReturn as o, UseWizardReturnType as p, WizardCtx as q, WizardCtxForm as r, WizardOnError as s, WizardOnSubmit as t, WizardOptions as u, WizardPersistFn as v, WizardRestoreFn as w, WizardRestoreState as x, WizardStatusesProxy as y, WizardSubmitContext as z };
2365
+ export { createRegistry as B, DEVTOOLS_WINDOW_KEY as D, escapeForInlineScript as E, gate as H, getRegistryFromApp as J, hydrateAttaformState as K, injectForm as M, injectWizard as N, OutsideSetupError as O, isRegisterValue as P, kAttaformRegistry as Q, RegistryNotInstalledError as R, lazy as T, renderAttaformState as V, useRegister as X, useRegistry as Y, useWizard as Z, AttaformError as c, AttaformErrorCode as d, InvalidPathError as g, InvalidUseFormConfigError as h, ReservedFormKeyError as i, SubmitErrorHandlerError as l, assignKey as y, createAttaform as z };
2366
+ export type { AttaformRegistry as A, CompiledStep as C, FormStatus as F, GateMarker as G, InjectWizardInput as I, LazyMarker as L, SerializedAttaformState as S, UseRegisterOptions as U, WizardAggregateError as W, AnyForm as a, AttaformDevtoolsBridge as b, AttaformPluginOptions as e, FormStatusSeed as f, SerializedFormData as j, StepSlot as k, UseRegisterReturn as m, UseWizardReturnType as n, WizardCtx as o, WizardCtxForm as p, WizardOnError as q, WizardOnSubmit as r, WizardOptions as s, WizardPersistFn as t, WizardRestoreFn as u, WizardRestoreState as v, WizardStatusesProxy as w, WizardSubmitContext as x };
@@ -1,4 +1,4 @@
1
- import { c as RegistryNotInstalledError, O as OutsideSetupError, g as pathKeyToDotted } from './attaform.CZYt_5N_.mjs';
1
+ import { c as RegistryNotInstalledError, O as OutsideSetupError, g as pathKeyToDotted } from './attaform.CdZIEf7_.mjs';
2
2
  import { shallowReactive, getCurrentInstance, inject } from 'vue';
3
3
 
4
4
  function detectSSR(options = {}) {
@@ -17,7 +17,6 @@ const kFormContext = /* @__PURE__ */ Symbol.for("attaform:form-context");
17
17
  const kFormInstanceId = /* @__PURE__ */ Symbol.for("attaform:form-instance-id");
18
18
  function createRegistry(options = {}) {
19
19
  const ssr = detectSSR(options);
20
- const defaults = Object.freeze({ ...options.defaults ?? {} });
21
20
  const forms = shallowReactive(/* @__PURE__ */ new Map());
22
21
  const wizards = shallowReactive(/* @__PURE__ */ new Map());
23
22
  const pendingHydration = shallowReactive(/* @__PURE__ */ new Map());
@@ -102,7 +101,6 @@ function createRegistry(options = {}) {
102
101
  wizards,
103
102
  pendingHydration,
104
103
  ssr,
105
- defaults,
106
104
  trackConsumer,
107
105
  trackWizardConsumer,
108
106
  enqueuePrefetch,