Files
Webklar.com/node_modules/@radix-ui/react-slider/dist/index.mjs
Basilosaurusrex f027651f9b main repo
2025-11-24 18:09:40 +01:00

554 lines
20 KiB
JavaScript

"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