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 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) => form.arrayPush(path, item),
1480
- [form, path]
1505
+ (item) => {
1506
+ form.arrayPush(path, item);
1507
+ revalidate();
1508
+ },
1509
+ [form, path, revalidate]
1481
1510
  );
1482
1511
  const remove = react.useCallback(
1483
- (index) => form.arrayRemove(path, index),
1484
- [form, path]
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) => form.arrayInsert(path, index, item),
1488
- [form, path]
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) => form.arrayMove(path, from, to),
1492
- [form, path]
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) => form.arraySwap(path, a, b),
1496
- [form, path]
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) => !controls.some((el) => nameMatchesAny(el, [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;