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 +66 -0
- package/dist/index.cjs +43 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +43 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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) =>
|
|
1480
|
-
|
|
1491
|
+
(item) => {
|
|
1492
|
+
form.arrayPush(path, item);
|
|
1493
|
+
revalidate();
|
|
1494
|
+
},
|
|
1495
|
+
[form, path, revalidate]
|
|
1481
1496
|
);
|
|
1482
1497
|
const remove = react.useCallback(
|
|
1483
|
-
(index) =>
|
|
1484
|
-
|
|
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) =>
|
|
1488
|
-
|
|
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) =>
|
|
1492
|
-
|
|
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) =>
|
|
1496
|
-
|
|
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) => !
|
|
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])]);
|