import type { JSX, ValidComponent } from "@solidjs/web"; import { dynamic } from "@solidjs/web"; import { createMemo, createSignal, omit, untrack } from "solid-js"; import { useBaseUiId } from "../../internals/base-ui-id"; import { createChangeEventDetails, createGenericEventDetails, REASONS, } from "../../internals/change-event-details"; import { composeRefs, userRef } from "../../internals/compose-refs"; import type { CompositeMetadata } from "../../internals/composite/composite-context.ts"; import { CompositeList, type CompositeListProps, } from "../../internals/composite/composite-list.tsx"; import { createControlledState } from "../../internals/create-controlled-state"; import { activeElement } from "../../internals/dom/active-element.ts"; import { contains } from "../../internals/dom/contains.ts"; import { ownerDocument } from "../../internals/dom/owner-document.ts"; import { useFieldRootContext } from "../../internals/field-context"; import { useFormContext } from "../../internals/form-context"; import { LabelableProvider } from "../../internals/labelable-provider/labelable-provider.tsx"; import { useAriaLabelledBy } from "../../internals/labelable-provider/use-aria-labelled-by.ts"; import { mergeRootProps } from "../../internals/merge-root-props"; import { getDefaultLabelId, resolveAriaLabelledBy, } from "../../internals/resolve-aria-labelled-by.ts"; import { resolveClassStyle } from "../../internals/resolve-class-style"; import { ariaLabelledBy as seedAriaLabelledBy } from "../../internals/seed-aria-attr"; import { areArraysEqual } from "../../internals/slider-utils/are-arrays-equal.ts"; import { asc } from "../../internals/slider-utils/asc.ts"; import { clamp } from "../../internals/slider-utils/clamp.ts"; import { getSliderValue } from "../../internals/slider-utils/get-slider-value.ts"; import type { ThumbCollisionBehavior } from "../../internals/slider-utils/resolve-thumb-collision.ts"; import { validateMinimumDistance } from "../../internals/slider-utils/validate-minimum-distance.ts"; import type { BaseUIComponentProps, Orientation } from "../../internals/types"; import { useFieldControlValidation } from "../../internals/use-field-control-validation/use-field-control-validation.ts"; import { useIsoLayoutEffect } from "../../internals/use-iso-layout-effect.ts"; import { useValueChanged } from "../../internals/use-value-changed.ts"; import { type IndicatorPosition, type SliderRootChangeEventDetails, type SliderRootChangeEventReason, type SliderRootCommitEventDetails, SliderRootContext, type SliderRootContextValue, type SliderRootState, type ThumbMetadata, } from "../slider-root-context.ts"; import { sliderDataAttrs } from "../utils/state-attrs.ts"; export type { SliderRootChangeEventDetails, SliderRootChangeEventReason, SliderRootCommitEventDetails, SliderRootState, }; type SliderValueShape = number | readonly number[]; export interface SliderRootProps< Value extends SliderValueShape = SliderValueShape, > extends BaseUIComponentProps<"div", SliderRootState> { /** Tag/component to render. Defaults to `"div"`. */ as?: ValidComponent; /** * Controlled value. For range sliders, an array of numbers. While not * `undefined` the consumer owns the state — pair with * {@link onValueChange}. When omitted, the slider manages its own value * (seeded from {@link defaultValue}). */ value?: Value | undefined; /** * Initial value when uncontrolled. For range sliders, an array of * numbers. Read once at mount; defaults to `min`. To render a controlled * slider, use the `value` prop instead. */ defaultValue?: Value | undefined; /** Disable all interaction. */ disabled?: boolean | undefined; /** `Intl.NumberFormat` options for value formatting. */ format?: Intl.NumberFormatOptions | undefined; /** Locale for `Intl.NumberFormat`. Defaults to runtime locale. */ locale?: Intl.LocalesArgument | undefined; /** Maximum value. Must be > min. @default 100 */ max?: number | undefined; /** Minimum value. Must be < max. @default 0 */ min?: number | undefined; /** Minimum number of steps between adjacent thumbs in a range slider. */ minStepsBetweenValues?: number | undefined; /** Form name for native submission. */ name?: string | undefined; /** Owner form id. */ form?: string | undefined; /** @default 'horizontal' */ orientation?: Orientation | undefined; /** @default 1 */ step?: number | undefined; /** Step used for Shift+Arrow and Page keys. @default 10 */ largeStep?: number | undefined; /** How colliding thumbs behave during drag. @default 'push' */ thumbCollisionBehavior?: ThumbCollisionBehavior | undefined; /** * How thumbs are aligned relative to the control edges: * - `'center'`: the thumb's center is pinned to the control's min/max * edges (thumb overshoots at the endpoints). * - `'edge'`: the thumb is inset by half its width so its *edge* aligns * with the control edge. A ResizeObserver measures the thumb + control * and writes CSS custom properties (`--position`, `--start-position`, * `--relative-size`) to keep geometry in sync. * - `'edge-client-only'`: same as `'edge'` but skips the SSR prehydration * script (not yet ported in this Solid build). For client-only apps the * two behave identically. * @default 'center' */ thumbAlignment?: "center" | "edge" | "edge-client-only" | undefined; /** Explicit `aria-labelledby`. Composes with registered labels. */ "aria-labelledby"?: string | undefined; /** Fires on every value update. */ onValueChange?: | (( value: Value extends number ? number : readonly number[], eventDetails: SliderRootChangeEventDetails, ) => void) | undefined; /** Fires on `pointerup` / commit. */ onValueCommitted?: | (( value: Value extends number ? number : readonly number[], eventDetails: SliderRootCommitEventDetails, ) => void) | undefined; } function areValuesEqual( newValue: SliderValueShape, oldValue: SliderValueShape, ): boolean { if (typeof newValue === "number" && typeof oldValue === "number") { return newValue === oldValue; } if (Array.isArray(newValue) && Array.isArray(oldValue)) { return areArraysEqual(newValue, oldValue); } return false; } function getSliderChangeEventReason( event: KeyboardEvent | Event, ): SliderRootChangeEventReason { return "key" in event ? REASONS.keyboard : REASONS.inputChange; } /** * Groups all parts of the slider. Renders a `