import type { JSX } from "@solidjs/web";
import { type Accessor } from "solid-js";
import { type MaybeAccessor } from "../internals/maybe-accessor.js";
/**
* 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
* `