import type { JSX, ValidComponent } from "@solidjs/web"; import { dynamic } from "@solidjs/web"; import { createMemo, omit } from "solid-js"; import { userRef } from "../../internals/compose-refs"; import { mergeRootProps } from "../../internals/merge-root-props"; import { resolveClassStyle } from "../../internals/resolve-class-style"; import type { BaseUIComponentProps } from "../../internals/types"; import { type SliderRootState, useSliderRootContext, } from "../slider-root-context.ts"; import { sliderDataAttrs } from "../utils/state-attrs.ts"; export interface SliderValueState extends SliderRootState {} export interface SliderValueProps extends Omit, "children"> { /** Tag/component to render. Defaults to `"output"`. */ as?: ValidComponent; /** * Consumer-supplied renderer for the displayed value. Receives the array * of formatted values and the raw numeric array. Returning `null` falls * back to the default join. */ children?: | null | (( formattedValues: readonly string[], values: readonly number[], ) => JSX.Element) | undefined; /** @default 'off' */ "aria-live"?: JSX.AriaAttributes["aria-live"] | undefined; } function formatNumber( value: number, locale: Intl.LocalesArgument | undefined, format: Intl.NumberFormatOptions | undefined, ): string { if (!format) return String(value); try { return new Intl.NumberFormat(locale, format).format(value); } catch { return String(value); } } /** * Displays the current value of the slider as text. Renders an `` * (or `as` override). * * `htmlFor` is composed from every registered thumb's `inputId` — reading * `thumbMap()` subscribes to thumb registrations so the attribute updates when * thumbs mount or unmount. * * §1 wave 3: replaces `renderElement` with `dynamic(() => props.as ?? "output")`. */ export function SliderValue(props: SliderValueProps): JSX.Element { const context = useSliderRootContext(); const outputFor = createMemo((): string | undefined => { let out = ""; for (const thumbMetadata of context.thumbMap().values()) { if (thumbMetadata?.inputId) { out += `${thumbMetadata.inputId} `; } } const trimmed = out.trim(); return trimmed.length === 0 ? undefined : trimmed; }); const formattedValues = createMemo((): readonly string[] => { const out: string[] = []; const format = context.formatOptions(); const locale = context.locale(); for (const v of context.values()) { out.push(formatNumber(v, locale, format)); } return out; }); const defaultDisplayValue = createMemo((): string => { return formattedValues().join(" – "); }); // Resolve `class`/`style` here so function values (stateful styling) are // invoked with the slider state instead of being spread to the DOM raw. const userClassStyle = resolveClassStyle(props, () => context.state()); // Strip our callback-shape `children` and `aria-live` (re-injected via the // internal slot below). const userProps = omit( props as Record, "as", "children", "aria-live", "class", "style", "attrs", ); const internalSlot = { get "aria-live"() { return props["aria-live"] ?? "off"; }, // Solid 2 DOM binding uses native attribute name `for`, not // React's `htmlFor`. Seed `""` so the key stays in `ownKeys` // on render 1 (see // `test/internals/merge-props-undefined-attr.test.tsx`). get for() { return outputFor() ?? ""; }, get children() { const consumer = props.children; if (typeof consumer === "function") { return consumer(formattedValues(), context.values()); } return defaultDisplayValue(); }, }; let merged: Record = mergeRootProps( internalSlot, sliderDataAttrs(context.state), ); merged = mergeRootProps(merged, userClassStyle); merged = mergeRootProps(merged, userProps); const Tag = dynamic(() => props.as ?? "output"); return ; } export namespace SliderValue { export type State = SliderValueState; export type Props = SliderValueProps; }