formstand 0.21.0 → 0.22.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 +259 -0
- package/README.md +2 -1
- package/dist/{chunk-OB23TUKY.js → chunk-BMXIZEI4.js} +14 -4
- package/dist/chunk-BMXIZEI4.js.map +1 -0
- package/dist/{chunk-MQ6262S3.js → chunk-V3PDMTPP.js} +7 -3
- package/dist/chunk-V3PDMTPP.js.map +1 -0
- package/dist/{createForm-B73p1TgL.d.cts → createForm-BJum0RBu.d.cts} +10 -7
- package/dist/{createForm-B73p1TgL.d.ts → createForm-BJum0RBu.d.ts} +10 -7
- package/dist/devtools/index.cjs +8 -2
- package/dist/devtools/index.cjs.map +1 -1
- package/dist/devtools/index.d.cts +7 -5
- package/dist/devtools/index.d.ts +7 -5
- package/dist/devtools/index.js +2 -3
- package/dist/devtools/index.js.map +1 -1
- package/dist/index.cjs +187 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -15
- package/dist/index.d.ts +18 -15
- package/dist/index.js +176 -79
- package/dist/index.js.map +1 -1
- package/dist/testing/index.cjs +4 -1
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-MQ6262S3.js.map +0 -1
- package/dist/chunk-OB23TUKY.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,264 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.0 — 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`useFieldArray` no longer remounts the row you are typing in when rows
|
|
8
|
+
are identical primitives.** With `["", ""]`, a keystroke into row 0 made
|
|
9
|
+
the value-matching pass hand row 0's id to row 1 and mint a new id for
|
|
10
|
+
row 0, so React remounted the focused input. A same-index identity pass
|
|
11
|
+
now runs first, so an edited row keeps its id and duplicates keep theirs.
|
|
12
|
+
- **`debounceMs` no longer suppresses blur validation on an untouched
|
|
13
|
+
form.** The reset-protection guard read "values is initialValues" as
|
|
14
|
+
pristine, which is also every form nobody has typed in yet, so a
|
|
15
|
+
required field tabbed through showed nothing. A rebase is now detected
|
|
16
|
+
as the values reference moving onto initialValues since scheduling,
|
|
17
|
+
which only reset and adoptValues produce.
|
|
18
|
+
- **`useFormSteps.next()` and `goTo()` no longer apply a stale step** when
|
|
19
|
+
the user presses `back()` while the async step gate is in flight; the
|
|
20
|
+
user's newer position wins and `goTo` returns it.
|
|
21
|
+
- **`useMaskedInput` follows the text binding's clearing rule.** Clearing a
|
|
22
|
+
required string writes `""` (clean against a `""` initial, the author's
|
|
23
|
+
message) instead of `undefined`; optional, nullable, and non-string
|
|
24
|
+
fields still clear to `emptyValue`.
|
|
25
|
+
- **`clearable` and `emptyValue` read the schema through wrappers and
|
|
26
|
+
unions.** A leaf under an optional object and a field inside a
|
|
27
|
+
discriminated-union variant (`useVariantField`) now report their own
|
|
28
|
+
schema's blank instead of the runtime heuristic.
|
|
29
|
+
- **`persistForm` refuses a `null` top-level draft** (an unversioned
|
|
30
|
+
`"null"` or a versioned wrapper holding `values: null`) instead of
|
|
31
|
+
adopting it as the whole values object. Nested nulls stay legal.
|
|
32
|
+
- **`handleSubmit` and the action bridge never reject when `process` is
|
|
33
|
+
undefined.** The dev-only "nothing observed the error" warning read
|
|
34
|
+
`process.env.NODE_ENV` unguarded, so in a runtime without a `process`
|
|
35
|
+
global the never-rejects contract broke exactly on the failure path. One
|
|
36
|
+
shared `isDevBuild()` now guards all three sites.
|
|
37
|
+
- **Array ops reject tuple paths at the type level.** `arrayPush("pair", x)`
|
|
38
|
+
on a `z.tuple` no longer compiles; it could only ever grow a fixed-arity
|
|
39
|
+
value the schema rejects.
|
|
40
|
+
- **`flattenIssues` and the plural error writes scale linearly.** The
|
|
41
|
+
issue grouping rebuilt its accumulator with a spread per issue, so
|
|
42
|
+
`validate()` on 3000 empty rows spent 1.3 s grouping what zod parsed in
|
|
43
|
+
7 ms; it now groups once through a Map. The same per-entry spread sat in
|
|
44
|
+
`validateFieldsAsync`'s in-flight flags and in `addErrors`, which both
|
|
45
|
+
now run one pass over the map.
|
|
46
|
+
- **Error keys named after `Object.prototype` members no longer crash or
|
|
47
|
+
leak.** `setError("constructor", "x")`, `addErrors({ constructor: [...] })`,
|
|
48
|
+
and `validate()` on a schema with a `constructor` field threw inside the
|
|
49
|
+
store write (a bare `prev[k]` read `Object` as the previous error list);
|
|
50
|
+
`getFieldState("toString").touched` and the `useField` / `useFieldArray`
|
|
51
|
+
selectors read prototype functions as field state. Every runtime-keyed
|
|
52
|
+
read of `errors`, `touched`, and `isValidating` is now an own-key read.
|
|
53
|
+
- **An async field verdict survives an unrelated field's change.** A
|
|
54
|
+
subschema `validateFieldAsync("username")` dropped its result, and
|
|
55
|
+
cleared its in-flight flag, the moment any other value changed while it
|
|
56
|
+
was in flight; it now compares its own slot, so "username taken" lands
|
|
57
|
+
after you move on to `email`. Full-form scopes (an ancestor refine may
|
|
58
|
+
read any field) keep the whole-values compare.
|
|
59
|
+
- **`validateFieldsAsync` takes the subschema fast path.** It always parsed
|
|
60
|
+
the whole schema while the sync `validateFields` parsed only the
|
|
61
|
+
requested slots, so `useFormSteps` paid for every other step's fields on
|
|
62
|
+
each `next()` and an async refine on another step's field fired every
|
|
63
|
+
time. The async plural now mirrors the sync routing (all paths extract:
|
|
64
|
+
parse those slots, in parallel; any path bails: one full parse) and
|
|
65
|
+
judges staleness per path.
|
|
66
|
+
- **`updateState` values patches release server errors.** A patch that
|
|
67
|
+
carries `values` now runs the same release as `setValues`: entries whose
|
|
68
|
+
value slice changed go, untouched siblings stay. An explicit
|
|
69
|
+
`serverErrors` in the same patch is taken as given.
|
|
70
|
+
- **The devtools gates fail closed without a readable `NODE_ENV`.**
|
|
71
|
+
`FormstandDevtools` rendered the full state panel and the zustand
|
|
72
|
+
devtools middleware stayed enabled in a CDN/ESM, Deno, or worker
|
|
73
|
+
production build, because "no `process`" read as a dev build. Both now
|
|
74
|
+
gate on `isDevToolsBuild()`, true only when `process.env.NODE_ENV` is a
|
|
75
|
+
readable string other than `"production"`; the console warnings keep the
|
|
76
|
+
fail-open `isDevBuild()`.
|
|
77
|
+
- **Two invalid Dates compare equal, and -0 equals 0, in dirty tracking.**
|
|
78
|
+
A field holding `new Date(NaN)` against an invalid initial read as
|
|
79
|
+
permanently dirty, and `-0` over an initial `0` was dirty while the input
|
|
80
|
+
rendered `"0"` either way.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- **Array ops and `useFieldArray` take array paths only.** `arrayRemove`,
|
|
85
|
+
`arrayMove`, and `arraySwap` accepted any `FieldPath`, so
|
|
86
|
+
`form.arrayRemove("name", 0)` on a `z.string()` compiled (and warned at
|
|
87
|
+
runtime), while `useFieldArray(form, "pair")` on a `z.tuple` bound with
|
|
88
|
+
the tuple's element union and `useFieldArray(form, "name")` bound with a
|
|
89
|
+
`never` item. Every array op and the hook's typed overload now constrain
|
|
90
|
+
the path to `ArrayFieldPath<Values, D>`: variable-length arrays, optional
|
|
91
|
+
and nullable included, tuples and scalars excluded, template paths like
|
|
92
|
+
`` `rows.${number}.tags` `` kept. Type-level only: the code it rejects was
|
|
93
|
+
already a runtime no-op or a schema failure. `ArrayFieldPath`,
|
|
94
|
+
`ArrayItemOf` (the one item type, tuple arm included, now shared by the
|
|
95
|
+
core and the hook), and the form-level `ArrayPathsOf<F>` are exported
|
|
96
|
+
beside `PathsOf`.
|
|
97
|
+
- **`PathsAccepting` and `SelectValueAt` are actually exported**, as the
|
|
98
|
+
bound-components entry below says they are; `BoundUseFields` and
|
|
99
|
+
`BoundUseVariantField` join the other `Bound*` types.
|
|
100
|
+
- **`FormstandDevtoolsProps` carries the form's depth.**
|
|
101
|
+
`<FormstandDevtools form={createForm(s, { pathDepth: 12 })} />` was a
|
|
102
|
+
type error against the depth-less `Form<TSchema>` prop; the props now
|
|
103
|
+
take `D extends PathDepth = DefaultPathDepth`. Widening only.
|
|
104
|
+
- **Bound components type their `path` by value.** `TextField` accepts
|
|
105
|
+
string-valued paths, `NumberField` number-valued, `DateField` Date-valued,
|
|
106
|
+
`CheckboxField` boolean-valued, `SelectField` string-valued (optional and
|
|
107
|
+
nullable fields qualify everywhere). `<TextField path="age" />` on a
|
|
108
|
+
`z.number()` used to compile and store `"42"`; it is a compile error now,
|
|
109
|
+
as `textInputProps(useField(form, "age"))` always was. Type-level only:
|
|
110
|
+
the code it rejects was already a runtime bug. `PathsAccepting<F, V>`
|
|
111
|
+
and `SelectValueAt<F, P>` are exported beside `PathsOf`.
|
|
112
|
+
- **`SelectField` checks its options against the field.** An option whose
|
|
113
|
+
`value` is not in the path's `z.enum` is a compile error at the option;
|
|
114
|
+
a free `z.string()` path still takes any string. `SelectFieldProps` is
|
|
115
|
+
now generic over the form and the path (`SelectFieldProps<F, P>`) rather
|
|
116
|
+
than the option value type.
|
|
117
|
+
- **`fillFields` types each value from its key**, the contract `fillField`
|
|
118
|
+
already had; a wrong-typed fixture value is a compile error.
|
|
119
|
+
- **`persistForm`'s `apply: "edit"`** is the new spelling for "load the
|
|
120
|
+
draft as an edit, dirty against the initial values". `"restore"` keeps
|
|
121
|
+
working as an alias; it collided with `form.restore(snapshot)` and with
|
|
122
|
+
the former `restore()` method.
|
|
123
|
+
|
|
124
|
+
### Docs
|
|
125
|
+
|
|
126
|
+
- Every em dash is gone from the documentation pages and the playground
|
|
127
|
+
sources (comments included, since they show in the View code panel).
|
|
128
|
+
- API pages match the shipped surface: `useFields`, `useIsValidating`,
|
|
129
|
+
and `usePersistForm` rows; `firstError` on `UseFieldReturn`; `path`,
|
|
130
|
+
`firstError`, `setError`, `clearError` on `UseFieldArrayReturn`;
|
|
131
|
+
`addErrors`, `SubmitOptions`, and the `devtools` option on the API
|
|
132
|
+
index; the missing exported types and the `formstand/testing` subpath
|
|
133
|
+
on the utilities page.
|
|
134
|
+
- The ssr.md provider snippet destructures `Provider` (there is no
|
|
135
|
+
`FormProvider`); components.md documents why a reusable field component
|
|
136
|
+
must be generic over its value type; multi-step.md notes that
|
|
137
|
+
`useFormSteps` memoizes on the steps array's identity.
|
|
138
|
+
- CLI pages: a `--tests` flag row, the current version floors in the CLI
|
|
139
|
+
README, three input modes everywhere, and a working link for the UI-kit
|
|
140
|
+
matrix.
|
|
141
|
+
- Playground: "Open in StackBlitz" seeds a project that installs and boots
|
|
142
|
+
(vite and plugin-react agree, react-dom is always a dependency), with a
|
|
143
|
+
test pinning the seed to examples/package.json. Array-row errors are
|
|
144
|
+
styled, textareas follow the theme, buttons show a keyboard focus ring,
|
|
145
|
+
row inputs carry aria-labels, and the nested-arrays tab says how to
|
|
146
|
+
start.
|
|
147
|
+
- Docs site: reference tables stop stacking at phone width, and the
|
|
148
|
+
dark-mode "Get started" button meets AA contrast.
|
|
149
|
+
|
|
150
|
+
## formstand-cli 0.23.0 — 2026-10-19
|
|
151
|
+
|
|
152
|
+
### Fixed
|
|
153
|
+
|
|
154
|
+
- `--watch` regenerated from a stale module: jiti's runtime module cache
|
|
155
|
+
survives a fresh `createJiti` per run, so a field added while watching
|
|
156
|
+
never reached the output. The loader now disables that cache.
|
|
157
|
+
- The config file's `tests` block (`runners`, `renderWrapper`,
|
|
158
|
+
`playwright.baseURL` / `route`) was read and then dropped as an unknown
|
|
159
|
+
key, so it never reached the spec emitter. It is now validated with the
|
|
160
|
+
same loud errors as the other keys and applied: `runners` is the
|
|
161
|
+
`--tests` default, kit specs render through the configured wrapper, and
|
|
162
|
+
the Playwright spec is emitted when a base URL is configured.
|
|
163
|
+
- Generated specs no longer fail on their own valid sample. A number
|
|
164
|
+
bounded on both sides by an exclusive window narrower than one unit
|
|
165
|
+
(`z.number().gt(0).lt(1)`) sampled the bound itself (0); it now samples
|
|
166
|
+
the midpoint (0.5), and the bound probe fills distinct bad and good
|
|
167
|
+
values. A schema with a degraded leaf (a `z.record`, a callable, an
|
|
168
|
+
unsupported kind) emits its round-trip case as `it.skip` with a comment
|
|
169
|
+
naming the path, since the stand-in's text sample can never parse.
|
|
170
|
+
- The module layout's field file for a degraded leaf (for example a
|
|
171
|
+
`z.record`) did not compile: the bound hook inferred the real value type
|
|
172
|
+
and spread it into a text control. The binding is now pinned to the text
|
|
173
|
+
control's value type, with the TODO kept above it.
|
|
174
|
+
- OpenAPI 3.0 discriminated unions: a branch tag spelled as a single-value
|
|
175
|
+
`enum` (3.0 has no `const`) or supplied only through
|
|
176
|
+
`discriminator.mapping` degraded to a string. All three spellings now
|
|
177
|
+
walk to the same union, and a branch that still lacks a tag is named in
|
|
178
|
+
the TODO.
|
|
179
|
+
- Every walker TODO (unsupported zod kinds such as `z.record`, callable
|
|
180
|
+
types, non-string enums, recursive schemas) is now mirrored on stderr as
|
|
181
|
+
`warning: field "<path>": <todo>`; only the depth, truncation, dotted-key,
|
|
182
|
+
and dropped-default degradations were mirrored before.
|
|
183
|
+
- Type mode: a function-typed member was dropped silently (the generated
|
|
184
|
+
schema lost a key of the type it mirrors); it now becomes the
|
|
185
|
+
string-plus-TODO placeholder. An index-signature-only object type
|
|
186
|
+
(`Record<string, number>`) walked to an empty section and an empty
|
|
187
|
+
`z.object({})`; it now degrades to a TODO like `z.record` does.
|
|
188
|
+
- Discriminant-only union variants (a branch with no fields beyond the
|
|
189
|
+
tag) emitted an empty `{x.value === "pickup" && (<></>)}` block in every
|
|
190
|
+
kit and in the module layout. The block is replaced by a one-line comment
|
|
191
|
+
naming the variant.
|
|
192
|
+
- Generated probe-case titles were duplicated when two sections shared a
|
|
193
|
+
field label; a repeated label now carries its path in the title
|
|
194
|
+
(`Email (billing.email) rejects ...`).
|
|
195
|
+
- Plain single-file output did not compile for any degraded leaf (a
|
|
196
|
+
`z.record`, `z.bigint`, `z.lazy`, `z.any`, a non-string literal or enum,
|
|
197
|
+
`z.map`): the stand-in was bound as `<TextField path="meta" />`, and the
|
|
198
|
+
library's bound components now type `path` by value, so a non-text leaf is
|
|
199
|
+
a compile error. The stand-in now renders an in-file `PlaceholderField`
|
|
200
|
+
bound through `useField` pinned to the text control's value type, the TODO
|
|
201
|
+
kept above it. Degraded leaves inside union variants are pinned at their
|
|
202
|
+
hoisted binding in every single-file backend (the kits failed there too).
|
|
203
|
+
- `z.coerce.number()` / `.date()` / `.boolean()` emitted a typed control
|
|
204
|
+
whose path the input type (`unknown`) refuses, in both layouts. Coerced
|
|
205
|
+
schemas now degrade to a TODO stand-in naming the reason.
|
|
206
|
+
- The module layout bound a degraded leaf inside an array row (an object
|
|
207
|
+
row, a tuple row, a union row) and in a union section without the field
|
|
208
|
+
file's pin, so those files did not compile. Every row and variant binding
|
|
209
|
+
now carries the same cast, and the section file imports the type. A
|
|
210
|
+
variant whose only field is degraded also gets the fragment its TODO
|
|
211
|
+
comment needs (two JSX nodes under `&& (...)` did not parse).
|
|
212
|
+
- `z.enum(Color)` / `z.nativeEnum(Color)` over a TypeScript enum did not
|
|
213
|
+
compile in the plain single-file layout: `SelectField` checks each option
|
|
214
|
+
literal against the field's type, which is the enum itself. The walker
|
|
215
|
+
marks such enums and plain renders them through an in-file
|
|
216
|
+
`EnumSelectField` on the raw `selectProps` builder, options unchanged.
|
|
217
|
+
- The generated spec's "filled with valid values" round trip ran live over
|
|
218
|
+
checks the walker never modeled (`.regex()`, `.startsWith()`,
|
|
219
|
+
`.multipleOf()`, a `.refine()`, `z.date().min()`, formats such as
|
|
220
|
+
`z.ipv4()`), and failed on its own sample. The walker now records the
|
|
221
|
+
unmodeled check kinds per path, the case is emitted as `it.skip` with the
|
|
222
|
+
paths and reasons in a comment (as it already was for degraded leaves),
|
|
223
|
+
and `--tests` mirrors each on stderr. The emitted schema is unchanged.
|
|
224
|
+
The method-form `.email()` / `.url()` / `.uuid()` are now modeled like
|
|
225
|
+
`z.email()` (blank-invalid, a format probe, the format in the schema).
|
|
226
|
+
- Security: schema text could break out of a `//` comment into generated
|
|
227
|
+
TypeScript. A JSON Schema `$ref` or `type` string carrying a line
|
|
228
|
+
terminator (`\n`, `\r`, U+2028, U+2029) reached `// TODO: ...` lines in
|
|
229
|
+
the emitted schema and module field files, so the text after the
|
|
230
|
+
terminator became code (a `$ref` of `"#/x\n injected: z.string(), //"`
|
|
231
|
+
minted a real property). Every comment and every stderr warning built
|
|
232
|
+
from schema text now collapses line terminators to a space, so a hostile
|
|
233
|
+
document can neither add code nor forge a `wrote ...` line.
|
|
234
|
+
- An enum option or union tag containing `"` or `\` broke the generated
|
|
235
|
+
spec's valid-values literal (interpolated raw where everything else was
|
|
236
|
+
JSON-quoted).
|
|
237
|
+
- Module layout file names that differed only in case (`email` / `eMail`
|
|
238
|
+
to `EmailField.tsx` / `EMailField.tsx`) were one file on Windows and
|
|
239
|
+
macOS, the second overwriting the first. Component names are now deduped
|
|
240
|
+
case-insensitively (`EMailField2`).
|
|
241
|
+
- The `--template` with `--layout module` error no longer promises module
|
|
242
|
+
support; it says templates apply to the single-file layout only.
|
|
243
|
+
|
|
244
|
+
### Changed
|
|
245
|
+
|
|
246
|
+
- Optional and nullable objects start empty. `billing: address.optional()`
|
|
247
|
+
used to seed `{ street: "", zip: "" }`, so a blank form carried a billing
|
|
248
|
+
object nobody filled and could not submit; it now seeds `undefined`
|
|
249
|
+
(nullable: `null`), like an optional union. This is a behavior change for
|
|
250
|
+
generated forms: an untouched optional section passes validation on
|
|
251
|
+
submit. Paths under it stay addressable and materialize on the first
|
|
252
|
+
write, the `initialValues` cast is no longer forced by their members, and
|
|
253
|
+
the generated spec's blank-submit case no longer lists them.
|
|
254
|
+
- The module layout's form body is emitted in schema order, interleaving
|
|
255
|
+
root fields and sections as declared, instead of all root fields first and
|
|
256
|
+
every section after.
|
|
257
|
+
- The generated-file header now reads
|
|
258
|
+
`// Generated by formstand-cli: edit freely, this file is yours.`; it and
|
|
259
|
+
every other emitted string (TODO text, spec titles, help, wizard labels,
|
|
260
|
+
error messages) no longer contain an em dash.
|
|
261
|
+
|
|
3
262
|
## 0.21.0 — 2026-09-23
|
|
4
263
|
|
|
5
264
|
### Fixed
|
package/README.md
CHANGED
|
@@ -83,7 +83,8 @@ npx formstand-gen src/profileSchema.ts --ui mui --sections panel --columns 2 --o
|
|
|
83
83
|
|
|
84
84
|
You get typed `initialValues`, a bound control per field, sections for nested objects,
|
|
85
85
|
`useFieldArray` blocks with add and remove buttons, and a wired submit. Point it at a
|
|
86
|
-
TypeScript type instead (`--type Profile`)
|
|
86
|
+
TypeScript type instead (`--type Profile`), or at a JSON Schema or OpenAPI document
|
|
87
|
+
(`api.json --schema Profile`), and it generates the zod schema too. `--ui`
|
|
87
88
|
targets plain formstand components, Material UI 5 through 9, shadcn/ui, Chakra 3,
|
|
88
89
|
Mantine 9, or Ant Design 6.
|
|
89
90
|
|
|
@@ -9,8 +9,10 @@ var isPlainObject = (v) => {
|
|
|
9
9
|
};
|
|
10
10
|
var valuesEqual = (a, b) => {
|
|
11
11
|
if (Object.is(a, b)) return true;
|
|
12
|
+
if (typeof a === "number" && typeof b === "number") return a === b;
|
|
12
13
|
if (a instanceof Date && b instanceof Date) {
|
|
13
|
-
|
|
14
|
+
const [ta, tb] = [a.getTime(), b.getTime()];
|
|
15
|
+
return ta === tb || Number.isNaN(ta) && Number.isNaN(tb);
|
|
14
16
|
}
|
|
15
17
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
16
18
|
return a.length === b.length && a.every((v, i) => valuesEqual(v, b[i]));
|
|
@@ -146,6 +148,14 @@ function useFormSelectorShallow(form, selector) {
|
|
|
146
148
|
return useStore(form.store, useShallow(selector));
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
// src/core/env.ts
|
|
152
|
+
var isDevBuild = () => typeof process === "undefined" || process.env?.["NODE_ENV"] !== "production";
|
|
153
|
+
var isDevToolsBuild = () => {
|
|
154
|
+
if (typeof process === "undefined") return false;
|
|
155
|
+
const env = process.env?.["NODE_ENV"];
|
|
156
|
+
return typeof env === "string" && env !== "production";
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export { getAtPath, hasOwnSlot, isDevBuild, isDevToolsBuild, isFieldDirty, isPlainObject, parsePath, setAtPath, slotAtPath, useFormSelector, useFormSelectorShallow, valuesEqual };
|
|
160
|
+
//# sourceMappingURL=chunk-BMXIZEI4.js.map
|
|
161
|
+
//# sourceMappingURL=chunk-BMXIZEI4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/equality.ts","../src/core/path.ts","../src/react/useFormSelector.ts","../src/core/env.ts"],"names":[],"mappings":"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","file":"chunk-BMXIZEI4.js","sourcesContent":["export const isPlainObject = (v: unknown): v is Record<string, unknown> => {\n if (typeof v !== \"object\" || v === null) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n};\n\n// Structural equality used to decide whether a field still matches its initial\n// value. Recurses into arrays and plain objects; Dates compare by timestamp\n// (re-picking the same date must not leave the field permanently dirty);\n// other exotic objects fall back to Object.is so a real change is never\n// mistaken for \"unchanged\".\nexport const valuesEqual = (a: unknown, b: unknown): boolean => {\n if (Object.is(a, b)) return true;\n // Numbers compare with `===` semantics on top of Object.is: Object.is\n // alone keeps NaN equal to itself (wanted) but tells -0 from 0, and a\n // number input renders both as \"0\", so a field holding -0 against an\n // initial 0 read as permanently dirty with nothing visible to undo.\n if (typeof a === \"number\" && typeof b === \"number\") return a === b;\n if (a instanceof Date && b instanceof Date) {\n // Two invalid Dates are equal: their timestamps are both NaN, which\n // `===` denies, so a cleared or half-typed date against an invalid\n // initial read as dirty forever.\n const [ta, tb] = [a.getTime(), b.getTime()];\n return ta === tb || (Number.isNaN(ta) && Number.isNaN(tb));\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => valuesEqual(v, b[i]));\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n // hasOwn matters: equal counts don't imply equal key SETS. Without it,\n // { realname: undefined } vs { nickname: \"Ann\" } passes (1 === 1, and\n // a.realname === missing b.realname === undefined) without b's real key\n // ever being inspected — dirty tracking would read a real change as\n // clean. With hasOwn plus the length check the key sets must match, so\n // the compare is also symmetric.\n return (\n aKeys.length === bKeys.length &&\n aKeys.every((k) => Object.hasOwn(b, k) && valuesEqual(a[k], b[k]))\n );\n }\n return false;\n};\n\n// The deep compare for a container-valued field runs inside store selectors\n// and watch subscriptions, i.e. on every store change — cache verdicts per\n// value object, keyed by the initial-slice reference each was computed for.\n// A small list (not a single slot) so distinct fields/forms sharing one value\n// object (a module-level default, a shared sentinel array) coexist instead of\n// evicting each other; bounded so no entry grows without limit. Form state is\n// immutable, so identical references always mean an identical verdict, and\n// the WeakMap releases everything when the value object dies.\ntype DirtyVerdict = Readonly<{ initial: unknown; dirty: boolean }>;\nconst dirtyCache = new WeakMap<object, readonly DirtyVerdict[]>();\nconst MAX_VERDICTS = 4;\n\n// The single definition of dirtiness: the current value slice differs\n// structurally from its initial-values slice. Derived (rather than tracked by\n// writers) so reads are accurate at any depth.\nexport const isFieldDirty = (value: unknown, initialValue: unknown): boolean => {\n if (Object.is(value, initialValue)) return false;\n if (typeof value !== \"object\" || value === null) {\n return !valuesEqual(value, initialValue);\n }\n const cached = dirtyCache.get(value) ?? [];\n const hit = cached.find((v) => Object.is(v.initial, initialValue));\n if (hit !== undefined) return hit.dirty;\n const dirty = !valuesEqual(value, initialValue);\n dirtyCache.set(\n value,\n [{ initial: initialValue, dirty }, ...cached].slice(0, MAX_VERDICTS),\n );\n return dirty;\n};\n","import { isPlainObject } from \"./equality\";\n\nexport type PathSegment = string | number;\n\n// Segment keys containing \".\" are not addressable (paths are split on dots);\n// use nested objects or index-free keys instead.\n\n// Writes above this index are refused — a typo'd path like \"items.4294967296\"\n// must not allocate a multi-gigabyte array.\nconst MAX_ARRAY_INDEX = 100_000;\n\nconst isIndexSegment = (s: string): boolean => {\n const n = Number(s);\n return Number.isInteger(n) && n >= 0 && String(n) === s;\n};\n\n// Hot path: every field subscription re-resolves its path on every store\n// notification (getAtPath in selectors), so parsing is memoized — a form's\n// paths are a small fixed set. Bounded as an LRU over Map insertion order:\n// a hit re-inserts its key (moving it to the back) and an overflow evicts\n// the front, so an app whose LIVE path set exceeds the cap keeps its hot\n// paths cached instead of re-parsing every one of them after each reset\n// (the old wholesale clear). Eviction only costs re-parsing, never\n// correctness. Same delete-then-set recency idiom as useFieldArray's\n// per-path id cache.\nconst PARSE_CACHE_MAX = 4096;\nconst parseCache = new Map<string, readonly PathSegment[]>();\n\nexport const parsePath = (path: string): readonly PathSegment[] => {\n const cached = parseCache.get(path);\n if (cached !== undefined) {\n parseCache.delete(path);\n parseCache.set(path, cached);\n return cached;\n }\n // Frozen because the array is now SHARED across every caller (and cached):\n // parsePath is a public export, and pre-cache each call returned a private\n // array that was safe to mutate. Freezing enforces the `readonly` type at\n // runtime so an external `.sort()`/`.reverse()`/`.push()` throws instead of\n // silently corrupting the cache for every later lookup of this path.\n const segments = Object.freeze(\n path === \"\"\n ? []\n : path.split(\".\").map((s) => (isIndexSegment(s) ? Number(s) : s)),\n );\n if (parseCache.size >= PARSE_CACHE_MAX) {\n const oldest = parseCache.keys().next().value;\n if (oldest !== undefined) parseCache.delete(oldest);\n }\n parseCache.set(path, segments);\n return segments;\n};\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\n// The existing container decides how a segment is interpreted: arrays take\n// numeric segments as indices, records take any segment as a string key (so a\n// z.record keyed \"0\" reads/writes the record key, not an array index). Only\n// when the container is absent does the segment type pick what gets created.\nconst readSegment = (obj: unknown, segment: PathSegment): unknown => {\n if (obj == null) return undefined;\n if (Array.isArray(obj)) {\n return typeof segment === \"number\" ? obj[segment] : undefined;\n }\n // Own properties only: a runtime-built path like \"lookup.constructor\" on a\n // z.record must read as absent, not leak Object.prototype members as field\n // values.\n return isPlainRecord(obj) && Object.hasOwn(obj, String(segment))\n ? obj[String(segment)]\n : undefined;\n};\n\nexport const getAtPath = (obj: unknown, path: string): unknown =>\n parsePath(path).reduce<unknown>(readSegment, obj);\n\nexport type SlotAtPath =\n | Readonly<{ exists: true; value: unknown }>\n | Readonly<{ exists: false }>;\n\n// Resolve `path` while checking that every segment is read from a real\n// container: an in-range array index, or a key on a plain record (containers\n// decide, mirroring readSegment). A segment into an absent or non-container\n// value addresses no slot at all — a full-form parse would key that error at\n// the missing ANCESTOR, not here, so field-scoped validation must skip it\n// rather than parse the leaf's subschema against undefined (which would\n// fabricate a spurious \"required\" error the full pass never produces). Note\n// this checks the CONTAINER, not the value: a leaf that is undefined at the\n// FINAL step through a real record is still an addressable (legitimately\n// empty) slot. Returns the resolved value so callers don't walk twice.\nexport const slotAtPath = (obj: unknown, path: string): SlotAtPath => {\n const walked = parsePath(path).reduce<\n Readonly<{ ok: boolean; current: unknown }>\n >(\n (acc, segment) => {\n const container = acc.current;\n const addressable =\n typeof segment === \"number\"\n ? Array.isArray(container)\n ? segment < container.length\n : isPlainRecord(container)\n : isPlainRecord(container);\n return !acc.ok || !addressable\n ? { ok: false, current: undefined }\n : { ok: true, current: readSegment(container, segment) };\n },\n { ok: true, current: obj },\n );\n return walked.ok ? { exists: true, value: walked.current } : { exists: false };\n};\n\n// Whether the path addresses an OWN slot: every step reads a real\n// container's own key / in-range array index, the final segment included.\n// This is stricter than slotAtPath, which treats a MISSING final key on a\n// real record as an addressable (legitimately empty) slot — here it reads\n// false. The consumer is setValue's identity guard: writing `undefined`\n// over an ABSENT key is NOT a no-op (it creates the key, a real state\n// change — key-count dirtiness, persisted shape), while writing it over a\n// key that exists holding undefined is.\nexport const hasOwnSlot = (obj: unknown, path: string): boolean =>\n parsePath(path).reduce<Readonly<{ ok: boolean; current: unknown }>>(\n (acc, segment) => {\n const container = acc.current;\n const owns = Array.isArray(container)\n ? typeof segment === \"number\" && segment < container.length\n : isPlainRecord(container) && Object.hasOwn(container, String(segment));\n return !acc.ok || !owns\n ? { ok: false, current: undefined }\n : { ok: true, current: readSegment(container, segment) };\n },\n { ok: true, current: obj },\n ).ok;\n\nconst writeIntoArray = (\n arr: readonly unknown[],\n index: number,\n rest: readonly PathSegment[],\n value: unknown,\n): readonly unknown[] => {\n const length = Math.max(arr.length, index + 1);\n return Array.from({ length }, (_, i) =>\n i === index ? writeSegments(arr[i], rest, value) : arr[i],\n );\n};\n\nconst refusesHugeIndex = (index: number): boolean => {\n if (index > MAX_ARRAY_INDEX) {\n console.warn(\n `[formstand] refusing to write array index ${index} (max ${MAX_ARRAY_INDEX}); value left unchanged.`,\n );\n return true;\n }\n return false;\n};\n\nconst writeSegments = (\n obj: unknown,\n segments: readonly PathSegment[],\n value: unknown,\n): unknown => {\n const [head, ...rest] = segments;\n if (head === undefined) return value;\n\n if (Array.isArray(obj)) {\n if (typeof head !== \"number\") {\n console.warn(\n `[formstand] cannot write string key \"${head}\" into an array; value left unchanged.`,\n );\n return obj;\n }\n if (refusesHugeIndex(head)) return obj;\n return writeIntoArray(obj, head, rest, value);\n }\n\n // Records take any segment as a string key (mirroring readSegment), so a\n // z.record keyed by a large numeric id is writable — the array-index cap\n // only applies where an array actually exists or would be created.\n if (isPlainRecord(obj)) {\n // Spreading is only safe for PLAIN objects: spreading a Date/Map/Set or\n // class instance keeps own enumerable props but drops the prototype (and\n // for Map/Set, every entry) — the original would be silently destroyed.\n // Refuse, like the other unwritable shapes above.\n if (!isPlainObject(obj)) {\n console.warn(\n `[formstand] cannot write key \"${String(head)}\" through a non-plain object (Date/Map/Set/class instance); value left unchanged.`,\n );\n return obj;\n }\n const key = String(head);\n return { ...obj, [key]: writeSegments(obj[key], rest, value) };\n }\n\n // Absent (or non-container) value: create a container from the segment type.\n if (typeof head === \"number\") {\n if (refusesHugeIndex(head)) return obj;\n return writeIntoArray([], head, rest, value);\n }\n return { [head]: writeSegments(undefined, rest, value) };\n};\n\nexport const setAtPath = <T>(obj: T, path: string, value: unknown): T =>\n writeSegments(obj, parsePath(path), value) as T;\n","import type { z } from \"zod\";\nimport type { StoreApi } from \"zustand/vanilla\";\nimport { useStore } from \"zustand/react\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport type { Form } from \"../core/createForm\";\nimport type { DefaultPathDepth, PathDepth } from \"../core/fieldPath\";\nimport type { FormState } from \"../core/types\";\n\ntype ReadonlyStore<T> = Pick<\n StoreApi<T>,\n \"getState\" | \"getInitialState\" | \"subscribe\"\n>;\n\nexport type FormStateApi = Readonly<{\n store: ReadonlyStore<FormState<unknown>>;\n}>;\n\nexport function useFormSelector<\n TSchema extends z.ZodType,\n U,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n selector: (state: FormState<z.input<TSchema>>) => U,\n): U;\n// The `schema?: undefined` brand forces Form<TSchema> (which has a real\n// `schema` property) to bind only the typed overload above. Without\n// it, Form<TSchema> would structurally satisfy FormStateApi too and a\n// bad selector could silently widen to FormState<unknown>.\nexport function useFormSelector<U>(\n form: FormStateApi & { readonly schema?: undefined },\n selector: (state: FormState<unknown>) => U,\n): U;\nexport function useFormSelector<U>(\n form: FormStateApi,\n selector: (state: FormState<unknown>) => U,\n): U {\n return useStore(form.store, selector);\n}\n\nexport function useFormSelectorShallow<\n TSchema extends z.ZodType,\n U,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n selector: (state: FormState<z.input<TSchema>>) => U,\n): U;\nexport function useFormSelectorShallow<U>(\n form: FormStateApi & { readonly schema?: undefined },\n selector: (state: FormState<unknown>) => U,\n): U;\nexport function useFormSelectorShallow<U>(\n form: FormStateApi,\n selector: (state: FormState<unknown>) => U,\n): U {\n return useStore(form.store, useShallow(selector));\n}\n","// The dev-build test the library consults before emitting a console\n// warning. Bundlers that define NODE_ENV fold it to a constant and drop the\n// dev-only branches; the `typeof process` guard keeps the SAME expression\n// from throwing in a runtime that defines no `process` at all (a CDN/ESM\n// consumer, a test runner without the define). That last case matters most\n// on the submit path: handleSubmit promises never to reject, and the\n// warning that fires only when a save failed was the one line that could\n// break the promise exactly on the failure path. It FAILS OPEN on purpose:\n// an unknowable environment gets the warnings, which cost nothing but a\n// console line.\nexport const isDevBuild = (): boolean =>\n typeof process === \"undefined\" ||\n process.env?.[\"NODE_ENV\"] !== \"production\";\n\n// The gate for the two sites that EXPOSE form state: the FormstandDevtools\n// panel (renders every value and error into the page) and the zustand\n// devtools middleware (streams every write to the Redux extension). These\n// must FAIL CLOSED: isDevBuild's \"no process means dev\" reading let a\n// CDN/ESM, Deno, or worker production build mount the panel and keep the\n// middleware live for any end user with the extension installed. Only a\n// readable NODE_ENV that is a string other than \"production\" counts as\n// development; no process, no env, or an unset NODE_ENV all read as\n// production here.\nexport const isDevToolsBuild = (): boolean => {\n if (typeof process === \"undefined\") return false;\n const env = process.env?.[\"NODE_ENV\"];\n return typeof env === \"string\" && env !== \"production\";\n};\n"]}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// src/core/maps.ts
|
|
2
|
+
var entryAt = (map, key) => Object.hasOwn(map, key) ? map[key] : void 0;
|
|
3
|
+
var flagAt = (map, key) => Object.hasOwn(map, key) && map[key] === true;
|
|
4
|
+
|
|
1
5
|
// src/core/mode.ts
|
|
2
6
|
var shouldValidateOn = (trigger, mode, reValidateMode, submitAttempted, touched = false) => {
|
|
3
7
|
const effective = submitAttempted ? reValidateMode : mode;
|
|
@@ -15,6 +19,6 @@ var shouldValidateOn = (trigger, mode, reValidateMode, submitAttempted, touched
|
|
|
15
19
|
}
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
export { shouldValidateOn };
|
|
19
|
-
//# sourceMappingURL=chunk-
|
|
20
|
-
//# sourceMappingURL=chunk-
|
|
22
|
+
export { entryAt, flagAt, shouldValidateOn };
|
|
23
|
+
//# sourceMappingURL=chunk-V3PDMTPP.js.map
|
|
24
|
+
//# sourceMappingURL=chunk-V3PDMTPP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/maps.ts","../src/core/mode.ts"],"names":[],"mappings":";AAYO,IAAM,OAAA,GAAU,CACrB,GAAA,EACA,GAAA,KACmB,MAAA,CAAO,MAAA,CAAO,GAAA,EAAK,GAAG,CAAA,GAAI,GAAA,CAAI,GAAG,CAAA,GAAI;AAEnD,IAAM,MAAA,GAAS,CAAC,GAAA,EAAc,GAAA,KACnC,MAAA,CAAO,MAAA,CAAO,GAAA,EAAK,GAAG,CAAA,IAAK,GAAA,CAAI,GAAG,CAAA,KAAM;;;ACPnC,IAAM,mBAAmB,CAC9B,OAAA,EACA,MACA,cAAA,EACA,eAAA,EACA,UAAmB,KAAA,KACP;AACZ,EAAA,MAAM,SAAA,GAAY,kBAAkB,cAAA,GAAiB,IAAA;AACrD,EAAA,QAAQ,SAAA;AAAW,IACjB,KAAK,UAAA;AACH,MAAA,OAAO,OAAA,KAAY,QAAA;AAAA,IACrB,KAAK,QAAA;AACH,MAAA,OAAO,OAAA,KAAY,MAAA;AAAA,IACrB,KAAK,UAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT,KAAK,WAAA;AACH,MAAA,OAAO,YAAY,MAAA,IAAU,OAAA;AAAA,IAC/B,KAAK,KAAA;AACH,MAAA,OAAO,IAAA;AAAA;AAEb","file":"chunk-V3PDMTPP.js","sourcesContent":["import type { BoolMap } from \"./types\";\n\n// Own-key reads for the path-keyed maps (errors, touched, isValidating).\n// Their keys are user-controlled strings: a schema may declare a field\n// named `constructor`, a server response may name `toString`, and\n// JSON.parse yields own `__proto__` keys. A bare `map[key]` on a plain\n// object walks the prototype chain for those, so an EMPTY error map read\n// `Object` as the error list at \"constructor\" (and `stringArraysEqual`\n// then threw inside setState on `.every`), while `touched[\"toString\"]` was\n// a truthy function. Every read of these maps by a runtime key goes\n// through here; writes are already safe (computed keys and\n// Object.fromEntries define own properties).\nexport const entryAt = <V>(\n map: Readonly<Record<string, V>>,\n key: string,\n): V | undefined => (Object.hasOwn(map, key) ? map[key] : undefined);\n\nexport const flagAt = (map: BoolMap, key: string): boolean =>\n Object.hasOwn(map, key) && map[key] === true;\n","export type ValidationMode =\n | \"onChange\"\n | \"onBlur\"\n | \"onSubmit\"\n // Validate on blur always; on change only once the field has been touched.\n | \"onTouched\"\n // Validate on both change and blur.\n | \"all\";\n\nexport type ValidationTrigger = \"change\" | \"blur\";\n\nexport const shouldValidateOn = (\n trigger: ValidationTrigger,\n mode: ValidationMode,\n reValidateMode: ValidationMode,\n submitAttempted: boolean,\n touched: boolean = false,\n): boolean => {\n const effective = submitAttempted ? reValidateMode : mode;\n switch (effective) {\n case \"onChange\":\n return trigger === \"change\";\n case \"onBlur\":\n return trigger === \"blur\";\n case \"onSubmit\":\n return false;\n case \"onTouched\":\n return trigger === \"blur\" || touched;\n case \"all\":\n return true;\n }\n};\n"]}
|
|
@@ -50,6 +50,10 @@ type FieldPathRec<T, D extends number> = [D] extends [0] ? never : IsTuple<T> ex
|
|
|
50
50
|
}[keyof T & string] : never;
|
|
51
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;
|
|
52
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;
|
|
53
|
+
type ArrayItemOf<T> = [NonNullable<T>] extends [readonly (infer U)[]] ? number extends NonNullable<T>["length"] ? U : never : never;
|
|
54
|
+
type IsListValue<V> = [NonNullable<V>] extends [readonly unknown[]] ? number extends NonNullable<V>["length"] ? true : false : false;
|
|
55
|
+
type ArrayFieldPath<T, D extends number = DefaultPathDepth> = ArrayPathsWhere<T, FieldPath<T, D>>;
|
|
56
|
+
type ArrayPathsWhere<T, P extends string> = P extends unknown ? IsListValue<FieldValue<T, P>> extends true ? P : never : never;
|
|
53
57
|
|
|
54
58
|
type ValidationMode = "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
|
|
55
59
|
type ValidationTrigger = "change" | "blur";
|
|
@@ -137,7 +141,6 @@ type SubmitResult<TOutput> = Readonly<{
|
|
|
137
141
|
kind: "error";
|
|
138
142
|
error: unknown;
|
|
139
143
|
}>;
|
|
140
|
-
type ArrayItemOf<T> = T extends readonly (infer U)[] ? U : never;
|
|
141
144
|
type ErrorPath<TValues, D extends PathDepth = DefaultPathDepth> = FieldPath<TValues, D> | "";
|
|
142
145
|
type ResetOptions = Readonly<{
|
|
143
146
|
keepErrors?: boolean;
|
|
@@ -183,11 +186,11 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
183
186
|
validateFieldAsync: (path: ErrorPath<z.input<TSchema>, D>) => Promise<SettledFieldValidationResult>;
|
|
184
187
|
validateFieldsAsync: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => Promise<SettledFieldsValidationResult>;
|
|
185
188
|
submit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => Promise<SubmitResult<z.output<TSchema>>>;
|
|
186
|
-
arrayPush: <P extends
|
|
187
|
-
arrayRemove: (path:
|
|
188
|
-
arrayInsert: <P extends
|
|
189
|
-
arrayMove: (path:
|
|
190
|
-
arraySwap: (path:
|
|
189
|
+
arrayPush: <P extends ArrayFieldPath<z.input<TSchema>, D>>(path: P, item: ArrayItemOf<FieldValue<z.input<TSchema>, P>>) => void;
|
|
190
|
+
arrayRemove: (path: ArrayFieldPath<z.input<TSchema>, D>, index: number) => void;
|
|
191
|
+
arrayInsert: <P extends ArrayFieldPath<z.input<TSchema>, D>>(path: P, index: number, item: ArrayItemOf<FieldValue<z.input<TSchema>, P>>) => void;
|
|
192
|
+
arrayMove: (path: ArrayFieldPath<z.input<TSchema>, D>, from: number, to: number) => void;
|
|
193
|
+
arraySwap: (path: ArrayFieldPath<z.input<TSchema>, D>, a: number, b: number) => void;
|
|
191
194
|
handleSubmit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => (event?: {
|
|
192
195
|
preventDefault: () => void;
|
|
193
196
|
}) => Promise<SubmitResult<z.output<TSchema>>>;
|
|
@@ -198,4 +201,4 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
198
201
|
}>;
|
|
199
202
|
declare const createForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(schema: TSchema, options: CreateFormOptions<TSchema, D>) => Form<TSchema, D>;
|
|
200
203
|
|
|
201
|
-
export { type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type
|
|
204
|
+
export { type ArrayFieldPath as A, type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type FormState as a, type FieldValidationResult as b, type FieldPath as c, type FieldValue as d, type ArrayItemOf as e, type FieldSnapshot as f, type FieldsValidationResult as g, type ResetOptions as h, type SettledFieldsValidationResult as i, type SettledValidationResult as j, type SubmitHandler as k, type SubmitOptions as l, type SubmitResult as m, type ValidationResult as n, type ValidationTrigger as o, createForm as p, emptyValueForSchema as q, flattenIssues as r, isAsyncRequiredError as s, isClearableSchema as t, shouldValidateOn as u, validateAsync as v, validateSync as w };
|
|
@@ -50,6 +50,10 @@ type FieldPathRec<T, D extends number> = [D] extends [0] ? never : IsTuple<T> ex
|
|
|
50
50
|
}[keyof T & string] : never;
|
|
51
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;
|
|
52
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;
|
|
53
|
+
type ArrayItemOf<T> = [NonNullable<T>] extends [readonly (infer U)[]] ? number extends NonNullable<T>["length"] ? U : never : never;
|
|
54
|
+
type IsListValue<V> = [NonNullable<V>] extends [readonly unknown[]] ? number extends NonNullable<V>["length"] ? true : false : false;
|
|
55
|
+
type ArrayFieldPath<T, D extends number = DefaultPathDepth> = ArrayPathsWhere<T, FieldPath<T, D>>;
|
|
56
|
+
type ArrayPathsWhere<T, P extends string> = P extends unknown ? IsListValue<FieldValue<T, P>> extends true ? P : never : never;
|
|
53
57
|
|
|
54
58
|
type ValidationMode = "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
|
|
55
59
|
type ValidationTrigger = "change" | "blur";
|
|
@@ -137,7 +141,6 @@ type SubmitResult<TOutput> = Readonly<{
|
|
|
137
141
|
kind: "error";
|
|
138
142
|
error: unknown;
|
|
139
143
|
}>;
|
|
140
|
-
type ArrayItemOf<T> = T extends readonly (infer U)[] ? U : never;
|
|
141
144
|
type ErrorPath<TValues, D extends PathDepth = DefaultPathDepth> = FieldPath<TValues, D> | "";
|
|
142
145
|
type ResetOptions = Readonly<{
|
|
143
146
|
keepErrors?: boolean;
|
|
@@ -183,11 +186,11 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
183
186
|
validateFieldAsync: (path: ErrorPath<z.input<TSchema>, D>) => Promise<SettledFieldValidationResult>;
|
|
184
187
|
validateFieldsAsync: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => Promise<SettledFieldsValidationResult>;
|
|
185
188
|
submit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => Promise<SubmitResult<z.output<TSchema>>>;
|
|
186
|
-
arrayPush: <P extends
|
|
187
|
-
arrayRemove: (path:
|
|
188
|
-
arrayInsert: <P extends
|
|
189
|
-
arrayMove: (path:
|
|
190
|
-
arraySwap: (path:
|
|
189
|
+
arrayPush: <P extends ArrayFieldPath<z.input<TSchema>, D>>(path: P, item: ArrayItemOf<FieldValue<z.input<TSchema>, P>>) => void;
|
|
190
|
+
arrayRemove: (path: ArrayFieldPath<z.input<TSchema>, D>, index: number) => void;
|
|
191
|
+
arrayInsert: <P extends ArrayFieldPath<z.input<TSchema>, D>>(path: P, index: number, item: ArrayItemOf<FieldValue<z.input<TSchema>, P>>) => void;
|
|
192
|
+
arrayMove: (path: ArrayFieldPath<z.input<TSchema>, D>, from: number, to: number) => void;
|
|
193
|
+
arraySwap: (path: ArrayFieldPath<z.input<TSchema>, D>, a: number, b: number) => void;
|
|
191
194
|
handleSubmit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => (event?: {
|
|
192
195
|
preventDefault: () => void;
|
|
193
196
|
}) => Promise<SubmitResult<z.output<TSchema>>>;
|
|
@@ -198,4 +201,4 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
198
201
|
}>;
|
|
199
202
|
declare const createForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(schema: TSchema, options: CreateFormOptions<TSchema, D>) => Form<TSchema, D>;
|
|
200
203
|
|
|
201
|
-
export { type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type
|
|
204
|
+
export { type ArrayFieldPath as A, type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type FormState as a, type FieldValidationResult as b, type FieldPath as c, type FieldValue as d, type ArrayItemOf as e, type FieldSnapshot as f, type FieldsValidationResult as g, type ResetOptions as h, type SettledFieldsValidationResult as i, type SettledValidationResult as j, type SubmitHandler as k, type SubmitOptions as l, type SubmitResult as m, type ValidationResult as n, type ValidationTrigger as o, createForm as p, emptyValueForSchema as q, flattenIssues as r, isAsyncRequiredError as s, isClearableSchema as t, shouldValidateOn as u, validateAsync as v, validateSync as w };
|
package/dist/devtools/index.cjs
CHANGED
|
@@ -7,6 +7,13 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
7
7
|
|
|
8
8
|
// src/devtools/FormstandDevtools.tsx
|
|
9
9
|
|
|
10
|
+
// src/core/env.ts
|
|
11
|
+
var isDevToolsBuild = () => {
|
|
12
|
+
if (typeof process === "undefined") return false;
|
|
13
|
+
const env = process.env?.["NODE_ENV"];
|
|
14
|
+
return typeof env === "string" && env !== "production";
|
|
15
|
+
};
|
|
16
|
+
|
|
10
17
|
// src/core/path.ts
|
|
11
18
|
var isIndexSegment = (s) => {
|
|
12
19
|
const n = Number(s);
|
|
@@ -156,7 +163,6 @@ var styles = {
|
|
|
156
163
|
cursor: "pointer"
|
|
157
164
|
}
|
|
158
165
|
};
|
|
159
|
-
var isDevBuild = () => typeof process === "undefined" || process.env?.["NODE_ENV"] !== "production";
|
|
160
166
|
var preview = (value) => {
|
|
161
167
|
if (value === void 0) return "undefined";
|
|
162
168
|
if (value instanceof Date) return value.toISOString();
|
|
@@ -311,7 +317,7 @@ var DevtoolsPanel = ({
|
|
|
311
317
|
}
|
|
312
318
|
) });
|
|
313
319
|
};
|
|
314
|
-
var FormstandDevtools = (props) =>
|
|
320
|
+
var FormstandDevtools = (props) => isDevToolsBuild() ? /* @__PURE__ */ jsxRuntime.jsx(DevtoolsPanel, { ...props }) : null;
|
|
315
321
|
|
|
316
322
|
exports.FormstandDevtools = FormstandDevtools;
|
|
317
323
|
exports.leafPaths = leafPaths;
|