formstand 0.11.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,71 @@
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
+
3
69
  ## 0.11.0 — 2026-07-29
4
70
 
5
71
  ### Added
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,7 +1961,10 @@ var nameMatchesAny = (el, paths) => {
1934
1961
  const name = el.getAttribute("name");
1935
1962
  return name !== null && paths.some((p) => isPathOrChild(name, p));
1936
1963
  };
1937
- var cssQuoted = (value) => `"${value.replace(/[\\"]/g, (ch) => `\\${ch}`)}"`;
1964
+ var cssQuoted = (value) => `"${value.replace(/[\\"]/g, (ch) => `\\${ch}`).replace(
1965
+ /[\x00-\x1f\x7f]/g,
1966
+ (ch) => `\\${ch.codePointAt(0).toString(16)} `
1967
+ )}"`;
1938
1968
  var idMatches = (scope, path) => path === "" ? [] : [...scope.querySelectorAll(`[id=${cssQuoted(path)}]`)];
1939
1969
  var inDomOrder = (els) => [...els].sort(
1940
1970
  (a, b) => a === b ? 0 : (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 ? -1 : 1
@@ -1942,8 +1972,9 @@ var inDomOrder = (els) => [...els].sort(
1942
1972
  var pathCandidates = (scope, paths) => {
1943
1973
  const controls = namedControls(scope);
1944
1974
  const named = controls.filter((el) => nameMatchesAny(el, paths));
1975
+ const focusableControls = controls.filter(isFocusCandidate);
1945
1976
  const unnamedPaths = paths.filter(
1946
- (p) => !controls.some((el) => nameMatchesAny(el, [p]))
1977
+ (p) => !focusableControls.some((el) => nameMatchesAny(el, [p]))
1947
1978
  );
1948
1979
  const byId = unnamedPaths.flatMap((p) => idMatches(scope, p));
1949
1980
  return inDomOrder([.../* @__PURE__ */ new Set([...named, ...byId])]);