"use client"; import * as React from "react"; import { parseSliderEditValue } from "../slider/slider-edit-value"; import { EditableSliderValueLabel, Field, FieldError, Slider, } from "../../primitives"; import { ControlFieldLabel } from "../../control-layout"; import { formatRangeSliderValue, parseRangeSliderDraft, } from "./range-slider-value"; import { applySliderValueLabelUnit } from "../slider/slider-value"; import { createControlHistoryGroupId, type ControlChangeMeta, type ControlValueChangeHandler, } from "../control-types"; export type RangeSliderControlProps = { baseValue?: readonly [number, number] | readonly number[]; disabled?: boolean; markerCount?: number; max?: number; min?: number; name: string; /** Optional numeric commit policy; return an error to retain the current value. */ onValueEdit?: (value: readonly number[], reason?: "reset") => string | undefined; onValueChange?: ControlValueChangeHandler; step?: number; unit?: string; value: readonly [number, number] | readonly number[]; valueLabel?: string; variant?: "continuous" | "discrete"; }; export function RangeSliderControl({ baseValue, disabled = false, markerCount, max = 100, min = 0, name, onValueChange, onValueEdit, step = 0.1, unit, value, valueLabel, variant = "continuous", }: RangeSliderControlProps): React.JSX.Element { const [rangeValue, setRangeValue] = React.useState(value); const [editError, setEditError] = React.useState(); const liveHistoryGroupRef = React.useRef(null); const resolvedValueLabel = valueLabel ? applySliderValueLabelUnit(valueLabel, unit) : formatRangeSliderValue(rangeValue, step, unit); React.useEffect(() => { setRangeValue(value); }, [value]); function getLiveHistoryMeta(): ControlChangeMeta { liveHistoryGroupRef.current ??= createControlHistoryGroupId(`range-slider:${name}`); return { history: "merge", historyGroup: liveHistoryGroupRef.current, }; } function finishLiveHistoryGroup(): void { liveHistoryGroupRef.current = null; } function updateValue( nextValue: number | readonly number[], meta?: ControlChangeMeta, ): void { const resolvedValue = typeof nextValue === "number" ? [nextValue, nextValue] : [...nextValue]; setEditError(undefined); setRangeValue(resolvedValue); onValueChange?.(resolvedValue, meta); } function commitValueLabel(nextValueLabel: string): void { if (onValueEdit) { finishLiveHistoryGroup(); const parsed = parseSliderEditValue(nextValueLabel, true, unit); setEditError(parsed ? onValueEdit(parsed) : "Enter two valid numbers."); return; } const nextValue = parseRangeSliderDraft(nextValueLabel, { max, min, step }); if (!nextValue) { return; } setRangeValue(nextValue); onValueChange?.(nextValue); } return (
{name}
updateValue(nextValue, getLiveHistoryMeta())} onValueCommitted={finishLiveHistoryGroup} onValueReset={onValueEdit ? nextValue => { setEditError(onValueEdit(nextValue, "reset")); finishLiveHistoryGroup(); } : undefined} resetValue={baseValue ?? value} step={step} value={rangeValue} variant={variant} /> {editError ? {editError} : null}
); }