import type {
InputHTMLAttributes,
SelectHTMLAttributes,
} from "react";
import { seedValue, type SeedSource } from "../../lib/formState";
// The labelled text/number input the editor's forms are built out of.
//
// ONE FIELD SINCE RELEASE 16 (slice FK). Slice 3 lifted this out of
// SettingsForm and left SiteForm's and ChannelForm's own copies alone on
// purpose ("leave them until something actually needs them to be one"). Slice
// FK is that something: all three had to learn to seed from a failed action's
// values, and they were the same label → input → hint, differing only in props
// (`step` here, `min` in SiteForm's, `placeholder`/`readOnly`/a controlled mode
// in ChannelForm's). Those props are all here now. OverviewPanel's `Field` is a
// different thing (a read-only figure, no input) and stays its own.
//
// `state` is the form's `useActionState` state. When the last submit failed,
// the input's default is what was submitted (lib/formState.ts), so the reset
// React runs after the action puts the operator's text back instead of the
// stored value. `defaultValue` is the INITIAL value — the stored one.
//
// `value` + `onChange` make the field CONTROLLED (ChannelForm's download-filter
// patterns are, so their live values can be read without touching the DOM); a
// controlled field survives the reset on its own and ignores `state`.
export function Field({
label,
name,
defaultValue,
state,
hint,
required,
type = "text",
step,
min,
placeholder,
readOnly,
value,
onChange,
}: {
label: string;
name: string;
defaultValue?: string;
state?: SeedSource;
hint?: string;
required?: boolean;
type?: string;
// `type="number"` carries an IMPLICIT step of 1, so a decimal value fails
// constraint validation and the browser blocks the submit SILENTLY — no
// error, no request, the save just never happens. Any numeric field that
// accepts fractions must pass a step.
step?: string;
min?: string;
placeholder?: string;
readOnly?: boolean;
value?: string;
onChange?: (v: string) => void;
}) {
const className = `rounded border border-border bg-card px-2 py-1 text-sm${readOnly ? " opacity-60" : ""}`;
const shared = { type, required, step, min, placeholder, readOnly, className };
return (
);
}
// An UNCONTROLLED input whose default is the failed submit's value, else
// `initial`. For the inputs that are not a `Field` (their own label markup).
//
// A NUMBER INPUT IS RE-MOUNTED when its default changes. React does not write a
// new `defaultValue` into a number input that has focus (react-dom
// `setDefaultValue` skips it, so typing "1." is not rewritten under the cursor)
// — and pressing Enter in one submits the form with the focus still there, so
// the reset would put the OLD default back. A new element takes its default at
// mount, focused or not. The key is the default itself: when it does not
// change there is nothing to re-apply.
export function SeededInput({
state,
name,
initial,
type = "text",
...rest
}: Omit<
InputHTMLAttributes,
"defaultValue" | "value" | "name"
> & {
state: SeedSource;
name: string;
initial: string;
}) {
const seeded = seedValue(state, name, initial);
return (
);
}
// An UNCONTROLLED select whose default is the failed submit's value, else
// `initial`.
//
// RE-MOUNTED WHENEVER ITS DEFAULT CHANGES: React applies a select's
// `defaultValue` only when it mounts (react-dom: an update re-applies it only
// when `multiple` toggles), so a new `defaultValue` never reaches the DOM and
// the reset puts back the option the select MOUNTED with — after a failure, and
// after a success too (the stored value changed, the select did not). The key
// is the default itself.
export function SeededSelect({
state,
name,
initial,
...rest
}: Omit<
SelectHTMLAttributes,
"defaultValue" | "value" | "name"
> & {
state: SeedSource;
name: string;
initial: string;
}) {
const seeded = seedValue(state, name, initial);
return ;
}