attaform 0.20.1 → 0.21.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 (152) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/dev-key-collision-warnings.cjs +58 -0
  3. package/dist/chunks/dev-key-collision-warnings.cjs.map +1 -0
  4. package/dist/chunks/dev-key-collision-warnings.mjs +55 -0
  5. package/dist/chunks/dev-key-collision-warnings.mjs.map +1 -0
  6. package/dist/chunks/devtools.cjs +2 -2
  7. package/dist/chunks/devtools.cjs.map +1 -1
  8. package/dist/chunks/devtools.mjs +2 -2
  9. package/dist/chunks/devtools.mjs.map +1 -1
  10. package/dist/chunks/fingerprint.cjs +186 -0
  11. package/dist/chunks/fingerprint.cjs.map +1 -0
  12. package/dist/chunks/fingerprint.mjs +184 -0
  13. package/dist/chunks/fingerprint.mjs.map +1 -0
  14. package/dist/chunks/fingerprint2.cjs +162 -0
  15. package/dist/chunks/fingerprint2.cjs.map +1 -0
  16. package/dist/chunks/fingerprint2.mjs +160 -0
  17. package/dist/chunks/fingerprint2.mjs.map +1 -0
  18. package/dist/chunks/indexeddb.cjs +1 -1
  19. package/dist/chunks/indexeddb.mjs +1 -1
  20. package/dist/chunks/local-storage.cjs +1 -1
  21. package/dist/chunks/local-storage.mjs +1 -1
  22. package/dist/chunks/multi-tab-sync.cjs +367 -0
  23. package/dist/chunks/multi-tab-sync.cjs.map +1 -0
  24. package/dist/chunks/multi-tab-sync.mjs +364 -0
  25. package/dist/chunks/multi-tab-sync.mjs.map +1 -0
  26. package/dist/chunks/session-storage.cjs +1 -1
  27. package/dist/chunks/session-storage.mjs +1 -1
  28. package/dist/chunks/wire-persistence.cjs +396 -0
  29. package/dist/chunks/wire-persistence.cjs.map +1 -0
  30. package/dist/chunks/wire-persistence.mjs +394 -0
  31. package/dist/chunks/wire-persistence.mjs.map +1 -0
  32. package/dist/esbuild.cjs +28 -0
  33. package/dist/esbuild.cjs.map +1 -0
  34. package/dist/esbuild.d.cts +56 -0
  35. package/dist/esbuild.d.mts +56 -0
  36. package/dist/esbuild.d.ts +56 -0
  37. package/dist/esbuild.mjs +26 -0
  38. package/dist/esbuild.mjs.map +1 -0
  39. package/dist/index.cjs +5 -3
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +65 -70
  42. package/dist/index.d.mts +65 -70
  43. package/dist/index.d.ts +65 -70
  44. package/dist/index.mjs +5 -5
  45. package/dist/nuxt.d.cts +1 -1
  46. package/dist/nuxt.d.mts +1 -1
  47. package/dist/nuxt.d.ts +1 -1
  48. package/dist/rollup.cjs +24 -0
  49. package/dist/rollup.cjs.map +1 -0
  50. package/dist/rollup.d.cts +35 -0
  51. package/dist/rollup.d.mts +35 -0
  52. package/dist/rollup.d.ts +35 -0
  53. package/dist/rollup.mjs +22 -0
  54. package/dist/rollup.mjs.map +1 -0
  55. package/dist/rspack.cjs +10 -0
  56. package/dist/rspack.cjs.map +1 -0
  57. package/dist/rspack.d.cts +40 -0
  58. package/dist/rspack.d.mts +40 -0
  59. package/dist/rspack.d.ts +40 -0
  60. package/dist/rspack.mjs +8 -0
  61. package/dist/rspack.mjs.map +1 -0
  62. package/dist/runtime/plugins/attaform.cjs +2 -2
  63. package/dist/runtime/plugins/attaform.mjs +2 -2
  64. package/dist/shared/{attaform.D5-1XGQU.d.cts → attaform.7lzO9pdM.d.mts} +95 -1
  65. package/dist/shared/{attaform.SfhU0OEY.d.mts → attaform.B1nyO4ec.d.cts} +108 -39
  66. package/dist/shared/{attaform.SfhU0OEY.d.cts → attaform.B1nyO4ec.d.mts} +108 -39
  67. package/dist/shared/{attaform.SfhU0OEY.d.ts → attaform.B1nyO4ec.d.ts} +108 -39
  68. package/dist/shared/{attaform.BPy-4qRx.cjs → attaform.BA3vRDos.cjs} +53 -36
  69. package/dist/shared/attaform.BA3vRDos.cjs.map +1 -0
  70. package/dist/shared/{attaform.GbDo_lJi.d.cts → attaform.BDIEq9qP.d.cts} +1 -1
  71. package/dist/shared/attaform.BJGA_UOS.mjs +37 -0
  72. package/dist/shared/attaform.BJGA_UOS.mjs.map +1 -0
  73. package/dist/shared/{attaform.Dl5kDY-A.d.ts → attaform.BK1RE2ha.d.ts} +1 -1
  74. package/dist/shared/{attaform.DoKXru-a.d.mts → attaform.BQ6drorq.d.mts} +1 -1
  75. package/dist/shared/attaform.BRGIpZo4.cjs +26 -0
  76. package/dist/shared/attaform.BRGIpZo4.cjs.map +1 -0
  77. package/dist/shared/{attaform.DLnE5bZa.cjs → attaform.BUszFoKq.cjs} +388 -912
  78. package/dist/shared/attaform.BUszFoKq.cjs.map +1 -0
  79. package/dist/shared/{attaform.iWo9soNX.mjs → attaform.BnK_bfcb.mjs} +39 -392
  80. package/dist/shared/attaform.BnK_bfcb.mjs.map +1 -0
  81. package/dist/shared/{attaform.BCBxTyMC.cjs → attaform.BzvOdiSI.cjs} +101 -417
  82. package/dist/shared/attaform.BzvOdiSI.cjs.map +1 -0
  83. package/dist/shared/attaform.C3Doa9Pt.mjs +24 -0
  84. package/dist/shared/attaform.C3Doa9Pt.mjs.map +1 -0
  85. package/dist/shared/{attaform.BPxsYtTe.cjs → attaform.CEf6wYfD.cjs} +2 -2
  86. package/dist/shared/{attaform.BPxsYtTe.cjs.map → attaform.CEf6wYfD.cjs.map} +1 -1
  87. package/dist/shared/attaform.CQN9R62B.cjs +39 -0
  88. package/dist/shared/attaform.CQN9R62B.cjs.map +1 -0
  89. package/dist/shared/{attaform.EMzJcQci.d.mts → attaform.CRsXyy-Y.d.ts} +95 -1
  90. package/dist/shared/{attaform.D6CwqkPx.mjs → attaform.CkjTapyq.mjs} +89 -405
  91. package/dist/shared/attaform.CkjTapyq.mjs.map +1 -0
  92. package/dist/shared/{attaform.BqZuwLTK.mjs → attaform.DSqO6Db7.mjs} +377 -913
  93. package/dist/shared/attaform.DSqO6Db7.mjs.map +1 -0
  94. package/dist/shared/attaform.DuzQYscR.d.cts +41 -0
  95. package/dist/shared/attaform.DuzQYscR.d.mts +41 -0
  96. package/dist/shared/attaform.DuzQYscR.d.ts +41 -0
  97. package/dist/shared/{attaform.Bh3ACtts.d.ts → attaform.F8LMHHWV.d.cts} +95 -1
  98. package/dist/shared/attaform.LEWUFqUw.cjs +54 -0
  99. package/dist/shared/attaform.LEWUFqUw.cjs.map +1 -0
  100. package/dist/shared/{attaform.BrrXNmfK.cjs → attaform.PnqML3xW.cjs} +68 -402
  101. package/dist/shared/attaform.PnqML3xW.cjs.map +1 -0
  102. package/dist/shared/{attaform.BKozEdTr.mjs → attaform.Y_Mgg0Yp.mjs} +53 -37
  103. package/dist/shared/attaform.Y_Mgg0Yp.mjs.map +1 -0
  104. package/dist/shared/{attaform.DHRWn-cu.cjs → attaform._rsCZy2j.cjs} +172 -20
  105. package/dist/shared/attaform._rsCZy2j.cjs.map +1 -0
  106. package/dist/shared/{attaform.EZG6fOFb.mjs → attaform.ezb5Nh2t.mjs} +2 -2
  107. package/dist/shared/{attaform.EZG6fOFb.mjs.map → attaform.ezb5Nh2t.mjs.map} +1 -1
  108. package/dist/shared/{attaform.tVkmQh5w.mjs → attaform.r3PePkDR.mjs} +172 -21
  109. package/dist/shared/attaform.r3PePkDR.mjs.map +1 -0
  110. package/dist/shared/attaform.sHkHv_98.mjs +51 -0
  111. package/dist/shared/attaform.sHkHv_98.mjs.map +1 -0
  112. package/dist/vite.cjs +9 -45
  113. package/dist/vite.cjs.map +1 -1
  114. package/dist/vite.d.cts +36 -0
  115. package/dist/vite.d.mts +36 -0
  116. package/dist/vite.d.ts +36 -0
  117. package/dist/vite.mjs +8 -44
  118. package/dist/vite.mjs.map +1 -1
  119. package/dist/webpack.cjs +10 -0
  120. package/dist/webpack.cjs.map +1 -0
  121. package/dist/webpack.d.cts +37 -0
  122. package/dist/webpack.d.mts +37 -0
  123. package/dist/webpack.d.ts +37 -0
  124. package/dist/webpack.mjs +8 -0
  125. package/dist/webpack.mjs.map +1 -0
  126. package/dist/zod-v3.cjs +3 -3
  127. package/dist/zod-v3.d.cts +3 -3
  128. package/dist/zod-v3.d.mts +3 -3
  129. package/dist/zod-v3.d.ts +3 -3
  130. package/dist/zod-v3.mjs +3 -3
  131. package/dist/zod-v4.cjs +3 -3
  132. package/dist/zod-v4.d.cts +4 -4
  133. package/dist/zod-v4.d.mts +4 -4
  134. package/dist/zod-v4.d.ts +4 -4
  135. package/dist/zod-v4.mjs +3 -3
  136. package/dist/zod.cjs +8 -8
  137. package/dist/zod.cjs.map +1 -1
  138. package/dist/zod.d.cts +5 -5
  139. package/dist/zod.d.mts +5 -5
  140. package/dist/zod.d.ts +5 -5
  141. package/dist/zod.mjs +6 -6
  142. package/package.json +21 -7
  143. package/dist/shared/attaform.BCBxTyMC.cjs.map +0 -1
  144. package/dist/shared/attaform.BKozEdTr.mjs.map +0 -1
  145. package/dist/shared/attaform.BPy-4qRx.cjs.map +0 -1
  146. package/dist/shared/attaform.BqZuwLTK.mjs.map +0 -1
  147. package/dist/shared/attaform.BrrXNmfK.cjs.map +0 -1
  148. package/dist/shared/attaform.D6CwqkPx.mjs.map +0 -1
  149. package/dist/shared/attaform.DHRWn-cu.cjs.map +0 -1
  150. package/dist/shared/attaform.DLnE5bZa.cjs.map +0 -1
  151. package/dist/shared/attaform.iWo9soNX.mjs.map +0 -1
  152. package/dist/shared/attaform.tVkmQh5w.mjs.map +0 -1
@@ -814,6 +814,12 @@ type AbstractSchema<Form, GetValueFormType> = {
814
814
  * Structural fingerprint of the schema. Same shape → same string;
815
815
  * different shape → (best-effort) different string.
816
816
  *
817
+ * Resolves a `Promise` so adapters can defer the structural walk (and
818
+ * its `canonicalStringify` helper) onto a dynamic import. The framework
819
+ * only ever needs the fingerprint on opt-in async paths (the multi-tab
820
+ * channel name, the persistence storage key) plus a dev-only mismatch
821
+ * warning, so none of those bytes belong on the eager `useForm` path.
822
+ *
817
823
  * The library uses this to detect schema mismatches at a shared
818
824
  * form key: two `useForm({ key: 'x', schema })` calls are allowed
819
825
  * to land on the same `FormStore` (the "shared store" semantic),
@@ -841,7 +847,7 @@ type AbstractSchema<Form, GetValueFormType> = {
841
847
  * look identical. The warning is a footgun catcher, not a
842
848
  * soundness guarantee.
843
849
  */
844
- fingerprint(): string;
850
+ fingerprint(): Promise<string>;
845
851
  getDefaultValues(config: GetDefaultValuesConfig<Form>): DefaultValuesResponse<Form>;
846
852
  /**
847
853
  * Return the schema-prescribed default value at the given path. The
@@ -2047,29 +2053,31 @@ type AttaformDefaults = {
2047
2053
  * useForm({ getDisplayState }) > AttaformDefaults > library default
2048
2054
  *
2049
2055
  * The library default opens one timing gate, then resolves by
2050
- * precedence: gate closed → `'idle'`; a run in flight → `'pending'`;
2051
- * an own-path error `'error'`; otherwise `valid` `'success'`, else
2052
- * `'idle'`. The gate opens after the first submit attempt OR once the
2053
- * field is touched and not currently focused:
2056
+ * precedence: gate closed → `'idle'`; a run in flight → a delayed
2057
+ * `'pending'` (held briefly to smooth fast validations, then held a
2058
+ * minimum so it never flashes); an own-path error `'error'`;
2059
+ * otherwise earned `valid` `'success'`, else `'idle'`. The gate opens
2060
+ * after the first submit attempt OR once the field is edited and left:
2054
2061
  *
2055
2062
  * ```ts
2056
- * (field, formMeta) => {
2063
+ * (prev, ctx) => {
2057
2064
  * const gateOpen =
2058
- * formMeta.submissionAttempts > 0 ||
2059
- * (field.touched === true && field.focused !== true)
2060
- * if (!gateOpen) return 'idle'
2061
- * if (field.validating === true) return 'pending'
2062
- * // ...own-path error → 'error'; valid → 'success'; else 'idle'
2065
+ * ctx.formMeta.submissionAttempts > 0 ||
2066
+ * ctx.field.blurredAfterInteraction === true
2067
+ * if (!gateOpen) return { display: 'idle' }
2068
+ * // ...timed 'pending' while validating; own-path error 'error';
2069
+ * // earned valid → 'success'; else 'idle'
2063
2070
  * }
2064
2071
  * ```
2065
2072
  *
2066
2073
  * Compose with the library default via the public `defaultDisplayState`
2067
- * export. The predicate runs on every field-state read, so it owns the
2074
+ * export, or retune its timing via `makeDefaultDisplayState`. The
2075
+ * reducer runs on every field-state read, so it owns the
2068
2076
  * idle / pending / error / success decision outright.
2069
2077
  *
2070
- * The predicate's args are `Omit`'d of the derived `displayState` /
2071
- * `show*` / `firstError` keys (see `FieldStateDerivedKey`) to prevent
2072
- * a self-referential predicate.
2078
+ * The reducer's `ctx.field` / `ctx.formMeta` are `Omit`'d of the
2079
+ * derived `displayState` / `show*` / `firstError` keys (see
2080
+ * `FieldStateDerivedKey`) to prevent a self-referential reducer.
2073
2081
  */
2074
2082
  getDisplayState?: GetDisplayState;
2075
2083
  /**
@@ -2230,16 +2238,58 @@ type DisplayState = 'idle' | 'pending' | 'error' | 'success';
2230
2238
  */
2231
2239
  type FieldStateDerivedKey = 'displayState' | 'showErrors' | 'showPending' | 'showSuccess' | 'showIdle' | 'firstError';
2232
2240
  /**
2233
- * Predicate that resolves a path's `displayState`. Receives the field's
2234
- * reactive state plus the form's reactive meta (both minus the derived
2235
- * `displayState` / `show*` / `firstError` keys see `FieldStateDerivedKey`)
2236
- * and returns the single enum verdict; the `show*` booleans derive from
2237
- * the result. Runs unconditionally on every field-state read, so the
2238
- * idle / pending / error / success decision lives in exactly one place
2239
- * and the whole app's validation-display behavior flows from it.
2241
+ * One step of the display state machine: the verdict the field should
2242
+ * render right now (`display`, projected to `displayState` and the
2243
+ * `show*` booleans) plus two optional timing cells the engine reads.
2244
+ *
2245
+ * - `reviewAt` an absolute `Date.now()` millisecond stamp telling the
2246
+ * engine "re-evaluate this field no later than here." The engine keeps
2247
+ * a single timer per form aimed at the nearest `reviewAt` across all
2248
+ * active fields; when it fires, the dependent field computeds re-run
2249
+ * and call the reducer again. A machine with no `reviewAt` and a
2250
+ * non-pending `display` is terminal — the engine evicts it.
2251
+ * - `pendingShownAt` — the stamp at which `'pending'` was first shown,
2252
+ * the memory the min-visible hold needs so a spinner that just appeared
2253
+ * is not yanked away the instant validation resolves. Opaque to the
2254
+ * engine; a custom reducer may carry its own extra memory fields too.
2255
+ */
2256
+ type DisplayMachine = {
2257
+ readonly display: DisplayState;
2258
+ readonly reviewAt?: number;
2259
+ readonly pendingShownAt?: number;
2260
+ };
2261
+ /**
2262
+ * Inputs to a `getDisplayState` reducer. `field` and `formMeta` are the
2263
+ * same reactive snapshots a predicate has always received (still minus
2264
+ * the derived `displayState` / `show*` / `firstError` keys — see
2265
+ * `FieldStateDerivedKey` — so a reducer can never read its own output and
2266
+ * form a cycle), now joined by:
2267
+ *
2268
+ * - `validatingSince` — `Date.now()` at which the field's current
2269
+ * validation streak opened, or `null` when nothing is in flight. This,
2270
+ * not `field.validating`, is the timing anchor: the elapsed wait is
2271
+ * `now - validatingSince`. Pinned to the start of the streak, so
2272
+ * overlapping sub-runs do not reset it.
2273
+ * - `now` — the engine's clock, injected so the reducer stays pure and
2274
+ * deterministic (and frozen to `0` under SSR, where there is no clock).
2275
+ */
2276
+ type DisplayCtx = {
2277
+ readonly field: Omit<FieldState, FieldStateDerivedKey>;
2278
+ readonly formMeta: Omit<FormMeta, FieldStateDerivedKey>;
2279
+ readonly validatingSince: number | null;
2280
+ readonly now: number;
2281
+ };
2282
+ /**
2283
+ * Pure transition reducer that resolves a path's `displayState`. Given
2284
+ * the field's previous `DisplayMachine` and the current `DisplayCtx`, it
2285
+ * returns the next machine; the engine owns the clock and the timers, the
2286
+ * reducer owns the timing policy. Runs on every field-state read (and
2287
+ * again whenever a `reviewAt` deadline fires), so the whole app's
2288
+ * validation-display behavior flows from this one function.
2240
2289
  *
2241
- * The library default — `defaultDisplayState` — is publicly exported so
2242
- * a layered predicate can compose with it:
2290
+ * The library default — `defaultDisplayState` — is publicly exported so a
2291
+ * layered reducer can compose with it, and `makeDefaultDisplayState`
2292
+ * builds a default with custom anti-flash timings:
2243
2293
  *
2244
2294
  * ```ts
2245
2295
  * import { defaultDisplayState } from 'attaform'
@@ -2247,14 +2297,16 @@ type FieldStateDerivedKey = 'displayState' | 'showErrors' | 'showPending' | 'sho
2247
2297
  * useForm({
2248
2298
  * schema,
2249
2299
  * // Defer to the default everywhere, but never show a success check on `username`.
2250
- * getDisplayState: (field, formMeta) => {
2251
- * const state = defaultDisplayState(field, formMeta)
2252
- * return field.path[0] === 'username' && state === 'success' ? 'idle' : state
2300
+ * getDisplayState: (prev, ctx) => {
2301
+ * const next = defaultDisplayState(prev, ctx)
2302
+ * return next.display === 'success' && ctx.field.path[0] === 'username'
2303
+ * ? { display: 'idle' }
2304
+ * : next
2253
2305
  * },
2254
2306
  * })
2255
2307
  * ```
2256
2308
  */
2257
- type GetDisplayState = (field: Omit<FieldState, FieldStateDerivedKey>, formMeta: Omit<FormMeta, FieldStateDerivedKey>) => DisplayState;
2309
+ type GetDisplayState = (prev: DisplayMachine, ctx: DisplayCtx) => DisplayMachine;
2258
2310
  /**
2259
2311
  * Submit handler returned by `handleSubmit(onSubmit, onError)`. Bind
2260
2312
  * it to a `<form>`:
@@ -2554,18 +2606,34 @@ type RegisterValue<Value = unknown> = Readonly<{
2554
2606
  * Attach an HTML element to this binding. Called by `v-register`
2555
2607
  * automatically; expose it to custom integrations that need to
2556
2608
  * register an element manually.
2609
+ *
2610
+ * Recording the element also enables `setValueWithInternalPath` to
2611
+ * auto-attach per-element persist meta on writes that don't carry
2612
+ * their own.
2557
2613
  */
2558
2614
  registerElement: (el: HTMLElement) => void;
2559
2615
  /**
2560
2616
  * Detach an HTML element from this binding. Pair with
2561
- * `registerElement` for custom integrations.
2617
+ * `registerElement` for custom integrations. Clears the recorded
2618
+ * element so subsequent writes without explicit `meta` fall back to
2619
+ * "no auto-persist".
2562
2620
  */
2563
2621
  deregisterElement: (el: HTMLElement) => void;
2564
2622
  /**
2565
2623
  * Write the field's value programmatically. Returns `true` when the
2566
2624
  * write was accepted, `false` when it was rejected (e.g. wrong
2567
- * primitive type for the path). The optional `meta` lets custom
2568
- * directives signal whether the write should be persisted.
2625
+ * primitive type for the path).
2626
+ *
2627
+ * When `meta` is undefined and the binding has a registered element,
2628
+ * the rv consults the per-element opt-in (set by
2629
+ * `register(path, { persist: true })` against that element) and
2630
+ * auto-attaches `{ persist: true }` when opted in. Custom directives
2631
+ * and consumer assigners can omit `meta` to participate in the same
2632
+ * persistence channel the default assigner uses.
2633
+ *
2634
+ * Pass an explicit `meta` to override the auto-derivation, e.g.
2635
+ * `{ persist: false }` to skip persistence for a transient write
2636
+ * even when the element is opted in.
2569
2637
  */
2570
2638
  setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
2571
2639
  /**
@@ -2808,9 +2876,9 @@ type RegisterValue<Value = unknown> = Readonly<{
2808
2876
  * on the bound element.
2809
2877
  *
2810
2878
  * The directive passes the extracted value plus the `RegisterValue`
2811
- * the directive is currently bound to. The second arg lets a
2812
- * top-level handler write back to form state without having to
2813
- * capture the RV via closure:
2879
+ * the directive is currently bound to, regardless of install path.
2880
+ * The second arg lets a top-level handler write back to form state
2881
+ * without having to capture the RV via closure:
2814
2882
  *
2815
2883
  * ```ts
2816
2884
  * function upperCaseAssigner(value: unknown, rv: RegisterValue): void {
@@ -2818,9 +2886,10 @@ type RegisterValue<Value = unknown> = Readonly<{
2818
2886
  * }
2819
2887
  * ```
2820
2888
  *
2821
- * `registerValue` is omitted only for assigners installed directly
2822
- * via `el[assignKey] = fn` those callers already have the RV in
2823
- * scope at install time.
2889
+ * The `registerValue` parameter is typed optional only to keep
2890
+ * standalone invocations from outside the directive (rare; manual
2891
+ * dispatch in tests, for example) type-checkable; the directive
2892
+ * itself always supplies it at fire time.
2824
2893
  *
2825
2894
  * Return `true` when the write was accepted, `false` when it was
2826
2895
  * rejected (e.g. the value didn't match the path's expected type).
@@ -4627,5 +4696,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4627
4696
  blankPaths: ComputedRef<BlankPathsView>;
4628
4697
  };
4629
4698
 
4630
- export { ROOT_PATH_KEY as $, ROOT_PATH as _, canonicalizePath as as, isPathPrefix as at, isUnset as au, parseDottedPath as av, unset as aw };
4631
- export type { AbstractSchema as A, NestedType as B, CoercionEntry as C, DeepPartial as D, ErrorsProxyShape as E, FieldMetaPayload as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, OnInvalidSubmitPolicy as P, OnSubmit as Q, PartialFlatPath as R, Path as S, PathKey as T, PendingValidationStatus as U, PersistConfig as V, PersistConfigOptions as W, PersistIncludeMode as X, PersistOptInRegistry as Y, Primitive as Z, ApiErrorDetails as a, ReactiveValidationStatus as a0, RegisterDirective as a1, RegisterFlatPath as a2, RegisterModelDynamicCustomDirective as a3, RegisterOptions as a4, RegisterSelectModifier as a5, RegisterTextModifier as a6, RegisterTransform as a7, RegisterValue as a8, SchemaFactoryOptions as a9, Segment as aa, SetValueCallback as ab, SetValuePayload as ac, SettledValidationStatus as ad, SlimPrimitiveKind as ae, SlimRuntimeOf as af, SubmitHandler as ag, Unset as ah, UseFormConfiguration as ai, UseFormReturnType as aj, ValidateOn as ak, ValidateOnConfig as al, ValidationError as am, ValidationResponse as an, ValidationResponseWithoutValue as ao, ValueOfUnion as ap, WriteMeta as aq, WriteShape as ar, ApiErrorEntry as b, ApiErrorEnvelope as c, ArrayItem as d, ArrayPath as e, AttaformDefaults as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DefaultValuesInput as j, DefaultValuesResponse as k, DefaultValuesShape as l, DisplayState as m, FieldState as n, FieldStateMap as o, FieldStateMapEntry as p, FlatPath as q, FormErrorRecord as r, FormErrorsSurface as s, FormKey as t, FormMeta as u, FormStorage as v, FormStorageKind as w, GetDisplayState as x, HistoryConfig as y, IsUnion as z };
4699
+ export { ROOT_PATH as a0, ROOT_PATH_KEY as a1, canonicalizePath as au, isPathPrefix as av, isUnset as aw, parseDottedPath as ax, unset as ay };
4700
+ export type { Primitive as $, AbstractSchema as A, HistoryConfig as B, CoercionEntry as C, DeepPartial as D, ErrorsProxyShape as E, FieldMetaPayload as F, GenericForm as G, HandleSubmit as H, IsTuple as I, IsUnion as J, JoinSegments as K, KeyofUnion as L, LiftedValueShape as M, MetaTrackerValue as N, NestedReadType as O, NestedType as P, OnError as Q, OnInvalidSubmitPolicy as R, OnSubmit as S, PartialFlatPath as T, Path as U, PathKey as V, PendingValidationStatus as W, PersistConfig as X, PersistConfigOptions as Y, PersistIncludeMode as Z, PersistOptInRegistry as _, ApiErrorDetails as a, ReactiveValidationStatus as a2, RegisterDirective as a3, RegisterFlatPath as a4, RegisterModelDynamicCustomDirective as a5, RegisterOptions as a6, RegisterSelectModifier as a7, RegisterTextModifier as a8, RegisterTransform as a9, RegisterValue as aa, SchemaFactoryOptions as ab, Segment as ac, SetValueCallback as ad, SetValuePayload as ae, SettledValidationStatus as af, SlimPrimitiveKind as ag, SlimRuntimeOf as ah, SubmitHandler as ai, Unset as aj, UseFormConfiguration as ak, UseFormReturnType as al, ValidateOn as am, ValidateOnConfig as an, ValidationError as ao, ValidationResponse as ap, ValidationResponseWithoutValue as aq, ValueOfUnion as ar, WriteMeta as as, WriteShape as at, ApiErrorEntry as b, ApiErrorEnvelope as c, ArrayItem as d, ArrayPath as e, AttaformDefaults as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DefaultValuesInput as j, DefaultValuesResponse as k, DefaultValuesShape as l, DisplayCtx as m, DisplayMachine as n, DisplayState as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormKey as v, FormMeta as w, FormStorage as x, FormStorageKind as y, GetDisplayState as z };
@@ -814,6 +814,12 @@ type AbstractSchema<Form, GetValueFormType> = {
814
814
  * Structural fingerprint of the schema. Same shape → same string;
815
815
  * different shape → (best-effort) different string.
816
816
  *
817
+ * Resolves a `Promise` so adapters can defer the structural walk (and
818
+ * its `canonicalStringify` helper) onto a dynamic import. The framework
819
+ * only ever needs the fingerprint on opt-in async paths (the multi-tab
820
+ * channel name, the persistence storage key) plus a dev-only mismatch
821
+ * warning, so none of those bytes belong on the eager `useForm` path.
822
+ *
817
823
  * The library uses this to detect schema mismatches at a shared
818
824
  * form key: two `useForm({ key: 'x', schema })` calls are allowed
819
825
  * to land on the same `FormStore` (the "shared store" semantic),
@@ -841,7 +847,7 @@ type AbstractSchema<Form, GetValueFormType> = {
841
847
  * look identical. The warning is a footgun catcher, not a
842
848
  * soundness guarantee.
843
849
  */
844
- fingerprint(): string;
850
+ fingerprint(): Promise<string>;
845
851
  getDefaultValues(config: GetDefaultValuesConfig<Form>): DefaultValuesResponse<Form>;
846
852
  /**
847
853
  * Return the schema-prescribed default value at the given path. The
@@ -2047,29 +2053,31 @@ type AttaformDefaults = {
2047
2053
  * useForm({ getDisplayState }) > AttaformDefaults > library default
2048
2054
  *
2049
2055
  * The library default opens one timing gate, then resolves by
2050
- * precedence: gate closed → `'idle'`; a run in flight → `'pending'`;
2051
- * an own-path error `'error'`; otherwise `valid` `'success'`, else
2052
- * `'idle'`. The gate opens after the first submit attempt OR once the
2053
- * field is touched and not currently focused:
2056
+ * precedence: gate closed → `'idle'`; a run in flight → a delayed
2057
+ * `'pending'` (held briefly to smooth fast validations, then held a
2058
+ * minimum so it never flashes); an own-path error `'error'`;
2059
+ * otherwise earned `valid` `'success'`, else `'idle'`. The gate opens
2060
+ * after the first submit attempt OR once the field is edited and left:
2054
2061
  *
2055
2062
  * ```ts
2056
- * (field, formMeta) => {
2063
+ * (prev, ctx) => {
2057
2064
  * const gateOpen =
2058
- * formMeta.submissionAttempts > 0 ||
2059
- * (field.touched === true && field.focused !== true)
2060
- * if (!gateOpen) return 'idle'
2061
- * if (field.validating === true) return 'pending'
2062
- * // ...own-path error → 'error'; valid → 'success'; else 'idle'
2065
+ * ctx.formMeta.submissionAttempts > 0 ||
2066
+ * ctx.field.blurredAfterInteraction === true
2067
+ * if (!gateOpen) return { display: 'idle' }
2068
+ * // ...timed 'pending' while validating; own-path error 'error';
2069
+ * // earned valid → 'success'; else 'idle'
2063
2070
  * }
2064
2071
  * ```
2065
2072
  *
2066
2073
  * Compose with the library default via the public `defaultDisplayState`
2067
- * export. The predicate runs on every field-state read, so it owns the
2074
+ * export, or retune its timing via `makeDefaultDisplayState`. The
2075
+ * reducer runs on every field-state read, so it owns the
2068
2076
  * idle / pending / error / success decision outright.
2069
2077
  *
2070
- * The predicate's args are `Omit`'d of the derived `displayState` /
2071
- * `show*` / `firstError` keys (see `FieldStateDerivedKey`) to prevent
2072
- * a self-referential predicate.
2078
+ * The reducer's `ctx.field` / `ctx.formMeta` are `Omit`'d of the
2079
+ * derived `displayState` / `show*` / `firstError` keys (see
2080
+ * `FieldStateDerivedKey`) to prevent a self-referential reducer.
2073
2081
  */
2074
2082
  getDisplayState?: GetDisplayState;
2075
2083
  /**
@@ -2230,16 +2238,58 @@ type DisplayState = 'idle' | 'pending' | 'error' | 'success';
2230
2238
  */
2231
2239
  type FieldStateDerivedKey = 'displayState' | 'showErrors' | 'showPending' | 'showSuccess' | 'showIdle' | 'firstError';
2232
2240
  /**
2233
- * Predicate that resolves a path's `displayState`. Receives the field's
2234
- * reactive state plus the form's reactive meta (both minus the derived
2235
- * `displayState` / `show*` / `firstError` keys see `FieldStateDerivedKey`)
2236
- * and returns the single enum verdict; the `show*` booleans derive from
2237
- * the result. Runs unconditionally on every field-state read, so the
2238
- * idle / pending / error / success decision lives in exactly one place
2239
- * and the whole app's validation-display behavior flows from it.
2241
+ * One step of the display state machine: the verdict the field should
2242
+ * render right now (`display`, projected to `displayState` and the
2243
+ * `show*` booleans) plus two optional timing cells the engine reads.
2244
+ *
2245
+ * - `reviewAt` an absolute `Date.now()` millisecond stamp telling the
2246
+ * engine "re-evaluate this field no later than here." The engine keeps
2247
+ * a single timer per form aimed at the nearest `reviewAt` across all
2248
+ * active fields; when it fires, the dependent field computeds re-run
2249
+ * and call the reducer again. A machine with no `reviewAt` and a
2250
+ * non-pending `display` is terminal — the engine evicts it.
2251
+ * - `pendingShownAt` — the stamp at which `'pending'` was first shown,
2252
+ * the memory the min-visible hold needs so a spinner that just appeared
2253
+ * is not yanked away the instant validation resolves. Opaque to the
2254
+ * engine; a custom reducer may carry its own extra memory fields too.
2255
+ */
2256
+ type DisplayMachine = {
2257
+ readonly display: DisplayState;
2258
+ readonly reviewAt?: number;
2259
+ readonly pendingShownAt?: number;
2260
+ };
2261
+ /**
2262
+ * Inputs to a `getDisplayState` reducer. `field` and `formMeta` are the
2263
+ * same reactive snapshots a predicate has always received (still minus
2264
+ * the derived `displayState` / `show*` / `firstError` keys — see
2265
+ * `FieldStateDerivedKey` — so a reducer can never read its own output and
2266
+ * form a cycle), now joined by:
2267
+ *
2268
+ * - `validatingSince` — `Date.now()` at which the field's current
2269
+ * validation streak opened, or `null` when nothing is in flight. This,
2270
+ * not `field.validating`, is the timing anchor: the elapsed wait is
2271
+ * `now - validatingSince`. Pinned to the start of the streak, so
2272
+ * overlapping sub-runs do not reset it.
2273
+ * - `now` — the engine's clock, injected so the reducer stays pure and
2274
+ * deterministic (and frozen to `0` under SSR, where there is no clock).
2275
+ */
2276
+ type DisplayCtx = {
2277
+ readonly field: Omit<FieldState, FieldStateDerivedKey>;
2278
+ readonly formMeta: Omit<FormMeta, FieldStateDerivedKey>;
2279
+ readonly validatingSince: number | null;
2280
+ readonly now: number;
2281
+ };
2282
+ /**
2283
+ * Pure transition reducer that resolves a path's `displayState`. Given
2284
+ * the field's previous `DisplayMachine` and the current `DisplayCtx`, it
2285
+ * returns the next machine; the engine owns the clock and the timers, the
2286
+ * reducer owns the timing policy. Runs on every field-state read (and
2287
+ * again whenever a `reviewAt` deadline fires), so the whole app's
2288
+ * validation-display behavior flows from this one function.
2240
2289
  *
2241
- * The library default — `defaultDisplayState` — is publicly exported so
2242
- * a layered predicate can compose with it:
2290
+ * The library default — `defaultDisplayState` — is publicly exported so a
2291
+ * layered reducer can compose with it, and `makeDefaultDisplayState`
2292
+ * builds a default with custom anti-flash timings:
2243
2293
  *
2244
2294
  * ```ts
2245
2295
  * import { defaultDisplayState } from 'attaform'
@@ -2247,14 +2297,16 @@ type FieldStateDerivedKey = 'displayState' | 'showErrors' | 'showPending' | 'sho
2247
2297
  * useForm({
2248
2298
  * schema,
2249
2299
  * // Defer to the default everywhere, but never show a success check on `username`.
2250
- * getDisplayState: (field, formMeta) => {
2251
- * const state = defaultDisplayState(field, formMeta)
2252
- * return field.path[0] === 'username' && state === 'success' ? 'idle' : state
2300
+ * getDisplayState: (prev, ctx) => {
2301
+ * const next = defaultDisplayState(prev, ctx)
2302
+ * return next.display === 'success' && ctx.field.path[0] === 'username'
2303
+ * ? { display: 'idle' }
2304
+ * : next
2253
2305
  * },
2254
2306
  * })
2255
2307
  * ```
2256
2308
  */
2257
- type GetDisplayState = (field: Omit<FieldState, FieldStateDerivedKey>, formMeta: Omit<FormMeta, FieldStateDerivedKey>) => DisplayState;
2309
+ type GetDisplayState = (prev: DisplayMachine, ctx: DisplayCtx) => DisplayMachine;
2258
2310
  /**
2259
2311
  * Submit handler returned by `handleSubmit(onSubmit, onError)`. Bind
2260
2312
  * it to a `<form>`:
@@ -2554,18 +2606,34 @@ type RegisterValue<Value = unknown> = Readonly<{
2554
2606
  * Attach an HTML element to this binding. Called by `v-register`
2555
2607
  * automatically; expose it to custom integrations that need to
2556
2608
  * register an element manually.
2609
+ *
2610
+ * Recording the element also enables `setValueWithInternalPath` to
2611
+ * auto-attach per-element persist meta on writes that don't carry
2612
+ * their own.
2557
2613
  */
2558
2614
  registerElement: (el: HTMLElement) => void;
2559
2615
  /**
2560
2616
  * Detach an HTML element from this binding. Pair with
2561
- * `registerElement` for custom integrations.
2617
+ * `registerElement` for custom integrations. Clears the recorded
2618
+ * element so subsequent writes without explicit `meta` fall back to
2619
+ * "no auto-persist".
2562
2620
  */
2563
2621
  deregisterElement: (el: HTMLElement) => void;
2564
2622
  /**
2565
2623
  * Write the field's value programmatically. Returns `true` when the
2566
2624
  * write was accepted, `false` when it was rejected (e.g. wrong
2567
- * primitive type for the path). The optional `meta` lets custom
2568
- * directives signal whether the write should be persisted.
2625
+ * primitive type for the path).
2626
+ *
2627
+ * When `meta` is undefined and the binding has a registered element,
2628
+ * the rv consults the per-element opt-in (set by
2629
+ * `register(path, { persist: true })` against that element) and
2630
+ * auto-attaches `{ persist: true }` when opted in. Custom directives
2631
+ * and consumer assigners can omit `meta` to participate in the same
2632
+ * persistence channel the default assigner uses.
2633
+ *
2634
+ * Pass an explicit `meta` to override the auto-derivation, e.g.
2635
+ * `{ persist: false }` to skip persistence for a transient write
2636
+ * even when the element is opted in.
2569
2637
  */
2570
2638
  setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
2571
2639
  /**
@@ -2808,9 +2876,9 @@ type RegisterValue<Value = unknown> = Readonly<{
2808
2876
  * on the bound element.
2809
2877
  *
2810
2878
  * The directive passes the extracted value plus the `RegisterValue`
2811
- * the directive is currently bound to. The second arg lets a
2812
- * top-level handler write back to form state without having to
2813
- * capture the RV via closure:
2879
+ * the directive is currently bound to, regardless of install path.
2880
+ * The second arg lets a top-level handler write back to form state
2881
+ * without having to capture the RV via closure:
2814
2882
  *
2815
2883
  * ```ts
2816
2884
  * function upperCaseAssigner(value: unknown, rv: RegisterValue): void {
@@ -2818,9 +2886,10 @@ type RegisterValue<Value = unknown> = Readonly<{
2818
2886
  * }
2819
2887
  * ```
2820
2888
  *
2821
- * `registerValue` is omitted only for assigners installed directly
2822
- * via `el[assignKey] = fn` those callers already have the RV in
2823
- * scope at install time.
2889
+ * The `registerValue` parameter is typed optional only to keep
2890
+ * standalone invocations from outside the directive (rare; manual
2891
+ * dispatch in tests, for example) type-checkable; the directive
2892
+ * itself always supplies it at fire time.
2824
2893
  *
2825
2894
  * Return `true` when the write was accepted, `false` when it was
2826
2895
  * rejected (e.g. the value didn't match the path's expected type).
@@ -4627,5 +4696,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4627
4696
  blankPaths: ComputedRef<BlankPathsView>;
4628
4697
  };
4629
4698
 
4630
- export { ROOT_PATH_KEY as $, ROOT_PATH as _, canonicalizePath as as, isPathPrefix as at, isUnset as au, parseDottedPath as av, unset as aw };
4631
- export type { AbstractSchema as A, NestedType as B, CoercionEntry as C, DeepPartial as D, ErrorsProxyShape as E, FieldMetaPayload as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, OnInvalidSubmitPolicy as P, OnSubmit as Q, PartialFlatPath as R, Path as S, PathKey as T, PendingValidationStatus as U, PersistConfig as V, PersistConfigOptions as W, PersistIncludeMode as X, PersistOptInRegistry as Y, Primitive as Z, ApiErrorDetails as a, ReactiveValidationStatus as a0, RegisterDirective as a1, RegisterFlatPath as a2, RegisterModelDynamicCustomDirective as a3, RegisterOptions as a4, RegisterSelectModifier as a5, RegisterTextModifier as a6, RegisterTransform as a7, RegisterValue as a8, SchemaFactoryOptions as a9, Segment as aa, SetValueCallback as ab, SetValuePayload as ac, SettledValidationStatus as ad, SlimPrimitiveKind as ae, SlimRuntimeOf as af, SubmitHandler as ag, Unset as ah, UseFormConfiguration as ai, UseFormReturnType as aj, ValidateOn as ak, ValidateOnConfig as al, ValidationError as am, ValidationResponse as an, ValidationResponseWithoutValue as ao, ValueOfUnion as ap, WriteMeta as aq, WriteShape as ar, ApiErrorEntry as b, ApiErrorEnvelope as c, ArrayItem as d, ArrayPath as e, AttaformDefaults as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DefaultValuesInput as j, DefaultValuesResponse as k, DefaultValuesShape as l, DisplayState as m, FieldState as n, FieldStateMap as o, FieldStateMapEntry as p, FlatPath as q, FormErrorRecord as r, FormErrorsSurface as s, FormKey as t, FormMeta as u, FormStorage as v, FormStorageKind as w, GetDisplayState as x, HistoryConfig as y, IsUnion as z };
4699
+ export { ROOT_PATH as a0, ROOT_PATH_KEY as a1, canonicalizePath as au, isPathPrefix as av, isUnset as aw, parseDottedPath as ax, unset as ay };
4700
+ export type { Primitive as $, AbstractSchema as A, HistoryConfig as B, CoercionEntry as C, DeepPartial as D, ErrorsProxyShape as E, FieldMetaPayload as F, GenericForm as G, HandleSubmit as H, IsTuple as I, IsUnion as J, JoinSegments as K, KeyofUnion as L, LiftedValueShape as M, MetaTrackerValue as N, NestedReadType as O, NestedType as P, OnError as Q, OnInvalidSubmitPolicy as R, OnSubmit as S, PartialFlatPath as T, Path as U, PathKey as V, PendingValidationStatus as W, PersistConfig as X, PersistConfigOptions as Y, PersistIncludeMode as Z, PersistOptInRegistry as _, ApiErrorDetails as a, ReactiveValidationStatus as a2, RegisterDirective as a3, RegisterFlatPath as a4, RegisterModelDynamicCustomDirective as a5, RegisterOptions as a6, RegisterSelectModifier as a7, RegisterTextModifier as a8, RegisterTransform as a9, RegisterValue as aa, SchemaFactoryOptions as ab, Segment as ac, SetValueCallback as ad, SetValuePayload as ae, SettledValidationStatus as af, SlimPrimitiveKind as ag, SlimRuntimeOf as ah, SubmitHandler as ai, Unset as aj, UseFormConfiguration as ak, UseFormReturnType as al, ValidateOn as am, ValidateOnConfig as an, ValidationError as ao, ValidationResponse as ap, ValidationResponseWithoutValue as aq, ValueOfUnion as ar, WriteMeta as as, WriteShape as at, ApiErrorEntry as b, ApiErrorEnvelope as c, ArrayItem as d, ArrayPath as e, AttaformDefaults as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DefaultValuesInput as j, DefaultValuesResponse as k, DefaultValuesShape as l, DisplayCtx as m, DisplayMachine as n, DisplayState as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormKey as v, FormMeta as w, FormStorage as x, FormStorageKind as y, GetDisplayState as z };