import type { JSX } from "@solidjs/web"; import { type Accessor, createContext, createMemo, untrack, useContext, } from "solid-js"; import { makeSlotRefDirective } from "../internals/make-slot-ref-directive"; import { type MaybeAccessor, readOption } from "../internals/maybe-accessor"; import { mergeRootProps } from "../internals/merge-root-props"; import { SNAPSHOT_KEYS, type Snapshottable, snapshotKeys, } from "../internals/observability/snapshot"; /** * Why the checked state changed. Emitted on the third arg of * `onCheckedChange(next, event, reason)` and accepted by * `c.select(event?, reason?)`. * * - `"trigger-press"` — click (or keyboard activation that bubbles to click) * on the trigger slot. * - `"imperative"` — programmatic `select()` with no originating DOM event. * * Outer compounds that embed Radio (RadioGroup) translate this to their own * reason taxonomy. */ export type RadioChangeReason = "trigger-press" | "imperative"; /** * Options for {@link createRadio}. * * Every reactive input is `MaybeAccessor` — pass an accessor to make the * value reactive, or a bare value for one-shot config. * * **Solid-native shape (2026-04-30).** No internal state. `checked` is * required and the consumer always owns the signal. The primitive is a pure * function from `(checked, onCheckedChange) → api`. See `solidify.md` §6. */ export interface CreateRadioOptions { /** * Checked state. Consumer-owned signal — the primitive reads through the * accessor and does NOT keep an internal copy. Writes happen via * `onCheckedChange` (the consumer's responsibility to update their signal). * When embedded in a ``, this is wired to * `() => group.value() === props.value` by the wrapper. */ checked: MaybeAccessor; /** * Whether interaction is suppressed. * @default false */ disabled?: MaybeAccessor; /** * Whether the radio is read-only — focusable but not selectable. * @default false */ readOnly?: MaybeAccessor; /** * Whether the radio must be selected before form submission. * @default false */ required?: MaybeAccessor; /** * Change callback. Called synchronously with `(next, event, reason)`. * `event.preventDefault()` is now a no-op for the primitive (the * consumer's signal write is the only writer). * * Radios are one-way: `select()` always sends `next === true`. There is * no path that sends `false` — clicking an already-checked radio is a * no-op (matches Base UI React). */ onCheckedChange?: ( checked: boolean, event: Event, reason: RadioChangeReason, ) => void; } /** * Public API returned by {@link createRadio}. Flat accessors and setters at * the root; no `state` snapshot blob. */ export interface RadioApi { checked: Accessor; disabled: Accessor; readOnly: Accessor; required: Accessor; /** * Select this radio (set `checked = true`). No-op while `disabled` / * `readOnly`, and no-op if already checked. Fires * `onCheckedChange(true, event, reason)`; if the consumer calls * `event.preventDefault()` the internal write is skipped. */ select: (event?: Event, reason?: RadioChangeReason) => void; /** * Trigger slot — getter-bag of attributes ready for spreading onto a * `