import type * as React from 'react'; import type { FormValidityHandle } from '../utils/formControl.js'; import type { PrimitivePropsWithRef } from '../utils/primitive.js'; import type { Orientation } from '../utils/useRovingFocus.js'; /** * Why the selected item changed: keyboard arrow/Home/End, pointer click/tap, * or programmatic value write. */ export type RadioGroupChangeReason = 'keyboard' | 'pointer' | 'programmatic'; /** * The payload passed to `onValueChange`, carrying the new value, prior value, and reason. */ export interface RadioGroupValueChangeDetail { /** The item that is now selected. */ value: string; /** The value selected before this change, or `null` when nothing was. */ previousValue: string | null; /** * What drove the change: `'pointer'` for a click, `'keyboard'` for the focus * an arrow key/Home/End moved, `'programmatic'` for a form reset. */ reason: RadioGroupChangeReason; } /** * The forwarded ref's handle: the root `
` with constraint-validation * methods assigned on, preserving all HTMLDivElement methods. */ export type RadioGroupHandle = HTMLDivElement & FormValidityHandle; /** @internal Shared state distributed from {@link RadioGroup} to its {@link RadioGroupItem}s via context. */ export interface RadioGroupContextValue { name?: string | undefined; /** The group's `form` id, forwarded to every item's hidden radio input. */ form?: string | undefined; /** `null` is the explicit empty selection; `undefined` only ever means "uncontrolled". */ value?: string | null | undefined; required: boolean; disabled: boolean; /** Frozen but not disabled: items keep their tab stop and refuse to change the value. */ readOnly: boolean; /** Selects `value` and reports it, tagged with what drove the selection. */ onValueChange: (value: string, reason: RadioGroupChangeReason) => void; /** Fallback single tab stop when no item is checked yet. */ firstEnabledValue: string | null; } /** * Props for the {@link RadioGroup} root, with `defaultValue` redeclared to accept `null`. */ export interface RadioGroupProps extends Omit, 'defaultValue'> { /** * The radios rendered from data, one derived `RadioGroupItem` each; ignored when `children` is given. * @default undefined — nothing is derived; compose the items as `children`. */ options?: RadioGroupOption[] | undefined; /** * Selected item's value for controlled usage; `null` is explicit empty selection, `undefined` is uncontrolled. * @default undefined — uncontrolled, tracks its own selected value internally. */ value?: string | null | undefined; /** * Initially selected item's value for uncontrolled usage; `null` and `undefined` both start unselected. * @default undefined — no item selected until the user picks one. */ defaultValue?: string | null | undefined; /** * Fires when the selected item changes, with the new value, prior value, and reason; re-selecting emits nothing. * @default undefined — no-op if omitted. */ onValueChange?: ((detail: RadioGroupValueChangeDetail) => void) | undefined; /** * Name for native form participation via a hidden input rendered by each item. * @default undefined — no hidden input rendered. */ name?: string | undefined; /** * Marks the group as required for native form validation. * @default false */ required?: boolean | undefined; /** * Id of the `
` that owns this group, for one rendered outside it (e.g. in a portal). * @default undefined — the group belongs to its nearest ancestor ``, if any. */ form?: string | undefined; /** * Disables every item in the group. * @default false */ disabled?: boolean | undefined; /** * Freezes the selection without freezing the group: focus and arrow keys still move, but `value` cannot change. * @default false */ readOnly?: boolean | undefined; /** * Marks the group as invalid, exposed as `aria-invalid`/`data-invalid` on * the `role="radiogroup"` root. * @default false */ invalid?: boolean | undefined; /** * Layout axis for arrow-key roving focus between items; `'both'` responds to all four arrow keys. * @default 'both' */ orientation?: Orientation | undefined; /** * Whether arrow-key navigation wraps from the last item back to the first * (and vice versa). * @default true */ loop?: boolean | undefined; } /** @internal Shared state distributed from a {@link RadioGroupItem} to its {@link RadioGroupIndicator} via context. */ export interface RadioItemContextValue { checked: boolean; disabled: boolean; } /** One entry of {@link RadioGroupProps.options} — a single radio. */ export interface RadioGroupOption { /** Unique identifier compared against the group's selected `value`. */ value: string; /** Text rendered inside the derived `RadioGroupItem`, which is what names it. */ label: React.ReactNode; /** * Prevents this radio from being selected and removes it from the roving order. * @default false */ disabled?: boolean | undefined; } /** Props for a single {@link RadioGroupItem}. */ export interface RadioGroupItemProps extends PrimitivePropsWithRef<'button'> { /** Unique identifier compared against the group's selected `value`. */ value: string; /** * Prevents this radio from being selected; when set, ORs with group's `disabled`. * @default false */ disabled?: boolean | undefined; } /** Props for a {@link RadioGroupIndicator}, the visual mark shown when its item is checked. */ export interface RadioGroupIndicatorProps extends PrimitivePropsWithRef<'span'> { /** * Keep mounted regardless of state (for exit animations). * @default false */ forceMount?: boolean | undefined; }