"use client"; // packages/react/slider/src/Slider.tsx import * as React from "react"; import { clamp } from "@radix-ui/number"; import { composeEventHandlers } from "@radix-ui/primitive"; import { useComposedRefs } from "@radix-ui/react-compose-refs"; import { createContextScope } from "@radix-ui/react-context"; import { useControllableState } from "@radix-ui/react-use-controllable-state"; import { useDirection } from "@radix-ui/react-direction"; import { usePrevious } from "@radix-ui/react-use-previous"; import { useSize } from "@radix-ui/react-use-size"; import { Primitive } from "@radix-ui/react-primitive"; import { createCollection } from "@radix-ui/react-collection"; import { jsx, jsxs } from "react/jsx-runtime"; var PAGE_KEYS = ["PageUp", "PageDown"]; var ARROW_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]; var BACK_KEYS = { "from-left": ["Home", "PageDown", "ArrowDown", "ArrowLeft"], "from-right": ["Home", "PageDown", "ArrowDown", "ArrowRight"], "from-bottom": ["Home", "PageDown", "ArrowDown", "ArrowLeft"], "from-top": ["Home", "PageDown", "ArrowUp", "ArrowLeft"] }; var SLIDER_NAME = "Slider"; var [Collection, useCollection, createCollectionScope] = createCollection(SLIDER_NAME); var [createSliderContext, createSliderScope] = createContextScope(SLIDER_NAME, [ createCollectionScope ]); var [SliderProvider, useSliderContext] = createSliderContext(SLIDER_NAME); var Slider = React.forwardRef( (props, forwardedRef) => { const { name, min = 0, max = 100, step = 1, orientation = "horizontal", disabled = false, minStepsBetweenThumbs = 0, defaultValue = [min], value, onValueChange = () => { }, onValueCommit = () => { }, inverted = false, form, ...sliderProps } = props; const thumbRefs = React.useRef(/* @__PURE__ */ new Set()); const valueIndexToChangeRef = React.useRef(0); const isHorizontal = orientation === "horizontal"; const SliderOrientation = isHorizontal ? SliderHorizontal : SliderVertical; const [values = [], setValues] = useControllableState({ prop: value, defaultProp: defaultValue, onChange: (value2) => { const thumbs = [...thumbRefs.current]; thumbs[valueIndexToChangeRef.current]?.focus(); onValueChange(value2); } }); const valuesBeforeSlideStartRef = React.useRef(values); function handleSlideStart(value2) { const closestIndex = getClosestValueIndex(values, value2); updateValues(value2, closestIndex); } function handleSlideMove(value2) { updateValues(value2, valueIndexToChangeRef.current); } function handleSlideEnd() { const prevValue = valuesBeforeSlideStartRef.current[valueIndexToChangeRef.current]; const nextValue = values[valueIndexToChangeRef.current]; const hasChanged = nextValue !== prevValue; if (hasChanged) onValueCommit(values); } function updateValues(value2, atIndex, { commit } = { commit: false }) { const decimalCount = getDecimalCount(step); const snapToStep = roundValue(Math.round((value2 - min) / step) * step + min, decimalCount); const nextValue = clamp(snapToStep, [min, max]); setValues((prevValues = []) => { const nextValues = getNextSortedValues(prevValues, nextValue, atIndex); if (hasMinStepsBetweenValues(nextValues, minStepsBetweenThumbs * step)) { valueIndexToChangeRef.current = nextValues.indexOf(nextValue); const hasChanged = String(nextValues) !== String(prevValues); if (hasChanged && commit) onValueCommit(nextValues); return hasChanged ? nextValues : prevValues; } else { return prevValues; } }); } return /* @__PURE__ */ jsx( SliderProvider, { scope: props.__scopeSlider, name, disabled, min, max, valueIndexToChangeRef, thumbs: thumbRefs.current, values, orientation, form, children: /* @__PURE__ */ jsx(Collection.Provider, { scope: props.__scopeSlider, children: /* @__PURE__ */ jsx(Collection.Slot, { scope: props.__scopeSlider, children: /* @__PURE__ */ jsx( SliderOrientation, { "aria-disabled": disabled, "data-disabled": disabled ? "" : void 0, ...sliderProps, ref: forwardedRef, onPointerDown: composeEventHandlers(sliderProps.onPointerDown, () => { if (!disabled) valuesBeforeSlideStartRef.current = values; }), min, max, inverted, onSlideStart: disabled ? void 0 : handleSlideStart, onSlideMove: disabled ? void 0 : handleSlideMove, onSlideEnd: disabled ? void 0 : handleSlideEnd, onHomeKeyDown: () => !disabled && updateValues(min, 0, { commit: true }), onEndKeyDown: () => !disabled && updateValues(max, values.length - 1, { commit: true }), onStepKeyDown: ({ event, direction: stepDirection }) => { if (!disabled) { const isPageKey = PAGE_KEYS.includes(event.key); const isSkipKey = isPageKey || event.shiftKey && ARROW_KEYS.includes(event.key); const multiplier = isSkipKey ? 10 : 1; const atIndex = valueIndexToChangeRef.current; const value2 = values[atIndex]; const stepInDirection = step * multiplier * stepDirection; updateValues(value2 + stepInDirection, atIndex, { commit: true }); } } } ) }) }) } ); } ); Slider.displayName = SLIDER_NAME; var [SliderOrientationProvider, useSliderOrientationContext] = createSliderContext(SLIDER_NAME, { startEdge: "left", endEdge: "right", size: "width", direction: 1 }); var SliderHorizontal = React.forwardRef( (props, forwardedRef) => { const { min, max, dir, inverted, onSlideStart, onSlideMove, onSlideEnd, onStepKeyDown, ...sliderProps } = props; const [slider, setSlider] = React.useState(null); const composedRefs = useComposedRefs(forwardedRef, (node) => setSlider(node)); const rectRef = React.useRef(); const direction = useDirection(dir); const isDirectionLTR = direction === "ltr"; const isSlidingFromLeft = isDirectionLTR && !inverted || !isDirectionLTR && inverted; function getValueFromPointer(pointerPosition) { const rect = rectRef.current || slider.getBoundingClientRect(); const input = [0, rect.width]; const output = isSlidingFromLeft ? [min, max] : [max, min]; const value = linearScale(input, output); rectRef.current = rect; return value(pointerPosition - rect.left); } return /* @__PURE__ */ jsx( SliderOrientationProvider, { scope: props.__scopeSlider, startEdge: isSlidingFromLeft ? "left" : "right", endEdge: isSlidingFromLeft ? "right" : "left", direction: isSlidingFromLeft ? 1 : -1, size: "width", children: /* @__PURE__ */ jsx( SliderImpl, { dir: direction, "data-orientation": "horizontal", ...sliderProps, ref: composedRefs, style: { ...sliderProps.style, ["--radix-slider-thumb-transform"]: "translateX(-50%)" }, onSlideStart: (event) => { const value = getValueFromPointer(event.clientX); onSlideStart?.(value); }, onSlideMove: (event) => { const value = getValueFromPointer(event.clientX); onSlideMove?.(value); }, onSlideEnd: () => { rectRef.current = void 0; onSlideEnd?.(); }, onStepKeyDown: (event) => { const slideDirection = isSlidingFromLeft ? "from-left" : "from-right"; const isBackKey = BACK_KEYS[slideDirection].includes(event.key); onStepKeyDown?.({ event, direction: isBackKey ? -1 : 1 }); } } ) } ); } ); var SliderVertical = React.forwardRef( (props, forwardedRef) => { const { min, max, inverted, onSlideStart, onSlideMove, onSlideEnd, onStepKeyDown, ...sliderProps } = props; const sliderRef = React.useRef(null); const ref = useComposedRefs(forwardedRef, sliderRef); const rectRef = React.useRef(); const isSlidingFromBottom = !inverted; function getValueFromPointer(pointerPosition) { const rect = rectRef.current || sliderRef.current.getBoundingClientRect(); const input = [0, rect.height]; const output = isSlidingFromBottom ? [max, min] : [min, max]; const value = linearScale(input, output); rectRef.current = rect; return value(pointerPosition - rect.top); } return /* @__PURE__ */ jsx( SliderOrientationProvider, { scope: props.__scopeSlider, startEdge: isSlidingFromBottom ? "bottom" : "top", endEdge: isSlidingFromBottom ? "top" : "bottom", size: "height", direction: isSlidingFromBottom ? 1 : -1, children: /* @__PURE__ */ jsx( SliderImpl, { "data-orientation": "vertical", ...sliderProps, ref, style: { ...sliderProps.style, ["--radix-slider-thumb-transform"]: "translateY(50%)" }, onSlideStart: (event) => { const value = getValueFromPointer(event.clientY); onSlideStart?.(value); }, onSlideMove: (event) => { const value = getValueFromPointer(event.clientY); onSlideMove?.(value); }, onSlideEnd: () => { rectRef.current = void 0; onSlideEnd?.(); }, onStepKeyDown: (event) => { const slideDirection = isSlidingFromBottom ? "from-bottom" : "from-top"; const isBackKey = BACK_KEYS[slideDirection].includes(event.key); onStepKeyDown?.({ event, direction: isBackKey ? -1 : 1 }); } } ) } ); } ); var SliderImpl = React.forwardRef( (props, forwardedRef) => { const { __scopeSlider, onSlideStart, onSlideMove, onSlideEnd, onHomeKeyDown, onEndKeyDown, onStepKeyDown, ...sliderProps } = props; const context = useSliderContext(SLIDER_NAME, __scopeSlider); return /* @__PURE__ */ jsx( Primitive.span, { ...sliderProps, ref: forwardedRef, onKeyDown: composeEventHandlers(props.onKeyDown, (event) => { if (event.key === "Home") { onHomeKeyDown(event); event.preventDefault(); } else if (event.key === "End") { onEndKeyDown(event); event.preventDefault(); } else if (PAGE_KEYS.concat(ARROW_KEYS).includes(event.key)) { onStepKeyDown(event); event.preventDefault(); } }), onPointerDown: composeEventHandlers(props.onPointerDown, (event) => { const target = event.target; target.setPointerCapture(event.pointerId); event.preventDefault(); if (context.thumbs.has(target)) { target.focus(); } else { onSlideStart(event); } }), onPointerMove: composeEventHandlers(props.onPointerMove, (event) => { const target = event.target; if (target.hasPointerCapture(event.pointerId)) onSlideMove(event); }), onPointerUp: composeEventHandlers(props.onPointerUp, (event) => { const target = event.target; if (target.hasPointerCapture(event.pointerId)) { target.releasePointerCapture(event.pointerId); onSlideEnd(event); } }) } ); } ); var TRACK_NAME = "SliderTrack"; var SliderTrack = React.forwardRef( (props, forwardedRef) => { const { __scopeSlider, ...trackProps } = props; const context = useSliderContext(TRACK_NAME, __scopeSlider); return /* @__PURE__ */ jsx( Primitive.span, { "data-disabled": context.disabled ? "" : void 0, "data-orientation": context.orientation, ...trackProps, ref: forwardedRef } ); } ); SliderTrack.displayName = TRACK_NAME; var RANGE_NAME = "SliderRange"; var SliderRange = React.forwardRef( (props, forwardedRef) => { const { __scopeSlider, ...rangeProps } = props; const context = useSliderContext(RANGE_NAME, __scopeSlider); const orientation = useSliderOrientationContext(RANGE_NAME, __scopeSlider); const ref = React.useRef(null); const composedRefs = useComposedRefs(forwardedRef, ref); const valuesCount = context.values.length; const percentages = context.values.map( (value) => convertValueToPercentage(value, context.min, context.max) ); const offsetStart = valuesCount > 1 ? Math.min(...percentages) : 0; const offsetEnd = 100 - Math.max(...percentages); return /* @__PURE__ */ jsx( Primitive.span, { "data-orientation": context.orientation, "data-disabled": context.disabled ? "" : void 0, ...rangeProps, ref: composedRefs, style: { ...props.style, [orientation.startEdge]: offsetStart + "%", [orientation.endEdge]: offsetEnd + "%" } } ); } ); SliderRange.displayName = RANGE_NAME; var THUMB_NAME = "SliderThumb"; var SliderThumb = React.forwardRef( (props, forwardedRef) => { const getItems = useCollection(props.__scopeSlider); const [thumb, setThumb] = React.useState(null); const composedRefs = useComposedRefs(forwardedRef, (node) => setThumb(node)); const index = React.useMemo( () => thumb ? getItems().findIndex((item) => item.ref.current === thumb) : -1, [getItems, thumb] ); return /* @__PURE__ */ jsx(SliderThumbImpl, { ...props, ref: composedRefs, index }); } ); var SliderThumbImpl = React.forwardRef( (props, forwardedRef) => { const { __scopeSlider, index, name, ...thumbProps } = props; const context = useSliderContext(THUMB_NAME, __scopeSlider); const orientation = useSliderOrientationContext(THUMB_NAME, __scopeSlider); const [thumb, setThumb] = React.useState(null); const composedRefs = useComposedRefs(forwardedRef, (node) => setThumb(node)); const isFormControl = thumb ? context.form || !!thumb.closest("form") : true; const size = useSize(thumb); const value = context.values[index]; const percent = value === void 0 ? 0 : convertValueToPercentage(value, context.min, context.max); const label = getLabel(index, context.values.length); const orientationSize = size?.[orientation.size]; const thumbInBoundsOffset = orientationSize ? getThumbInBoundsOffset(orientationSize, percent, orientation.direction) : 0; React.useEffect(() => { if (thumb) { context.thumbs.add(thumb); return () => { context.thumbs.delete(thumb); }; } }, [thumb, context.thumbs]); return /* @__PURE__ */ jsxs( "span", { style: { transform: "var(--radix-slider-thumb-transform)", position: "absolute", [orientation.startEdge]: `calc(${percent}% + ${thumbInBoundsOffset}px)` }, children: [ /* @__PURE__ */ jsx(Collection.ItemSlot, { scope: props.__scopeSlider, children: /* @__PURE__ */ jsx( Primitive.span, { role: "slider", "aria-label": props["aria-label"] || label, "aria-valuemin": context.min, "aria-valuenow": value, "aria-valuemax": context.max, "aria-orientation": context.orientation, "data-orientation": context.orientation, "data-disabled": context.disabled ? "" : void 0, tabIndex: context.disabled ? void 0 : 0, ...thumbProps, ref: composedRefs, style: value === void 0 ? { display: "none" } : props.style, onFocus: composeEventHandlers(props.onFocus, () => { context.valueIndexToChangeRef.current = index; }) } ) }), isFormControl && /* @__PURE__ */ jsx( BubbleInput, { name: name ?? (context.name ? context.name + (context.values.length > 1 ? "[]" : "") : void 0), form: context.form, value }, index ) ] } ); } ); SliderThumb.displayName = THUMB_NAME; var BubbleInput = (props) => { const { value, ...inputProps } = props; const ref = React.useRef(null); const prevValue = usePrevious(value); React.useEffect(() => { const input = ref.current; const inputProto = window.HTMLInputElement.prototype; const descriptor = Object.getOwnPropertyDescriptor(inputProto, "value"); const setValue = descriptor.set; if (prevValue !== value && setValue) { const event = new Event("input", { bubbles: true }); setValue.call(input, value); input.dispatchEvent(event); } }, [prevValue, value]); return /* @__PURE__ */ jsx("input", { style: { display: "none" }, ...inputProps, ref, defaultValue: value }); }; function getNextSortedValues(prevValues = [], nextValue, atIndex) { const nextValues = [...prevValues]; nextValues[atIndex] = nextValue; return nextValues.sort((a, b) => a - b); } function convertValueToPercentage(value, min, max) { const maxSteps = max - min; const percentPerStep = 100 / maxSteps; const percentage = percentPerStep * (value - min); return clamp(percentage, [0, 100]); } function getLabel(index, totalValues) { if (totalValues > 2) { return `Value ${index + 1} of ${totalValues}`; } else if (totalValues === 2) { return ["Minimum", "Maximum"][index]; } else { return void 0; } } function getClosestValueIndex(values, nextValue) { if (values.length === 1) return 0; const distances = values.map((value) => Math.abs(value - nextValue)); const closestDistance = Math.min(...distances); return distances.indexOf(closestDistance); } function getThumbInBoundsOffset(width, left, direction) { const halfWidth = width / 2; const halfPercent = 50; const offset = linearScale([0, halfPercent], [0, halfWidth]); return (halfWidth - offset(left) * direction) * direction; } function getStepsBetweenValues(values) { return values.slice(0, -1).map((value, index) => values[index + 1] - value); } function hasMinStepsBetweenValues(values, minStepsBetweenValues) { if (minStepsBetweenValues > 0) { const stepsBetweenValues = getStepsBetweenValues(values); const actualMinStepsBetweenValues = Math.min(...stepsBetweenValues); return actualMinStepsBetweenValues >= minStepsBetweenValues; } return true; } function linearScale(input, output) { return (value) => { if (input[0] === input[1] || output[0] === output[1]) return output[0]; const ratio = (output[1] - output[0]) / (input[1] - input[0]); return output[0] + ratio * (value - input[0]); }; } function getDecimalCount(value) { return (String(value).split(".")[1] || "").length; } function roundValue(value, decimalCount) { const rounder = Math.pow(10, decimalCount); return Math.round(value * rounder) / rounder; } var Root = Slider; var Track = SliderTrack; var Range = SliderRange; var Thumb = SliderThumb; export { Range, Root, Slider, SliderRange, SliderThumb, SliderTrack, Thumb, Track, createSliderScope }; //# sourceMappingURL=index.mjs.map