You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
367 lines
16 KiB
367 lines
16 KiB
var __rest = (this && this.__rest) || function (s, e) {
|
|
var t = {};
|
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
t[p] = s[p];
|
|
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
t[p[i]] = s[p[i]];
|
|
}
|
|
return t;
|
|
};
|
|
import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
import { View, StyleSheet, Animated, Easing, PanResponder, Platform, } from 'react-native';
|
|
import { Rect } from './components/Rect';
|
|
import { SliderThumb } from './components/SliderThumb';
|
|
const TRACK_SIZE = 4;
|
|
const THUMB_SIZE = 40;
|
|
const TRACK_STYLE = Platform.select({ web: 0, default: -1 });
|
|
const DEFAULT_ANIMATION_CONFIGS = {
|
|
spring: {
|
|
friction: 7,
|
|
tension: 100,
|
|
useNativeDriver: true,
|
|
},
|
|
timing: {
|
|
duration: 150,
|
|
easing: Easing.inOut(Easing.ease),
|
|
delay: 0,
|
|
useNativeDriver: true,
|
|
},
|
|
};
|
|
const getBoundedValue = (value, maximumValue, minimumValue) => Math.max(Math.min(value, maximumValue), minimumValue);
|
|
const handlePanResponderRequestEnd = () => false;
|
|
const handleMoveShouldSetPanResponder = () => !TRACK_STYLE;
|
|
var SizableVars;
|
|
(function (SizableVars) {
|
|
SizableVars["containerSize"] = "containerSize";
|
|
SizableVars["thumbSize"] = "thumbSize";
|
|
SizableVars["trackSize"] = "trackSize";
|
|
})(SizableVars || (SizableVars = {}));
|
|
var EventTypes;
|
|
(function (EventTypes) {
|
|
EventTypes["onSlidingStart"] = "onSlidingStart";
|
|
EventTypes["onValueChange"] = "onValueChange";
|
|
EventTypes["onSlidingComplete"] = "onSlidingComplete";
|
|
})(EventTypes || (EventTypes = {}));
|
|
export const Slider = (_a) => {
|
|
var { allowTouchTrack = false, animateTransitions, animationConfig, animationType = 'timing', containerStyle, debugTouchArea = false, disabled, maximumTrackTintColor = '#b3b3b3', maximumValue = 1, minimumTrackTintColor = '#3f3f3f', minimumValue = 0, onSlidingComplete, onSlidingStart, onValueChange, orientation = 'horizontal', step = 0, style, thumbProps, thumbStyle, thumbTintColor = 'red', thumbTouchSize = { height: THUMB_SIZE, width: THUMB_SIZE }, trackStyle, value: _propValue = 0 } = _a, other = __rest(_a, ["allowTouchTrack", "animateTransitions", "animationConfig", "animationType", "containerStyle", "debugTouchArea", "disabled", "maximumTrackTintColor", "maximumValue", "minimumTrackTintColor", "minimumValue", "onSlidingComplete", "onSlidingStart", "onValueChange", "orientation", "step", "style", "thumbProps", "thumbStyle", "thumbTintColor", "thumbTouchSize", "trackStyle", "value"]);
|
|
const propValue = getBoundedValue(_propValue, maximumValue, minimumValue);
|
|
const prevPropValue = useRef(propValue);
|
|
const animatedValue = useRef(new Animated.Value(propValue));
|
|
const _previousLeft = useRef(0);
|
|
const gestureStartPosition = useRef(0);
|
|
const [allMeasured, setAllMeasured] = useState(false);
|
|
const [containerSize, setContainerSize] = useState({
|
|
width: 0,
|
|
height: 0,
|
|
});
|
|
const [trackSize, setTrackSize] = useState({ width: 0, height: 0 });
|
|
const [thumbSize, setThumbSize] = useState({ width: 0, height: 0 });
|
|
const isVertical = orientation === 'vertical';
|
|
const handleMeasure = useCallback((name, event) => {
|
|
var _a, _b;
|
|
const varInfo = {
|
|
containerSize: { size: containerSize, setSize: setContainerSize },
|
|
thumbSize: { size: thumbSize, setSize: setThumbSize },
|
|
trackSize: { size: trackSize, setSize: setTrackSize },
|
|
};
|
|
const { size, setSize } = varInfo[name];
|
|
const rect = event.nativeEvent.layout;
|
|
const rectWidth = (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : size.width;
|
|
const rectHeight = (_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : size.height;
|
|
const newSize = {
|
|
height: isVertical ? rectWidth : rectHeight,
|
|
width: isVertical ? rectHeight : rectWidth,
|
|
};
|
|
setSize(newSize);
|
|
}, [containerSize, isVertical, thumbSize, trackSize]);
|
|
useEffect(() => setAllMeasured(!!(containerSize.height &&
|
|
containerSize.width &&
|
|
thumbSize.height &&
|
|
thumbSize.width &&
|
|
trackSize.height &&
|
|
trackSize.width)), [
|
|
containerSize.height,
|
|
containerSize.width,
|
|
thumbSize.height,
|
|
thumbSize.width,
|
|
trackSize.height,
|
|
trackSize.width,
|
|
]);
|
|
const measureContainer = useCallback((event) => handleMeasure(SizableVars.containerSize, event), [handleMeasure]);
|
|
const measureTrack = useCallback((event) => handleMeasure(SizableVars.trackSize, event), [handleMeasure]);
|
|
const measureThumb = useCallback((event) => handleMeasure(SizableVars.thumbSize, event), [handleMeasure]);
|
|
const setCurrentValue = useCallback((v) => animatedValue.current.setValue(v), [animatedValue]);
|
|
const setCurrentValueAnimated = useCallback((v) => Animated[animationType](animatedValue.current, Object.assign(Object.assign(Object.assign({}, DEFAULT_ANIMATION_CONFIGS[animationType]), animationConfig), { toValue: v })).start(), [animationConfig, animationType]);
|
|
useEffect(() => {
|
|
if (prevPropValue.current !== propValue) {
|
|
prevPropValue.current = propValue;
|
|
if (animateTransitions) {
|
|
setCurrentValueAnimated(propValue);
|
|
}
|
|
else {
|
|
setCurrentValue(propValue);
|
|
}
|
|
}
|
|
}, [
|
|
animateTransitions,
|
|
maximumValue,
|
|
minimumValue,
|
|
setCurrentValue,
|
|
setCurrentValueAnimated,
|
|
propValue,
|
|
]);
|
|
const getValueForTouch = useCallback((location) => {
|
|
const length = containerSize.width - thumbSize.width;
|
|
const ratio = location / length;
|
|
let newValue = ratio * (maximumValue - minimumValue);
|
|
if (step) {
|
|
newValue = Math.round(newValue / step) * step;
|
|
}
|
|
return getBoundedValue(newValue + minimumValue, maximumValue, minimumValue);
|
|
}, [containerSize.width, maximumValue, minimumValue, step, thumbSize.width]);
|
|
const getOnTouchValue = useCallback(({ nativeEvent }) => {
|
|
const location = isVertical
|
|
? nativeEvent.locationY
|
|
: nativeEvent.locationX;
|
|
return getValueForTouch(location);
|
|
}, [getValueForTouch, isVertical]);
|
|
const getThumbLeft = useCallback((v) => {
|
|
const ratio = (v - minimumValue) / (maximumValue - minimumValue);
|
|
return ratio * (containerSize.width - thumbSize.width);
|
|
}, [containerSize.width, maximumValue, minimumValue, thumbSize.width]);
|
|
const getTouchOverflowSize = useCallback(() => allMeasured
|
|
? {
|
|
height: Math.max(0, thumbTouchSize.height - containerSize.height),
|
|
width: Math.max(0, thumbTouchSize.width - thumbSize.width),
|
|
}
|
|
: { height: 0, width: 0 }, [
|
|
allMeasured,
|
|
containerSize.height,
|
|
thumbSize.width,
|
|
thumbTouchSize.height,
|
|
thumbTouchSize.width,
|
|
]);
|
|
const getCurrentValue = useCallback(() => animatedValue.current.__getValue(), []);
|
|
const getThumbTouchRect = useCallback(() => {
|
|
const touchOverflowSize = getTouchOverflowSize();
|
|
const height = touchOverflowSize.height / 2 +
|
|
(containerSize.height - thumbTouchSize.height) / 2;
|
|
const width = touchOverflowSize.width / 2 +
|
|
getThumbLeft(getCurrentValue()) +
|
|
(thumbSize.width - thumbTouchSize.width) / 2;
|
|
return isVertical
|
|
? new Rect(height, width, thumbTouchSize.width, thumbTouchSize.height)
|
|
: new Rect(width, height, thumbTouchSize.width, thumbTouchSize.height);
|
|
}, [
|
|
containerSize.height,
|
|
getCurrentValue,
|
|
getThumbLeft,
|
|
getTouchOverflowSize,
|
|
isVertical,
|
|
thumbSize.width,
|
|
thumbTouchSize.height,
|
|
thumbTouchSize.width,
|
|
]);
|
|
const getValue = useCallback((gestureState) => {
|
|
const delta = (isVertical ? gestureState.moveY : gestureState.moveX) -
|
|
gestureStartPosition.current;
|
|
const location = _previousLeft.current + delta;
|
|
return getValueForTouch(location);
|
|
}, [getValueForTouch, isVertical]);
|
|
const fireChangeEvent = useCallback((event) => {
|
|
const v = getCurrentValue();
|
|
if (event === EventTypes.onSlidingStart) {
|
|
onSlidingStart === null || onSlidingStart === void 0 ? void 0 : onSlidingStart(v);
|
|
}
|
|
else if (event === EventTypes.onSlidingComplete) {
|
|
onSlidingComplete === null || onSlidingComplete === void 0 ? void 0 : onSlidingComplete(v);
|
|
}
|
|
else if (event === EventTypes.onValueChange) {
|
|
onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(v);
|
|
}
|
|
}, [getCurrentValue, onSlidingComplete, onSlidingStart, onValueChange]);
|
|
const handlePanResponderGrant = useCallback((e, gestureState) => {
|
|
_previousLeft.current = getThumbLeft(getCurrentValue());
|
|
gestureStartPosition.current = isVertical
|
|
? gestureState.y0
|
|
: gestureState.x0;
|
|
fireChangeEvent(EventTypes.onSlidingStart);
|
|
}, [fireChangeEvent, getCurrentValue, getThumbLeft, isVertical]);
|
|
const handlePanResponderMove = useCallback((_, gestureState) => {
|
|
if (!disabled) {
|
|
setCurrentValue(getValue(gestureState));
|
|
fireChangeEvent(EventTypes.onValueChange);
|
|
}
|
|
}, [disabled, fireChangeEvent, getValue, setCurrentValue]);
|
|
const handlePanResponderEnd = useCallback(() => {
|
|
if (!disabled) {
|
|
fireChangeEvent(EventTypes.onSlidingComplete);
|
|
}
|
|
}, [disabled, fireChangeEvent]);
|
|
const thumbHitTest = useCallback(({ nativeEvent }) => {
|
|
const thumbTouchRect = getThumbTouchRect();
|
|
return thumbTouchRect.containsPoint(nativeEvent.locationX, nativeEvent.locationY);
|
|
}, [getThumbTouchRect]);
|
|
const handleStartShouldSetPanResponder = useCallback((e) => {
|
|
if (!allowTouchTrack) {
|
|
return thumbHitTest(e);
|
|
}
|
|
setCurrentValue(getOnTouchValue(e));
|
|
fireChangeEvent(EventTypes.onValueChange);
|
|
return true;
|
|
}, [
|
|
allowTouchTrack,
|
|
fireChangeEvent,
|
|
getOnTouchValue,
|
|
setCurrentValue,
|
|
thumbHitTest,
|
|
]);
|
|
const getTouchOverflowStyle = useCallback(() => {
|
|
const { width, height } = getTouchOverflowSize();
|
|
const touchOverflowStyle = {};
|
|
const verticalMargin = -height / 2;
|
|
touchOverflowStyle.marginTop = verticalMargin;
|
|
touchOverflowStyle.marginBottom = verticalMargin;
|
|
const horizontalMargin = -width / 2;
|
|
touchOverflowStyle.marginLeft = horizontalMargin;
|
|
touchOverflowStyle.marginRight = horizontalMargin;
|
|
if (debugTouchArea === true) {
|
|
touchOverflowStyle.backgroundColor = 'orange';
|
|
touchOverflowStyle.opacity = 0.5;
|
|
}
|
|
return touchOverflowStyle;
|
|
}, [debugTouchArea, getTouchOverflowSize]);
|
|
const renderDebugThumbTouchRect = useCallback((thumbLeft) => {
|
|
const thumbTouchRect = getThumbTouchRect();
|
|
const positionStyle = {
|
|
left: thumbLeft,
|
|
top: thumbTouchRect.y,
|
|
width: thumbTouchRect.width,
|
|
height: thumbTouchRect.height,
|
|
};
|
|
return React.createElement(Animated.View, { style: positionStyle, pointerEvents: "none" });
|
|
}, [getThumbTouchRect]);
|
|
const getMinimumTrackStyles = useCallback((thumbStart) => {
|
|
const minimumTrackStyle = {
|
|
position: 'absolute',
|
|
};
|
|
if (!allMeasured) {
|
|
minimumTrackStyle.height = 0;
|
|
minimumTrackStyle.width = 0;
|
|
}
|
|
else if (isVertical) {
|
|
minimumTrackStyle.height = Animated.add(thumbStart, thumbSize.height / 2);
|
|
minimumTrackStyle.marginLeft = trackSize.width * TRACK_STYLE;
|
|
}
|
|
else {
|
|
minimumTrackStyle.width = Animated.add(thumbStart, thumbSize.width / 2);
|
|
minimumTrackStyle.marginTop = trackSize.height * TRACK_STYLE;
|
|
}
|
|
return minimumTrackStyle;
|
|
}, [
|
|
allMeasured,
|
|
isVertical,
|
|
thumbSize.height,
|
|
thumbSize.width,
|
|
trackSize.height,
|
|
trackSize.width,
|
|
]);
|
|
const panResponder = useMemo(() => PanResponder.create({
|
|
onStartShouldSetPanResponder: handleStartShouldSetPanResponder,
|
|
onMoveShouldSetPanResponder: handleMoveShouldSetPanResponder,
|
|
onPanResponderGrant: handlePanResponderGrant,
|
|
onPanResponderMove: handlePanResponderMove,
|
|
onPanResponderRelease: handlePanResponderEnd,
|
|
onPanResponderTerminationRequest: handlePanResponderRequestEnd,
|
|
onPanResponderTerminate: handlePanResponderEnd,
|
|
}), [
|
|
handleStartShouldSetPanResponder,
|
|
handlePanResponderGrant,
|
|
handlePanResponderMove,
|
|
handlePanResponderEnd,
|
|
]);
|
|
const mainStyles = containerStyle !== null && containerStyle !== void 0 ? containerStyle : styles;
|
|
const appliedTrackStyle = StyleSheet.flatten([styles.track, trackStyle]);
|
|
const thumbStart = animatedValue.current.interpolate({
|
|
inputRange: [minimumValue, maximumValue],
|
|
outputRange: [0, containerSize.width - thumbSize.width],
|
|
});
|
|
const minimumTrackStyle = Object.assign(Object.assign({}, getMinimumTrackStyles(thumbStart)), { backgroundColor: minimumTrackTintColor });
|
|
const touchOverflowStyle = getTouchOverflowStyle();
|
|
return (React.createElement(View, Object.assign({ testID: "RNE__Slider_Container" }, other, { style: StyleSheet.flatten([
|
|
isVertical
|
|
? mainStyles.containerVertical
|
|
: mainStyles.containerHorizontal,
|
|
style,
|
|
]), onLayout: measureContainer, accessibilityRole: "adjustable", accessibilityValue: {
|
|
min: minimumValue,
|
|
max: maximumValue,
|
|
now: getCurrentValue(),
|
|
} }),
|
|
React.createElement(View, { testID: "RNE__Slider_Track_maximum", style: StyleSheet.flatten([
|
|
mainStyles.track,
|
|
isVertical ? mainStyles.trackVertical : mainStyles.trackHorizontal,
|
|
appliedTrackStyle,
|
|
{ backgroundColor: maximumTrackTintColor },
|
|
]), onLayout: measureTrack }),
|
|
React.createElement(Animated.View, { testID: "RNE__Slider_Track_minimum", style: StyleSheet.flatten([
|
|
mainStyles.track,
|
|
isVertical ? mainStyles.trackVertical : mainStyles.trackHorizontal,
|
|
appliedTrackStyle,
|
|
minimumTrackStyle,
|
|
]) }),
|
|
React.createElement(SliderThumb, Object.assign({ isVisible: allMeasured, onLayout: measureThumb, style: thumbStyle, color: thumbTintColor, start: thumbStart, vertical: isVertical }, thumbProps)),
|
|
React.createElement(View, Object.assign({ testID: "RNE__Slider_TouchArea", style: StyleSheet.flatten([styles.touchArea, touchOverflowStyle]) }, panResponder.panHandlers), debugTouchArea === true && renderDebugThumbTouchRect(thumbStart))));
|
|
};
|
|
Slider.defaultProps = {
|
|
value: 0,
|
|
minimumValue: 0,
|
|
maximumValue: 1,
|
|
step: 0,
|
|
minimumTrackTintColor: '#3f3f3f',
|
|
maximumTrackTintColor: '#b3b3b3',
|
|
allowTouchTrack: false,
|
|
thumbTintColor: 'red',
|
|
thumbTouchSize: { width: THUMB_SIZE, height: THUMB_SIZE },
|
|
debugTouchArea: false,
|
|
animationType: 'timing',
|
|
orientation: 'horizontal',
|
|
};
|
|
const styles = StyleSheet.create({
|
|
containerHorizontal: {
|
|
height: 40,
|
|
justifyContent: 'center',
|
|
},
|
|
containerVertical: {
|
|
width: 40,
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
},
|
|
track: {
|
|
borderRadius: TRACK_SIZE / 2,
|
|
},
|
|
trackHorizontal: {
|
|
height: TRACK_SIZE,
|
|
},
|
|
trackVertical: {
|
|
flex: 1,
|
|
width: TRACK_SIZE,
|
|
},
|
|
touchArea: {
|
|
position: 'absolute',
|
|
backgroundColor: 'transparent',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
},
|
|
debugThumbTouchArea: {
|
|
position: 'absolute',
|
|
backgroundColor: 'green',
|
|
opacity: 0.5,
|
|
},
|
|
});
|
|
Slider.displayName = 'Slider';
|