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

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';