import * as React from "react" import { View } from "@tarojs/components" import Taro from "@tarojs/taro" import { type ITouchEvent } from "@tarojs/components" import { cn } from "@/lib/utils" const Slider = React.forwardRef< React.ElementRef, Omit, "value" | "onChange"> & { value?: number[] defaultValue?: number[] min?: number max?: number step?: number onValueChange?: (value: number[]) => void disabled?: boolean orientation?: "horizontal" | "vertical" trackClassName?: string rangeClassName?: string thumbClassName?: string } >(({ className, trackClassName, rangeClassName, thumbClassName, value: valueProp, defaultValue, min = 0, max = 100, step = 1, onValueChange, disabled, orientation = "horizontal", ...props }, ref) => { const [localValue, setLocalValue] = React.useState( valueProp || defaultValue || [min] ) const [isDragging, setIsDragging] = React.useState(false) const [rect, setRect] = React.useState<{ left: number; top: number; width: number; height: number } | null>(null) const rectRef = React.useRef<{ left: number; top: number; width: number; height: number } | null>(null) const idRef = React.useRef(`slider-${Math.random().toString(36).substr(2, 9)}`) const value = valueProp !== undefined ? valueProp : localValue const currentValue = value[0] ?? min React.useEffect(() => { rectRef.current = rect }, [rect]) React.useEffect(() => { // Delay measurement to ensure the component is mounted and layout is ready const timer = setTimeout(() => { const query = Taro.createSelectorQuery() query .select(`#${idRef.current}`) .boundingClientRect((res) => { const measuredRect = Array.isArray(res) ? res[0] : res if (measuredRect) { setRect({ left: measuredRect.left, top: measuredRect.top, width: measuredRect.width, height: measuredRect.height }) rectRef.current = { left: measuredRect.left, top: measuredRect.top, width: measuredRect.width, height: measuredRect.height } } }) .exec() }, 100) return () => clearTimeout(timer) }, []) const updateValue = (pageX: number, pageY: number, passedRect?: { left: number; top: number; width: number; height: number } | null) => { const currentRect = passedRect || rectRef.current if (!currentRect || disabled) return let percentage = 0 if (orientation === "horizontal") { const { left, width } = currentRect percentage = Math.min(Math.max((pageX - left) / width, 0), 1) } else { const { top, height } = currentRect percentage = Math.min(Math.max(1 - (pageY - top) / height, 0), 1) } const rawValue = min + percentage * (max - min) const steppedValue = Math.round((rawValue - min) / step) * step + min const newValue = Math.min(Math.max(steppedValue, min), max) if (newValue !== currentValue) { const nextValue = [newValue] if (valueProp === undefined) { setLocalValue(nextValue) } onValueChange?.(nextValue) } } const handleTouchStart = (e: ITouchEvent) => { if (disabled) return setIsDragging(true) // Try to update rect on touch start in case of layout changes const query = Taro.createSelectorQuery() query .select(`#${idRef.current}`) .boundingClientRect((res) => { const measuredRect = Array.isArray(res) ? res[0] : res if (measuredRect) { setRect({ left: measuredRect.left, top: measuredRect.top, width: measuredRect.width, height: measuredRect.height }) rectRef.current = { left: measuredRect.left, top: measuredRect.top, width: measuredRect.width, height: measuredRect.height } // If we have a touch event, update value immediately after getting fresh rect const touch = e.touches[0] || e.changedTouches[0] if (touch) { updateValue(touch.pageX, touch.pageY, rectRef.current) } } }) .exec() } const handleMouseDown = (e: React.MouseEvent) => { if (disabled) return setIsDragging(true) const query = Taro.createSelectorQuery() query .select(`#${idRef.current}`) .boundingClientRect((res) => { const measuredRect = Array.isArray(res) ? res[0] : res if (measuredRect) { setRect({ left: measuredRect.left, top: measuredRect.top, width: measuredRect.width, height: measuredRect.height }) rectRef.current = { left: measuredRect.left, top: measuredRect.top, width: measuredRect.width, height: measuredRect.height } updateValue(e.pageX, e.pageY, rectRef.current) } }) .exec() const onMouseMove = (moveEvent: MouseEvent) => { updateValue(moveEvent.pageX, moveEvent.pageY) } const onMouseUp = (upEvent: MouseEvent) => { setIsDragging(false) updateValue(upEvent.pageX, upEvent.pageY) document.removeEventListener('mousemove', onMouseMove) document.removeEventListener('mouseup', onMouseUp) } if (typeof document !== 'undefined') { document.addEventListener('mousemove', onMouseMove) document.addEventListener('mouseup', onMouseUp) } } const handleTouchMove = (e: ITouchEvent) => { if (disabled) return const touch = e.touches[0] || e.changedTouches[0] if (touch) { updateValue(touch.pageX, touch.pageY) } } const handleTouchEnd = (e: ITouchEvent) => { if (disabled) return setIsDragging(false) const touch = e.touches[0] || e.changedTouches[0] if (touch) { updateValue(touch.pageX, touch.pageY) } } const percentage = ((currentValue - min) / (max - min)) * 100 return ( ) }) Slider.displayName = "Slider" export { Slider }