attaform 0.31.0 → 0.31.2
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.
- package/dist/abstract.d.mts +5 -5
- package/dist/abstract.mjs +5 -4
- package/dist/dev/abstract.mjs +5 -4
- package/dist/dev/chunks/devtools.mjs +1 -1
- package/dist/dev/directive.mjs +1 -1
- package/dist/dev/history.mjs +126 -6
- package/dist/dev/index.mjs +6 -5
- package/dist/dev/runtime/plugins/attaform.mjs +1 -1
- package/dist/dev/shared/{attaform-dev.zlvDr0WB.mjs → attaform-dev.04aupFBW.mjs} +3 -2
- package/dist/dev/shared/{attaform-dev.CtyqGia7.mjs → attaform-dev.BKWPjEB2.mjs} +5 -4
- package/dist/dev/shared/{attaform-dev.DlaT4xGd.mjs → attaform-dev.BnLLxX2l.mjs} +4 -4
- package/dist/dev/shared/{attaform-dev.B1q-x9R9.mjs → attaform-dev.Bu6wgrA5.mjs} +1 -1
- package/dist/dev/shared/{attaform-dev.k5X47GMD.mjs → attaform-dev.CKSqs_xQ.mjs} +3 -2
- package/dist/dev/shared/{attaform-dev.DJ3nsWw6.mjs → attaform-dev.Cf_hZfbT.mjs} +18 -9
- package/dist/dev/shared/{attaform-dev.y9x9Vp9F.mjs → attaform-dev.DLk51W7-.mjs} +565 -926
- package/dist/dev/shared/attaform-dev.DoG8mZTq.mjs +127 -0
- package/dist/dev/shared/attaform-dev.G-3XDl9F.mjs +1192 -0
- package/dist/dev/shared/{attaform-dev.Caibt7sb.mjs → attaform-dev.oguHqv1W.mjs} +11 -10
- package/dist/dev/zod-v3.mjs +7 -6
- package/dist/dev/zod-v4.mjs +7 -6
- package/dist/dev/zod.mjs +6 -5
- package/dist/directive.d.mts +2 -2
- package/dist/directive.mjs +1 -1
- package/dist/history.d.mts +1 -1
- package/dist/history.mjs +126 -6
- package/dist/index.d.mts +16 -55
- package/dist/index.mjs +6 -5
- package/dist/runtime/plugins/attaform.mjs +1 -1
- package/dist/shared/{attaform.DL2nl_QR.mjs → attaform.3kV6PqVJ.mjs} +11 -10
- package/dist/shared/{attaform.L011tHgJ.mjs → attaform.BJeiuJE2.mjs} +4 -4
- package/dist/shared/attaform.BNUhW4qC.mjs +103 -0
- package/dist/shared/{attaform.CdZIEf7_.mjs → attaform.BhtAZysa.mjs} +3 -2
- package/dist/shared/{attaform.BWUn8Kjy.mjs → attaform.BlAmZ-WU.mjs} +18 -9
- package/dist/shared/{attaform.LUPC-2Qg.mjs → attaform.CMzc4bPI.mjs} +5 -4
- package/dist/shared/{attaform.C4shmwyJ.mjs → attaform.CdA9bfRv.mjs} +1 -1
- package/dist/shared/attaform.CgyyJVuq.mjs +1187 -0
- package/dist/shared/{attaform.DAo0QBmS.d.mts → attaform.CibLYe9-.d.mts} +214 -173
- package/dist/shared/{attaform.DNPJPOO7.mjs → attaform.DCUJCKyG.mjs} +562 -925
- package/dist/shared/{attaform.D9RqPgGu.d.mts → attaform.DCyZT7u-.d.mts} +1 -1
- package/dist/shared/{attaform.CRVe8s82.d.mts → attaform.DD0Y8krq.d.mts} +43 -19
- package/dist/shared/{attaform.DxKQsqFZ.mjs → attaform.T0YjhjPU.mjs} +3 -2
- package/dist/shared/{attaform.DZnrDrNb.d.mts → attaform.bI_4nKZp.d.mts} +119 -8
- package/dist/zod-v3.d.mts +4 -4
- package/dist/zod-v3.mjs +7 -6
- package/dist/zod-v4.d.mts +15 -11
- package/dist/zod-v4.mjs +7 -6
- package/dist/zod.d.mts +4 -4
- package/dist/zod.mjs +6 -5
- package/package.json +2 -1
- package/skills/attaform/SKILL.md +1 -0
- package/dist/dev/shared/attaform-dev.CZz4GYZ6.mjs +0 -386
- package/dist/shared/attaform.8mfKZCwH.mjs +0 -358
|
@@ -1,160 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { v as Path, w as PathKey, aq as DisplayCtx, ar as GetDisplayState, as as DisplayMachine, G as GenericForm, F as FormKey, at as FieldRecord, au as ErrorCell, ac as ValidationError, av as OriginalsRecord, A as AbstractSchema, ag as WriteMeta, d as DeepPartial, ah as WriteShape, aw as StructureSnapshot, ax as IndexRemap, ay as AttaformDomBinding, az as TransformAbortHolder, a as UseFormReturnType, _ as RegisterValue } from './attaform.bI_4nKZp.mjs';
|
|
2
2
|
import { Ref, ComputedRef, ShallowRef, App, InjectionKey, Plugin } from 'vue';
|
|
3
3
|
|
|
4
|
-
/** Options accepted by `injectForm` in place of a bare key string. */
|
|
5
|
-
type InjectFormInput = {
|
|
6
|
-
readonly key?: FormKey;
|
|
7
|
-
/**
|
|
8
|
-
* Set by the Vite transform when this `injectForm` call site sits in
|
|
9
|
-
* a component whose template / script reads the form's reactive
|
|
10
|
-
* state. On the server, this enqueues the form for SSR prefetch and
|
|
11
|
-
* wires `onServerPrefetch` so the descendant awaits the activation
|
|
12
|
-
* promise before its render emits HTML.
|
|
13
|
-
*
|
|
14
|
-
* @internal Transform-emitted. Manual use is the documented escape
|
|
15
|
-
* hatch when the transform can't reach the reference (dynamic
|
|
16
|
-
* property access, untransformed bundlers).
|
|
17
|
-
*/
|
|
18
|
-
readonly __ssrAccessed?: boolean;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Reach an existing form from a descendant component without threading
|
|
22
|
-
* it through props. The counterpart to `useForm`: `useForm` creates and
|
|
23
|
-
* provides, `injectForm` looks up.
|
|
24
|
-
*
|
|
25
|
-
* ```ts
|
|
26
|
-
* // The nearest ancestor's anonymous useForm() call.
|
|
27
|
-
* const form = injectForm<SignupShape>()
|
|
28
|
-
*
|
|
29
|
-
* // A specific form by key, from anywhere in the app.
|
|
30
|
-
* const cart = injectForm<CartShape>('cart')
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* A keyed call is a registry lookup, so it is independent of
|
|
34
|
-
* component-tree position. A no-key call takes the closest ambient
|
|
35
|
-
* ancestor and inherits its `formInstanceId`; only an anonymous
|
|
36
|
-
* `useForm()` fills that ambient slot, so a keyed form is reachable
|
|
37
|
-
* only by its key.
|
|
38
|
-
*
|
|
39
|
-
* Returns `null` when nothing matches, so narrow before use:
|
|
40
|
-
*
|
|
41
|
-
* ```ts
|
|
42
|
-
* const form = injectForm<Shape>('signup')
|
|
43
|
-
* if (!form) return
|
|
44
|
-
* form.register('email')
|
|
45
|
-
* ```
|
|
46
|
-
*
|
|
47
|
-
* A keyed miss warns in dev with the registered keys and the call site.
|
|
48
|
-
* Pass the `Form` generic explicitly, since Vue's provide/inject erases
|
|
49
|
-
* generics. The form stays alive for this component's lifetime and is
|
|
50
|
-
* cleaned up once every consumer has unmounted.
|
|
51
|
-
*
|
|
52
|
-
* Components whose nearest `useForm`-calling ancestor is the same one get
|
|
53
|
-
* the same form object for the same form: `injectForm('cart')` in two of
|
|
54
|
-
* its descendants returns one form, with one set of field caches.
|
|
55
|
-
*/
|
|
56
|
-
declare function injectForm<Form extends GenericForm, GetValueFormType extends GenericForm = Form>(input?: FormKey | InjectFormInput): UseFormReturnType<Form, GetValueFormType> | null;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Return type of `useRegister()`. Hybrid of `RegisterValue<V>` (so
|
|
60
|
-
* `rv.path` / `rv.segments` / `rv.formKey` etc. work directly in
|
|
61
|
-
* script setup) and `Ref<RegisterValue<V> | undefined>` (so Vue's
|
|
62
|
-
* template auto-unwrap surfaces the underlying RV to `v-register`
|
|
63
|
-
* and the directive's path-migration diff sees the real RV across
|
|
64
|
-
* renders).
|
|
65
|
-
*
|
|
66
|
-
* The two surfaces don't clash at the type level: `RegisterValue`
|
|
67
|
-
* doesn't carry a `value` field, and `Ref<T>`'s `value: T` becomes
|
|
68
|
-
* the hybrid's only `.value`. Older code that read `rv.value?.path`
|
|
69
|
-
* keeps working; new code can write `rv.path` directly.
|
|
70
|
-
*/
|
|
71
|
-
type UseRegisterReturn<V = unknown> = RegisterValue<V> & Ref<RegisterValue<V> | undefined> & {
|
|
72
|
-
/**
|
|
73
|
-
* Whether a parent has actually bound this wrapper. `false` until a
|
|
74
|
-
* `RegisterValue` lands, `true` once one has, and reactive in both
|
|
75
|
-
* directions: a parent that binds on a later render flips it, and
|
|
76
|
-
* so does one that stops binding.
|
|
77
|
-
*
|
|
78
|
-
* This is the test a dual-mode wrapper reaches for, and the reason
|
|
79
|
-
* it exists as a field rather than a truthiness check on the
|
|
80
|
-
* return: the composable cannot answer "is this bound" at setup
|
|
81
|
-
* time. A parent is free to bind on a later render
|
|
82
|
-
* (`v-register="ready ? form.register('x') : undefined"`), and
|
|
83
|
-
* `useRegister` has to keep serving that, so it can never return a
|
|
84
|
-
* plain `undefined` for "unbound right now". Script-setup code
|
|
85
|
-
* reading `rv` directly sees the hybrid Proxy, which is an object
|
|
86
|
-
* and therefore always truthy; a template sees the unwrapped ref,
|
|
87
|
-
* which IS `undefined` when unbound. `rv?.isBound` reads the same
|
|
88
|
-
* on both sides (#620).
|
|
89
|
-
*/
|
|
90
|
-
readonly isBound: boolean;
|
|
91
|
-
};
|
|
92
|
-
/**
|
|
93
|
-
* Options for `useRegister`.
|
|
94
|
-
*/
|
|
95
|
-
type UseRegisterOptions = {
|
|
96
|
-
/**
|
|
97
|
-
* Declares that this wrapper is used both with and without a form, so
|
|
98
|
-
* a render with no parent binding is intended rather than a mistake.
|
|
99
|
-
* Silences the unbound diagnostic; nothing else changes.
|
|
100
|
-
*
|
|
101
|
-
* The diagnostic exists because a single-mode wrapper whose parent
|
|
102
|
-
* forgot `v-register` renders a field that looks fine and stores
|
|
103
|
-
* nothing, which is an expensive afternoon. Once a wrapper is
|
|
104
|
-
* deliberately dual-mode, the same signal fires on correct code and
|
|
105
|
-
* tells the author to do something wrong, so this is the one fact
|
|
106
|
-
* that separates the two cases and only the author has it (#620).
|
|
107
|
-
*
|
|
108
|
-
* Pair it with `rv?.isBound` to branch on what actually happened.
|
|
109
|
-
*/
|
|
110
|
-
readonly optional?: boolean;
|
|
111
|
-
};
|
|
112
|
-
/**
|
|
113
|
-
* Re-bind a parent's `v-register` onto an inner native element. Use it
|
|
114
|
-
* inside a component that wraps a single form field whose root is NOT
|
|
115
|
-
* the input itself, such as a labelled row that renders `<label>`
|
|
116
|
-
* around the input.
|
|
117
|
-
*
|
|
118
|
-
* ```vue
|
|
119
|
-
* <!-- Parent -->
|
|
120
|
-
* <MyInput v-register="form.register('email')" />
|
|
121
|
-
*
|
|
122
|
-
* <!-- MyInput.vue -->
|
|
123
|
-
* <script setup lang="ts">
|
|
124
|
-
* import { useRegister } from 'attaform'
|
|
125
|
-
* const rv = useRegister()
|
|
126
|
-
* </script>
|
|
127
|
-
*
|
|
128
|
-
* <template>
|
|
129
|
-
* <label class="field">
|
|
130
|
-
* <span>Email</span>
|
|
131
|
-
* <input v-register="rv" />
|
|
132
|
-
* </label>
|
|
133
|
-
* </template>
|
|
134
|
-
* ```
|
|
135
|
-
*
|
|
136
|
-
* The return is a hybrid: `v-register="rv"` hands the directive the
|
|
137
|
-
* parent's own `RegisterValue`, and `rv.path` / `rv.segments` /
|
|
138
|
-
* `rv.formKey` / `rv.innerRef` read directly in script setup with no
|
|
139
|
-
* `.value` unwrap. Reads inside a `computed` or `watchEffect` re-run
|
|
140
|
-
* when the parent rebinds to a different path.
|
|
141
|
-
*
|
|
142
|
-
* Modifiers on the parent's binding apply at the inner control, so
|
|
143
|
-
* `<MyInput v-register.trim="form.register('email')" />` trims there,
|
|
144
|
-
* together with any the wrapper writes on its own `v-register`.
|
|
145
|
-
*
|
|
146
|
-
* When no parent bound, every field reads `undefined`, so reach for
|
|
147
|
-
* `rv?.path`. `v-register="rv"` is still safe. A wrapper that is meant
|
|
148
|
-
* to render both with and without a form declares it with
|
|
149
|
-
* `useRegister({ optional: true })` and branches on `rv?.isBound`.
|
|
150
|
-
*
|
|
151
|
-
* When the wrapper's root IS the input, Vue's attribute fallthrough
|
|
152
|
-
* already binds it and `useRegister` is unnecessary. For a wrapper
|
|
153
|
-
* that binds several fields, use `injectForm<Form>(key?)` and call
|
|
154
|
-
* `ctx.register(...)` directly.
|
|
155
|
-
*/
|
|
156
|
-
declare function useRegister<V = unknown>(options?: UseRegisterOptions): UseRegisterReturn<V> | undefined;
|
|
157
|
-
|
|
158
4
|
type DynamicPathSweep = {
|
|
159
5
|
/**
|
|
160
6
|
* Note that a cache has just stored an entry for `segments` under
|
|
@@ -286,6 +132,13 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
|
|
|
286
132
|
readonly pathSweep: DynamicPathSweep;
|
|
287
133
|
readonly formKey: FormKey;
|
|
288
134
|
readonly form: Ref<F>;
|
|
135
|
+
/**
|
|
136
|
+
* Per-path field records: one per leaf from construction, and one per
|
|
137
|
+
* container, the root (`[]`) included, once a directive binds to it or a
|
|
138
|
+
* write takes leaves away beneath it. A container's own record holds the
|
|
139
|
+
* stamp of that write and the interaction history of the descendants it
|
|
140
|
+
* took; see `stampContainers` and `foldInteractionHistory`.
|
|
141
|
+
*/
|
|
289
142
|
readonly fields: Map<PathKey, FieldRecord>;
|
|
290
143
|
/**
|
|
291
144
|
* The tagged error store: one cell per error-bearing path, each segregating
|
|
@@ -610,6 +463,13 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
|
|
|
610
463
|
*/
|
|
611
464
|
setValueAtPath(path: Path, value: unknown, meta?: WriteMeta): boolean;
|
|
612
465
|
getValueAtPath(path: Path): unknown;
|
|
466
|
+
/**
|
|
467
|
+
* The value `path` started as, for a container's `original`: the baseline
|
|
468
|
+
* at `path`, each array index followed to the slot its element held there.
|
|
469
|
+
* `undefined` for an element added since and for a path the baseline lacks.
|
|
470
|
+
* Frozen, since every read shares it.
|
|
471
|
+
*/
|
|
472
|
+
getOriginalAtPath(path: Path): unknown;
|
|
613
473
|
/**
|
|
614
474
|
* Stable identity for the array element at `path`, the token the array
|
|
615
475
|
* engine maintains across structural mutations. Empty for anything that is
|
|
@@ -641,6 +501,31 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
|
|
|
641
501
|
* empty is skipped.
|
|
642
502
|
*/
|
|
643
503
|
restoreErrorCells(entries: ReadonlyArray<readonly [PathKey, ErrorCell]>): void;
|
|
504
|
+
/**
|
|
505
|
+
* The per-element bookkeeping as it stands, for a history position to put
|
|
506
|
+
* back with its value (see `StructureSnapshot`). The entries are copied,
|
|
507
|
+
* and the records they hold are never mutated in place, so the snapshot
|
|
508
|
+
* stays detached from the live store.
|
|
509
|
+
*/
|
|
510
|
+
captureStructure(): StructureSnapshot;
|
|
511
|
+
/**
|
|
512
|
+
* Put a `captureStructure()` back, before the value it was taken with is
|
|
513
|
+
* restored, so the write that restores it compares each row against its
|
|
514
|
+
* own baseline. Every reading it can change is woken.
|
|
515
|
+
*
|
|
516
|
+
* The field records follow their rows: each of `remaps` moves the records
|
|
517
|
+
* under its array's elements, in order, handing a removed element's
|
|
518
|
+
* history to the array and its ancestors, and each of `returning`, a
|
|
519
|
+
* record of a row the restore brings back, takes its place at its path,
|
|
520
|
+
* its element not yet on the page.
|
|
521
|
+
*/
|
|
522
|
+
restoreStructure(snapshot: StructureSnapshot, remaps: ReadonlyArray<readonly [Path, IndexRemap]>, returning: readonly FieldRecord[]): void;
|
|
523
|
+
/**
|
|
524
|
+
* Hears the records a removal releases while a history module is attached,
|
|
525
|
+
* so an undo can bring a row back with its interaction history. Set by the
|
|
526
|
+
* module, so a form without one never carries it.
|
|
527
|
+
*/
|
|
528
|
+
noteReleased?: ((records: readonly FieldRecord[]) => void) | undefined;
|
|
644
529
|
/**
|
|
645
530
|
* Merged read of the cell at `path`, in schema -> blank -> user order:
|
|
646
531
|
* structural validation first, the synthesized blank entry between, user
|
|
@@ -695,18 +580,19 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
|
|
|
695
580
|
*/
|
|
696
581
|
markInteracted(path: Path): void;
|
|
697
582
|
/**
|
|
698
|
-
* Walk every active-variant leaf under `segments
|
|
699
|
-
* Powers `form.touch(path?)`.
|
|
700
|
-
* `focused` / `blurred`, nor does
|
|
583
|
+
* Walk every active-variant leaf under `segments`, and every container there
|
|
584
|
+
* holding no leaf, and flip `touched: true`. Powers `form.touch(path?)`.
|
|
585
|
+
* Idempotent, and touches neither value nor `focused` / `blurred`, nor does
|
|
586
|
+
* it trigger validation.
|
|
701
587
|
*/
|
|
702
588
|
touchAtPath(segments: Path): void;
|
|
703
589
|
/**
|
|
704
|
-
* Walk every active-variant leaf under `segments
|
|
705
|
-
* interaction ladder (`touched` /
|
|
706
|
-
* as though the user had focused,
|
|
707
|
-
* `form.interact(path?)`. Idempotent, and
|
|
708
|
-
* `blurred` alone. Returns whether
|
|
709
|
-
*
|
|
590
|
+
* Walk every active-variant leaf under `segments`, and every container there
|
|
591
|
+
* holding no leaf, and flip the whole interaction ladder (`touched` /
|
|
592
|
+
* `interacted` / `blurredAfterInteraction`), as though the user had focused,
|
|
593
|
+
* edited and left each one. Powers `form.interact(path?)`. Idempotent, and
|
|
594
|
+
* leaves value / `focused` / `blurred` alone. Returns whether anything
|
|
595
|
+
* resolved, so the caller can skip validation on an unresolved path.
|
|
710
596
|
*/
|
|
711
597
|
interactAtPath(segments: Path): boolean;
|
|
712
598
|
/**
|
|
@@ -729,18 +615,19 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
|
|
|
729
615
|
* insert or removal) against its construction / reset baseline. The
|
|
730
616
|
* structural half of `dirty`: per-element baselines travel with their
|
|
731
617
|
* element across a mutation, so a positional value comparison alone cannot
|
|
732
|
-
* see the shape change.
|
|
618
|
+
* see the shape change. `key` is the canonical key of `path`.
|
|
733
619
|
*/
|
|
734
|
-
hasStructuralChangeUnder(path: Path): boolean;
|
|
620
|
+
hasStructuralChangeUnder(path: Path, key: PathKey): boolean;
|
|
735
621
|
/**
|
|
736
|
-
* Whether a
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
622
|
+
* Whether a path under `path` that held a real baseline value was removed
|
|
623
|
+
* and is still absent: a dropped key, an emptied container, a row a
|
|
624
|
+
* wholesale write took away. The other half of removal-driven `dirty`: the
|
|
625
|
+
* present-leaf walk never visits an absent leaf, and the array tracker
|
|
626
|
+
* follows only array lengths and orders. A path a later write brought back
|
|
627
|
+
* stops counting, and the present-leaf walk judges it instead. `key` is the
|
|
628
|
+
* canonical key of `path`.
|
|
742
629
|
*/
|
|
743
|
-
hasRemovedSubtreeUnder(path: Path): boolean;
|
|
630
|
+
hasRemovedSubtreeUnder(path: Path, key: PathKey): boolean;
|
|
744
631
|
getFieldRecord(path: Path): FieldRecord | undefined;
|
|
745
632
|
/**
|
|
746
633
|
* Cancel every in-flight field-level validation run: clear the timers of
|
|
@@ -849,6 +736,160 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
|
|
|
849
736
|
dispose(): void;
|
|
850
737
|
};
|
|
851
738
|
|
|
739
|
+
/** Options accepted by `injectForm` in place of a bare key string. */
|
|
740
|
+
type InjectFormInput = {
|
|
741
|
+
readonly key?: FormKey;
|
|
742
|
+
/**
|
|
743
|
+
* Set by the Vite transform when this `injectForm` call site sits in
|
|
744
|
+
* a component whose template / script reads the form's reactive
|
|
745
|
+
* state. On the server, this enqueues the form for SSR prefetch and
|
|
746
|
+
* wires `onServerPrefetch` so the descendant awaits the activation
|
|
747
|
+
* promise before its render emits HTML.
|
|
748
|
+
*
|
|
749
|
+
* @internal Transform-emitted. Manual use is the documented escape
|
|
750
|
+
* hatch when the transform can't reach the reference (dynamic
|
|
751
|
+
* property access, untransformed bundlers).
|
|
752
|
+
*/
|
|
753
|
+
readonly __ssrAccessed?: boolean;
|
|
754
|
+
};
|
|
755
|
+
/**
|
|
756
|
+
* Reach an existing form from a descendant component without threading
|
|
757
|
+
* it through props. The counterpart to `useForm`: `useForm` creates and
|
|
758
|
+
* provides, `injectForm` looks up.
|
|
759
|
+
*
|
|
760
|
+
* ```ts
|
|
761
|
+
* // The nearest ancestor's anonymous useForm() call.
|
|
762
|
+
* const form = injectForm<SignupShape>()
|
|
763
|
+
*
|
|
764
|
+
* // A specific form by key, from anywhere in the app.
|
|
765
|
+
* const cart = injectForm<CartShape>('cart')
|
|
766
|
+
* ```
|
|
767
|
+
*
|
|
768
|
+
* A keyed call is a registry lookup, so it is independent of
|
|
769
|
+
* component-tree position. A no-key call takes the closest ambient
|
|
770
|
+
* ancestor and inherits its `formInstanceId`; only an anonymous
|
|
771
|
+
* `useForm()` fills that ambient slot, so a keyed form is reachable
|
|
772
|
+
* only by its key.
|
|
773
|
+
*
|
|
774
|
+
* Returns `null` when nothing matches, so narrow before use:
|
|
775
|
+
*
|
|
776
|
+
* ```ts
|
|
777
|
+
* const form = injectForm<Shape>('signup')
|
|
778
|
+
* if (!form) return
|
|
779
|
+
* form.register('email')
|
|
780
|
+
* ```
|
|
781
|
+
*
|
|
782
|
+
* A keyed miss warns in dev with the registered keys and the call site.
|
|
783
|
+
* Pass the `Form` generic explicitly, since Vue's provide/inject erases
|
|
784
|
+
* generics. The form stays alive for this component's lifetime and is
|
|
785
|
+
* cleaned up once every consumer has unmounted.
|
|
786
|
+
*
|
|
787
|
+
* Components whose nearest `useForm`-calling ancestor is the same one get
|
|
788
|
+
* the same form object for the same form: `injectForm('cart')` in two of
|
|
789
|
+
* its descendants returns one form, with one set of field caches.
|
|
790
|
+
*/
|
|
791
|
+
declare function injectForm<Form extends GenericForm, GetValueFormType extends GenericForm = Form>(input?: FormKey | InjectFormInput): UseFormReturnType<Form, GetValueFormType> | null;
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Return type of `useRegister()`. Hybrid of `RegisterValue<V>` (so
|
|
795
|
+
* `rv.path` / `rv.segments` / `rv.formKey` etc. work directly in
|
|
796
|
+
* script setup) and `Ref<RegisterValue<V> | undefined>` (so Vue's
|
|
797
|
+
* template auto-unwrap surfaces the underlying RV to `v-register`
|
|
798
|
+
* and the directive's path-migration diff sees the real RV across
|
|
799
|
+
* renders).
|
|
800
|
+
*
|
|
801
|
+
* The two surfaces don't clash at the type level: `RegisterValue`
|
|
802
|
+
* doesn't carry a `value` field, and `Ref<T>`'s `value: T` becomes
|
|
803
|
+
* the hybrid's only `.value`. Older code that read `rv.value?.path`
|
|
804
|
+
* keeps working; new code can write `rv.path` directly.
|
|
805
|
+
*/
|
|
806
|
+
type UseRegisterReturn<V = unknown> = RegisterValue<V> & Ref<RegisterValue<V> | undefined> & {
|
|
807
|
+
/**
|
|
808
|
+
* Whether a parent has actually bound this wrapper. `false` until a
|
|
809
|
+
* `RegisterValue` lands, `true` once one has, and reactive in both
|
|
810
|
+
* directions: a parent that binds on a later render flips it, and
|
|
811
|
+
* so does one that stops binding.
|
|
812
|
+
*
|
|
813
|
+
* This is the test a dual-mode wrapper reaches for, and the reason
|
|
814
|
+
* it exists as a field rather than a truthiness check on the
|
|
815
|
+
* return: the composable cannot answer "is this bound" at setup
|
|
816
|
+
* time. A parent is free to bind on a later render
|
|
817
|
+
* (`v-register="ready ? form.register('x') : undefined"`), and
|
|
818
|
+
* `useRegister` has to keep serving that, so it can never return a
|
|
819
|
+
* plain `undefined` for "unbound right now". Script-setup code
|
|
820
|
+
* reading `rv` directly sees the hybrid Proxy, which is an object
|
|
821
|
+
* and therefore always truthy; a template sees the unwrapped ref,
|
|
822
|
+
* which IS `undefined` when unbound. `rv?.isBound` reads the same
|
|
823
|
+
* on both sides (#620).
|
|
824
|
+
*/
|
|
825
|
+
readonly isBound: boolean;
|
|
826
|
+
};
|
|
827
|
+
/**
|
|
828
|
+
* Options for `useRegister`.
|
|
829
|
+
*/
|
|
830
|
+
type UseRegisterOptions = {
|
|
831
|
+
/**
|
|
832
|
+
* Declares that this wrapper is used both with and without a form, so
|
|
833
|
+
* a render with no parent binding is intended rather than a mistake.
|
|
834
|
+
* Silences the unbound diagnostic; nothing else changes.
|
|
835
|
+
*
|
|
836
|
+
* The diagnostic exists because a single-mode wrapper whose parent
|
|
837
|
+
* forgot `v-register` renders a field that looks fine and stores
|
|
838
|
+
* nothing, which is an expensive afternoon. Once a wrapper is
|
|
839
|
+
* deliberately dual-mode, the same signal fires on correct code and
|
|
840
|
+
* tells the author to do something wrong, so this is the one fact
|
|
841
|
+
* that separates the two cases and only the author has it (#620).
|
|
842
|
+
*
|
|
843
|
+
* Pair it with `rv?.isBound` to branch on what actually happened.
|
|
844
|
+
*/
|
|
845
|
+
readonly optional?: boolean;
|
|
846
|
+
};
|
|
847
|
+
/**
|
|
848
|
+
* Re-bind a parent's `v-register` onto an inner native element. Use it
|
|
849
|
+
* inside a component that wraps a single form field whose root is NOT
|
|
850
|
+
* the input itself, such as a labelled row that renders `<label>`
|
|
851
|
+
* around the input.
|
|
852
|
+
*
|
|
853
|
+
* ```vue
|
|
854
|
+
* <!-- Parent -->
|
|
855
|
+
* <MyInput v-register="form.register('email')" />
|
|
856
|
+
*
|
|
857
|
+
* <!-- MyInput.vue -->
|
|
858
|
+
* <script setup lang="ts">
|
|
859
|
+
* import { useRegister } from 'attaform'
|
|
860
|
+
* const rv = useRegister()
|
|
861
|
+
* </script>
|
|
862
|
+
*
|
|
863
|
+
* <template>
|
|
864
|
+
* <label class="field">
|
|
865
|
+
* <span>Email</span>
|
|
866
|
+
* <input v-register="rv" />
|
|
867
|
+
* </label>
|
|
868
|
+
* </template>
|
|
869
|
+
* ```
|
|
870
|
+
*
|
|
871
|
+
* The return is a hybrid: `v-register="rv"` hands the directive the
|
|
872
|
+
* parent's own `RegisterValue`, and `rv.path` / `rv.segments` /
|
|
873
|
+
* `rv.formKey` / `rv.innerRef` read directly in script setup with no
|
|
874
|
+
* `.value` unwrap. Reads inside a `computed` or `watchEffect` re-run
|
|
875
|
+
* when the parent rebinds to a different path.
|
|
876
|
+
*
|
|
877
|
+
* Modifiers on the parent's binding apply at the inner control, so
|
|
878
|
+
* `<MyInput v-register.trim="form.register('email')" />` trims there,
|
|
879
|
+
* together with any the wrapper writes on its own `v-register`.
|
|
880
|
+
*
|
|
881
|
+
* When no parent bound, every field reads `undefined`, so reach for
|
|
882
|
+
* `rv?.path`. `v-register="rv"` is still safe. A wrapper that is meant
|
|
883
|
+
* to render both with and without a form declares it with
|
|
884
|
+
* `useRegister({ optional: true })` and branches on `rv?.isBound`.
|
|
885
|
+
*
|
|
886
|
+
* When the wrapper's root IS the input, Vue's attribute fallthrough
|
|
887
|
+
* already binds it and `useRegister` is unnecessary. For a wrapper
|
|
888
|
+
* that binds several fields, use `injectForm<Form>(key?)` and call
|
|
889
|
+
* `ctx.register(...)` directly.
|
|
890
|
+
*/
|
|
891
|
+
declare function useRegister<V = unknown>(options?: UseRegisterOptions): UseRegisterReturn<V> | undefined;
|
|
892
|
+
|
|
852
893
|
/**
|
|
853
894
|
* Public types for `useWizard`, the multistep-form orchestrator.
|
|
854
895
|
*
|