/** * `useFormField` — Single source of truth for the ARIA wiring that every * Form primitive duplicates: error/helper id derivation, `aria-describedby`, * `aria-invalid`, and the mutually-exclusive error-over-helper convention. * * Layout (label position, error/helper placement, required marker) stays * component-specific because real Form primitives differ in shape — Input * stacks the label on top, Checkbox/Toggle put it inline, RadioGroup and * SegmentGroup have a group caption + per-item labels. The hook does not * touch markup; it just exposes the IDs and the `invalid` boolean. * * **`fieldId` must be supplied by the caller** because Svelte's * `$props.id()` rune is only valid at component top-level — the caller * computes it once with `$props.id()` and threads it through here. That * keeps the hook itself usable from any context (test runs, non-Svelte * code, future refactors) without inheriting the rune placement rules. * * The pure `computeFormFieldAria` helper carries the logic and is * directly unit-tested. `useFormField` is a one-line reactive wrapper * around it that re-evaluates on every prop change via `$derived`. * * @example * ```svelte * * * * * {#if ff.errorId}{/if} * ``` */ export interface UseFormFieldInputs { /** The DOM id to apply to the field element. Compute once in the caller via `$props.id()`. */ fieldId: string; /** Helper text shown below the field when no error is set. */ helper?: string; /** Error message — when truthy, replaces `helper` and flags the field invalid. */ error?: string; /** Required flag — exposed verbatim for the caller's label markup. */ required?: boolean; /** Disabled flag — exposed verbatim. */ disabled?: boolean; } export interface UseFormFieldReturn { /** Stable id to apply to the field's interactive element + its `