formstand 0.10.0 → 0.12.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,101 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0 — 2026-07-29
4
+
5
+ ### Fixed
6
+
7
+ - **`useFieldArray` ops revalidate the array path** — push/remove/insert/
8
+ move/swap now re-run field validation on the array's own path after the
9
+ op, under the exact change-trigger gate `useField.setValue` applies
10
+ (`shouldValidateOn` over mode/reValidateMode/submitCount/touched). A
11
+ visible array-level error (`z.array().min(1)` after a failed submit) used
12
+ to go stale: adding the row didn't clear the message, and removing below
13
+ `.min` raised nothing until the next submit. The gate means `onSubmit`
14
+ forms stay silent before the first submit, exactly like typing in a
15
+ field. `FieldArrayFormApi` gains an OPTIONAL `validateField` member
16
+ (probed before use) — hand-rolled implementations without it keep
17
+ working, their ops simply skip revalidation. The IMPERATIVE
18
+ `form.arrayPush`/`arrayRemove` stay non-revalidating on purpose, matching
19
+ the `form.setValue` vs `useField.setValue` layering.
20
+ - **`focusFirstError` / `focusField`: a hidden name-carrier no longer
21
+ suppresses the `[id=path]` fallback.** Composite widgets like
22
+ react-select render a hidden `<input name={path}>` for form posting next
23
+ to a focusable control carrying `id={path}`; the hidden input claimed the
24
+ path as "named" even though it can never take focus, so the id element
25
+ was never tried. Whether a path counts as name-matched is now judged
26
+ against focus-CANDIDATE controls only.
27
+ - **`focusFirstError` / `focusField` harden against control characters in
28
+ paths** — the CSS-string escaping behind the `[id=path]` fallback now
29
+ hex-escapes control characters (`\n`, DEL, ...), so a hostile or
30
+ accidental newline in a path builds a valid selector that matches
31
+ nothing instead of making `querySelectorAll` throw.
32
+
33
+ ## formstand-cli 0.9.1 — 2026-07-29
34
+
35
+ ### Fixed
36
+
37
+ - **Hoisted `${var}NumberProps` consts respect the identifier registry.** A
38
+ schema field literally named like the derived const of a kit number
39
+ binding (`price` + `priceNumberProps`) generated a duplicate `const`
40
+ declaration — the file failed tsc — in single-file union bodies AND
41
+ module-layout rows/tuple/union sections. Number bindings now reserve BOTH
42
+ their own var and the derived `${var}NumberProps` name through the
43
+ identifier-suffix machinery at allocation time, so the colliding field
44
+ suffixes (`priceNumberProps2`) instead.
45
+ - **Array-level error lines announce in every kit**: the mui / chakra /
46
+ mantine array errors (both the single-file emitters and the module
47
+ layout's list shells) now carry `role="alert"`, matching plain, shadcn,
48
+ and antd — a `z.array().min()` message is announced to assistive tech,
49
+ not just painted.
50
+ - **Version-floor wording updated for the shipped release**: the emitted
51
+ antd comment, its non-emitted twin, the inline `useNumberText` note, and
52
+ the README now say "formstand >= 0.11.0" for the focus-helper `[id=path]`
53
+ fallback (previously "formstand > 0.10.0", written before 0.11.0 had a
54
+ number).
55
+
56
+ ### Changed
57
+
58
+ - **HELP's `--ui` line is built from structure, not regex surgery**: the
59
+ flag spelling derives from the new `UI_KITS` list plus `MUI_VERSIONS`
60
+ (exported from `uiTarget.ts`, which `UI_CHOICES` also derives from), so a
61
+ new kit or MUI major updates every listing at once. The rendered text is
62
+ unchanged.
63
+ - Internal consolidation: the string/date adapter names at the module
64
+ layout's import sites and the single-file Bound* component bodies now
65
+ route through `kitScalarBinding` — one production feeds both the import
66
+ and JSX spellings per kit — and `ModuleUi` derives from the exported
67
+ `KitUi` union instead of restating the kit list.
68
+
69
+ ## 0.11.0 — 2026-07-29
70
+
71
+ ### Added
72
+
73
+ - **`useNumberInput` is exported** (along with its `NumberInputBinding`
74
+ return type) — the text-preserving number binding behind `NumberField`.
75
+ It was implemented and tested (via `NumberField`) all along, just never
76
+ exported: local raw text while editing (partial entries like `-`, `1.`,
77
+ `85000.` stay visible), keystrokes that parse pushed to the form via the
78
+ shared `parseNumberText` rules, blur snapping the display to the
79
+ canonical value, and an external form-value change while editing
80
+ resetting the local text. Spread it onto an `<input type="text">` for
81
+ custom number inputs and UI-kit adapters — a naive controlled
82
+ `value={String(n)}` binding eats the `.` of `85000.50` and the `-` of
83
+ `-5` as they are typed.
84
+
85
+ ### Fixed
86
+
87
+ - **`focusFirstError` / `focusField` gain an `[id=path]` fallback** for
88
+ paths that match NO named control: composite widgets that render no
89
+ `name` anywhere — Ant Design's `Select`, notably, which has no
90
+ form-posting input — were unreachable by the name walk even though they
91
+ forward `id={path}` to their real focusable control. When a path has no
92
+ name-matched control, the helpers now try the element whose `id` is
93
+ exactly that path (EXACT match only — descendant semantics stay with the
94
+ name walk, since an id names one element), with the same focusability
95
+ filter, post-focus verification, and DOM-order merging as name matches.
96
+ Paths with any named control never consult ids, so existing behavior is
97
+ unchanged wherever a `name` exists.
98
+
3
99
  ## 0.10.0 — 2026-07-28
4
100
 
5
101
  ### Breaking
@@ -137,6 +233,241 @@
137
233
  - `FieldPathArg` is now actually used by the `useField` / `useFieldArray`
138
234
  overloads it describes (it was exported dead).
139
235
 
236
+ ## formstand-cli 0.9.0 — 2026-07-29
237
+
238
+ ### Added
239
+
240
+ - **Version-aware `--ui`: `mui@5` … `mui@9`** — `--ui mui` can now pin an
241
+ `@mui/material` major: `mui@5`, `mui@6`, `mui@7`, `mui@9` (bare `mui`
242
+ keeps meaning the latest supported major, 9, and its output is
243
+ byte-identical to before). The config file's `ui` key accepts the same
244
+ spellings. Scope: only React-19-capable majors — formstand peers
245
+ `react: ^19`, so MUI 4 and older can never install alongside it — and MUI
246
+ skipped major 8 entirely (7.x jumps to 9 on the registry), so `mui@8`
247
+ and `mui@4` fail with explanations, as do `plain@N`/`shadcn@N` (those
248
+ kits take no version). Internally the flag/config value parses to a
249
+ structured `UiTarget` (`{ kit: "mui", version }` | `{ kit: "plain" }` |
250
+ `{ kit: "shadcn" }`, exported with `parseUiTarget` / `MUI_VERSIONS` from
251
+ `formstand-cli/codegen`), and ONE mui emitter serves every major through
252
+ a small per-version config. The only prop-surface delta across the
253
+ emitted component set (verified empirically against each major's .d.ts)
254
+ is TextField's slot-props API: `mui@5` emits the legacy
255
+ `InputProps: { inputMode }` / `InputLabelProps: { shrink: true }`, v6+
256
+ emit `slotProps.{input,inputLabel}` (v9 removed the legacy spelling;
257
+ v6–7 accept both and get the modern one). Custom `--template` modules
258
+ compose with a versioned `--ui` exactly as they did with bare `mui`
259
+ (a template still overrides the ui entirely, `--layout single` only).
260
+ - **`cli/matrix/`: the UI-kit version-matrix typecheck harness** — an isolated
261
+ workspace (own `package.json` + lockfile; not part of the root or cli
262
+ installs) with every supported `@mui/material` major installed side by
263
+ side under npm aliases (`mui5` … `mui9`). `npm run matrix` (from `cli/`)
264
+ generates a kitchen-sink form plus the nested-array stress form with
265
+ `--ui mui@N` — both layouts, every section/column variant — and
266
+ typechecks the output against each major's real type declarations
267
+ (`@mui/material` path-mapped onto the alias, `formstand` onto the
268
+ library source). A per-version literal-attribute probe restates the
269
+ adapter's TextField props style outside a JSX spread (spreads bypass
270
+ TypeScript's excess-property checks), so a wrong per-version config
271
+ fails the matrix instead of compiling silently. Run it before releasing
272
+ any UI-kit backend change; it is deliberately not part of `npm test`.
273
+ (The chakra, mantine, and antd entries below each extend the harness
274
+ with their kit's alias and job — it ends this cycle proving seven
275
+ targets: four mui majors, chakra 3, mantine 9, antd 6.)
276
+ - **First-class `--ui chakra`: a Chakra UI v3 backend** — both layouts
277
+ (single-file and `--layout module`), the full field-kind surface the mui
278
+ backend covers (strings/numbers/dates/enums/booleans, nested objects,
279
+ arrays with add/remove and recursive nested-row extraction, tuples,
280
+ discriminated unions, TODO degradations), and every `--sections` /
281
+ `--columns` variant. Emits the v3 compound-component API (verified
282
+ against the installed 3.36 declarations, not v2's): `Field.Root` (with
283
+ `invalid`) / `Field.Label` / `Field.ErrorText` for labels and errors,
284
+ `Input` for text/number/date (native bindings — `inputMode="decimal"`,
285
+ `type="date"`), `NativeSelect.Root` + `NativeSelect.Field` for enums (a
286
+ real `<select>`, preferred over the Ark collection `Select` so the
287
+ adapter speaks DOM change events), and `Switch.Root` /
288
+ `Switch.HiddenInput` / `Switch.Control` / `Switch.Thumb` /
289
+ `Switch.Label` for booleans (`checked` + `onCheckedChange` details
290
+ callback, spread on the root where the state lives). Sections render
291
+ `Stack`/`Heading` (flat), `Card.Root`/`Card.Body` (panel), or one
292
+ `Accordion.Root` (`collapsible defaultValue={["section"]}`) per section
293
+ (collapsible); columns use the same CSS-grid shape as the other kits via
294
+ chakra style props. The generated file assumes the host app mounts
295
+ `ChakraProvider` (same policy as the mui backend and its theme). The
296
+ module layout gains a shared `adapter.ts` (`chakraTextInputProps` /
297
+ `useChakraNumberInputProps` / `chakraDateInputProps` /
298
+ `chakraSelectProps` / `chakraSwitchProps` + `fieldError`). chakra takes no version suffix —
299
+ v3 is the only supported major (v2 and older lack the compound API and
300
+ predate formstand's React 19 peer) — with `chakra@3` accepted as the
301
+ explicit spelling and `chakra@1`/`chakra@2` failing with the scope
302
+ rationale like `mui@0`–`mui@4`. The config file's `ui` key accepts the
303
+ same spellings, and the matrix harness gained a `chakra3` alias
304
+ (`npm:@chakra-ui/react@^3`) plus a chakra job — both schemas, both
305
+ layouts, all section styles, and a literal-attribute probe for the
306
+ Input/NativeSelect/Switch prop surfaces — so `npm run matrix` now proves
307
+ five kit targets against their real .d.ts.
308
+ - **First-class `--ui mantine`: a Mantine v9 backend** — both layouts, the
309
+ full field-kind surface the other kit backends cover (strings/numbers/
310
+ dates/enums/booleans, nested objects, arrays with add/remove and
311
+ recursive nested-row extraction, tuples, discriminated unions, TODO
312
+ degradations), and every `--sections` / `--columns` variant. Mantine
313
+ field components carry their own `label` + `error` props, so there is no
314
+ Field wrapper: `TextInput` binds text/number/date natively
315
+ (`inputMode="decimal"`, `type="date"`; Mantine's `NumberInput` widget is
316
+ deliberately NOT used — its `onChange` takes `(value: number | string)`,
317
+ not a DOM event, so it can't share formstand's input-shaped adapters),
318
+ `NativeSelect` binds enums as a real `<select>` with `<option>` children
319
+ (same native-first choice as the chakra backend), and `Switch` binds
320
+ booleans through plain DOM `checked`/`onChange` (a `ChangeEvent`, unlike
321
+ chakra's details callback). Error text rides inside the adapter builders
322
+ (`error: fieldError(field)`), so leaves are one-liners like the mui
323
+ backend's. Sections render `Stack`/`Title` (flat), `Card withBorder` +
324
+ `SimpleGrid` (panel), or `Accordion`/`Accordion.Item`/`Control`/`Panel`
325
+ (collapsible, `defaultValue="section"` + `variant="contained"`); columns
326
+ use `SimpleGrid cols={N}`. The generated file assumes the host app
327
+ mounts `MantineProvider` (same policy as the mui/chakra providers). The
328
+ module layout gains a shared `adapter.ts` (`mantineTextInputProps` /
329
+ `useMantineNumberInputProps` / `mantineDateInputProps` /
330
+ `mantineSelectProps` / `mantineSwitchProps` + `fieldError`). mantine
331
+ takes no version suffix — v9 (the current major) is the only supported
332
+ target, with `mantine@9` accepted as the explicit spelling. The scope is
333
+ empirical: 9.x peers `react ^19.2` while 7.17+/8.x peer `^18 || ^19`
334
+ (7.0 was ^18-only, ≤6 predate the v7 emotion→CSS-modules rewrite), and
335
+ the emitted component surface typechecks IDENTICALLY against 7.17/8.3/9.5
336
+ except the `bdrs` style prop (absent in 7) — so `mantine@0`–`6` fail
337
+ with the React-19/styling-rewrite rationale and `mantine@7`/`@8` fail
338
+ with a precise verified-against-v9-only message instead of a false
339
+ incompatibility claim. The config file's `ui` key accepts the same
340
+ spellings, and the matrix harness gained a `mantine9` alias
341
+ (`npm:@mantine/core@^9.5`, plus its `@mantine/hooks` peer) and a
342
+ mantine job — both schemas, both layouts, all section styles, and a
343
+ literal-attribute probe for the TextInput/NativeSelect/Switch spread
344
+ surfaces — so `npm run matrix` now proves six kit targets against their
345
+ real .d.ts.
346
+ - **First-class `--ui antd`: an Ant Design v6 backend** — both layouts, the
347
+ full field-kind surface the other kit backends cover (strings/numbers/
348
+ dates/enums/booleans, nested objects, arrays with add/remove and
349
+ recursive nested-row extraction, tuples, discriminated unions, TODO
350
+ degradations), and every `--sections` / `--columns` variant. The one
351
+ hard rule: antd's own `Form`/`Form.Item` (name-based bindings, its own
352
+ state store) is NEVER emitted — formstand owns the form state, so the
353
+ generated code binds antd's input components as plain controlled
354
+ components. The bindings, decided empirically against the installed
355
+ 6.5 declarations: `Input` binds text/number/date natively (it extends
356
+ the DOM input props — `inputMode="decimal"`, `type="date"`; antd's
357
+ `InputNumber` is rejected on evidence, its `onChange` is
358
+ `(value: number | null)`, not an event, and the dayjs-value-based
359
+ `DatePicker` is not used — the CLI pulls no date library); enums bind
360
+ antd's `Select`, the backend's ONE value-shaped adapter — antd has no
361
+ native-`<select>` component anywhere, so `onChange` receives the
362
+ selected value directly, `value ?? null` keeps the placeholder visible,
363
+ and there is no `name` (antd's Select renders no form-posting input);
364
+ booleans bind `Checkbox` (its `onChange` is antd's DOM-ish
365
+ `CheckboxChangeEvent` with `e.target.checked`, and it has a real
366
+ `onBlur`) — NOT `Switch`, which has no `onBlur` prop at all and a
367
+ value-shaped `(checked, event)` callback (both rejections verified as
368
+ failing compiles against the real .d.ts). With no `Form.Item` there is
369
+ no built-in error slot: every non-boolean control paints
370
+ `status="error"` (`fieldStatus`) and renders an explicit
371
+ `Typography.Text type="danger" role="alert"` error line, with a plain
372
+ `<label htmlFor>`/`id` pair. Sections render `Flex`/`Typography.Title`
373
+ (flat), `Card variant="outlined"` (panel), or `Collapse` via the items
374
+ API (collapsible — children-panels are deprecated in antd 5+); columns
375
+ use the shared inline-style CSS grid. No provider is required
376
+ (`ConfigProvider` is optional theming). The module layout gains a shared
377
+ `adapter.tsx` (JSX for its `FieldError` line, like shadcn's):
378
+ `antdTextInputProps` / `useAntdNumberInputProps` /
379
+ `antdDateInputProps` / `antdSelectProps` / `antdCheckboxProps` +
380
+ `fieldError` / `fieldStatus` / `FieldError`. antd takes no version suffix — v6 (the current major) is
381
+ the only supported target, with `antd@6` accepted as the explicit
382
+ spelling. The scope is empirical: antd 6.x peers `react >=18` (React 19
383
+ natively in range), while antd 5 nominally peers `react >=16.9` but
384
+ needs the `@ant-design/v5-patch-for-react-19` import in the HOST app on
385
+ React 19 — so `antd@5` fails with a precise
386
+ verified-against-v6-only message (naming the patch, no false
387
+ incompatibility claim) and `antd@0`–`4` fail with the missing-surface
388
+ rationale (no `Flex`, no Collapse items API, no `status` props; 4 and
389
+ older predate the v5 CSS-in-JS rewrite). The config file's `ui` key
390
+ accepts the same spellings, and the matrix harness gained an `antd6`
391
+ alias (`npm:antd@^6.5`) and an antd job — both schemas, both layouts,
392
+ all section styles, and a literal-attribute probe for the
393
+ Input/Select/Checkbox spread surfaces, restating the value-shaped
394
+ Select `onChange` with its explicit parameter type — so
395
+ `npm run matrix` now proves seven kit targets against their real .d.ts.
396
+ - **Playground: the new kit backends as living demos** — three new
397
+ Generated tabs (**Gen: Chakra UI**, **Gen: Mantine**, **Gen: Ant
398
+ Design**) render the SAME Onboarding schema the mui module tab uses,
399
+ generated single-file with `--ui chakra` / `--ui mantine` / `--ui antd`
400
+ and the same `--sections panel --columns 2` chrome — a direct kit
401
+ comparison across the four tabs, with one deliberate second axis: the
402
+ mui tab is `--layout module` (the module-layout showcase) while these
403
+ three are single-file. Committed
404
+ untouched and drift-checked by CI like the other generated demos
405
+ (`scripts/generate-cli-demos.mjs`), rendered behind scoped provider
406
+ bridges that follow the playground's light/dark switch (ChakraProvider +
407
+ a mode class, MantineProvider `forceColorScheme`, antd `ConfigProvider`
408
+ algorithm — antd needs no provider to function), and smoke-tested in
409
+ the root suite (mount, label association, array add/remove per kit).
410
+
411
+ ### Fixed
412
+
413
+ - **Kit number adapters no longer corrupt typed numbers.** The mui, chakra,
414
+ mantine, and antd backends bound numbers as a controlled text input
415
+ (`inputMode="decimal"`) whose onChange immediately reparsed and
416
+ re-rendered the canonical string — so typing `85000.50` yielded
417
+ `8500050` (the `.` eaten as `85000.` reparsed to `85000`), `-5` yielded
418
+ `5`, and `0.050` yielded `50` (jsdom-verified). All four now emit an
419
+ inline `useNumberText` hook replicating formstand's own `useNumberInput`
420
+ semantics exactly (local raw text while editing, valid keystrokes pushed,
421
+ partial entries kept, blur snap, external-change reset) — emitted inline
422
+ rather than imported so generated output keeps its formstand >= 0.3.0
423
+ floor — and their number adapters became use-prefixed HOOKS
424
+ (`useMuiNumberFieldProps` / `useChakraNumberInputProps` /
425
+ `useMantineNumberInputProps` / `useAntdNumberInputProps`) composing kit
426
+ chrome over it, in both layouts. Union variant blocks are conditional
427
+ JSX, so number bindings there hoist
428
+ `const ${var}NumberProps = use...(${var})` next to the field hooks and
429
+ spread the const (React's rules of hooks). plain and shadcn were never
430
+ affected (stateless `type="number"` bindings).
431
+ - **Single-file array sections render the array-level error.** All six
432
+ single-file backends dropped `useFieldArray`'s `.error`, so a
433
+ `z.array().min(1, "add at least one contact")` message was invisible on
434
+ submit; the module layout already rendered it per kit. Each backend now
435
+ emits the same error line its module-layout list shell uses (plain and
436
+ shadcn `<p role="alert">`, mui `Typography color="error"`, chakra
437
+ `Text color="red.500"`, mantine `Text c="red"`, antd
438
+ `Typography.Text role="alert" type="danger"`), between the rows and the
439
+ Add button.
440
+ - **Version-matrix hardening.** (1) The stale-install gate now derives its
441
+ required-alias list from the job list itself and fails loudly naming
442
+ what's missing — it previously checked only 4 of the 7 aliases, so a
443
+ missing `mui6`/`mui7`/`mui9` silently typechecked against the repo
444
+ root's @mui/material copy. (2) Every kit gained a module-layout PANEL
445
+ job (`KitchenSinkPanel`, panel@2col): module-panel emitters were
446
+ previously typechecked nowhere (a deliberate typo in that arm shipped
447
+ green), so the matrix now genuinely covers all three section styles in
448
+ both layouts. (3) The matrix compiles under `exactOptionalPropertyTypes`
449
+ in addition to `strict` — the strictest consumer configuration — after
450
+ fixing the stale antd probe lines, which restated `status={undefined}`,
451
+ a shape the adapter no longer emits (it emits `""`, a member of antd's
452
+ `InputStatus`). (4) The four ~60-line per-kit generator copies collapsed
453
+ into one parameterized `generateKit` with the jobs as data.
454
+ - **Docs corrections**: the module layout works with all six uis (the
455
+ README said three) and the adapter split is named (`adapter.ts` for
456
+ mui/chakra/mantine, `adapter.tsx` for shadcn/antd); the
457
+ formstand >= 0.3.0 requirement explicitly covers mantine and antd (they
458
+ import the same surface); and the playground/README wording about the
459
+ four "CLI output" tabs now acknowledges both axes — the mui tab is
460
+ `--layout module` (doubling as the module-layout showcase) while the
461
+ three kit tabs are single-file, so `--ui` is not the only variable.
462
+ - **antd `fieldStatus` returns `""` — antd's own no-status value — instead
463
+ of `undefined`** when a field has no error. Spreading
464
+ `status: "error" | undefined` onto antd's `status?: InputStatus` props
465
+ fails to compile in host apps built with `exactOptionalPropertyTypes`
466
+ (which rejects explicit `undefined` for optional properties); `""` sits
467
+ in antd's `InputStatus` union precisely for "no status". Caught by
468
+ wiring the playground demo — the examples package compiles with
469
+ `exactOptionalPropertyTypes` on.
470
+
140
471
  ## formstand-cli 0.8.0 — 2026-07-28
141
472
 
142
473
  ### Changed
package/dist/index.cjs CHANGED
@@ -1475,25 +1475,52 @@ function useFieldArray(form, pathArg) {
1475
1475
  const items = slice.items;
1476
1476
  const error = slice.error;
1477
1477
  const ids = deriveIds(form.store, path, items).ids;
1478
+ const revalidate = react.useCallback(() => {
1479
+ const state = form.store.getState();
1480
+ if (shouldValidateOn(
1481
+ "change",
1482
+ state.mode,
1483
+ state.reValidateMode,
1484
+ state.submitCount > 0,
1485
+ state.touched[path] ?? false
1486
+ )) {
1487
+ form.validateField?.(path);
1488
+ }
1489
+ }, [form, path]);
1478
1490
  const push = react.useCallback(
1479
- (item) => form.arrayPush(path, item),
1480
- [form, path]
1491
+ (item) => {
1492
+ form.arrayPush(path, item);
1493
+ revalidate();
1494
+ },
1495
+ [form, path, revalidate]
1481
1496
  );
1482
1497
  const remove = react.useCallback(
1483
- (index) => form.arrayRemove(path, index),
1484
- [form, path]
1498
+ (index) => {
1499
+ form.arrayRemove(path, index);
1500
+ revalidate();
1501
+ },
1502
+ [form, path, revalidate]
1485
1503
  );
1486
1504
  const insert = react.useCallback(
1487
- (index, item) => form.arrayInsert(path, index, item),
1488
- [form, path]
1505
+ (index, item) => {
1506
+ form.arrayInsert(path, index, item);
1507
+ revalidate();
1508
+ },
1509
+ [form, path, revalidate]
1489
1510
  );
1490
1511
  const move = react.useCallback(
1491
- (from, to) => form.arrayMove(path, from, to),
1492
- [form, path]
1512
+ (from, to) => {
1513
+ form.arrayMove(path, from, to);
1514
+ revalidate();
1515
+ },
1516
+ [form, path, revalidate]
1493
1517
  );
1494
1518
  const swap = react.useCallback(
1495
- (a, b) => form.arraySwap(path, a, b),
1496
- [form, path]
1519
+ (a, b) => {
1520
+ form.arraySwap(path, a, b);
1521
+ revalidate();
1522
+ },
1523
+ [form, path, revalidate]
1497
1524
  );
1498
1525
  const fields = react.useMemo(
1499
1526
  () => items.map((value, index) => ({
@@ -1934,6 +1961,24 @@ var nameMatchesAny = (el, paths) => {
1934
1961
  const name = el.getAttribute("name");
1935
1962
  return name !== null && paths.some((p) => isPathOrChild(name, p));
1936
1963
  };
1964
+ var cssQuoted = (value) => `"${value.replace(/[\\"]/g, (ch) => `\\${ch}`).replace(
1965
+ /[\x00-\x1f\x7f]/g,
1966
+ (ch) => `\\${ch.codePointAt(0).toString(16)} `
1967
+ )}"`;
1968
+ var idMatches = (scope, path) => path === "" ? [] : [...scope.querySelectorAll(`[id=${cssQuoted(path)}]`)];
1969
+ var inDomOrder = (els) => [...els].sort(
1970
+ (a, b) => a === b ? 0 : (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 ? -1 : 1
1971
+ );
1972
+ var pathCandidates = (scope, paths) => {
1973
+ const controls = namedControls(scope);
1974
+ const named = controls.filter((el) => nameMatchesAny(el, paths));
1975
+ const focusableControls = controls.filter(isFocusCandidate);
1976
+ const unnamedPaths = paths.filter(
1977
+ (p) => !focusableControls.some((el) => nameMatchesAny(el, [p]))
1978
+ );
1979
+ const byId = unnamedPaths.flatMap((p) => idMatches(scope, p));
1980
+ return inDomOrder([.../* @__PURE__ */ new Set([...named, ...byId])]);
1981
+ };
1937
1982
  var focusFirstError = (errors, root) => {
1938
1983
  const scope = root ?? document;
1939
1984
  const erroredPaths = Object.keys(errors).filter(
@@ -1941,16 +1986,16 @@ var focusFirstError = (errors, root) => {
1941
1986
  );
1942
1987
  const hasRootError = (errors[""]?.length ?? 0) > 0;
1943
1988
  const controls = namedControls(scope);
1944
- const fieldMatches = controls.filter((el) => nameMatchesAny(el, erroredPaths)).filter(isFocusCandidate);
1989
+ const fieldMatches = pathCandidates(scope, erroredPaths).filter(
1990
+ isFocusCandidate
1991
+ );
1945
1992
  const rootFallbackApplies = hasRootError && (root !== void 0 || document.querySelectorAll("form").length <= 1);
1946
1993
  const candidates = fieldMatches.length > 0 ? fieldMatches : rootFallbackApplies ? controls.filter(isFocusCandidate) : [];
1947
1994
  return focusFirstOf(candidates);
1948
1995
  };
1949
1996
  var focusField = (path, root) => {
1950
- const controls = namedControls(root ?? document);
1951
- return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(controls.filter(isFocusCandidate)) : focusFirstOf(
1952
- controls.filter((el) => nameMatchesAny(el, [path])).filter(isFocusCandidate)
1953
- );
1997
+ const scope = root ?? document;
1998
+ return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(namedControls(scope).filter(isFocusCandidate)) : focusFirstOf(pathCandidates(scope, [path]).filter(isFocusCandidate));
1954
1999
  };
1955
2000
 
1956
2001
  exports.CheckboxField = CheckboxField;
@@ -1990,6 +2035,7 @@ exports.useFormSelectorShallow = useFormSelectorShallow;
1990
2035
  exports.useIsDirty = useIsDirty;
1991
2036
  exports.useIsSubmitting = useIsSubmitting;
1992
2037
  exports.useIsValid = useIsValid;
2038
+ exports.useNumberInput = useNumberInput;
1993
2039
  exports.useSubmitCount = useSubmitCount;
1994
2040
  exports.useVariantField = useVariantField;
1995
2041
  exports.validateAsync = validateAsync;