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)`. * * - `"trigger-press"` — click / keyboard activation that bubbles to click on * the checkbox root, OR a change on the co-located hidden ``. * - `"imperative"` — programmatic `setChecked` / `toggle` with no originating * DOM event. */ export type CheckboxChangeReason = "trigger-press" | "imperative"; /** * Options for {@link createCheckbox}. Every reactive input is * `MaybeAccessor` — pass an accessor to make the value reactive. * * Design note on tri-state: the rollout doc floated * `checked: boolean | "indeterminate"` for the api shape; this primitive * mirrors React's two-signal convention instead (`checked` + `indeterminate` * are independent). Reason: indeterminate is a presentation concern (what * `aria-checked` reports + native `.indeterminate` property) layered * on top of the underlying boolean. Folding them into a single union loses * the "what does the next click flip to" answer (native checkboxes flip an * indeterminate one to `true`, regardless of the underlying boolean), and * forces consumers to encode/decode the union at every read site. * * **Solid-native shape (2026-04-30).** No internal state. `checked` is * required and the consumer always owns the signal. See `solidify.md` §6. */ export interface CreateCheckboxOptions { /** * 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 in response). */ checked: MaybeAccessor; /** * Whether the checkbox is in mixed state. Independent of `checked`: * a click on an indeterminate checkbox flips `checked` to `true` * regardless of the current `checked` value (matches the native * `.indeterminate` semantics). Always reactive. * @default false */ indeterminate?: MaybeAccessor; /** * Whether interaction is suppressed. * @default false */ disabled?: MaybeAccessor; /** * Whether the checkbox is read-only — focusable but not activatable. * @default false */ readOnly?: MaybeAccessor; /** * Whether the checkbox must be ticked for form submission. * @default false */ required?: MaybeAccessor; /** * Pre-change callback. `event.preventDefault()` is now a no-op for * the primitive (the consumer's signal write is the only writer); * left in the signature for parity with other event channels. * * Pre/post asymmetry: reading `c.checked()` inside this handler returns * the OLD value (the consumer's signal write hasn't flushed yet). For * post-change observation use `createEffect(c.checked, ...)`. */ onCheckedChange?: ( checked: boolean, event: Event, reason: CheckboxChangeReason, ) => void; } /** * Public API returned by {@link createCheckbox}. Flat accessors and setters * at the root; no `state` snapshot blob. */ export interface CheckboxApi { checked: Accessor; indeterminate: Accessor; disabled: Accessor; readOnly: Accessor; required: Accessor; /** * Click-style toggle. While indeterminate, flips to `true` regardless * of the underlying `checked` value (native `` parity). * No-op while `disabled` or `readOnly`. */ toggle: (event?: Event, reason?: CheckboxChangeReason) => void; /** * Pure event-emitter: fires `onCheckedChange(next, event, reason)`. * No-op while `disabled` or `readOnly`. The consumer's signal write * (in their `onCheckedChange` handler) is the only writer. */ setChecked: ( next: boolean, event?: Event, reason?: CheckboxChangeReason, ) => void; /** * Trigger slot — getter-bag of attributes ready for spreading onto a * `