"use client"; import * as React from "react"; import { Slider as SliderPrimitive } from "@base-ui/react/slider"; import { cn } from "../../../lib/utils"; import type { SliderThumbDoubleClickHandler } from "./slider-reset"; type SliderOrientation = "horizontal" | "vertical"; type SliderControlContentProps = { count: number; disabled?: boolean; getAriaLabel?: (index: number) => string; isDiscrete: boolean; isPointerDragging: boolean; markerCount: number; markerValues?: readonly number[]; max: number; min: number; onThumbDoubleClick?: SliderThumbDoubleClickHandler; orientation: SliderOrientation; showFill: boolean; }; type SliderThumbPointerSnapshot = { clientX: number; clientY: number; index: number; pointerType: string; timeStamp: number; }; const sliderThumbDoubleClickDelayMs = 340; const sliderThumbDoubleClickDistancePx = 6; function useActiveSliderThumbDrag(disabled: boolean): { activeDragIndex: number | null; handlePointerDown: (event: React.PointerEvent, index: number) => void; } { const [activeDragIndex, setActiveDragIndex] = React.useState(null); const clearActiveDragIndex = React.useCallback(() => { setActiveDragIndex(null); }, []); const handlePointerDown = React.useCallback( (event: React.PointerEvent, index: number) => { if (event.defaultPrevented || disabled || event.button !== 0) { return; } setActiveDragIndex(index); }, [disabled], ); React.useEffect(() => { if (activeDragIndex === null) { return undefined; } window.addEventListener("pointerup", clearActiveDragIndex); window.addEventListener("pointercancel", clearActiveDragIndex); window.addEventListener("blur", clearActiveDragIndex); return () => { window.removeEventListener("pointerup", clearActiveDragIndex); window.removeEventListener("pointercancel", clearActiveDragIndex); window.removeEventListener("blur", clearActiveDragIndex); }; }, [activeDragIndex, clearActiveDragIndex]); return { activeDragIndex, handlePointerDown }; } function SliderMarkers({ disabled = false, isPointerDragging, markerCount, markerValues, max, min, orientation, }: { disabled?: boolean; isPointerDragging: boolean; markerCount: number; markerValues?: readonly number[]; max: number; min: number; orientation: SliderOrientation; }): React.JSX.Element | null { const markerOffsets = markerValues?.length ? markerValues .filter((value) => value > min && value < max) .map((value) => ((value - min) / (max - min)) * 100) : Array.from({ length: markerCount }, (_, index) => { if (index === 0 || index === markerCount - 1) { return null; } return (index / (markerCount - 1)) * 100; }).filter((offset): offset is number => offset !== null); if (markerOffsets.length === 0) { return null; } return ( <> {markerOffsets.map((offsetValue, index) => { const offset = `${offsetValue}%`; return ( ); })} ); } function SliderThumbs({ count, disabled = false, getAriaLabel, onDoubleClick, }: { count: number; disabled?: boolean; getAriaLabel?: (index: number) => string; onDoubleClick?: SliderThumbDoubleClickHandler; }): React.JSX.Element { const { activeDragIndex, handlePointerDown } = useActiveSliderThumbDrag(disabled); const lastPointerDownRef = React.useRef(null); const suppressNextDoubleClickRef = React.useRef(false); const shouldResetFromPointerDown = React.useCallback( (event: React.PointerEvent, index: number): boolean => { if (event.detail >= 2) { return true; } const previousPointerDown = lastPointerDownRef.current; if (!previousPointerDown || previousPointerDown.index !== index) { return false; } const deltaTime = event.timeStamp - previousPointerDown.timeStamp; const deltaX = event.clientX - previousPointerDown.clientX; const deltaY = event.clientY - previousPointerDown.clientY; const distance = Math.hypot(deltaX, deltaY); return ( previousPointerDown.pointerType === event.pointerType && deltaTime >= 0 && deltaTime <= sliderThumbDoubleClickDelayMs && distance <= sliderThumbDoubleClickDistancePx ); }, [], ); const handlePointerDownCapture = React.useCallback( (event: React.PointerEvent, index: number) => { if (!onDoubleClick || disabled || event.button !== 0) { return; } if (shouldResetFromPointerDown(event, index)) { lastPointerDownRef.current = null; suppressNextDoubleClickRef.current = true; onDoubleClick(event, index); return; } lastPointerDownRef.current = { clientX: event.clientX, clientY: event.clientY, index, pointerType: event.pointerType, timeStamp: event.timeStamp, }; }, [disabled, onDoubleClick, shouldResetFromPointerDown], ); const handleDoubleClick = React.useCallback( (event: React.MouseEvent, index: number) => { if (suppressNextDoubleClickRef.current) { suppressNextDoubleClickRef.current = false; event.preventDefault(); event.stopPropagation(); return; } onDoubleClick?.(event, index); }, [onDoubleClick], ); return ( <> {Array.from({ length: count }, (_, index) => ( handleDoubleClick(event, index)} onPointerDownCapture={(event) => handlePointerDownCapture(event, index)} onPointerDown={(event) => handlePointerDown(event, index)} className={cn( "group/slider-thumb relative block size-[9px] shrink-0 cursor-pointer rounded-[2px] select-none before:absolute before:top-1/2 before:left-1/2 before:block before:size-[18px] before:-translate-x-1/2 before:-translate-y-1/2 before:content-[''] transition-[inset-inline-start,bottom] duration-200 ease-out data-[dragging]:transition-none disabled:pointer-events-none motion-reduce:transition-none", )} > ))} ); } function SliderFill({ show }: { show: boolean }): React.JSX.Element | null { if (!show) { return null; } return ( ); } function SliderControlContent({ count, disabled, getAriaLabel, isDiscrete, isPointerDragging, markerCount, markerValues, max, min, onThumbDoubleClick, orientation, showFill, }: SliderControlContentProps): React.JSX.Element { return ( {isDiscrete ? ( ) : null} ); } export { SliderControlContent, SliderFill, SliderMarkers, SliderThumbs };