import { type Accessor, createContext, useContext } from "solid-js"; import type { BaseUIChangeEventDetails, BaseUIEventReasons, } from "../internals/change-event-details"; import type { Orientation } from "../internals/types"; /** * Change-event details handed to `setGroupValue` by child `` elements. * Mirrors `ToggleGroup.ChangeEventDetails` from the React port. */ export type ToggleGroupChangeEventDetails = BaseUIChangeEventDetails< BaseUIEventReasons["none"] >; /** * The value handed down by `` to descendant `` elements. * * Solid 2 adaptations from Base UI React's `ToggleGroupContext`: * * - Reactive slots (`value`, `disabled`) are `Accessor` so toggles pull on * demand without tripping the top-level-reactive-read rule (RFC 01). * - `setGroupValue` is wrapped in `createStableCallback` by the group root so * toggles can capture it into closures without re-wiring across renders. * - `orientation` is a plain value (not an accessor) — it feeds both the * composite root's keyboard dispatcher and the data-orientation projection, * and changing it mid-lifetime is not supported by Base UI React either. * - `present` is true when a real `` is an ancestor; child * toggles branch on it to decide whether to override their own * `pressed`/`onPressedChange` behaviour. */ export interface ToggleGroupContextValue { /** * Current group value — always an array. Single-select groups carry at * most one entry; multi-select carries any subset. */ value: Accessor; /** * Write-through for the group's value. Receives the individual toggle's * `value` string, its `nextPressed` flag (so the group can branch on * single vs. multi select), and the change-event details. Dispatches * `onValueChange(next, details)` and honours `details.cancel()`. */ setGroupValue: ( newValue: Value, nextPressed: boolean, details: ToggleGroupChangeEventDetails, ) => void; /** Whether the group is disabled — propagates to every child toggle. */ disabled: Accessor; /** Navigation orientation. */ orientation: Orientation; /** When `true` the group allows multiple toggles to be pressed at once. */ multiple: Accessor; /** * Whether the group's `value` / `defaultValue` was initialized. Children * use this to detect the "toggle with no value inside an initialized * group" dev-mode error path (matches React). */ isValueInitialized: Accessor; /** Marker — `true` when provided by a real ``. */ readonly present: boolean; } /** * No-op sentinel for when no `` ancestor exists. Solid 2's * `useContext` throws `ContextNotFoundError` when the default is literal * `undefined` — same trap the checkbox-group and radio-group ports sidestep * via sentinel. Children branch on `.present` to decide whether to defer to * the group. */ const NOOP_TOGGLE_GROUP_CONTEXT: ToggleGroupContextValue = { value: () => [], setGroupValue: () => {}, disabled: () => false, orientation: "horizontal", multiple: () => false, isValueInitialized: () => false, present: false, }; /** * Toggle-group context. Default is the NOOP sentinel so `useContext` never * throws. Children branch on `.present` to stay single-path. */ export const ToggleGroupContext = createContext< // biome-ignore lint/suspicious/noExplicitAny: Value is erased at the context boundary; callers narrow per-toggle. ToggleGroupContextValue >(NOOP_TOGGLE_GROUP_CONTEXT); /** * Read the surrounding `` context. Always non-undefined — the * sentinel's `.present === false` signals no group. Child toggle branches on * `context.present` to decide behaviour. */ export function useToggleGroupContext< Value extends string = string, >(): ToggleGroupContextValue { return useContext(ToggleGroupContext) as ToggleGroupContextValue; }