@nafr/echo-ui
Version:
A UI library born for WAA
137 lines (136 loc) • 6.84 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useRef, useState, useCallback, forwardRef, useImperativeHandle, useMemo, } from 'react';
import { cn, halfRange, validValue } from '../../../lib/utils';
import { useCommandAltClick } from '../../../lib/hooks';
import { Axis } from '../../visualization/Axis';
import { checkPropsIsValid } from './utils';
import { useStyle } from './styles';
import { MIN, MAX, STEP } from './constants';
export const Slider = forwardRef((props, ref) => {
const { value: _value = MIN, min = MIN, max = MAX, step = STEP, vertical = false, bilateral = false, hideThumb = false, hideThumbLabel = false, prohibitInteraction = false, disabled = false, axis = false, axisProps, classNames, styles, onChange, onChangeEnd, onMouseDown, ...restProps } = props;
useImperativeHandle(ref, () => sliderRef.current);
useEffect(() => {
checkPropsIsValid({ value: _value, min, max });
}, []);
const [value, setValue] = useState(validValue(_value, min, max)); // Internal state for slider's value
const [isDragging, setIsDragging] = useState(false); // State to track if slider is being dragged
const [direction, setDirection] = useState('positive');
const currentValue = useRef(value); // Ref to store current value
const sliderRef = useRef(null); // Ref for the slider element
const sliderRect = useRef({ left: 0, width: 0, bottom: 0, height: 0 }); // Ref to store slider dimensions
// ================ events ================= //
useEffect(() => {
if (disabled)
return;
const validatedValue = validValue(_value, min, max);
setValue(validatedValue);
}, [_value, min, max, disabled]);
const handleResetClick = useCommandAltClick(() => {
if (bilateral)
setValue(halfRange(min, max));
else
setValue(min);
onChange?.(bilateral ? halfRange(min, max) : min);
});
// Update slider value based on mouse event
const updateSliderValue = useCallback((e) => {
e.stopPropagation();
const { left, width, bottom, height } = sliderRect.current;
const radio = vertical ? (bottom - e.clientY) / height : (e.clientX - left) / width;
let newValue = radio * (max - min) + min;
newValue = parseFloat((Math.round(newValue / step) * step).toFixed(10));
newValue = Math.max(min, Math.min(newValue, max));
currentValue.current = newValue;
setValue(newValue);
onChange?.(newValue);
}, [min, max, step, vertical]);
const startDragging = (e) => {
onMouseDown && onMouseDown(e);
if (disabled || prohibitInteraction || !sliderRef.current)
return;
const slider = sliderRef.current;
sliderRect.current = slider.getBoundingClientRect();
setIsDragging(true);
updateSliderValue(e);
document.addEventListener('mousemove', onDragging);
document.addEventListener('mouseup', stopDragging);
handleResetClick(e);
};
const onDragging = (e) => {
e.preventDefault();
requestAnimationFrame(() => updateSliderValue(e));
};
const stopDragging = (e) => {
e.preventDefault();
updateSliderValue(e);
setIsDragging(false);
onChangeEnd && onChangeEnd(currentValue.current);
document.removeEventListener('mousemove', onDragging);
document.removeEventListener('mouseup', stopDragging);
handleResetClick(e);
};
// ================ styles ================= //
useEffect(() => {
if (isDragging)
document.getElementsByTagName('body')[0].style.cursor = 'grabbing';
else
document.getElementsByTagName('body')[0].style.cursor = '';
}, [isDragging]);
const progressStyle = useMemo(() => {
const percentage = ((value - min) / (max - min)) * 100;
let progressLength = percentage;
let progressStart = 0;
// if (bilateral) {
if (bilateral) {
if (value >= halfRange(min, max)) {
setDirection('positive');
progressLength = percentage - 50;
progressStart = 50;
}
else {
setDirection('negative');
progressLength = 50 - percentage;
progressStart = percentage;
}
}
return {
height: vertical ? `${progressLength}%` : '100%',
width: vertical ? '100%' : `${progressLength}%`,
bottom: vertical ? `${progressStart}%` : '0',
left: vertical ? '0' : `${progressStart}%`,
};
}, [value, min, max, bilateral, disabled, vertical]);
const { base, progress, thumb, thumbLabel } = useStyle({
disabled,
vertical,
isDragging,
prohibitInteraction,
hideThumbLabel,
});
return (_jsxs("div", { ...restProps, ref: sliderRef, "data-dragging": isDragging, "data-bilateral": bilateral, "data-vertical": vertical, "data-disabled": disabled, "data-direction": direction, onMouseDown: startDragging, className: cn(base(), restProps.className), style: {
...restProps.style,
position: 'relative',
userSelect: 'none',
}, children: [_jsx("div", { className: cn(progress(), classNames?.progress), style: {
...styles?.progress,
...progressStyle,
borderRadius: bilateral ? 0 : undefined,
} }), !hideThumb && (_jsx("div", { className: cn(thumb(), classNames?.thumb), style: {
...styles?.thumb,
position: 'absolute',
left: vertical ? '50%' : `${((value - min) / (max - min)) * 100}%`,
top: vertical ? '' : '50%',
bottom: vertical ? `${((value - min) / (max - min)) * 100}%` : '',
transform: vertical
? 'translateX(-50%) translateY(50%)'
: 'translateX(-50%) translateY(-50%)',
}, children: _jsx("div", { className: cn(thumbLabel(), classNames?.thumbLabel), style: {
...styles?.thumbLabel,
position: 'absolute',
bottom: vertical ? '50%' : '100%',
transform: vertical ? 'translateX(-0.25rem) translateY(50%)' : 'translateX(-50%)',
left: vertical ? '100%' : '50%',
userSelect: 'none',
pointerEvents: 'none',
}, children: value }) })), axis && (_jsx(Axis, { className: cn(vertical ? 'ml-5' : 'mt-3', classNames?.axis), style: styles?.axis, min: min, max: max, vertical: vertical, relatedRef: sliderRef, ...axisProps }))] }));
});