formstand 0.17.0 → 0.18.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 +61 -0
- package/dist/{createForm-D0xLZH-Q.d.cts → createForm-B73p1TgL.d.cts} +2 -1
- package/dist/{createForm-D0xLZH-Q.d.ts → createForm-B73p1TgL.d.ts} +2 -1
- package/dist/devtools/index.d.cts +1 -1
- package/dist/devtools/index.d.ts +1 -1
- package/dist/index.cjs +19 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0 — 2026-09-17
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **`PersistHandle.restore()` is now `load()`** (breaking). The method's
|
|
8
|
+
DEFAULT application is `adoptValues` — a rebase that reads clean — and
|
|
9
|
+
the old name promised the setValues semantics it only has under
|
|
10
|
+
`apply: "restore"`. The name now describes what it does; the `apply`
|
|
11
|
+
mode names are unchanged. `usePersistForm`'s handle follows.
|
|
12
|
+
- **Cleared inputs write `emptyValue` whenever the schema accepts it.**
|
|
13
|
+
`textInputProps`/`selectProps` (and the bound `TextField`/`SelectField`)
|
|
14
|
+
used to write the empty value only for nullable fields, so clearing
|
|
15
|
+
`z.string().optional()` left `""` behind — a documented asymmetry that
|
|
16
|
+
kept the field dirty against an `undefined` initial and made an optional
|
|
17
|
+
enum select unclearable. Clearing now writes `undefined` for optional
|
|
18
|
+
and defaulted fields and `null` for nullable ones; a REQUIRED string
|
|
19
|
+
cleared to `""` still stays `""`, so the schema judges a visible value
|
|
20
|
+
instead of a hole it rejects. `SelectField`'s empty option is
|
|
21
|
+
accordingly selectable for every clearable field, not just nullable
|
|
22
|
+
ones.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`field.clearable`** on `UseFieldReturn`: whether the schema accepts
|
|
27
|
+
the field's `emptyValue` (optional, nullable, or defaulted),
|
|
28
|
+
introspected from the zod schema with the same shallow walk as
|
|
29
|
+
`emptyValue` so the pair can never disagree. Schema-less forms fall
|
|
30
|
+
back to the null-initial heuristic. **`isClearableSchema(schema)`** is
|
|
31
|
+
the exported rule behind it, next to `emptyValueForSchema`.
|
|
32
|
+
|
|
33
|
+
## formstand-cli 0.18.0 — 2026-09-17
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **Generated kit adapters clear through `field.clearable`.** The
|
|
38
|
+
emitted text and select bindings wrote the empty value only for
|
|
39
|
+
nullable fields, mirroring the library's old rule; they now write
|
|
40
|
+
`field.emptyValue` whenever `field.clearable` says the schema accepts
|
|
41
|
+
it, so generated optional fields clear to `undefined` instead of
|
|
42
|
+
holding `""`. Kit output therefore needs formstand 0.18; regenerate
|
|
43
|
+
to pick the rule up, or keep the old adapter — both compile.
|
|
44
|
+
|
|
45
|
+
## formstand-cli 0.17.0 — 2026-09-17
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **Containers inside array rows generate.** The remaining row-shape
|
|
50
|
+
TODO matrix is gone (formstand 0.17 types the paths): a tuple as the
|
|
51
|
+
row item or as a row-object field binds its positions inline at static
|
|
52
|
+
sub-indices (`points.${index}.0`, `segments.${index}.span.1`); a
|
|
53
|
+
discriminated union as a row-object FIELD binds on the holed template
|
|
54
|
+
(`invoices.${p0}.pay`), optional/nullable ones keeping the clearable
|
|
55
|
+
select; an array-of-arrays item extracts an inner Rows component; and
|
|
56
|
+
the same shapes under nested arrays work at any depth, union items
|
|
57
|
+
included (`teams.${p0}.methods.${index}`). Single-file extracts a
|
|
58
|
+
child component per site that needs hooks (rows render inside
|
|
59
|
+
rows.map); the module layout binds in the Row component, which already
|
|
60
|
+
is one. The one shape that still degrades to a TODO is a container
|
|
61
|
+
inside a union variant — binding it needs variant sub-paths the
|
|
62
|
+
library does not type.
|
|
63
|
+
|
|
3
64
|
## 0.17.0 — 2026-09-17
|
|
4
65
|
|
|
5
66
|
### Added
|
|
@@ -108,6 +108,7 @@ declare const validateSync: <TSchema extends z.ZodType>(schema: TSchema, values:
|
|
|
108
108
|
declare const validateAsync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => Promise<SettledValidationResult<z.output<TSchema>>>;
|
|
109
109
|
declare const isAsyncRequiredError: (e: unknown) => boolean;
|
|
110
110
|
declare const emptyValueForSchema: (schema: z.ZodType) => null | undefined;
|
|
111
|
+
declare const isClearableSchema: (schema: z.ZodType) => boolean;
|
|
111
112
|
|
|
112
113
|
type ReadonlyStoreApi<T> = Pick<StoreApi<T>, "getState" | "getInitialState" | "subscribe">;
|
|
113
114
|
type CreateFormOptions<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = Readonly<{
|
|
@@ -197,4 +198,4 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
197
198
|
}>;
|
|
198
199
|
declare const createForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(schema: TSchema, options: CreateFormOptions<TSchema, D>) => Form<TSchema, D>;
|
|
199
200
|
|
|
200
|
-
export { type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type FieldValidationResult as a, type FormState as b, type FieldPath as c, type FieldValue as d, type FieldSnapshot as e, type FieldsValidationResult as f, type ResetOptions as g, type SettledFieldsValidationResult as h, type SettledValidationResult as i, type SubmitHandler as j, type SubmitOptions as k, type SubmitResult as l, type ValidationResult as m, type ValidationTrigger as n, createForm as o, emptyValueForSchema as p, flattenIssues as q, isAsyncRequiredError as r,
|
|
201
|
+
export { type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type FieldValidationResult as a, type FormState as b, type FieldPath as c, type FieldValue as d, type FieldSnapshot as e, type FieldsValidationResult as f, type ResetOptions as g, type SettledFieldsValidationResult as h, type SettledValidationResult as i, type SubmitHandler as j, type SubmitOptions as k, type SubmitResult as l, type ValidationResult as m, type ValidationTrigger as n, createForm as o, emptyValueForSchema as p, flattenIssues as q, isAsyncRequiredError as r, isClearableSchema as s, shouldValidateOn as t, validateSync as u, validateAsync as v };
|
|
@@ -108,6 +108,7 @@ declare const validateSync: <TSchema extends z.ZodType>(schema: TSchema, values:
|
|
|
108
108
|
declare const validateAsync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => Promise<SettledValidationResult<z.output<TSchema>>>;
|
|
109
109
|
declare const isAsyncRequiredError: (e: unknown) => boolean;
|
|
110
110
|
declare const emptyValueForSchema: (schema: z.ZodType) => null | undefined;
|
|
111
|
+
declare const isClearableSchema: (schema: z.ZodType) => boolean;
|
|
111
112
|
|
|
112
113
|
type ReadonlyStoreApi<T> = Pick<StoreApi<T>, "getState" | "getInitialState" | "subscribe">;
|
|
113
114
|
type CreateFormOptions<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = Readonly<{
|
|
@@ -197,4 +198,4 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
|
|
|
197
198
|
}>;
|
|
198
199
|
declare const createForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(schema: TSchema, options: CreateFormOptions<TSchema, D>) => Form<TSchema, D>;
|
|
199
200
|
|
|
200
|
-
export { type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type FieldValidationResult as a, type FormState as b, type FieldPath as c, type FieldValue as d, type FieldSnapshot as e, type FieldsValidationResult as f, type ResetOptions as g, type SettledFieldsValidationResult as h, type SettledValidationResult as i, type SubmitHandler as j, type SubmitOptions as k, type SubmitResult as l, type ValidationResult as m, type ValidationTrigger as n, createForm as o, emptyValueForSchema as p, flattenIssues as q, isAsyncRequiredError as r,
|
|
201
|
+
export { type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type FieldValidationResult as a, type FormState as b, type FieldPath as c, type FieldValue as d, type FieldSnapshot as e, type FieldsValidationResult as f, type ResetOptions as g, type SettledFieldsValidationResult as h, type SettledValidationResult as i, type SubmitHandler as j, type SubmitOptions as k, type SubmitResult as l, type ValidationResult as m, type ValidationTrigger as n, createForm as o, emptyValueForSchema as p, flattenIssues as q, isAsyncRequiredError as r, isClearableSchema as s, shouldValidateOn as t, validateSync as u, validateAsync as v };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { F as Form } from '../createForm-
|
|
3
|
+
import { F as Form } from '../createForm-B73p1TgL.cjs';
|
|
4
4
|
import 'zustand/vanilla';
|
|
5
5
|
|
|
6
6
|
type DevtoolsPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
package/dist/devtools/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { F as Form } from '../createForm-
|
|
3
|
+
import { F as Form } from '../createForm-B73p1TgL.js';
|
|
4
4
|
import 'zustand/vanilla';
|
|
5
5
|
|
|
6
6
|
type DevtoolsPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
package/dist/index.cjs
CHANGED
|
@@ -276,6 +276,7 @@ var emptyValueForSchema = (schema) => {
|
|
|
276
276
|
};
|
|
277
277
|
return walk(schema, false);
|
|
278
278
|
};
|
|
279
|
+
var isClearableSchema = (schema) => schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodNullable || schema instanceof zod.z.ZodDefault || schema instanceof zod.z.ZodPrefault;
|
|
279
280
|
var unwrapForWalk = (s) => {
|
|
280
281
|
if (s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodNullable) {
|
|
281
282
|
return s.unwrap();
|
|
@@ -1197,7 +1198,7 @@ var persistForm = (form, options) => {
|
|
|
1197
1198
|
return void 0;
|
|
1198
1199
|
}
|
|
1199
1200
|
};
|
|
1200
|
-
const
|
|
1201
|
+
const load = () => {
|
|
1201
1202
|
try {
|
|
1202
1203
|
const raw = storage === null ? null : storage.getItem(options.key);
|
|
1203
1204
|
if (raw === null) return false;
|
|
@@ -1242,7 +1243,7 @@ var persistForm = (form, options) => {
|
|
|
1242
1243
|
}
|
|
1243
1244
|
};
|
|
1244
1245
|
if (apply !== "manual") {
|
|
1245
|
-
|
|
1246
|
+
load();
|
|
1246
1247
|
}
|
|
1247
1248
|
const unsubscribe = form.watchValues((values) => {
|
|
1248
1249
|
cancelPending();
|
|
@@ -1256,7 +1257,7 @@ var persistForm = (form, options) => {
|
|
|
1256
1257
|
}
|
|
1257
1258
|
});
|
|
1258
1259
|
return Object.freeze({
|
|
1259
|
-
|
|
1260
|
+
load,
|
|
1260
1261
|
clear: () => {
|
|
1261
1262
|
cancelPending();
|
|
1262
1263
|
try {
|
|
@@ -1287,7 +1288,7 @@ var usePersistForm = (form, options) => {
|
|
|
1287
1288
|
}, [form, key]);
|
|
1288
1289
|
return react.useMemo(
|
|
1289
1290
|
() => ({
|
|
1290
|
-
|
|
1291
|
+
load: () => inner.current?.load() ?? false,
|
|
1291
1292
|
clear: () => inner.current?.clear(),
|
|
1292
1293
|
// dispose stops THIS component's watching early; the effect cleanup
|
|
1293
1294
|
// still runs its own dispose harmlessly (persistForm's dispose is
|
|
@@ -1351,12 +1352,17 @@ function useField(form, pathArg, options) {
|
|
|
1351
1352
|
const path = slice.path;
|
|
1352
1353
|
const formSchema = form.schema;
|
|
1353
1354
|
const initialValue = slice.initialValue;
|
|
1354
|
-
const emptyValue = react.useMemo(() => {
|
|
1355
|
+
const { emptyValue, clearable } = react.useMemo(() => {
|
|
1355
1356
|
if (formSchema !== void 0) {
|
|
1356
1357
|
const sub = fieldSchemaAtPath(formSchema, path);
|
|
1357
|
-
if (sub !== null)
|
|
1358
|
+
if (sub !== null) {
|
|
1359
|
+
return {
|
|
1360
|
+
emptyValue: emptyValueForSchema(sub),
|
|
1361
|
+
clearable: isClearableSchema(sub)
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1358
1364
|
}
|
|
1359
|
-
return initialValue === null ? null : void 0;
|
|
1365
|
+
return initialValue === null ? { emptyValue: null, clearable: true } : { emptyValue: void 0, clearable: false };
|
|
1360
1366
|
}, [formSchema, path, initialValue]);
|
|
1361
1367
|
const debounceMs = options?.debounceMs;
|
|
1362
1368
|
const debounceTimerRef = react.useRef(null);
|
|
@@ -1442,6 +1448,7 @@ function useField(form, pathArg, options) {
|
|
|
1442
1448
|
value: slice.value,
|
|
1443
1449
|
initialValue: slice.initialValue,
|
|
1444
1450
|
emptyValue,
|
|
1451
|
+
clearable,
|
|
1445
1452
|
error: slice.error,
|
|
1446
1453
|
// `?.[0]` covers both the undefined channel and an empty array.
|
|
1447
1454
|
firstError: slice.error?.[0],
|
|
@@ -1456,7 +1463,7 @@ function useField(form, pathArg, options) {
|
|
|
1456
1463
|
validateAsync: validateAsync2,
|
|
1457
1464
|
onBlur
|
|
1458
1465
|
}),
|
|
1459
|
-
[slice, emptyValue, setValue, setTouched, setError, clearError, validate, validateAsync2, onBlur]
|
|
1466
|
+
[slice, emptyValue, clearable, setValue, setTouched, setError, clearError, validate, validateAsync2, onBlur]
|
|
1460
1467
|
);
|
|
1461
1468
|
}
|
|
1462
1469
|
|
|
@@ -1797,9 +1804,7 @@ var textInputProps = (field) => ({
|
|
|
1797
1804
|
"aria-invalid": ariaInvalid(field),
|
|
1798
1805
|
onChange: (e) => {
|
|
1799
1806
|
const text = e.target.value;
|
|
1800
|
-
field.setValue(
|
|
1801
|
-
text === "" && field.emptyValue === null ? null : text
|
|
1802
|
-
);
|
|
1807
|
+
field.setValue(text === "" && field.clearable ? field.emptyValue : text);
|
|
1803
1808
|
},
|
|
1804
1809
|
onBlur: field.onBlur
|
|
1805
1810
|
});
|
|
@@ -1873,9 +1878,7 @@ var selectProps = (field) => ({
|
|
|
1873
1878
|
"aria-invalid": ariaInvalid(field),
|
|
1874
1879
|
onChange: (e) => {
|
|
1875
1880
|
const v = e.target.value;
|
|
1876
|
-
field.setValue(
|
|
1877
|
-
v === "" && field.emptyValue === null ? null : v
|
|
1878
|
-
);
|
|
1881
|
+
field.setValue(v === "" && field.clearable ? field.emptyValue : v);
|
|
1879
1882
|
},
|
|
1880
1883
|
onBlur: field.onBlur
|
|
1881
1884
|
});
|
|
@@ -2082,7 +2085,7 @@ var SelectField = ({
|
|
|
2082
2085
|
const field = useField(form, path);
|
|
2083
2086
|
const a11y = useFieldA11y(field.error);
|
|
2084
2087
|
const select = selectProps(field);
|
|
2085
|
-
const clearable = field.
|
|
2088
|
+
const clearable = field.clearable;
|
|
2086
2089
|
const hasExplicitEmptyOption = options.some((opt) => opt.value === "");
|
|
2087
2090
|
const showEmptyOption = !hasExplicitEmptyOption && (clearable || select.value === "" || placeholder !== void 0);
|
|
2088
2091
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2189,6 +2192,7 @@ exports.focusFirstError = focusFirstError;
|
|
|
2189
2192
|
exports.getAtPath = getAtPath;
|
|
2190
2193
|
exports.hasFieldError = hasFieldError;
|
|
2191
2194
|
exports.isAsyncRequiredError = isAsyncRequiredError;
|
|
2195
|
+
exports.isClearableSchema = isClearableSchema;
|
|
2192
2196
|
exports.numberInputProps = numberInputProps;
|
|
2193
2197
|
exports.numberToInputText = numberToInputText;
|
|
2194
2198
|
exports.parseDateText = parseDateText;
|