@mantine/core
Version:
React components library focused on usability, accessibility and developer experience
322 lines (321 loc) • 12.7 kB
JavaScript
"use client";
import { rem } from "../../../core/utils/units-converters/rem.mjs";
import { getRadius, getSize } from "../../../core/utils/get-size/get-size.mjs";
import { findClosestNumber } from "../../../core/utils/find-closest-number/find-closest-number.mjs";
import { createVarsResolver } from "../../../core/styles-api/create-vars-resolver/create-vars-resolver.mjs";
import { getThemeColor } from "../../../core/MantineProvider/color-functions/get-theme-color/get-theme-color.mjs";
import { useProps } from "../../../core/MantineProvider/use-props/use-props.mjs";
import { useStyles } from "../../../core/styles-api/use-styles/use-styles.mjs";
import { factory } from "../../../core/factory/factory.mjs";
import { useDirection } from "../../../core/DirectionProvider/DirectionProvider.mjs";
import { SliderProvider } from "../Slider.context.mjs";
import { SliderRoot } from "../SliderRoot/SliderRoot.mjs";
import { Thumb } from "../Thumb/Thumb.mjs";
import { getPosition } from "../utils/get-position/get-position.mjs";
import { Track } from "../Track/Track.mjs";
import { getChangeValue } from "../utils/get-change-value/get-change-value.mjs";
import { getFloatingValue } from "../utils/get-floating-value/get-floating-value.mjs";
import { getPrecision } from "../utils/get-precision/get-precision.mjs";
import { getFirstMarkValue, getLastMarkValue, getNextMarkValue, getPreviousMarkValue } from "../utils/get-step-mark-value/get-step-mark-value.mjs";
import Slider_module_default from "../Slider.module.mjs";
import { getClientPosition } from "../utils/get-client-position/get-client-position.mjs";
import { useEffect, useRef, useState } from "react";
import { clamp, useMergedRef, useMove, useUncontrolled } from "@mantine/hooks";
import { jsx, jsxs } from "react/jsx-runtime";
//#region packages/@mantine/core/src/components/Slider/RangeSlider/RangeSlider.tsx
const varsResolver = createVarsResolver((theme, { size, color, thumbSize, radius }) => ({ root: {
"--slider-size": getSize(size, "slider-size"),
"--slider-color": color ? getThemeColor(color, theme) : void 0,
"--slider-radius": radius === void 0 ? void 0 : getRadius(radius),
"--slider-thumb-size": thumbSize !== void 0 ? rem(thumbSize) : "calc(var(--slider-size) * 2)"
} }));
const defaultProps = {
min: 0,
max: 100,
minRange: 10,
step: 1,
marks: [],
label: (f) => f,
labelTransitionProps: {
transition: "fade",
duration: 0
},
labelAlwaysOn: false,
showLabelOnHover: true,
disabled: false,
pushOnOverlap: true,
scale: (v) => v,
size: "md",
maxRange: Infinity
};
const RangeSlider = factory((_props) => {
const props = useProps("RangeSlider", defaultProps, _props);
const { classNames, styles, value, onChange, onChangeEnd, size, min, max, domain, minRange, maxRange, step, precision: _precision, defaultValue, name, marks, label, labelTransitionProps, labelAlwaysOn, thumbFromLabel, thumbToLabel, showLabelOnHover, thumbChildren, disabled, unstyled, scale, inverted, orientation, className, style, vars, hiddenInputProps, restrictToMarks, thumbProps, pushOnOverlap, attributes, ref, ...others } = props;
const getStyles = useStyles({
name: "RangeSlider",
props,
classes: Slider_module_default,
classNames,
className,
styles,
style,
attributes,
vars,
varsResolver,
unstyled
});
const containerRef = useRef(null);
const { dir } = useDirection();
const [focused, setFocused] = useState(-1);
const [hovered, setHovered] = useState(false);
const [_value, setValue] = useUncontrolled({
value,
defaultValue,
finalValue: [min, max],
onChange
});
const valueRef = useRef(_value);
const thumbs = useRef([]);
const root = useRef(null);
const thumbIndex = useRef(void 0);
const [domainMin, domainMax] = domain || [min, max];
const positions = [getPosition({
value: _value[0],
min: domainMin,
max: domainMax
}), getPosition({
value: _value[1],
min: domainMin,
max: domainMax
})];
const precision = _precision ?? getPrecision(step);
const _setValue = (val) => {
setValue(val);
valueRef.current = val;
};
useEffect(() => {
if (Array.isArray(value)) valueRef.current = value;
}, Array.isArray(value) ? [value[0], value[1]] : [null, null]);
const setRangedValue = (val, index, triggerChangeEnd) => {
if (index === -1) return;
const clone = [...valueRef.current];
if (restrictToMarks && marks) {
const closest = findClosestNumber(val, marks.map((m) => m.value));
const current = clone[index];
clone[index] = closest;
const otherIndex = index === 0 ? 1 : 0;
const lastMarkValue = getLastMarkValue(marks);
const firstMarkValue = getFirstMarkValue(marks);
if (closest === lastMarkValue && clone[otherIndex] === lastMarkValue) clone[index] = current;
else if (closest === firstMarkValue && clone[otherIndex] === firstMarkValue) clone[index] = current;
else if (closest === clone[otherIndex]) if (current > clone[otherIndex]) clone[otherIndex] = getPreviousMarkValue(closest, marks);
else clone[otherIndex] = getNextMarkValue(closest, marks);
} else {
const clampedVal = clamp(val, min, max);
clone[index] = clampedVal;
if (index === 0) {
if (clampedVal > clone[1] - (minRange - 1e-9)) if (pushOnOverlap) clone[1] = Math.min(val + minRange, max);
else clone[index] = valueRef.current[index];
if (clampedVal > (max - (minRange - 1e-9) || min)) clone[index] = valueRef.current[index];
if (clone[1] - val > maxRange) if (pushOnOverlap) clone[1] = val + maxRange;
else clone[index] = valueRef.current[index];
}
if (index === 1) {
if (clampedVal < clone[0] + minRange) if (pushOnOverlap) clone[0] = Math.max(val - minRange, min);
else clone[index] = valueRef.current[index];
if (clampedVal < clone[0] + minRange) clone[index] = valueRef.current[index];
if (clampedVal - clone[0] > maxRange) if (pushOnOverlap) clone[0] = val - maxRange;
else clone[index] = valueRef.current[index];
}
}
clone[0] = getFloatingValue(clone[0], precision);
clone[1] = getFloatingValue(clone[1], precision);
if (clone[0] > clone[1]) {
const temp = clone[0];
clone[0] = clone[1];
clone[1] = temp;
}
_setValue(clone);
if (triggerChangeEnd) onChangeEnd?.(valueRef.current);
};
const handleChange = (val) => {
if (!disabled && thumbIndex.current !== void 0) setRangedValue(getChangeValue({
value: val,
min: domainMin,
max: domainMax,
step,
precision
}), thumbIndex.current, false);
};
const { ref: useMoveRef, active } = useMove(({ x, y }) => handleChange(orientation === "vertical" ? 1 - y : x), { onScrubEnd: () => !disabled && onChangeEnd?.(valueRef.current) }, dir);
function handleThumbMouseDown(index) {
thumbIndex.current = index;
}
const handleTrackMouseDownCapture = (event) => {
if (containerRef.current) {
containerRef.current.focus();
const rect = containerRef.current.getBoundingClientRect();
const changePosition = getClientPosition(event.nativeEvent, orientation);
const changeValue = orientation === "vertical" ? getChangeValue({
value: rect.bottom - changePosition,
max,
min,
step,
containerWidth: rect.height
}) : getChangeValue({
value: changePosition - rect.left,
max,
min,
step,
containerWidth: rect.width
});
const nearestHandle = Math.abs(_value[0] - changeValue) > Math.abs(_value[1] - changeValue) ? 1 : 0;
thumbIndex.current = orientation === "vertical" ? nearestHandle : dir === "ltr" ? nearestHandle : nearestHandle === 1 ? 0 : 1;
}
};
const getFocusedThumbIndex = () => {
if (focused !== 1 && focused !== 0) {
setFocused(0);
return 0;
}
return focused;
};
const handleTrackKeydownCapture = (event) => {
if (!disabled) switch (event.key) {
case "ArrowUp": {
event.preventDefault();
const focusedIndex = getFocusedThumbIndex();
thumbs.current[focusedIndex].focus();
setRangedValue(getFloatingValue(restrictToMarks && marks ? getNextMarkValue(valueRef.current[focusedIndex], marks) : Math.min(Math.max(valueRef.current[focusedIndex] + step, domainMin), domainMax), precision), focusedIndex, true);
break;
}
case "ArrowRight": {
event.preventDefault();
const focusedIndex = getFocusedThumbIndex();
thumbs.current[focusedIndex].focus();
setRangedValue(getFloatingValue(restrictToMarks && marks ? (dir === "rtl" ? getPreviousMarkValue : getNextMarkValue)(valueRef.current[focusedIndex], marks) : Math.min(Math.max(dir === "rtl" ? valueRef.current[focusedIndex] - step : valueRef.current[focusedIndex] + step, domainMin), domainMax), precision), focusedIndex, true);
break;
}
case "ArrowDown": {
event.preventDefault();
const focusedIndex = getFocusedThumbIndex();
thumbs.current[focusedIndex].focus();
setRangedValue(getFloatingValue(restrictToMarks && marks ? getPreviousMarkValue(valueRef.current[focusedIndex], marks) : Math.min(Math.max(valueRef.current[focusedIndex] - step, domainMin), domainMax), precision), focusedIndex, true);
break;
}
case "ArrowLeft": {
event.preventDefault();
const focusedIndex = getFocusedThumbIndex();
thumbs.current[focusedIndex].focus();
setRangedValue(getFloatingValue(restrictToMarks && marks ? (dir === "rtl" ? getNextMarkValue : getPreviousMarkValue)(valueRef.current[focusedIndex], marks) : Math.min(Math.max(dir === "rtl" ? valueRef.current[focusedIndex] + step : valueRef.current[focusedIndex] - step, domainMin), domainMax), precision), focusedIndex, true);
break;
}
default: break;
}
};
const sharedThumbProps = {
max,
min,
size,
labelTransitionProps,
labelAlwaysOn,
orientation,
onBlur: () => setFocused(-1)
};
const hasArrayThumbChildren = Array.isArray(thumbChildren);
return /* @__PURE__ */ jsx(SliderProvider, {
value: { getStyles },
children: /* @__PURE__ */ jsxs(SliderRoot, {
...others,
size,
ref: useMergedRef(ref, root),
disabled,
orientation,
onMouseDownCapture: () => root.current?.focus(),
onKeyDownCapture: () => {
if (thumbs.current[0]?.parentElement?.contains(document.activeElement)) return;
thumbs.current[0]?.focus();
},
children: [
/* @__PURE__ */ jsxs(Track, {
offset: positions[0],
marksOffset: _value[0],
filled: positions[1] - positions[0],
marks,
inverted,
min: domainMin,
max: domainMax,
value: _value[1],
disabled,
containerProps: {
ref: useMergedRef(containerRef, useMoveRef),
onMouseEnter: showLabelOnHover ? () => setHovered(true) : void 0,
onMouseLeave: showLabelOnHover ? () => setHovered(false) : void 0,
onTouchStartCapture: handleTrackMouseDownCapture,
onTouchEndCapture: () => {
thumbIndex.current = -1;
},
onMouseDownCapture: handleTrackMouseDownCapture,
onMouseUpCapture: () => {
thumbIndex.current = -1;
},
onKeyDownCapture: handleTrackKeydownCapture
},
children: [/* @__PURE__ */ jsx(Thumb, {
...sharedThumbProps,
value: scale(_value[0]),
position: positions[0],
dragging: active,
label: typeof label === "function" ? label(getFloatingValue(scale(_value[0]), precision)) : label,
ref: (node) => {
if (node) thumbs.current[0] = node;
},
thumbLabel: thumbFromLabel,
onMouseDown: () => handleThumbMouseDown(0),
onFocus: () => setFocused(0),
showLabelOnHover,
isHovered: hovered,
disabled,
...thumbProps?.(0),
children: hasArrayThumbChildren ? thumbChildren[0] : thumbChildren
}), /* @__PURE__ */ jsx(Thumb, {
...sharedThumbProps,
thumbLabel: thumbToLabel,
value: scale(_value[1]),
position: positions[1],
dragging: active,
label: typeof label === "function" ? label(getFloatingValue(scale(_value[1]), precision)) : label,
ref: (node) => {
if (node) thumbs.current[1] = node;
},
onMouseDown: () => handleThumbMouseDown(1),
onFocus: () => setFocused(1),
showLabelOnHover,
isHovered: hovered,
disabled,
...thumbProps?.(1),
children: hasArrayThumbChildren ? thumbChildren[1] : thumbChildren
})]
}),
/* @__PURE__ */ jsx("input", {
type: "hidden",
name: `${name}_from`,
value: _value[0],
...hiddenInputProps
}),
/* @__PURE__ */ jsx("input", {
type: "hidden",
name: `${name}_to`,
value: _value[1],
...hiddenInputProps
})
]
})
});
});
RangeSlider.classes = Slider_module_default;
RangeSlider.varsResolver = varsResolver;
RangeSlider.displayName = "@mantine/core/RangeSlider";
//#endregion
export { RangeSlider };
//# sourceMappingURL=RangeSlider.mjs.map