Input
Displays a form input field.
src/components/rahti_ui/input.rs
Types
`type` is an ordinary attribute, and left unset an `<input>` is a text field — which is why the first one writes no `type` at all. `.email()`, `.password()`, `.search()`, `.number()`, `.tel()`, `.url()` and `.file()` are shorthands for `.kind(…)`.
States
A `false` leaves the attribute off rather than writing `disabled="false"`, which HTML would read as disabled. `.invalid(true)` writes `aria-invalid`, and the `aria-invalid:` utilities in the class list do the rest.
With a label
`id` and `name` are named props for the same reason `placeholder` is: they are what a form is actually written with. Everything rarer goes through `attrs`.
We will not send anything anywhere.
Attributes
`.attr(…)` writes anything; `.unset(…)` takes one away, which is the thing a value cannot express and the reason a forwarded set is an `Attrs` rather than a list of pairs. The last field has no `data-slot`.
Class override
`class` is merged, not appended: a utility written here replaces the one it conflicts with and leaves the rest of the field alone. The merge is `twMerge`'s, in the browser — so unlike every other field on this page, these three are unstyled until PulsePoint mounts.
Controlled, page scope
A field whose value is this page's state belongs on markup this page owns. `input_variants` returns the same class list the component renders, so a native `<input>` here is an Input in every way that shows. `value={…}` is a controlled binding; PulsePoint keeps it and the state in step.
You typed {query.length} character(s): {query || "—"}
Validation, inline
`.render()` returns the element itself, with no boundary around it, so interpolating it into this block puts it in this block's scope. Handlers and bindings both work, and it is still the same component. `aria-invalid` is bound here, which is what paints the field — the accessible state and the styling are one fact.
{email === "" ? "Waiting for an address." : email.includes("@") ? "Looks like an address." : "Missing an @ — the field is marked invalid."}
The component tag, and its boundary
The tag is the right call for a field this page does not drive — a type, a name, a placeholder, a `required`. What a plain form posts, the tag renders.
Where the tag stops
A form control's own state does not cross the boundary. A click is resolved when it fires, late enough to reach this page — which is why `<Button onclick={…}>` drives a page's counter through the component. `value` and `oninput` are not: PulsePoint owns them, and takes that ownership in the scope the element is mounted in, which through a tag is the component's.
// Compiles against a scope with no `query`, and does nothing.
<Input value={query} oninput={setQuery(target.value)} />It is left out of this page rather than demonstrated: the binding throws a `ReferenceError` into `.rahti/dev.log` on every load, and that log is worth keeping quiet. The two working spellings are the first two sections — `.render()` interpolated into this block, or `input_variants` on a native `<input>`.