docs: correct how a null reaches the number field

Copilot review: the comments offered "a nullable number the user cleared" as a
way to get an explicit `null` into this field. Clearing the box reports
`undefined` — `toNumericValue("")` says so, and an existing test pins it — so
the only way `null` arrives is from parent state: a value received from the
server, restored from a deep link, or written by a caller for a nullable
schema. Say that in all three places instead.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-16 01:08:40 -04:00
commit a945fb71c6
2 changed files with 17 additions and 9 deletions

View file

@ -1743,8 +1743,11 @@ describe("SchemaForm defaulted fields (#2026)", () => {
});
// An explicit `null` is a value, not an absent one, so a non-null default
// must not be substituted for it. Reachable for a nullable number field the
// user cleared, or one the server sent as null.
// must not be substituted for it. Note the field itself never emits `null` —
// clearing it reports `undefined` (pinned by "passes undefined to onChange
// when a number field is cleared") — so this arrives from parent state: a
// value received from the server, restored from a deep link, or written by a
// caller for a nullable schema.
it("shows an explicit null as empty, not as the default", () => {
const schema: InspectorFormSchema = {
type: "object",

View file

@ -341,9 +341,9 @@ interface SchemaNumberInputProps {
* Raw, not `resolveValue`-substituted — see `SchemaJsonFieldProps.value`.
*
* `null` is admitted alongside `undefined` because a nullable number schema
* produces it, and the two are not interchangeable here: `undefined` means
* "no value supplied" and takes `defaultValue`, while `null` is a value and
* displays as an empty box.
* produces it — by way of parent state, never by clearing the box — and the
* two are not interchangeable here: `undefined` means "no value supplied"
* and takes `defaultValue`, while `null` is a value and displays empty.
*/
value: number | null | undefined;
/** The schema `default`, used to seed the draft only (#2026). May be `null`. */
@ -387,10 +387,15 @@ function SchemaNumberInput({
...inputProps
}: SchemaNumberInputProps) {
// `undefined` means "no value supplied", which is what the default is for.
// An explicit `null` is a value — a nullable number the user cleared, or one
// the server sent — so it must not be overwritten by a non-null default; it
// displays as the empty box `resolveValue` produced before. Hence the
// `undefined` test rather than `??`, matching `SchemaJsonField`.
// An explicit `null` is a value, so it must not be overwritten by a non-null
// default; it displays as the empty box `resolveValue` produced before.
// Hence the `undefined` test rather than `??`, matching `SchemaJsonField`.
//
// Clearing the box does *not* produce that `null` — `toNumericValue("")`
// reports `undefined`, which is the behavior the "passes undefined to
// onChange when a number field is cleared" test pins. A `null` reaches this
// field only from parent state: a value received from the server, restored
// from a deep link, or written by a caller for a nullable schema.
const [draft, setDraft] = useState<string | number>(
(value === undefined ? defaultValue : value) ?? "",
);