/* Copyright 2026 Marimo. All rights reserved. */ import { Slider as SliderPrimitive } from "radix-ui"; import * as React from "react"; import { useLocale } from "react-aria"; import { cn } from "@/utils/cn"; import { prettyScientificNumber } from "@/utils/numbers"; import { useBoolean } from "../../hooks/useBoolean"; import { TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger, } from "./tooltip"; const RangeSlider = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { valueMap: (sliderValue: number) => number; steps?: number[]; } >(({ className, valueMap, ...props }, ref) => { const [open, openActions] = useBoolean(false); const { locale } = useLocale(); const isDraggingRange = React.useRef(false); const dragStartX = React.useRef(0); const dragStartY = React.useRef(0); const dragStartValue = React.useRef([]); const currentDragValue = React.useRef([]); const rootRef = React.useRef>(null); const trackRef = React.useRef(null); const dragTrackRect = React.useRef(null); const mergedRef = React.useCallback( (node: React.ElementRef) => { rootRef.current = node; if (typeof ref === "function") { ref(node); } else if (ref) { ref.current = node; } }, [ref], ); const handleRangePointerDown = (e: React.PointerEvent) => { if (!props.value || props.value.length !== 2) { return; } if (props.disabled) { return; } e.preventDefault(); e.stopPropagation(); isDraggingRange.current = true; dragStartX.current = e.clientX; dragStartY.current = e.clientY; dragStartValue.current = [...props.value]; currentDragValue.current = [...props.value]; dragTrackRect.current = trackRef.current?.getBoundingClientRect() ?? null; (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); }; const handleRangePointerMove = (e: React.PointerEvent) => { if (!isDraggingRange.current) { return; } e.stopPropagation(); const trackRect = dragTrackRect.current; if (!trackRect) { return; } const isVertical = props.orientation === "vertical"; const min = props.min ?? 0; const max = props.max ?? 100; const totalRange = max - min; let delta: number; if (isVertical) { const trackLength = trackRect.height; delta = -((e.clientY - dragStartY.current) / trackLength) * totalRange; } else { const trackLength = trackRect.width; delta = ((e.clientX - dragStartX.current) / trackLength) * totalRange; } const [origLeft, origRight] = dragStartValue.current; const rangeWidth = origRight - origLeft; const steps = props.steps; const step: number = steps && steps.length > 1 ? Math.min(...steps.slice(1).map((s, i) => s - steps[i])) : (props.step ?? 1); const snappedDelta = Math.round(delta / step) * step; const clampedDelta = Math.max( min - origLeft, Math.min(max - origRight, snappedDelta), ); const newLeft = origLeft + clampedDelta; const newRight = newLeft + rangeWidth; currentDragValue.current = [newLeft, newRight]; props.onValueChange?.([newLeft, newRight]); }; const handleRangePointerUp = (e: React.PointerEvent) => { if (!isDraggingRange.current) { return; } (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId); isDraggingRange.current = false; if (currentDragValue.current.length === 2) { props.onValueCommit?.(currentDragValue.current); } }; return ( {props.value != null && props.value.length === 2 && ( {prettyScientificNumber(valueMap(props.value[0]), { locale })} )} {props.value != null && props.value.length === 2 && ( {prettyScientificNumber(valueMap(props.value[1]), { locale })} )} ); }); RangeSlider.displayName = SliderPrimitive.Root.displayName; export { RangeSlider };