formstand 0.11.0 → 0.13.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 +294 -0
- package/README.md +4 -0
- package/dist/index.cjs +61 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +61 -13
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,299 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0 — 2026-07-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`useFormValues(form)`** — whole-values subscription in render: returns
|
|
8
|
+
the form's current values object, typed `z.input<TSchema>` (structural
|
|
9
|
+
`FormStateApi` forms read `unknown`). Sugar for
|
|
10
|
+
`useFormSelector(form, (s) => s.values)`, reference-compared — the store
|
|
11
|
+
replaces the values object immutably, so the hook re-renders exactly when
|
|
12
|
+
some value changes and never on touched/error/submit churn. The motivating
|
|
13
|
+
case: form values driving derived rendering (a map re-rendering from live
|
|
14
|
+
coordinates). The playground's three whole-values subscriptions (Autosave,
|
|
15
|
+
the MUI checkout's review step, the Schema builder's live preview) now use
|
|
16
|
+
it, and the MUI adapter's number binding now goes through the exported
|
|
17
|
+
`useNumberInput` (as `useMuiNumberFieldProps`) — the same shape
|
|
18
|
+
`formstand-gen --ui mui` emits.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Explicit type arguments on a schema-typed form now fail with a readable
|
|
23
|
+
error.** `useField<Values>(form, "email")` (any explicit generics on a
|
|
24
|
+
`Form` — the value type infers from the path, so they were always an
|
|
25
|
+
error) used to resolve onto the schema-less overload and die with a
|
|
26
|
+
baffling `"... not assignable to type 'undefined'"` brand mismatch. A
|
|
27
|
+
trap-guard overload now intercepts that shape and blames the path
|
|
28
|
+
argument with instructions: `Argument of type '"email"' is not assignable
|
|
29
|
+
to parameter of type '"Remove the explicit type argument: a schema-typed
|
|
30
|
+
form infers the value type from the path"'`. Same guard on
|
|
31
|
+
`useFieldArray` (`"... infers the item type from the path"`) and
|
|
32
|
+
`useVariantField` (`"... infers the variant value from the union path
|
|
33
|
+
and field"`). Inferred calls, structural-form explicit generics,
|
|
34
|
+
selector paths, and explicit instantiation expressions
|
|
35
|
+
(`typeof useField<Schema, "name">`,
|
|
36
|
+
`typeof useVariantField<Schema, "payment", "cardNumber">`) are all
|
|
37
|
+
unaffected — the guard's type parameter defaults to an unexported
|
|
38
|
+
sentinel no inferred call can produce.
|
|
39
|
+
- **`setValue` with the identical value is now a full no-op.** Writing the
|
|
40
|
+
value already at the path used to rebuild the values object anyway, so
|
|
41
|
+
`useFormValues` re-rendered and `watchValues`/`onValuesChange` fired on
|
|
42
|
+
writes that changed nothing — and a two-way sync that echoes values back
|
|
43
|
+
(each side writing what it just received) recursed until the stack
|
|
44
|
+
overflowed. `form.setValue` now identity-guards (`Object.is`) the write:
|
|
45
|
+
the state reference is unchanged, no dirty/error recompute runs, no
|
|
46
|
+
subscribers fire, and `useField().setValue` skips its revalidation gate
|
|
47
|
+
too (nothing changed, so the current error state already reflects the
|
|
48
|
+
value). The no-op is deliberately FULL: a no-op write also no longer
|
|
49
|
+
releases server errors on the path or clears array-op records — those
|
|
50
|
+
side effects belonged to an actual value change. One carve-out: writing
|
|
51
|
+
`undefined` over an ABSENT key is still a real write (it creates the
|
|
52
|
+
key — `{}` vs `{ nickname: undefined }` differ in key count, which
|
|
53
|
+
dirtiness and the persisted shape observe). `setValues`, `adoptValues`,
|
|
54
|
+
and `updateState` replace wholesale by design and are unchanged.
|
|
55
|
+
|
|
56
|
+
## formstand-cli 0.10.0 — 2026-07-29
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- **Per-field component overrides — `fields` in `formstand.config.ts`,
|
|
61
|
+
with an `autocomplete` flavor and a generated options prop.** The
|
|
62
|
+
motivating case (straight from the dogfood project): an ICAO airport
|
|
63
|
+
field is a plain `z.string()` whose suggestion list is DATA — an airport
|
|
64
|
+
list, not a zod enum — so the CLI emitted a bare text input and the page
|
|
65
|
+
hand-swapped it for the kit's autocomplete on every regeneration. Now
|
|
66
|
+
the config names the field and the component:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export default defineConfig({
|
|
70
|
+
fields: {
|
|
71
|
+
"icao": { component: "autocomplete", optionsProp: true },
|
|
72
|
+
"crew.*.role": { component: "autocomplete", optionsProp: true },
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Paths are exact dot paths against the walked schema, `*` matching one
|
|
78
|
+
array-index segment. The semantic is **free text with suggestions** (the
|
|
79
|
+
one autocomplete meaning every kit shares): the field stays a string the
|
|
80
|
+
user can type freely; strict select-from-list remains the enum/Select
|
|
81
|
+
path. Overrides apply to **string** fields (`optionsProp: true` is
|
|
82
|
+
required — there is no other options source; the generated component
|
|
83
|
+
gains `{camelPath}Options: readonly string[]`, e.g. `"crew.*.role"` →
|
|
84
|
+
`crewRoleOptions`, collision-suffixed like every derived identifier) and
|
|
85
|
+
**enum** fields (the select upgrades to a combobox seeded with the enum
|
|
86
|
+
values baked in; `optionsProp: true` REPLACES them with the prop).
|
|
87
|
+
Everything else is a loud generation-time ERROR, exit 1, nothing
|
|
88
|
+
written: an unknown path (with near-miss suggestions), a non-string/enum
|
|
89
|
+
target, a string without `optionsProp`, a walker-degraded or
|
|
90
|
+
over-FieldPath-budget field, an override under a row-nested object with
|
|
91
|
+
`--layout module` (that layout degrades those to a TODO). Per-kit
|
|
92
|
+
bindings, each proven against the real installed `.d.ts` by the matrix:
|
|
93
|
+
MUI `Autocomplete` `freeSolo` bound through the INPUT value
|
|
94
|
+
(`inputValue`/`onInputChange`, so typing and picking both update the
|
|
95
|
+
form); Mantine `Autocomplete` (natively this semantic — value-shaped
|
|
96
|
+
`onChange`, `data` accepts readonly arrays); antd `AutoComplete`
|
|
97
|
+
(value-shaped like its Select, `status` for errors, `id={path}` for the
|
|
98
|
+
focus-helper fallback — no `name` exists); plain, shadcn, and Chakra ride
|
|
99
|
+
an `Input` + native `<datalist>` (shadcn's combobox is a copy-paste
|
|
100
|
+
recipe, not an installable component; Chakra 3's `Combobox` is Ark's
|
|
101
|
+
collection-API compound component — disproportionate for generated
|
|
102
|
+
free-text suggestions, so the DOM-shaped datalist is the honest
|
|
103
|
+
binding). Both layouts thread the options prop root → section → row/field
|
|
104
|
+
files (nested-array extractions included); composes with `--live` and
|
|
105
|
+
`--form-prop` (the props join the same props type); an overridden field
|
|
106
|
+
wins over a custom `--template`'s per-kind renderer (templates own
|
|
107
|
+
per-KIND rendering; an override opts the field out of its kind). Each
|
|
108
|
+
override site carries a short generated comment naming the options
|
|
109
|
+
source. `component: "autocomplete"` is the only flavor this cycle; the
|
|
110
|
+
config shape is a discriminated union so future flavors (textarea,
|
|
111
|
+
slider, ...) slot in. The playground's flight-search demo now generates
|
|
112
|
+
its origin/destination ICAO fields through the override (the suggestion
|
|
113
|
+
list passed by the page), making the dogfood project's exact page real
|
|
114
|
+
CLI output.
|
|
115
|
+
- **`--live` — live/no-submit scaffold mode** (config key `live`). For
|
|
116
|
+
forms nothing ever submits (a map or preview re-rendering from every
|
|
117
|
+
value change): the submit scaffold — `handleSubmit`, the submit button,
|
|
118
|
+
`useIsSubmitting` — is omitted entirely; the generated component instead
|
|
119
|
+
accepts an optional `onValuesChange?: (values) => void` prop wired
|
|
120
|
+
through `form.watchValues` in a `useEffect` (watchValues has shipped
|
|
121
|
+
since formstand 0.2 and returns its own unsubscribe, so generated output
|
|
122
|
+
keeps a low version floor; the emitted comment names `useFormValues` as
|
|
123
|
+
the post-0.12 render-side one-liner). The root element stays a `<form>`
|
|
124
|
+
(label association, the form landmark, kit chrome unchanged) with a
|
|
125
|
+
`preventDefault` `onSubmit` so the browser's implicit Enter-key
|
|
126
|
+
submission can't navigate the page. The emitted validation mode defaults
|
|
127
|
+
to `"onChange"` — a live consumer wants validity that tracks the values,
|
|
128
|
+
not the library-default `"onBlur"` lag (noted in the emitted comment).
|
|
129
|
+
Kit `Button` imports stay usage-gated: array add/remove buttons keep
|
|
130
|
+
them, an array-free live form drops them. Works with all six `--ui`
|
|
131
|
+
kits, custom `--template`s, and both layouts (`--layout module` defaults
|
|
132
|
+
the singleton's `createForm` mode and rewires the form file; the
|
|
133
|
+
pre-wired hook API is unchanged).
|
|
134
|
+
- **`--form-prop` — page-owned-form scaffold mode** (config key
|
|
135
|
+
`formProp`). The generated component's props gain
|
|
136
|
+
`form: Form<typeof schema>` and it stops creating its own form; the
|
|
137
|
+
`useForm` scaffold is still emitted, as an exported `use{Name}Form()`
|
|
138
|
+
hook the page calls — so one instance can feed the generated UI and any
|
|
139
|
+
other consumer (the dogfood case: a page-level form driving a map AND
|
|
140
|
+
the form UI). In `--layout module` the component takes the same prop for
|
|
141
|
+
its shell (submit/subscription) while the field hooks stay pre-wired to
|
|
142
|
+
the module singleton — the emitted props comment says to pass that
|
|
143
|
+
exported instance. Composes with `--live`: the component becomes pure
|
|
144
|
+
rendering and `onValuesChange` subscribes to the passed form.
|
|
145
|
+
- Internal: the per-kit form shells (single-file backends and the module
|
|
146
|
+
layout) are restructured into shared open/onSubmit/submit-button/close
|
|
147
|
+
pieces, so the submit and `--live` shapes are composed from one
|
|
148
|
+
production and cannot drift per kit. Default-flag output is
|
|
149
|
+
byte-identical.
|
|
150
|
+
- **Playground: the two modes as a living demo** — a new Generated tab
|
|
151
|
+
(**Gen: live + form prop**) renders a compact flight-search schema
|
|
152
|
+
generated with `--live --form-prop` (`--ui plain`, single-file,
|
|
153
|
+
committed untouched and drift-checked via
|
|
154
|
+
`scripts/generate-cli-demos.mjs` like the other generated demos) next to
|
|
155
|
+
a hand-written consumer page: the page owns the form via the exported
|
|
156
|
+
`useFlightSearchForm()` hook and a live values panel renders only what
|
|
157
|
+
`onValuesChange` delivers — updating per keystroke, the map-driven case
|
|
158
|
+
in miniature. Smoke-tested in the root suite (mount, type, assert the
|
|
159
|
+
panel reflects the keystroke; no submit scaffold in the output).
|
|
160
|
+
- **zod `.describe()` / `.meta({ description })` → generated helper text.**
|
|
161
|
+
The walkers capture a field's description into the IR — zod v4 stores
|
|
162
|
+
both spellings in ONE registry entry (`z.globalRegistry`, surfaced by the
|
|
163
|
+
schema's `description` getter; nothing on `def`), so the rule is simply
|
|
164
|
+
"the last `.describe()`/`.meta()` call wins", and across wrappers
|
|
165
|
+
(`z.number().describe("x").optional()` vs `.optional().describe("x")`)
|
|
166
|
+
the outermost entry present wins. Type mode captures the member's leading
|
|
167
|
+
JSDoc description the same way (and the generated zod schema re-emits it
|
|
168
|
+
as `.describe()`, outermost, so it round-trips). Emission per kit, both
|
|
169
|
+
layouts, union variant fields and tuple elements included: mui routes it
|
|
170
|
+
into `helperText` as `fieldError(field) ?? description` (the error keeps
|
|
171
|
+
the one slot while present), shadcn a muted
|
|
172
|
+
`<p className="text-sm text-muted-foreground">` and antd a
|
|
173
|
+
`Typography.Text type="secondary"` line — each rendered only while the
|
|
174
|
+
error line is not — chakra a `Field.HelperText` under the same guard,
|
|
175
|
+
mantine the native `description` prop (its own slot, coexists with
|
|
176
|
+
`error`), and plain an always-visible `<p className="zf-help">` line
|
|
177
|
+
(formstand's built-in components own the error slot internally).
|
|
178
|
+
Booleans are skipped where the control has no slot (MUI's
|
|
179
|
+
FormControlLabel/Switch, chakra's Switch.Root, antd's bare Checkbox);
|
|
180
|
+
shadcn/mantine/plain booleans render it. Custom templates receive the
|
|
181
|
+
description as `ctx.description` (an expression like `ctx.label`; `""`
|
|
182
|
+
when absent). Description-free schemas emit byte-identical output.
|
|
183
|
+
Adornments from `.meta` (unit prefixes/suffixes) are deliberately NOT
|
|
184
|
+
generated — the four kits' adornment APIs are value-shaped and mutually
|
|
185
|
+
incompatible (InputAdornment / rightSection / InputElement / suffix);
|
|
186
|
+
helper text covers the units case. Visible in the playground: the
|
|
187
|
+
flight-search demo's `cruiseAltitude` now carries `.describe("feet MSL")`.
|
|
188
|
+
|
|
189
|
+
### Fixed
|
|
190
|
+
|
|
191
|
+
- **`--layout module`: autocomplete-only schemas now compile for every
|
|
192
|
+
kit.** The module adapter file's import gates missed
|
|
193
|
+
`usage.autocomplete`: a schema whose every string/enum field carries the
|
|
194
|
+
autocomplete override emitted chakra/shadcn adapters using `ChangeEvent`
|
|
195
|
+
without importing it (their override rides the kits' text adapter), and
|
|
196
|
+
the antd adapter's `FieldError` used `Typography` without importing it.
|
|
197
|
+
The single-file backends already gated these correctly; the module gates
|
|
198
|
+
now mirror them (mui/mantine/plain were already correct — mui's
|
|
199
|
+
autocomplete types `SyntheticEvent`, mantine's and antd's are
|
|
200
|
+
value-shaped).
|
|
201
|
+
- **`--layout module --form-prop`: the emitted component now warns in dev
|
|
202
|
+
when the passed form is not the module singleton.** The module's field
|
|
203
|
+
hooks are pre-wired to the exported singleton; a different form of the
|
|
204
|
+
same schema compiled and rendered but silently split state (the shell on
|
|
205
|
+
the prop, the fields on the singleton). The generated component now
|
|
206
|
+
compares the prop against the singleton and `console.warn`s outside
|
|
207
|
+
production (`NODE_ENV` gate, same shape as the library's own dev
|
|
208
|
+
warnings), naming the instance to pass. Documented as an emphasized
|
|
209
|
+
contract note in the CLI README's flags section.
|
|
210
|
+
- **Override near-miss suggestions no longer offer paths the validator
|
|
211
|
+
would reject.** The "did you mean ...?" candidate list for an unknown
|
|
212
|
+
`fields` path now filters out dot-containing (unaddressable) names,
|
|
213
|
+
walker-degraded leaves, and paths past the FieldPath depth budget — the
|
|
214
|
+
same predicates `applyFieldOverrides` errors on, so a suggestion can
|
|
215
|
+
always actually be used.
|
|
216
|
+
- Internal: `ownerHookName` and `hasStaticDescriptions` are no longer
|
|
217
|
+
exported from codegen (no external consumer), and `overridablePaths`
|
|
218
|
+
left the `formstand-cli/codegen` public surface (it is the validator's
|
|
219
|
+
internal near-miss walk).
|
|
220
|
+
|
|
221
|
+
## 0.12.0 — 2026-07-29
|
|
222
|
+
|
|
223
|
+
### Changed
|
|
224
|
+
|
|
225
|
+
- **`useFieldArray` ops revalidate the array path** — push/remove/insert/
|
|
226
|
+
move/swap now re-run field validation on the array's own path after the
|
|
227
|
+
op, under the exact change-trigger gate `useField.setValue` applies
|
|
228
|
+
(`shouldValidateOn` over mode/reValidateMode/submitCount/touched). This
|
|
229
|
+
fixes a visible array-level error (`z.array().min(1)` after a failed
|
|
230
|
+
submit) going stale: adding the row didn't clear the message, and
|
|
231
|
+
removing below `.min` raised nothing until the next submit. Upgraders
|
|
232
|
+
note: in `onChange` mode (or after the first submit) array ops now
|
|
233
|
+
trigger a validation pass — including async refines — where 0.11 stayed
|
|
234
|
+
silent; `onSubmit` forms remain silent before the first submit, exactly
|
|
235
|
+
like typing in a field. The IMPERATIVE `form.arrayPush`/`arrayRemove`
|
|
236
|
+
stay non-revalidating on purpose, matching the `form.setValue` vs
|
|
237
|
+
`useField.setValue` layering.
|
|
238
|
+
|
|
239
|
+
### Added
|
|
240
|
+
|
|
241
|
+
- **`FieldArrayFormApi.validateField` (optional)** — the member the hook
|
|
242
|
+
probes for the revalidation above. A `Form<TSchema>` always provides it;
|
|
243
|
+
hand-rolled `FieldArrayFormApi` implementations without it keep working,
|
|
244
|
+
their ops simply skip revalidation (documented in the API reference's
|
|
245
|
+
implementing-these-interfaces notes).
|
|
246
|
+
|
|
247
|
+
### Fixed
|
|
248
|
+
- **`focusFirstError` / `focusField`: a hidden name-carrier no longer
|
|
249
|
+
suppresses the `[id=path]` fallback.** Composite widgets like
|
|
250
|
+
react-select render a hidden `<input name={path}>` for form posting next
|
|
251
|
+
to a focusable control carrying `id={path}`; the hidden input claimed the
|
|
252
|
+
path as "named" even though it can never take focus, so the id element
|
|
253
|
+
was never tried. Whether a path counts as name-matched is now judged
|
|
254
|
+
against focus-CANDIDATE controls only.
|
|
255
|
+
- **`focusFirstError` / `focusField` harden against control characters in
|
|
256
|
+
paths** — the CSS-string escaping behind the `[id=path]` fallback now
|
|
257
|
+
hex-escapes control characters (`\n`, DEL, ...), so a hostile or
|
|
258
|
+
accidental newline in a path builds a valid selector that matches
|
|
259
|
+
nothing instead of making `querySelectorAll` throw.
|
|
260
|
+
|
|
261
|
+
## formstand-cli 0.9.1 — 2026-07-29
|
|
262
|
+
|
|
263
|
+
### Fixed
|
|
264
|
+
|
|
265
|
+
- **Hoisted `${var}NumberProps` consts respect the identifier registry.** A
|
|
266
|
+
schema field literally named like the derived const of a kit number
|
|
267
|
+
binding (`price` + `priceNumberProps`) generated a duplicate `const`
|
|
268
|
+
declaration — the file failed tsc — in single-file union bodies AND
|
|
269
|
+
module-layout rows/tuple/union sections. Number bindings now reserve BOTH
|
|
270
|
+
their own var and the derived `${var}NumberProps` name through the
|
|
271
|
+
identifier-suffix machinery at allocation time, so the colliding field
|
|
272
|
+
suffixes (`priceNumberProps2`) instead.
|
|
273
|
+
- **Array-level error lines announce in every kit**: the mui / chakra /
|
|
274
|
+
mantine array errors (both the single-file emitters and the module
|
|
275
|
+
layout's list shells) now carry `role="alert"`, matching plain, shadcn,
|
|
276
|
+
and antd — a `z.array().min()` message is announced to assistive tech,
|
|
277
|
+
not just painted.
|
|
278
|
+
- **Version-floor wording updated for the shipped release**: the emitted
|
|
279
|
+
antd comment, its non-emitted twin, the inline `useNumberText` note, and
|
|
280
|
+
the README now say "formstand >= 0.11.0" for the focus-helper `[id=path]`
|
|
281
|
+
fallback (previously "formstand > 0.10.0", written before 0.11.0 had a
|
|
282
|
+
number).
|
|
283
|
+
|
|
284
|
+
### Changed
|
|
285
|
+
|
|
286
|
+
- **HELP's `--ui` line is built from structure, not regex surgery**: the
|
|
287
|
+
flag spelling derives from the new `UI_KITS` list plus `MUI_VERSIONS`
|
|
288
|
+
(exported from `uiTarget.ts`, which `UI_CHOICES` also derives from), so a
|
|
289
|
+
new kit or MUI major updates every listing at once. The rendered text is
|
|
290
|
+
unchanged.
|
|
291
|
+
- Internal consolidation: the string/date adapter names at the module
|
|
292
|
+
layout's import sites and the single-file Bound* component bodies now
|
|
293
|
+
route through `kitScalarBinding` — one production feeds both the import
|
|
294
|
+
and JSX spellings per kit — and `ModuleUi` derives from the exported
|
|
295
|
+
`KitUi` union instead of restating the kit list.
|
|
296
|
+
|
|
3
297
|
## 0.11.0 — 2026-07-29
|
|
4
298
|
|
|
5
299
|
### Added
|
package/README.md
CHANGED
|
@@ -181,6 +181,10 @@ Selector-style subscription. Use `useFormSelectorShallow` for selectors that ret
|
|
|
181
181
|
> ships its own (deprecated) `useFormState` and auto-imports regularly grabbed
|
|
182
182
|
> the wrong one. The old names were removed in 0.4.0.
|
|
183
183
|
|
|
184
|
+
### `useFormValues(form)`
|
|
185
|
+
|
|
186
|
+
The whole values object, reactively — sugar for `useFormSelector(form, (s) => s.values)`, reference-compared so it re-renders exactly when some value changes (never on touched/error churn); the render-side sibling of `form.watchValues`.
|
|
187
|
+
|
|
184
188
|
### `useFormError(form)`
|
|
185
189
|
|
|
186
190
|
Shortcut for the root-level error (errors at the `""` key from a schema-level `.refine`).
|
package/dist/index.cjs
CHANGED
|
@@ -181,6 +181,14 @@ var slotAtPath = (obj, path) => {
|
|
|
181
181
|
);
|
|
182
182
|
return walked.ok ? { exists: true, value: walked.current } : { exists: false };
|
|
183
183
|
};
|
|
184
|
+
var hasOwnSlot = (obj, path) => parsePath(path).reduce(
|
|
185
|
+
(acc, segment) => {
|
|
186
|
+
const container = acc.current;
|
|
187
|
+
const owns = Array.isArray(container) ? typeof segment === "number" && segment < container.length : isPlainRecord(container) && Object.hasOwn(container, String(segment));
|
|
188
|
+
return !acc.ok || !owns ? { ok: false, current: void 0 } : { ok: true, current: readSegment(container, segment) };
|
|
189
|
+
},
|
|
190
|
+
{ ok: true, current: obj }
|
|
191
|
+
).ok;
|
|
184
192
|
var writeIntoArray = (arr, index, rest, value) => {
|
|
185
193
|
const length = Math.max(arr.length, index + 1);
|
|
186
194
|
return Array.from(
|
|
@@ -883,6 +891,10 @@ var createForm = (schema, options) => {
|
|
|
883
891
|
});
|
|
884
892
|
},
|
|
885
893
|
setValue: (path, value) => {
|
|
894
|
+
const currentValues = store.getState().values;
|
|
895
|
+
if (Object.is(getAtPath(currentValues, path), value) && (value !== void 0 || hasOwnSlot(currentValues, path))) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
886
898
|
clearArrayOpsUnder(store, path);
|
|
887
899
|
store.setState((state) => ({
|
|
888
900
|
...state,
|
|
@@ -1280,8 +1292,10 @@ function useField(form, pathArg, options) {
|
|
|
1280
1292
|
}, [form, path, debounceMs]);
|
|
1281
1293
|
const setValue = react.useCallback(
|
|
1282
1294
|
(value) => {
|
|
1295
|
+
const before = form.store.getState();
|
|
1283
1296
|
form.setValue(path, value);
|
|
1284
1297
|
const state = form.store.getState();
|
|
1298
|
+
if (state === before) return;
|
|
1285
1299
|
if (shouldValidateOn(
|
|
1286
1300
|
"change",
|
|
1287
1301
|
state.mode,
|
|
@@ -1475,25 +1489,52 @@ function useFieldArray(form, pathArg) {
|
|
|
1475
1489
|
const items = slice.items;
|
|
1476
1490
|
const error = slice.error;
|
|
1477
1491
|
const ids = deriveIds(form.store, path, items).ids;
|
|
1492
|
+
const revalidate = react.useCallback(() => {
|
|
1493
|
+
const state = form.store.getState();
|
|
1494
|
+
if (shouldValidateOn(
|
|
1495
|
+
"change",
|
|
1496
|
+
state.mode,
|
|
1497
|
+
state.reValidateMode,
|
|
1498
|
+
state.submitCount > 0,
|
|
1499
|
+
state.touched[path] ?? false
|
|
1500
|
+
)) {
|
|
1501
|
+
form.validateField?.(path);
|
|
1502
|
+
}
|
|
1503
|
+
}, [form, path]);
|
|
1478
1504
|
const push = react.useCallback(
|
|
1479
|
-
(item) =>
|
|
1480
|
-
|
|
1505
|
+
(item) => {
|
|
1506
|
+
form.arrayPush(path, item);
|
|
1507
|
+
revalidate();
|
|
1508
|
+
},
|
|
1509
|
+
[form, path, revalidate]
|
|
1481
1510
|
);
|
|
1482
1511
|
const remove = react.useCallback(
|
|
1483
|
-
(index) =>
|
|
1484
|
-
|
|
1512
|
+
(index) => {
|
|
1513
|
+
form.arrayRemove(path, index);
|
|
1514
|
+
revalidate();
|
|
1515
|
+
},
|
|
1516
|
+
[form, path, revalidate]
|
|
1485
1517
|
);
|
|
1486
1518
|
const insert = react.useCallback(
|
|
1487
|
-
(index, item) =>
|
|
1488
|
-
|
|
1519
|
+
(index, item) => {
|
|
1520
|
+
form.arrayInsert(path, index, item);
|
|
1521
|
+
revalidate();
|
|
1522
|
+
},
|
|
1523
|
+
[form, path, revalidate]
|
|
1489
1524
|
);
|
|
1490
1525
|
const move = react.useCallback(
|
|
1491
|
-
(from, to) =>
|
|
1492
|
-
|
|
1526
|
+
(from, to) => {
|
|
1527
|
+
form.arrayMove(path, from, to);
|
|
1528
|
+
revalidate();
|
|
1529
|
+
},
|
|
1530
|
+
[form, path, revalidate]
|
|
1493
1531
|
);
|
|
1494
1532
|
const swap = react.useCallback(
|
|
1495
|
-
(a, b) =>
|
|
1496
|
-
|
|
1533
|
+
(a, b) => {
|
|
1534
|
+
form.arraySwap(path, a, b);
|
|
1535
|
+
revalidate();
|
|
1536
|
+
},
|
|
1537
|
+
[form, path, revalidate]
|
|
1497
1538
|
);
|
|
1498
1539
|
const fields = react.useMemo(
|
|
1499
1540
|
() => items.map((value, index) => ({
|
|
@@ -1582,6 +1623,9 @@ var createFormHooks = (form, name) => {
|
|
|
1582
1623
|
[`use${prefix}SubmitCount`]: useBoundSubmitCount
|
|
1583
1624
|
};
|
|
1584
1625
|
};
|
|
1626
|
+
function useFormValues(form) {
|
|
1627
|
+
return react$1.useStore(form.store, (state) => state.values);
|
|
1628
|
+
}
|
|
1585
1629
|
|
|
1586
1630
|
// src/react/inputProps.ts
|
|
1587
1631
|
var hasFieldError = (error) => error !== void 0 && error.length > 0;
|
|
@@ -1934,7 +1978,10 @@ var nameMatchesAny = (el, paths) => {
|
|
|
1934
1978
|
const name = el.getAttribute("name");
|
|
1935
1979
|
return name !== null && paths.some((p) => isPathOrChild(name, p));
|
|
1936
1980
|
};
|
|
1937
|
-
var cssQuoted = (value) => `"${value.replace(/[\\"]/g, (ch) => `\\${ch}`)
|
|
1981
|
+
var cssQuoted = (value) => `"${value.replace(/[\\"]/g, (ch) => `\\${ch}`).replace(
|
|
1982
|
+
/[\x00-\x1f\x7f]/g,
|
|
1983
|
+
(ch) => `\\${ch.codePointAt(0).toString(16)} `
|
|
1984
|
+
)}"`;
|
|
1938
1985
|
var idMatches = (scope, path) => path === "" ? [] : [...scope.querySelectorAll(`[id=${cssQuoted(path)}]`)];
|
|
1939
1986
|
var inDomOrder = (els) => [...els].sort(
|
|
1940
1987
|
(a, b) => a === b ? 0 : (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 ? -1 : 1
|
|
@@ -1942,8 +1989,9 @@ var inDomOrder = (els) => [...els].sort(
|
|
|
1942
1989
|
var pathCandidates = (scope, paths) => {
|
|
1943
1990
|
const controls = namedControls(scope);
|
|
1944
1991
|
const named = controls.filter((el) => nameMatchesAny(el, paths));
|
|
1992
|
+
const focusableControls = controls.filter(isFocusCandidate);
|
|
1945
1993
|
const unnamedPaths = paths.filter(
|
|
1946
|
-
(p) => !
|
|
1994
|
+
(p) => !focusableControls.some((el) => nameMatchesAny(el, [p]))
|
|
1947
1995
|
);
|
|
1948
1996
|
const byId = unnamedPaths.flatMap((p) => idMatches(scope, p));
|
|
1949
1997
|
return inDomOrder([.../* @__PURE__ */ new Set([...named, ...byId])]);
|
|
@@ -2001,6 +2049,7 @@ exports.useForm = useForm;
|
|
|
2001
2049
|
exports.useFormError = useFormError;
|
|
2002
2050
|
exports.useFormSelector = useFormSelector;
|
|
2003
2051
|
exports.useFormSelectorShallow = useFormSelectorShallow;
|
|
2052
|
+
exports.useFormValues = useFormValues;
|
|
2004
2053
|
exports.useIsDirty = useIsDirty;
|
|
2005
2054
|
exports.useIsSubmitting = useIsSubmitting;
|
|
2006
2055
|
exports.useIsValid = useIsValid;
|