Spaces:
Running
Running
| "use client"; | |
| // 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(void 0); | |
| 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(void 0); | |
| 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( | |
| SliderBubbleInput, | |
| { | |
| name: name ?? (context.name ? context.name + (context.values.length > 1 ? "[]" : "") : void 0), | |
| form: context.form, | |
| value | |
| }, | |
| index | |
| ) | |
| ] | |
| } | |
| ); | |
| } | |
| ); | |
| SliderThumb.displayName = THUMB_NAME; | |
| var BUBBLE_INPUT_NAME = "RadioBubbleInput"; | |
| var SliderBubbleInput = React.forwardRef( | |
| ({ __scopeSlider, value, ...props }, forwardedRef) => { | |
| const ref = React.useRef(null); | |
| const composedRefs = useComposedRefs(ref, forwardedRef); | |
| const prevValue = usePrevious(value); | |
| React.useEffect(() => { | |
| const input = ref.current; | |
| if (!input) return; | |
| 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( | |
| Primitive.input, | |
| { | |
| style: { display: "none" }, | |
| ...props, | |
| ref: composedRefs, | |
| defaultValue: value | |
| } | |
| ); | |
| } | |
| ); | |
| SliderBubbleInput.displayName = BUBBLE_INPUT_NAME; | |
| 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 | |