formstand 0.15.2 → 0.17.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.
- package/CHANGELOG.md +287 -0
- package/dist/{createForm-jlzK2lCG.d.cts → createForm-D0xLZH-Q.d.cts} +15 -5
- package/dist/{createForm-jlzK2lCG.d.ts → createForm-D0xLZH-Q.d.ts} +15 -5
- package/dist/devtools/index.d.cts +1 -1
- package/dist/devtools/index.d.ts +1 -1
- package/dist/index.cjs +127 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -6
- package/dist/index.d.ts +44 -6
- package/dist/index.js +126 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,292 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0 — 2026-09-17
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`form.addErrors(errors)`.** The merge-many sibling of `setError`:
|
|
8
|
+
takes a server response's own runtime-keyed error map as-is and merges
|
|
9
|
+
it into the server channel — no per-key `FieldPath` cast, no
|
|
10
|
+
`setErrors` wholesale replacement. An empty array removes its key,
|
|
11
|
+
like `setError`. The docs' own examples now use it.
|
|
12
|
+
- **`SubmitOptions.onError` and a dev-mode swallowed-failure warning.**
|
|
13
|
+
A throwing submit handler resolves `{kind: "error"}` writing no state
|
|
14
|
+
anywhere, so the fire-and-forget `handleSubmit` form could swallow a
|
|
15
|
+
save failure completely — button re-enables, zero feedback. `onError`
|
|
16
|
+
is the save-failure hook mirroring `onInvalid`, and dev builds warn
|
|
17
|
+
when an unobserved handler failure resolves without one.
|
|
18
|
+
- **`useFieldArray` parity with its siblings.** The return gains `path`,
|
|
19
|
+
`firstError`, and the array-level server channel (`setError` /
|
|
20
|
+
`clearError`) — a "too many rows" verdict from the API is set and
|
|
21
|
+
cleared from the hook that owns the array. Custom `FieldArrayFormApi`
|
|
22
|
+
implementations opt in via the optional `setError`/`clearErrors`
|
|
23
|
+
members (same contract as the optional `validateField`).
|
|
24
|
+
- **`useIsValidating(form, path?)`.** The boolean-only in-flight flag,
|
|
25
|
+
form-level (whole-form pass or any field) or scoped to a subtree —
|
|
26
|
+
previously reachable only through a selector, asymmetric with
|
|
27
|
+
`useIsSubmitting`.
|
|
28
|
+
- **`usePersistForm(form, options)`.** The React lifecycle wrapper
|
|
29
|
+
`persistForm` was missing: owns the mount/dispose effect (StrictMode
|
|
30
|
+
safe), returns a reference-stable handle safe to close over in submit
|
|
31
|
+
handlers, and reads options when the subscription starts (the
|
|
32
|
+
`useForm` locked-options rule).
|
|
33
|
+
- **`createFormHooks` binds the newer hooks.** `use{Name}Values`,
|
|
34
|
+
`use{Name}Fields` (position-typed like the plain `useFields`), and
|
|
35
|
+
`use{Name}IsValidating` join the factory.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **`numberInputProps` keeps the value on a partial entry.** The
|
|
40
|
+
`type="number"` builder wrote `emptyValue` for BOTH empty and invalid
|
|
41
|
+
text, so a transient "-" or "1e" nulled the field mid-keystroke
|
|
42
|
+
(error flash, dirty flip) — diverging from `useNumberInput`'s
|
|
43
|
+
deliberate keep-the-value semantics. Invalid now keeps the stored
|
|
44
|
+
value; empty still clears. (Browsers report `""` for badInput on
|
|
45
|
+
number inputs, so `useNumberInput` remains the binding that preserves
|
|
46
|
+
every keystroke.)
|
|
47
|
+
- **Tuples resolve positionally in the typed paths.** Tuples fell into
|
|
48
|
+
the generic-array arms of `FieldPath` and `UnionValueAt`, which union
|
|
49
|
+
every element — so nested paths under heterogeneous tuples were
|
|
50
|
+
rejected (`"hetero.0.a"` unbindable), out-of-range indices
|
|
51
|
+
(`"coord.5"`) accepted with a lying element type, and a discriminated
|
|
52
|
+
union below a tuple position unbindable through `useVariantField`.
|
|
53
|
+
Both types gained a tuple arm keyed on the literal indices; `"coord.0"`
|
|
54
|
+
spellings the CLI generates are unchanged.
|
|
55
|
+
- **Platform containers are path leaves.** `File`/`Blob` (matched
|
|
56
|
+
structurally, so no DOM lib requirement), `Map`, `Set`, `WeakMap`,
|
|
57
|
+
`WeakSet`, `Promise`, and functions no longer descend as records:
|
|
58
|
+
bogus sub-paths like `"file.name"` or `"meta.size"` used to
|
|
59
|
+
type-check, and a `setValue` through one would have spread the
|
|
60
|
+
instance into a plain object.
|
|
61
|
+
- **Literal dotted keys are no longer offered as paths.** A key named
|
|
62
|
+
`"a.b"` appeared in `FieldPath` but resolved to `never` (the runtime
|
|
63
|
+
walk splits on `"."`), so the offer was the lie; it is now excluded
|
|
64
|
+
where it is offered rather than where it resolves.
|
|
65
|
+
- **`validateField` no longer crashes on prototype-key paths.** The
|
|
66
|
+
schema walk read the ZodObject shape with a bare index, so a
|
|
67
|
+
runtime-built path like `"user.constructor.x"` (server error fields
|
|
68
|
+
mapped onto `validateField`) resolved Object's constructor as a
|
|
69
|
+
"schema" and threw — and the subschema cache kept the poisoned entry.
|
|
70
|
+
Both walks now do own-key reads, like every other walk in the library.
|
|
71
|
+
- **Persist discards version-downgrade drafts instead of adopting the
|
|
72
|
+
wrapper.** A draft written with `version: n` is a `{__v, values}`
|
|
73
|
+
wrapper; if the app later shipped without the `version` option, the
|
|
74
|
+
wrapper's keys evaded the shape guard and the wrapper OBJECT became
|
|
75
|
+
the form's values, then re-persisted. A versioned draft under a
|
|
76
|
+
version-less config is now discarded like any other draft the config
|
|
77
|
+
cannot vouch for.
|
|
78
|
+
- **Debounced validation no longer survives `reset()`.** A pending
|
|
79
|
+
debounce timer fired after `reset()`/`adoptValues()` and re-committed
|
|
80
|
+
errors onto the pristine form. The fire now bails when the form is
|
|
81
|
+
pristine (values is the initialValues reference) or when the path's
|
|
82
|
+
value changed since scheduling; an unrelated field's edit inside the
|
|
83
|
+
window still lets the pending validation run.
|
|
84
|
+
- **Array ops accept a null-valued nullable array.** `arrayPush` on a
|
|
85
|
+
`.nullable()` array field holding `null` warned and silently no-op'd,
|
|
86
|
+
while `undefined` created the array — even though the op item types
|
|
87
|
+
are built on `NonNullable<...>` precisely so nullable arrays accept
|
|
88
|
+
ops. Both empties now start the op from `[]`.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- **Awaited async validation is typed settled.** `validateAsync` and
|
|
93
|
+
`validateFieldAsync` (and `useField`'s `validateAsync`) resolved a
|
|
94
|
+
union carrying a `"pending"` arm that can never occur — the promise IS
|
|
95
|
+
the pending state — forcing every exhaustive `switch` to carry a dead
|
|
96
|
+
case. All three now resolve the `Settled*` result types
|
|
97
|
+
`validateFieldsAsync` always had. A `switch` that handled `"pending"`
|
|
98
|
+
on an awaited result will now fail to compile; delete the dead arm.
|
|
99
|
+
|
|
100
|
+
## formstand-cli 0.16.0 — 2026-09-16
|
|
101
|
+
|
|
102
|
+
### Added
|
|
103
|
+
|
|
104
|
+
- **Union array rows generate.** A discriminated union that is an
|
|
105
|
+
array's row item — the shape the airfield-maps trial hand-wrote — now
|
|
106
|
+
compiles to a `{Stem}Row` component per row instead of a TODO, in
|
|
107
|
+
both layouts: the discriminant and common fields bind with the field
|
|
108
|
+
hook, variant-only fields with the variant hook, all on the
|
|
109
|
+
row-indexed path (`methods.${index}`), with the same conditional
|
|
110
|
+
variant blocks, fragment elision, and hoisted number-props hooks a
|
|
111
|
+
static union gets. Needs formstand 0.16 for the row-indexed variant
|
|
112
|
+
path types. A union or tuple nested as a FIELD inside a row object,
|
|
113
|
+
and a tuple row item, keep their TODOs.
|
|
114
|
+
|
|
115
|
+
## 0.16.0 — 2026-09-16
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- **`useFields`: composite fields.** One control backed by several schema
|
|
120
|
+
paths (a coordinate pair behind a DMS input, a range, a date span)
|
|
121
|
+
binds them in one call: `useFields(form, ["latitude", "longitude"])`
|
|
122
|
+
returns the position-typed tuple of full field bindings plus the
|
|
123
|
+
combined view — `error` merged in path order and deduped (a
|
|
124
|
+
cross-field `superRefine` stamping both paths reads once),
|
|
125
|
+
`firstError`, any-of-them `touched`/`dirty`/`isValidating`, and one
|
|
126
|
+
`onBlur` covering every path. The paths tuple must be render-stable;
|
|
127
|
+
React itself throws its hooks-count error otherwise, at the exact
|
|
128
|
+
render that broke the contract.
|
|
129
|
+
- **`field.firstError`.** The first error message or `undefined`, derived
|
|
130
|
+
on `UseFieldReturn` — the display shorthand every consumer (and every
|
|
131
|
+
generated kit adapter) re-derived by hand as `error?.[0]` with an
|
|
132
|
+
empty-array guard. `{field.firstError}` is now the whole helper line;
|
|
133
|
+
the full `field.error` array is unchanged beside it. The CLI keeps
|
|
134
|
+
emitting its own helper for now so generated code stays runnable on
|
|
135
|
+
older formstand; a future major of the generated adapters can lean on
|
|
136
|
+
this instead.
|
|
137
|
+
|
|
138
|
+
### Fixed
|
|
139
|
+
|
|
140
|
+
- **`useVariantField` accepts row-indexed union paths.** A discriminated
|
|
141
|
+
union that is an array's row item was unbindable: `UnionValueAt`
|
|
142
|
+
resolved paths through `keyof` only, so any array-index segment (a
|
|
143
|
+
template `` `pavements.${number}` `` or a literal `"pavements.0"`)
|
|
144
|
+
collapsed the variant-key set to `never` and the `field` argument
|
|
145
|
+
rejected everything. The type machinery now resolves the array element
|
|
146
|
+
through numeric segments, so a row component binds
|
|
147
|
+
`useVariantField(form, `` `pavements.${index}` ``, "designator")` fully
|
|
148
|
+
typed, in both the plain and the `createFormHooks`-bound flavors.
|
|
149
|
+
Runtime behavior was always correct; only the types refused. Found by
|
|
150
|
+
the airfield-maps trial, whose generated TODO pointed at exactly this
|
|
151
|
+
call.
|
|
152
|
+
|
|
153
|
+
## formstand-cli 0.15.0 — 2026-09-16
|
|
154
|
+
|
|
155
|
+
### Changed
|
|
156
|
+
|
|
157
|
+
- **`--form-prop` is single-file only.** With `--layout module` the
|
|
158
|
+
fields are pre-wired to the module's own singleton, so the `form`
|
|
159
|
+
prop was decorative when it told the truth and silently split state
|
|
160
|
+
when it lied (the dev-mode guard existed to catch the lie). The CLI
|
|
161
|
+
now rejects the combination with the fix in the message: the page
|
|
162
|
+
already owns the module's form by importing the exported instance and
|
|
163
|
+
driving it directly. The wizard stops asking the question under the
|
|
164
|
+
module layout, and the playground's command builder disables the
|
|
165
|
+
checkbox. The programmatic `emitModuleForm` surface keeps the option
|
|
166
|
+
for callers who accept the singleton contract knowingly.
|
|
167
|
+
|
|
168
|
+
### Fixed
|
|
169
|
+
|
|
170
|
+
- **The array-row union TODO gives honest advice.** The single-file
|
|
171
|
+
spelling claimed the union was "not supported"; the module spelling
|
|
172
|
+
recommended a `useVariantField` call that did not typecheck before the
|
|
173
|
+
library fix above. Both now describe the working binding and name the
|
|
174
|
+
formstand 0.16+ floor for row-indexed union paths.
|
|
175
|
+
|
|
176
|
+
## formstand-cli 0.14.0 — 2026-09-10
|
|
177
|
+
|
|
178
|
+
### Added
|
|
179
|
+
|
|
180
|
+
- **`component: "textarea"` in the config `fields` block.** The second
|
|
181
|
+
override flavor: a multi-line control for string fields, the binding
|
|
182
|
+
unchanged. Each kit renders its own control (mui `TextField multiline`,
|
|
183
|
+
mantine/chakra/shadcn `Textarea`, antd `Input.TextArea`, plain a native
|
|
184
|
+
`<textarea>`), and it composes with `span` in one entry, so a
|
|
185
|
+
`span: "full"` cover letter can finally look like one. `optionsProp`
|
|
186
|
+
and enum targets are rejected loudly.
|
|
187
|
+
- **JSON Schema defaults and string formats reach the generated
|
|
188
|
+
validator.** A document `default` now emits `.default(value)` in the
|
|
189
|
+
generated zod schema beside the `initialValues` seed it already fed,
|
|
190
|
+
and `format: email`/`uri`/`uuid` emits `z.email()`/`z.url()`/
|
|
191
|
+
`z.uuid()` while the control stays a text field. The emitted schema is
|
|
192
|
+
the runtime source of truth, so it no longer drops contracts the
|
|
193
|
+
source document carried. Note zod's `.default()` makes the input
|
|
194
|
+
optional by design.
|
|
195
|
+
|
|
196
|
+
### Fixed
|
|
197
|
+
|
|
198
|
+
- **An optional union starts empty and its select can clear it.** An
|
|
199
|
+
optional/nullable discriminated union was seeded with its first
|
|
200
|
+
variant, so an untouched form submitted a variant the user never
|
|
201
|
+
chose, and the discriminant select offered no way back. The union now
|
|
202
|
+
starts `undefined`/`null`, and its select reads the discriminant but
|
|
203
|
+
writes the whole union: the empty choice clears it, a tag starts that
|
|
204
|
+
variant blank. mui's select gains an explicit None item; shadcn's
|
|
205
|
+
Radix select cannot hold an empty item, so there the fixed seed alone
|
|
206
|
+
closes the phantom-submit bug. Required unions are unchanged.
|
|
207
|
+
- **Array add buttons read as the action they perform.** "Add
|
|
208
|
+
references" becomes "Add reference": a conservative singularizer on
|
|
209
|
+
the label's last word, with irregulars passing through unchanged.
|
|
210
|
+
- **A union variant with one field no longer wraps it in a fragment.**
|
|
211
|
+
|
|
212
|
+
## formstand-cli 0.13.0 — 2026-09-03
|
|
213
|
+
|
|
214
|
+
### Added
|
|
215
|
+
|
|
216
|
+
- **JSON Schema and OpenAPI inputs.** A `.json` input file is read as a
|
|
217
|
+
bare JSON Schema (2020-12) or an OpenAPI 3.x document: a third
|
|
218
|
+
front-end into the same IR, following type mode's path, so the CLI
|
|
219
|
+
generates the zod schema beside the component (`--schema-out` places
|
|
220
|
+
it; `--layout module` makes it the module's `schema.ts`). The new
|
|
221
|
+
`--schema` flag selects inside the document: a component schema name,
|
|
222
|
+
or a full `#/...` JSON pointer that can reach an operation's request
|
|
223
|
+
body. What translates: `properties`/`required`, `enum`/`const`,
|
|
224
|
+
`oneOf` string consts, discriminated `oneOf` into `useVariantField`
|
|
225
|
+
unions, `allOf` object merging, `$ref` chains, `prefixItems` tuples,
|
|
226
|
+
`format: date`/`date-time` as date fields, primitive `default`s into
|
|
227
|
+
`initialValues`, `description` as helper text, `title` as the label,
|
|
228
|
+
and every nullable dialect (3.1 type arrays, 3.0 `nullable: true`,
|
|
229
|
+
`oneOf` with a null branch). Everything else degrades to the loud TODO
|
|
230
|
+
fallback; Swagger 2.0 and YAML are refused with conversion hints. The
|
|
231
|
+
wizard understands `.json` inputs too, asking `--schema` in place of
|
|
232
|
+
the zod-or-type question.
|
|
233
|
+
|
|
234
|
+
### Fixed
|
|
235
|
+
|
|
236
|
+
- **A PascalCase schema export no longer collides with its module type
|
|
237
|
+
alias.** `export const MySchema` derives the form name MyForm, whose
|
|
238
|
+
schema type alias was also named MySchema, so the module barrel
|
|
239
|
+
exported two different MySchemas (the value from `schema.ts`, the type
|
|
240
|
+
from `types.ts`) and the generated `index.ts` failed to compile with
|
|
241
|
+
TS2308. The alias now yields the colliding name to the user's value
|
|
242
|
+
and becomes `MySchemaType`, both in `types.ts` and in the
|
|
243
|
+
`--form-prop` component that consumes it.
|
|
244
|
+
|
|
245
|
+
## formstand-cli 0.12.0 — 2026-08-18
|
|
246
|
+
|
|
247
|
+
### Added
|
|
248
|
+
|
|
249
|
+
- **`--wizard`: the flag questions asked one at a time.** Which file, zod
|
|
250
|
+
export or TS type, which kit, layout, section chrome, columns, name, and
|
|
251
|
+
where to write — Enter accepts the default on every question, invalid
|
|
252
|
+
answers re-ask, and an existing output target offers `--force` on the
|
|
253
|
+
spot. The interview ends by printing the composed `formstand-gen`
|
|
254
|
+
command before running it, so the run is reproducible without the wizard
|
|
255
|
+
from then on. Strictly opt-in (never a TTY sniff, and the flag runs
|
|
256
|
+
alone), prompts on stderr so stdout output stays pipeable, answers
|
|
257
|
+
pipeable on stdin, and zero new dependencies: numbered readline lists,
|
|
258
|
+
not a prompts library.
|
|
259
|
+
- **Per-field layout placement: `span` in the config `fields` block.** With
|
|
260
|
+
`--columns 2|3`, `"employment.notes": { span: "full" }` gives a field the
|
|
261
|
+
whole row and `{ span: 2 }` two of three columns; it composes with an
|
|
262
|
+
autocomplete override in the same entry. Each backend emits the span in
|
|
263
|
+
the dialect its section grids already use (mui `Grid` size objects, with
|
|
264
|
+
the legacy `item xs/sm` spelling on mui@5/6; mantine `Grid.Col` span
|
|
265
|
+
objects; antd `Col` `xs`/`sm`; shadcn `md:col-span-N`; chakra a `Box`
|
|
266
|
+
with responsive `gridColumn`), so every spelling still collapses to one
|
|
267
|
+
column on a phone. Plain is the one exception, for partial spans only:
|
|
268
|
+
inline styles cannot carry a media query, so a numeric span widens to the
|
|
269
|
+
full row with a comment saying so at the site. A span with no grid to act
|
|
270
|
+
on (a root-level field, array rows, a container, a 1-column form, or a
|
|
271
|
+
module-layout field below a section's direct children) is a loud
|
|
272
|
+
generation-time error, never a silent no-op.
|
|
273
|
+
|
|
274
|
+
### Fixed
|
|
275
|
+
|
|
276
|
+
- **Generated bodies indent under their real container.** Panel and
|
|
277
|
+
collapsible chrome nests the body's container deeper than the section tag
|
|
278
|
+
(Card > CardContent > Grid), but cells were emitted shallower than the
|
|
279
|
+
container holding them and array row stacks sat beside their wrapper
|
|
280
|
+
cells instead of inside them. Both layouts now shift bodies to the
|
|
281
|
+
chrome's true depth, so generated files survive a host Prettier pass
|
|
282
|
+
without reflowing. Cosmetic only; the demos regenerate with a
|
|
283
|
+
whitespace-only diff.
|
|
284
|
+
- **A union riding a grid cell keeps its vertical rhythm.** Inside an
|
|
285
|
+
object section at `--columns 2|3`, a discriminated union's controls are
|
|
286
|
+
bare siblings in one full-row cell and had lost the spacing the 1-column
|
|
287
|
+
chrome provides; mui and mantine now wrap them in the kit's Stack. antd
|
|
288
|
+
needs no wrapper, since each of its controls is already a vertical Flex.
|
|
289
|
+
|
|
3
290
|
## 0.15.2 — 2026-08-11
|
|
4
291
|
|
|
5
292
|
Identical to 0.15.1, republished under a fresh tag. The v0.15.1 tag was
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { StoreApi } from 'zustand/vanilla';
|
|
3
3
|
|
|
4
|
-
type
|
|
4
|
+
type BlobLike = Readonly<{
|
|
5
|
+
size: number;
|
|
6
|
+
type: string;
|
|
7
|
+
arrayBuffer: () => Promise<unknown>;
|
|
8
|
+
}>;
|
|
9
|
+
type LeafType = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | ((...args: never[]) => unknown) | BlobLike;
|
|
5
10
|
type IsRecord<T> = T extends LeafType ? false : T extends readonly unknown[] ? false : T extends object ? true : false;
|
|
6
11
|
type IsArray<T> = T extends readonly unknown[] ? true : false;
|
|
12
|
+
type IsTuple<T> = T extends readonly unknown[] ? number extends T["length"] ? false : true : false;
|
|
7
13
|
type Prev = [
|
|
8
14
|
never,
|
|
9
15
|
0,
|
|
@@ -37,8 +43,10 @@ type PathDepth = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14
|
|
|
37
43
|
type IsUnion<T, U = T> = [T] extends [never] ? false : T extends unknown ? [U] extends [T] ? false : true : never;
|
|
38
44
|
type NormalizeDepth<D extends number> = number extends D ? DefaultPathDepth : [D] extends [PathDepth] ? IsUnion<D> extends true ? DefaultPathDepth : D : DefaultPathDepth;
|
|
39
45
|
type FieldPath<T, D extends number = DefaultPathDepth> = FieldPathRec<T, NormalizeDepth<D>>;
|
|
40
|
-
type FieldPathRec<T, D extends number> = [D] extends [0] ? never :
|
|
41
|
-
[K in keyof T &
|
|
46
|
+
type FieldPathRec<T, D extends number> = [D] extends [0] ? never : IsTuple<T> extends true ? {
|
|
47
|
+
[K in keyof T & `${number}`]: K | (IsRecord<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : IsArray<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : never);
|
|
48
|
+
}[keyof T & `${number}`] : IsArray<T> extends true ? T extends readonly (infer U)[] ? `${number}` | (IsRecord<NonNullable<U>> extends true ? `${number}.${FieldPathRec<NonNullable<U>, Prev[D]>}` : IsArray<NonNullable<U>> extends true ? `${number}.${FieldPathRec<NonNullable<U>, Prev[D]>}` : never) : never : IsRecord<T> extends true ? {
|
|
49
|
+
[K in keyof T & string]: K extends `${string}.${string}` ? never : K | (IsRecord<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : IsArray<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : never);
|
|
42
50
|
}[keyof T & string] : never;
|
|
43
51
|
type StepValue<T, K extends string> = T extends readonly (infer U)[] ? K extends keyof T ? T[K] : K extends `${number}` ? U : never : T extends object ? K extends keyof T ? T[K] : never : never;
|
|
44
52
|
type FieldValue<T, P extends string> = [T] extends [NonNullable<T>] ? P extends `${infer Head}.${infer Tail}` ? FieldValue<StepValue<T, Head>, Tail> : StepValue<T, P> : FieldValue<NonNullable<T>, P> | undefined;
|
|
@@ -114,6 +122,7 @@ type SubmitHandler<TSchema extends z.ZodType> = (data: z.output<TSchema>) => voi
|
|
|
114
122
|
type InvalidSubmitHandler = (errors: ErrorMap) => void;
|
|
115
123
|
type SubmitOptions = Readonly<{
|
|
116
124
|
force?: boolean;
|
|
125
|
+
onError?: (error: unknown) => void;
|
|
117
126
|
}>;
|
|
118
127
|
type SubmitResult<TOutput> = Readonly<{
|
|
119
128
|
kind: "valid";
|
|
@@ -159,6 +168,7 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
159
168
|
setReValidateMode: (mode: ValidationMode) => void;
|
|
160
169
|
setError: (path: ErrorPath<z.input<TSchema>, D>, errors: string | readonly string[]) => void;
|
|
161
170
|
setErrors: (errors: ErrorMap) => void;
|
|
171
|
+
addErrors: (errors: ErrorMap) => void;
|
|
162
172
|
clearErrors: (path?: ErrorPath<z.input<TSchema>, D>) => void;
|
|
163
173
|
updateState: (updater: (state: FormState<z.input<TSchema>>) => Partial<Omit<FormState<z.input<TSchema>>, "errors">>) => void;
|
|
164
174
|
reset: (nextInitial?: Partial<z.input<TSchema>>, options?: ResetOptions) => void;
|
|
@@ -168,8 +178,8 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
168
178
|
validate: () => ValidationResult<z.output<TSchema>>;
|
|
169
179
|
validateField: (path: ErrorPath<z.input<TSchema>, D>) => FieldValidationResult;
|
|
170
180
|
validateFields: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => FieldsValidationResult;
|
|
171
|
-
validateAsync: () => Promise<
|
|
172
|
-
validateFieldAsync: (path: ErrorPath<z.input<TSchema>, D>) => Promise<
|
|
181
|
+
validateAsync: () => Promise<SettledValidationResult<z.output<TSchema>>>;
|
|
182
|
+
validateFieldAsync: (path: ErrorPath<z.input<TSchema>, D>) => Promise<SettledFieldValidationResult>;
|
|
173
183
|
validateFieldsAsync: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => Promise<SettledFieldsValidationResult>;
|
|
174
184
|
submit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => Promise<SubmitResult<z.output<TSchema>>>;
|
|
175
185
|
arrayPush: <P extends FieldPath<z.input<TSchema>, D>>(path: P, item: ArrayItemOf<NonNullable<FieldValue<z.input<TSchema>, P>>>) => void;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { StoreApi } from 'zustand/vanilla';
|
|
3
3
|
|
|
4
|
-
type
|
|
4
|
+
type BlobLike = Readonly<{
|
|
5
|
+
size: number;
|
|
6
|
+
type: string;
|
|
7
|
+
arrayBuffer: () => Promise<unknown>;
|
|
8
|
+
}>;
|
|
9
|
+
type LeafType = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | ((...args: never[]) => unknown) | BlobLike;
|
|
5
10
|
type IsRecord<T> = T extends LeafType ? false : T extends readonly unknown[] ? false : T extends object ? true : false;
|
|
6
11
|
type IsArray<T> = T extends readonly unknown[] ? true : false;
|
|
12
|
+
type IsTuple<T> = T extends readonly unknown[] ? number extends T["length"] ? false : true : false;
|
|
7
13
|
type Prev = [
|
|
8
14
|
never,
|
|
9
15
|
0,
|
|
@@ -37,8 +43,10 @@ type PathDepth = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14
|
|
|
37
43
|
type IsUnion<T, U = T> = [T] extends [never] ? false : T extends unknown ? [U] extends [T] ? false : true : never;
|
|
38
44
|
type NormalizeDepth<D extends number> = number extends D ? DefaultPathDepth : [D] extends [PathDepth] ? IsUnion<D> extends true ? DefaultPathDepth : D : DefaultPathDepth;
|
|
39
45
|
type FieldPath<T, D extends number = DefaultPathDepth> = FieldPathRec<T, NormalizeDepth<D>>;
|
|
40
|
-
type FieldPathRec<T, D extends number> = [D] extends [0] ? never :
|
|
41
|
-
[K in keyof T &
|
|
46
|
+
type FieldPathRec<T, D extends number> = [D] extends [0] ? never : IsTuple<T> extends true ? {
|
|
47
|
+
[K in keyof T & `${number}`]: K | (IsRecord<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : IsArray<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : never);
|
|
48
|
+
}[keyof T & `${number}`] : IsArray<T> extends true ? T extends readonly (infer U)[] ? `${number}` | (IsRecord<NonNullable<U>> extends true ? `${number}.${FieldPathRec<NonNullable<U>, Prev[D]>}` : IsArray<NonNullable<U>> extends true ? `${number}.${FieldPathRec<NonNullable<U>, Prev[D]>}` : never) : never : IsRecord<T> extends true ? {
|
|
49
|
+
[K in keyof T & string]: K extends `${string}.${string}` ? never : K | (IsRecord<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : IsArray<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : never);
|
|
42
50
|
}[keyof T & string] : never;
|
|
43
51
|
type StepValue<T, K extends string> = T extends readonly (infer U)[] ? K extends keyof T ? T[K] : K extends `${number}` ? U : never : T extends object ? K extends keyof T ? T[K] : never : never;
|
|
44
52
|
type FieldValue<T, P extends string> = [T] extends [NonNullable<T>] ? P extends `${infer Head}.${infer Tail}` ? FieldValue<StepValue<T, Head>, Tail> : StepValue<T, P> : FieldValue<NonNullable<T>, P> | undefined;
|
|
@@ -114,6 +122,7 @@ type SubmitHandler<TSchema extends z.ZodType> = (data: z.output<TSchema>) => voi
|
|
|
114
122
|
type InvalidSubmitHandler = (errors: ErrorMap) => void;
|
|
115
123
|
type SubmitOptions = Readonly<{
|
|
116
124
|
force?: boolean;
|
|
125
|
+
onError?: (error: unknown) => void;
|
|
117
126
|
}>;
|
|
118
127
|
type SubmitResult<TOutput> = Readonly<{
|
|
119
128
|
kind: "valid";
|
|
@@ -159,6 +168,7 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
159
168
|
setReValidateMode: (mode: ValidationMode) => void;
|
|
160
169
|
setError: (path: ErrorPath<z.input<TSchema>, D>, errors: string | readonly string[]) => void;
|
|
161
170
|
setErrors: (errors: ErrorMap) => void;
|
|
171
|
+
addErrors: (errors: ErrorMap) => void;
|
|
162
172
|
clearErrors: (path?: ErrorPath<z.input<TSchema>, D>) => void;
|
|
163
173
|
updateState: (updater: (state: FormState<z.input<TSchema>>) => Partial<Omit<FormState<z.input<TSchema>>, "errors">>) => void;
|
|
164
174
|
reset: (nextInitial?: Partial<z.input<TSchema>>, options?: ResetOptions) => void;
|
|
@@ -168,8 +178,8 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
168
178
|
validate: () => ValidationResult<z.output<TSchema>>;
|
|
169
179
|
validateField: (path: ErrorPath<z.input<TSchema>, D>) => FieldValidationResult;
|
|
170
180
|
validateFields: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => FieldsValidationResult;
|
|
171
|
-
validateAsync: () => Promise<
|
|
172
|
-
validateFieldAsync: (path: ErrorPath<z.input<TSchema>, D>) => Promise<
|
|
181
|
+
validateAsync: () => Promise<SettledValidationResult<z.output<TSchema>>>;
|
|
182
|
+
validateFieldAsync: (path: ErrorPath<z.input<TSchema>, D>) => Promise<SettledFieldValidationResult>;
|
|
173
183
|
validateFieldsAsync: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => Promise<SettledFieldsValidationResult>;
|
|
174
184
|
submit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => Promise<SubmitResult<z.output<TSchema>>>;
|
|
175
185
|
arrayPush: <P extends FieldPath<z.input<TSchema>, D>>(path: P, item: ArrayItemOf<NonNullable<FieldValue<z.input<TSchema>, P>>>) => void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { F as Form } from '../createForm-
|
|
3
|
+
import { F as Form } from '../createForm-D0xLZH-Q.cjs';
|
|
4
4
|
import 'zustand/vanilla';
|
|
5
5
|
|
|
6
6
|
type DevtoolsPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
package/dist/devtools/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { F as Form } from '../createForm-
|
|
3
|
+
import { F as Form } from '../createForm-D0xLZH-Q.js';
|
|
4
4
|
import 'zustand/vanilla';
|
|
5
5
|
|
|
6
6
|
type DevtoolsPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
|