import type { JSX } from "@solidjs/web"; import { type Accessor } from "solid-js"; import { type FieldRegistration, type FieldValidateFn, type FieldValidationMode, type FieldValidationProps, type FieldValidityData, type MutableRef } from "../internals/field-context.js"; import { type MaybeAccessor } from "../internals/maybe-accessor.js"; /** * Why a value/touched/lifecycle change happened. Emitted as the third * argument of `onValueChange(next, event, reason)` and accepted by * imperative setters. * * - `"input"` — change originated from the control's `onInput` handler. * - `"reset"` — change originated from {@link FieldApi.reset}. * - `"imperative"` — programmatic change with no originating DOM event, * or caller-supplied reason not covered above. */ export type FieldChangeReason = "input" | "reset" | "imperative"; /** * Options for {@link createField}. * * Everything reactive accepts `MaybeAccessor` so a consumer can flip * `disabled`/`invalid`/`name` etc. at runtime without re-creating the * primitive. */ export interface CreateFieldOptions { /** * Value accessor. Consumer-owned signal — `createField` reads through * this and never owns its own value state. The setup-time read is * captured and used as the reset target (`reset()` fires * `onValueChange(initial, event, "reset")` so the consumer can * restore their signal). See `solidify.md` §6. */ value?: MaybeAccessor; /** * Pre-change callback. Called synchronously before the internal value * write. The consumer may call `event.preventDefault()` to veto the * change. * * **Pre/post asymmetry — important.** Reading `c.value()` inside this * handler returns the OLD value. For post-change observation, use * `createEffect(c.value, (next) => ...)`. */ onValueChange?: (value: V, event: Event, reason: FieldChangeReason) => void; /** Whether interaction is suppressed. */ disabled?: MaybeAccessor; /** * Force `valid: false` regardless of validator output. Useful when * field validity is owned by an external system (server response, * separate model layer). */ invalid?: MaybeAccessor; /** * External `dirty` override. When defined, `c.dirty()` reads this * instead of the internal flip-flop. Setter calls become no-ops * (consumer is the source of truth). */ dirty?: MaybeAccessor; /** External `touched` override. Same shape as {@link dirty}. */ touched?: MaybeAccessor; /** * Identifier this field uses when registering into a surrounding * `
`. Optional — when omitted, the field still validates but * isn't keyed into the form's `fields` map by name (server-error * lookup uses this name; cross-field `formValues` keys by it). */ name?: MaybeAccessor; /** * Custom validator. Receives the current value and a snapshot of all * sibling field values keyed by name. Return a string / list of * strings (errors) or `null` (valid). Both sync and `async` shapes * are supported — `async` validators (`zod.parseAsync`, async * refinements, server-side checks) are awaited by the surrounding * `` on submit before validity is read. */ validate?: FieldValidateFn; /** * When validation should run. Inherited from `` * if undefined. * * - `"onSubmit"` — only on form submit (default after a failed submit * triggers re-validation on every change). * - `"onBlur"` — when the control loses focus. * - `"onChange"` — every input event (with optional debounce). */ validationMode?: MaybeAccessor; /** Debounce for `validationMode === "onChange"` (ms). */ validationDebounceTime?: MaybeAccessor; } /** * Public API returned by {@link createField}. Flat accessors at the root — * no `state` blob. * * The `[SNAPSHOT_KEYS]` symbol is attached post-construction (NOT declared * on this interface; see `primitive-template.md` §2.5) so `snapshot(c)` * returns a debug-friendly plain-object view of the field's logical state. */ export interface FieldApi { /** Current value (controlled accessor wins; falls back to internal). */ value: Accessor; /** * Imperative value setter. Fires `onValueChange(next, event, reason)` * before the internal write; consumer can `event.preventDefault()` to * veto. */ setValue: (next: V, event?: Event, reason?: FieldChangeReason) => void; /** Whether interaction is suppressed. */ disabled: Accessor; /** External-invalid override or form-level error presence. */ invalid: Accessor; /** Whether the user has typed/cleared since mount. */ dirty: Accessor; /** Whether the control has lost focus at least once. */ touched: Accessor; /** Whether the input element currently has a non-empty value. */ filled: Accessor; /** Whether the input element currently has focus. */ focused: Accessor; setDirty: (value: boolean) => void; setTouched: (value: boolean) => void; setFilled: (value: boolean) => void; setFocused: (value: boolean) => void; /** Raw validity record (state + errors + value snapshot). */ validity: Accessor; /** * Tri-state: `true` (passed all checks), `false` (custom or native * error), `null` (not yet validated). */ valid: Accessor; /** True while an async `validate` is in-flight. */ validating: Accessor; /** Active error messages from the last validation. */ errors: Accessor; /** * First / primary error, if any. Convenience for the common * single-error rendering case. */ error: Accessor; /** * Imperatively validate the current value. Returns the in-flight * Promise when `options.validate` is `async` (so callers can `await`); * returns `void` when the validator is sync (writes have already * landed). The surrounding ``'s submit path uses the same * shape to gate submission on async validators. */ validate: (event?: Event) => Promise | void; /** * Restore the value to the setup-time captured initial, clear * touched/dirty/validity. Fires `onValueChange(initial, event, * "reset")` with the synthesized event so consumers can observe (and * optionally veto). */ reset: (event?: Event) => void; /** * Control slot — getter-bag of HTML attributes ready for spreading * onto an `` (or any compatible element). Includes the * validation pipeline's `onInput` handler, `aria-invalid`, focus/blur * handlers, and the registered control id. */ control: (userProps?: U) => FieldControlSlotProps & (U extends object ? U : object); /** Directive form of {@link control}. */ controlRef: () => (el: HTMLInputElement | null) => void; /** * Context provider. Wrap a subtree to make this api discoverable via * {@link useField} without prop-drilling. */ Provider: (props: { children: JSX.Element; }) => JSX.Element; /** * Internal bridge surface for `` composition. Not part of * the public API — see {@link FieldApiInternal}. */ _internal: FieldApiInternal; } /** * Bridge surface exposed on `api._internal`. Consumed by `` to * compose the `FieldContextValue` without duplicating state. **Not part of * the public API** — the leading underscore signals intent; downstream code * should use `FieldApi` members directly. */ export interface FieldApiInternal<_V = unknown> { /** The raw `validate` option as a reactive accessor (for useFieldValidation). */ validateFn: Accessor; /** Props-factories + inputRef + commit from the composed validation pipeline. */ validation: FieldValidationProps; /** The `markedDirtyRef` used to gate `valueMissing`-only errors. */ markedDirtyRef: MutableRef; /** Resolved validation mode (inherits from Form). */ validationMode: Accessor; /** Debounce time in ms for `onChange` mode. */ validationDebounceTime: Accessor; /** Whether validation should run on every input change. */ shouldValidateOnChange: Accessor; /** Direct setter for `validityData` (bypasses the tracked commit path). */ setValidityData: (next: FieldValidityData) => void; /** Combined field state for `data-*` projections. */ state: Accessor<{ disabled: boolean; touched: boolean; dirty: boolean; valid: boolean | null; filled: boolean; focused: boolean; }>; /** * Register (or unregister) a control via the same symbol+registration * protocol used by ``. FieldRoot builds its own thin * `registerFieldControl` on top of this to sync `initialValue` while * leaving form-entry management to createField. * * `controlRef` on the registration is intentionally not used here — * `Field.Control` already writes `field.validation.inputRef.current` * directly via its `ref` prop, which is the same ref `useFieldValidation` * reads for `element.validity` checks. */ pushInitialValue: (registration: FieldRegistration | undefined) => void; /** * Override createField's auto-generated controlId. FieldRoot's bridge * calls this from `registerFieldControl` so the form-fields-map key * matches the actual ``'s DOM id (which comes from the * labelable-provider id pool, not createField's `createUniqueId()`). */ setControlId: (id: string) => void; } /** * Shape of attributes emitted by `c.control()`. Intersected with any * caller-supplied extras at the call site. */ export interface FieldControlSlotProps { id: string; name: string | undefined; disabled: boolean; "aria-invalid": "true" | "false"; onInput: (event: InputEvent & { currentTarget: HTMLInputElement; }) => void; onFocus: (event: FocusEvent) => void; onBlur: (event: FocusEvent & { currentTarget: HTMLInputElement; }) => void; } /** * Read the nearest {@link FieldApi} provided by `c.Provider`. Throws if * called outside one — match the * `useCollapsible()` / `useToggle()` pattern. */ export declare function useField(): FieldApi; /** * Solid 2.0-native primitive for a single form field. Owns value * (controlled/uncontrolled), interaction lifecycle (touched/dirty/ * filled/focused), and the validation pipeline. Composes * `useFieldValidation` under the hood — async validators (`async fn`, * `zod.parseAsync`, server-side checks) are first-class and gate the * surrounding ``'s submit when used inside one. * * Consumers can drive markup either via `c.control()` (slot getter-bag * spread onto an ``) or via `c.controlRef()` (directive form, for * cva-style variants and any consumer-owned JSX). Both surfaces wire the * same internal handlers. * * When called inside a ``, the field auto-registers into the form's * fields map under the generated control id so submit-time validation * can find it. Outside a Form, the field works standalone. */ export declare function createField(options?: CreateFieldOptions): FieldApi; //# sourceMappingURL=create-field.d.ts.map