react-native-sticky-range-slider
Version:
[React Native | TypeScript] A pure TypeScript component offering a customizable range slider optimized for performance. It supports dragging functionalities for selecting a range of values, with values that smoothly follow the thumb. This component is ful
240 lines • 8.8 kB
JavaScript
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Animated, PanResponder, StyleSheet, View } from 'react-native';
import { useLowHigh, useSelectedRail, useWidthLayout } from '../hooks';
import { clamp, getValueForPosition, isLowCloser } from '../utils/helpers';
import Rail from './Rail';
import RailSelected from './RailSelected';
import TextValue from './TextValue';
import Thumb from './Thumb';
const trueFunc = () => true;
const falseFunc = () => false;
const Slider = ({
min,
max,
minRange: minRangeProp = 0,
step,
low: lowProp,
high: highProp,
onValueChanged,
renderThumb = () => /*#__PURE__*/React.createElement(Thumb, null),
renderRail = () => /*#__PURE__*/React.createElement(Rail, null),
renderRailSelected = () => /*#__PURE__*/React.createElement(RailSelected, null),
renderLowValue = low => /*#__PURE__*/React.createElement(TextValue, {
value: low
}),
renderHighValue = high => /*#__PURE__*/React.createElement(TextValue, {
value: high
}),
pannableAreaStyle,
disableRange = false,
disabled = false,
...restProps
}) => {
const minRange = disableRange ? 0 : minRangeProp;
const {
inPropsRef,
inPropsRefPrev,
setLow,
setHigh
} = useLowHigh(lowProp, disableRange ? max : highProp, min, max, step);
const lowThumbXRef = useRef(new Animated.Value(0));
const highThumbXRef = useRef(new Animated.Value(0));
const pointerX = useRef(new Animated.Value(0)).current;
const {
current: lowThumbX
} = lowThumbXRef;
const {
current: highThumbX
} = highThumbXRef;
const gestureStateRef = useRef({
isLow: true,
lastValue: 0,
lastPosition: 0
});
const containerWidthRef = useRef(0);
const [thumbWidth, setThumbWidth] = useState(0);
const {
selectedRailStyle,
updateSelectedRail
} = useSelectedRail(inPropsRef, containerWidthRef, thumbWidth, disableRange);
const updateThumbs = useCallback(() => {
const {
current: containerWidth
} = containerWidthRef;
if (!thumbWidth || !containerWidth) {
return;
}
const {
low,
high
} = inPropsRef.current;
if (!disableRange) {
const highPosition = (high - min) / (max - min) * (containerWidth - thumbWidth);
highThumbXRef.current.setValue(highPosition);
}
const lowPosition = (low - min) / (max - min) * (containerWidth - thumbWidth);
lowThumbXRef.current.setValue(lowPosition);
updateSelectedRail();
onValueChanged?.(low, high);
}, [disableRange, inPropsRef, max, min, onValueChanged, thumbWidth, updateSelectedRail]);
useEffect(() => {
const {
lowPrev,
highPrev
} = inPropsRefPrev;
if (lowProp !== undefined && lowProp !== lowPrev || highProp !== undefined && highProp !== highPrev) {
updateThumbs();
}
}, [highProp, inPropsRefPrev, lowProp, updateThumbs]);
useEffect(() => {
updateThumbs();
}, [updateThumbs]);
const handleContainerLayout = useWidthLayout(containerWidthRef, updateThumbs);
const handleThumbLayout = useCallback(({
nativeEvent
}) => {
const {
layout: {
width
}
} = nativeEvent;
if (thumbWidth !== width) {
setThumbWidth(width);
}
}, [thumbWidth]);
const lowStyles = useMemo(() => {
return {
transform: [{
translateX: lowThumbX
}]
};
}, [lowThumbX]);
const highStyles = useMemo(() => {
return disableRange ? null : [styles.highThumbContainer, {
transform: [{
translateX: highThumbX
}]
}];
}, [disableRange, highThumbX]);
const railContainerStyles = useMemo(() => {
return [styles.railsContainer, {
marginHorizontal: thumbWidth / 2
}];
}, [thumbWidth]);
const lowThumb = renderThumb('low');
const highThumb = renderThumb('high');
const {
panHandlers
} = useMemo(() => PanResponder.create({
onStartShouldSetPanResponderCapture: falseFunc,
onMoveShouldSetPanResponderCapture: falseFunc,
onPanResponderTerminationRequest: falseFunc,
onPanResponderTerminate: trueFunc,
onShouldBlockNativeResponder: trueFunc,
onMoveShouldSetPanResponder: (_evt, gestureState) => Math.abs(gestureState.dx) > 2 * Math.abs(gestureState.dy),
onPanResponderGrant: ({
nativeEvent
}, gestureState) => {
if (disabled) {
return;
}
const {
numberActiveTouches
} = gestureState;
if (numberActiveTouches > 1) {
return;
}
const {
locationX: downX,
pageX
} = nativeEvent;
const containerX = pageX - downX;
const containerWidth = containerWidthRef.current;
const lowPosition = thumbWidth / 2 + (inPropsRef.current.low - inPropsRef.current.min) / (inPropsRef.current.max - inPropsRef.current.min) * (containerWidth - thumbWidth);
const highPosition = thumbWidth / 2 + (inPropsRef.current.high - inPropsRef.current.min) / (inPropsRef.current.max - inPropsRef.current.min) * (containerWidth - thumbWidth);
const isLow = disableRange || isLowCloser(downX, lowPosition, highPosition);
gestureStateRef.current.isLow = isLow;
const handlePositionChange = positionInView => {
const minValue = isLow ? inPropsRef.current.min : inPropsRef.current.low + minRange;
const maxValue = isLow ? inPropsRef.current.high - minRange : inPropsRef.current.max;
const value = clamp(getValueForPosition(positionInView, containerWidth, thumbWidth, inPropsRef.current.min, inPropsRef.current.max, inPropsRef.current.step), minValue, maxValue);
if (gestureStateRef.current.lastValue === value) {
return;
}
const availableSpace = containerWidth - thumbWidth;
const absolutePosition = (value - inPropsRef.current.min) / (inPropsRef.current.max - inPropsRef.current.min) * availableSpace;
gestureStateRef.current.lastValue = value;
gestureStateRef.current.lastPosition = absolutePosition + thumbWidth / 2;
(isLow ? lowThumbXRef.current : highThumbXRef.current).setValue(absolutePosition);
onValueChanged?.(isLow ? value : inPropsRef.current.low, isLow ? inPropsRef.current.high : value);
(isLow ? setLow : setHigh)(value);
updateSelectedRail();
};
handlePositionChange(downX);
pointerX.removeAllListeners();
pointerX.addListener(({
value: pointerPosition
}) => {
const positionInView = pointerPosition - containerX;
handlePositionChange(positionInView);
});
},
onPanResponderMove: disabled ? undefined : Animated.event([null, {
moveX: pointerX
}], {
useNativeDriver: false
})
}), [disableRange, disabled, inPropsRef, minRange, onValueChanged, pointerX, setHigh, setLow, thumbWidth, updateSelectedRail]);
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, restProps, /*#__PURE__*/React.createElement(View, {
onLayout: handleContainerLayout,
style: styles.controlsContainer
}, /*#__PURE__*/React.createElement(View, {
style: railContainerStyles
}, renderRail(), /*#__PURE__*/React.createElement(Animated.View, {
style: selectedRailStyle
}, renderRailSelected())), /*#__PURE__*/React.createElement(Animated.View, {
style: lowStyles,
onLayout: handleThumbLayout
}, /*#__PURE__*/React.createElement(View, {
style: styles.value
}, renderLowValue(inPropsRef.current.low)), lowThumb), !disableRange && /*#__PURE__*/React.createElement(Animated.View, {
style: highStyles
}, /*#__PURE__*/React.createElement(View, {
style: styles.value
}, renderHighValue(inPropsRef.current.high)), highThumb), /*#__PURE__*/React.createElement(View, _extends({}, panHandlers, {
style: [styles.touchableArea, pannableAreaStyle],
collapsable: false
})))));
};
const styles = StyleSheet.create({
controlsContainer: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center'
},
highThumbContainer: {
position: 'absolute'
},
railsContainer: {
...StyleSheet.absoluteFillObject,
flexDirection: 'row',
alignItems: 'center'
},
touchableArea: {
position: "absolute",
top: -20,
bottom: -20,
left: -20,
right: -20,
opacity: 0.5,
zIndex: 1000
},
value: {
position: 'absolute',
top: -20,
width: 40
}
});
export default /*#__PURE__*/memo(Slider);
//# sourceMappingURL=RangeSlider.js.map