1 | var __rest = (this && this.__rest) || function (s, e) {
|
2 | var t = {};
|
3 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
4 | t[p] = s[p];
|
5 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
6 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
7 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
8 | t[p[i]] = s[p[i]];
|
9 | }
|
10 | return t;
|
11 | };
|
12 | import React, { useEffect, useRef, useState } from 'react';
|
13 | import { View, StyleSheet, Animated, Easing, PanResponder, Platform, } from 'react-native';
|
14 | import { withTheme } from '../config';
|
15 | const TRACK_SIZE = 4;
|
16 | const THUMB_SIZE = 40;
|
17 | const TRACK_STYLE = Platform.select({ web: 0, default: -1 });
|
18 | const DEFAULT_ANIMATION_CONFIGS = {
|
19 | spring: {
|
20 | friction: 7,
|
21 | tension: 100,
|
22 | useNativeDriver: true,
|
23 | },
|
24 | timing: {
|
25 | duration: 150,
|
26 | easing: Easing.inOut(Easing.ease),
|
27 | delay: 0,
|
28 | useNativeDriver: true,
|
29 | },
|
30 | };
|
31 | const getBoundedValue = (value, maximumValue, minimumValue) => {
|
32 | return Math.max(Math.min(value, maximumValue), minimumValue);
|
33 | };
|
34 | class Rect {
|
35 | constructor(x, y, width, height) {
|
36 | this.x = x;
|
37 | this.y = y;
|
38 | this.width = width;
|
39 | this.height = height;
|
40 | }
|
41 | containsPoint(x, y) {
|
42 | return (x >= this.x &&
|
43 | y >= this.y &&
|
44 | x <= this.x + this.width &&
|
45 | y <= this.y + this.height);
|
46 | }
|
47 | }
|
48 | const Slider = (props) => {
|
49 | const _previousLeft = useRef(0);
|
50 | const [allMeasured, setAllMeasured] = useState(false);
|
51 | const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
52 | const [trackSize, setTrackSize] = useState({ width: 0, height: 0 });
|
53 | const [thumbSize, setThumbSize] = useState({ width: 0, height: 0 });
|
54 | const containerSizeValue = useRef(containerSize);
|
55 | const trackSizeValue = useRef(trackSize);
|
56 | const thumbSizeValue = useRef(thumbSize);
|
57 | const isVertical = useRef(props.orientation === 'vertical');
|
58 | const [value] = useState(new Animated.Value(getBoundedValue(props.value || 0, props.maximumValue || 1, props.minimumValue || 0)));
|
59 | useEffect(() => {
|
60 | containerSizeValue.current = containerSize;
|
61 | }, [containerSize]);
|
62 | useEffect(() => {
|
63 | trackSizeValue.current = trackSize;
|
64 | }, [trackSize]);
|
65 | useEffect(() => {
|
66 | thumbSizeValue.current = thumbSize;
|
67 | }, [thumbSize]);
|
68 | const measureContainer = (x) => {
|
69 | handleMeasure('containerSize', x);
|
70 | };
|
71 | const measureTrack = (x) => {
|
72 | handleMeasure('trackSize', x);
|
73 | };
|
74 | const measureThumb = (x) => {
|
75 | handleMeasure('thumbSize', x);
|
76 | };
|
77 | const handleMeasure = (name, x) => {
|
78 | const { width: layoutWidth, height: layoutHeight } = x.nativeEvent.layout;
|
79 | const width = isVertical.current ? layoutHeight : layoutWidth;
|
80 | const height = isVertical.current ? layoutWidth : layoutHeight;
|
81 | const size = { width, height };
|
82 | if (name === 'containerSize') {
|
83 | setContainerSize(size);
|
84 | }
|
85 | if (name === 'thumbSize') {
|
86 | setThumbSize(size);
|
87 | }
|
88 | if (name === 'trackSize') {
|
89 | setTrackSize(size);
|
90 | }
|
91 | if (thumbSize && trackSize && containerSize) {
|
92 | setAllMeasured(true);
|
93 | }
|
94 | };
|
95 | const currentPropValue = useRef(props.value || 0);
|
96 | const prevPropValue = useRef(0);
|
97 | const didMountRef = useRef(false);
|
98 | const setCurrentValue = React.useCallback((value1) => {
|
99 | value.setValue(value1);
|
100 | }, [value]);
|
101 | React.useEffect(() => {
|
102 | setCurrentValue(props.value || 0);
|
103 | }, [props.value, setCurrentValue]);
|
104 | useEffect(() => {
|
105 | prevPropValue.current = props.value || 0;
|
106 | if (didMountRef.current) {
|
107 | const newValue = getBoundedValue(props.value || 0, props.maximumValue || 1, props.minimumValue || 0);
|
108 | if (prevPropValue.current !== newValue) {
|
109 | if (props.animateTransitions) {
|
110 | setCurrentValueAnimated(new Animated.Value(newValue));
|
111 | }
|
112 | else {
|
113 | setCurrentValue(newValue);
|
114 | }
|
115 | }
|
116 | }
|
117 | else {
|
118 | didMountRef.current = true;
|
119 | }
|
120 | });
|
121 | const setCurrentValueAnimated = (value1) => {
|
122 | const { animationType } = props;
|
123 | const animationConfig = Object.assign({}, DEFAULT_ANIMATION_CONFIGS[animationType || 'timing'], props.animationConfig, {
|
124 | toValue: value1,
|
125 | });
|
126 | Animated[animationType || 'timing'](value, animationConfig).start();
|
127 | };
|
128 | const handleMoveShouldSetPanResponder = () => {
|
129 |
|
130 | if (!TRACK_STYLE) {
|
131 | return true;
|
132 | }
|
133 | return false;
|
134 | };
|
135 | const handlePanResponderGrant = () => {
|
136 | _previousLeft.current = getThumbLeft(currentPropValue.current);
|
137 | fireChangeEvent('onSlidingStart');
|
138 | };
|
139 | const handlePanResponderMove = (_, gestureState) => {
|
140 | if (props.disabled) {
|
141 | return;
|
142 | }
|
143 | setCurrentValue(getValue(gestureState));
|
144 | fireChangeEvent('onValueChange');
|
145 | };
|
146 | const handlePanResponderRequestEnd = () => {
|
147 |
|
148 | return false;
|
149 | };
|
150 | const handlePanResponderEnd = (_, gestureState) => {
|
151 | if (props.disabled) {
|
152 | return;
|
153 | }
|
154 | setCurrentValue(getValue(gestureState));
|
155 | fireChangeEvent('onSlidingComplete');
|
156 | };
|
157 | const thumbHitTest = ({ nativeEvent }) => {
|
158 | const thumbTouchRect = getThumbTouchRect();
|
159 | return thumbTouchRect.containsPoint(nativeEvent.locationX, nativeEvent.locationY);
|
160 | };
|
161 | const handleStartShouldSetPanResponder = (e /* gestureState: Object */) => {
|
162 |
|
163 | if (!props.allowTouchTrack && !TRACK_STYLE) {
|
164 | return thumbHitTest(e);
|
165 | }
|
166 | if (!trackStyle) {
|
167 | setCurrentValue(getOnTouchValue(e));
|
168 | }
|
169 | fireChangeEvent('onValueChange');
|
170 | return true;
|
171 | };
|
172 | const fireChangeEvent = (event) => {
|
173 | var _a;
|
174 | if (props === null || props === void 0 ? void 0 : props[event]) {
|
175 | (_a = props === null || props === void 0 ? void 0 : props[event]) === null || _a === void 0 ? void 0 : _a.call(props, currentPropValue.current);
|
176 | }
|
177 | };
|
178 |
|
179 | const getOnTouchValue = ({ nativeEvent }) => {
|
180 | const location = isVertical.current
|
181 | ? nativeEvent.locationY
|
182 | : nativeEvent.locationX;
|
183 | return getValueForTouch(location);
|
184 | };
|
185 | const getValueForTouch = (location) => {
|
186 | const length = containerSizeValue.current.width - thumbSizeValue.current.width;
|
187 | const ratio = location / length;
|
188 | let newValue = ratio * ((props.maximumValue || 1) - (props.minimumValue || 0));
|
189 | if (props.step) {
|
190 | newValue = Math.round(newValue / props.step) * props.step;
|
191 | }
|
192 | return getBoundedValue(newValue + (props.minimumValue || 0), props.maximumValue || 1, props.minimumValue || 0);
|
193 | };
|
194 | const getTouchOverflowSize = () => {
|
195 | const { thumbTouchSize } = props;
|
196 | const size = {};
|
197 | if (allMeasured === true) {
|
198 | size.width = Math.max(0, ((thumbTouchSize === null || thumbTouchSize === void 0 ? void 0 : thumbTouchSize.width) || THUMB_SIZE) - thumbSizeValue.current.width);
|
199 | size.height = Math.max(0, ((thumbTouchSize === null || thumbTouchSize === void 0 ? void 0 : thumbTouchSize.height) || THUMB_SIZE) -
|
200 | containerSizeValue.current.height);
|
201 | }
|
202 | return size;
|
203 | };
|
204 | const getTouchOverflowStyle = () => {
|
205 | const { width, height } = getTouchOverflowSize();
|
206 | const touchOverflowStyle = {};
|
207 | if (width !== undefined && height !== undefined) {
|
208 | const verticalMargin = -height / 2;
|
209 | touchOverflowStyle.marginTop = verticalMargin;
|
210 | touchOverflowStyle.marginBottom = verticalMargin;
|
211 | const horizontalMargin = -width / 2;
|
212 | touchOverflowStyle.marginLeft = horizontalMargin;
|
213 | touchOverflowStyle.marginRight = horizontalMargin;
|
214 | }
|
215 | if (props.debugTouchArea === true) {
|
216 | touchOverflowStyle.backgroundColor = 'orange';
|
217 | touchOverflowStyle.opacity = 0.5;
|
218 | }
|
219 | return touchOverflowStyle;
|
220 | };
|
221 | const getValue = (gestureState) => {
|
222 | const location = _previousLeft.current +
|
223 | (isVertical.current ? gestureState.dy : gestureState.dx);
|
224 | return getValueForTouch(location);
|
225 | };
|
226 | React.useEffect(() => {
|
227 | let listenerID = value.addListener((obj) => {
|
228 | currentPropValue.current = obj.value;
|
229 | });
|
230 | return () => {
|
231 | value.removeListener(listenerID);
|
232 | };
|
233 | });
|
234 | const getRatio = (value1) => {
|
235 | return ((value1 - (props.minimumValue || 0)) /
|
236 | ((props.maximumValue || 1) - (props.minimumValue || 0)));
|
237 | };
|
238 | const getThumbLeft = (value1) => {
|
239 | const ratio = getRatio(value1);
|
240 | return (ratio * (containerSizeValue.current.width - thumbSizeValue.current.width));
|
241 | };
|
242 | const getThumbTouchRect = () => {
|
243 | const { thumbTouchSize } = props;
|
244 | const touchOverflowSize = getTouchOverflowSize();
|
245 | const height = (touchOverflowSize === null || touchOverflowSize === void 0 ? void 0 : touchOverflowSize.height) / 2 +
|
246 | (containerSizeValue.current.height -
|
247 | ((thumbTouchSize === null || thumbTouchSize === void 0 ? void 0 : thumbTouchSize.height) || THUMB_SIZE)) /
|
248 | 2;
|
249 | const width = touchOverflowSize.width / 2 +
|
250 | getThumbLeft(currentPropValue.current) +
|
251 | (thumbSizeValue.current.width - ((thumbTouchSize === null || thumbTouchSize === void 0 ? void 0 : thumbTouchSize.width) || THUMB_SIZE)) /
|
252 | 2;
|
253 | if (isVertical.current) {
|
254 | return new Rect(height, width, (thumbTouchSize === null || thumbTouchSize === void 0 ? void 0 : thumbTouchSize.width) || THUMB_SIZE, (thumbTouchSize === null || thumbTouchSize === void 0 ? void 0 : thumbTouchSize.height) || THUMB_SIZE);
|
255 | }
|
256 | return new Rect(width, height, (thumbTouchSize === null || thumbTouchSize === void 0 ? void 0 : thumbTouchSize.width) || THUMB_SIZE, (thumbTouchSize === null || thumbTouchSize === void 0 ? void 0 : thumbTouchSize.height) || THUMB_SIZE);
|
257 | };
|
258 | const renderDebugThumbTouchRect = (thumbLeft) => {
|
259 | const thumbTouchRect = getThumbTouchRect();
|
260 | const positionStyle = {
|
261 | left: thumbLeft,
|
262 | top: thumbTouchRect.y,
|
263 | width: thumbTouchRect.width,
|
264 | height: thumbTouchRect.height,
|
265 | };
|
266 | return <Animated.View style={positionStyle} pointerEvents="none"/>;
|
267 | };
|
268 | const getMinimumTrackStyles = (thumbStart) => {
|
269 | const minimumTrackStyle = {
|
270 | position: 'absolute',
|
271 | };
|
272 | if (isVertical.current) {
|
273 | minimumTrackStyle.height = Animated.add(thumbStart, thumbSizeValue.current.height / 2);
|
274 | minimumTrackStyle.marginLeft = trackSize.width * TRACK_STYLE;
|
275 | }
|
276 | else {
|
277 | minimumTrackStyle.width = Animated.add(thumbStart, thumbSizeValue.current.width / 2);
|
278 | minimumTrackStyle.marginTop = trackSize.height * TRACK_STYLE;
|
279 | }
|
280 | return minimumTrackStyle;
|
281 | };
|
282 | const panResponder = useRef(PanResponder.create({
|
283 | onStartShouldSetPanResponder: handleStartShouldSetPanResponder,
|
284 | onMoveShouldSetPanResponder: handleMoveShouldSetPanResponder,
|
285 | onPanResponderGrant: handlePanResponderGrant,
|
286 | onPanResponderMove: handlePanResponderMove,
|
287 | onPanResponderRelease: handlePanResponderEnd,
|
288 | onPanResponderTerminationRequest: handlePanResponderRequestEnd,
|
289 | onPanResponderTerminate: handlePanResponderEnd,
|
290 | })).current;
|
291 | const { minimumValue, maximumValue, minimumTrackTintColor, maximumTrackTintColor, thumbTintColor, containerStyle, style, trackStyle, thumbStyle, thumbProps, debugTouchArea } = props, other = __rest(props, ["minimumValue", "maximumValue", "minimumTrackTintColor", "maximumTrackTintColor", "thumbTintColor", "containerStyle", "style", "trackStyle", "thumbStyle", "thumbProps", "debugTouchArea"]);
|
292 | const mainStyles = containerStyle || styles;
|
293 | const appliedTrackStyle = StyleSheet.flatten([styles.track, trackStyle]);
|
294 | const thumbStart = value.interpolate({
|
295 | inputRange: [minimumValue || 0, maximumValue || 1],
|
296 | outputRange: [0, containerSize.width - thumbSize.width],
|
297 | });
|
298 | const valueVisibleStyle = {};
|
299 | if (!allMeasured) {
|
300 | valueVisibleStyle.height = 0;
|
301 | valueVisibleStyle.width = 0;
|
302 | }
|
303 | const minimumTrackStyle = Object.assign(Object.assign(Object.assign({}, getMinimumTrackStyles(thumbStart)), { backgroundColor: minimumTrackTintColor }), valueVisibleStyle);
|
304 | const touchOverflowStyle = getTouchOverflowStyle();
|
305 | return (<View {...other} style={StyleSheet.flatten([
|
306 | isVertical.current
|
307 | ? mainStyles.containerVertical
|
308 | : mainStyles.containerHorizontal,
|
309 | style,
|
310 | ])} onLayout={measureContainer} accessibilityRole="adjustable" accessibilityValue={{
|
311 | min: minimumValue,
|
312 | max: maximumValue,
|
313 | now: props.value,
|
314 | }}>
|
315 | <View style={StyleSheet.flatten([
|
316 | mainStyles.track,
|
317 | isVertical.current
|
318 | ? mainStyles.trackVertical
|
319 | : mainStyles.trackHorizontal,
|
320 | appliedTrackStyle,
|
321 | { backgroundColor: maximumTrackTintColor },
|
322 | ])} onLayout={measureTrack}/>
|
323 |
|
324 | <Animated.View style={StyleSheet.flatten([
|
325 | mainStyles.track,
|
326 | isVertical.current
|
327 | ? mainStyles.trackVertical
|
328 | : mainStyles.trackHorizontal,
|
329 | appliedTrackStyle,
|
330 | minimumTrackStyle,
|
331 | ])}/>
|
332 | <SliderThumb isVisible={allMeasured} onLayout={measureThumb} style={thumbStyle} color={thumbTintColor} start={thumbStart} vertical={isVertical.current} {...thumbProps}/>
|
333 | <View style={StyleSheet.flatten([styles.touchArea, touchOverflowStyle])} {...panResponder.panHandlers}>
|
334 | {debugTouchArea === true && renderDebugThumbTouchRect(thumbStart)}
|
335 | </View>
|
336 | </View>);
|
337 | };
|
338 | const SliderThumb = (_a) => {
|
339 | var { Component, isVisible, onLayout, style, start, color, vertical } = _a, props = __rest(_a, ["Component", "isVisible", "onLayout", "style", "start", "color", "vertical"]);
|
340 | const ThumbComponent = Component || Animated.View;
|
341 | const axis = vertical ? 'translateY' : 'translateX';
|
342 | const thumbPosition = [{ [axis]: start }];
|
343 | const styleTransform = (style && style.transform) || [];
|
344 | const visibleStyle = isVisible ? {} : { height: 0, width: 0 };
|
345 | return (<ThumbComponent testID="sliderThumb" onLayout={onLayout} style={StyleSheet.flatten([
|
346 | Object.assign({ backgroundColor: color, transform: [...thumbPosition, ...styleTransform] }, visibleStyle),
|
347 | styles.thumb,
|
348 | style,
|
349 | ])} {...props}/>);
|
350 | };
|
351 | Slider.defaultProps = {
|
352 | value: 0,
|
353 | minimumValue: 0,
|
354 | maximumValue: 1,
|
355 | step: 0,
|
356 | minimumTrackTintColor: '#3f3f3f',
|
357 | maximumTrackTintColor: '#b3b3b3',
|
358 | allowTouchTrack: false,
|
359 | thumbTintColor: 'red',
|
360 | thumbTouchSize: { width: THUMB_SIZE, height: THUMB_SIZE },
|
361 | debugTouchArea: false,
|
362 | animationType: 'timing',
|
363 | orientation: 'horizontal',
|
364 | };
|
365 | const styles = StyleSheet.create({
|
366 | containerHorizontal: {
|
367 | height: 40,
|
368 | justifyContent: 'center',
|
369 | },
|
370 | containerVertical: {
|
371 | width: 40,
|
372 | flexDirection: 'column',
|
373 | alignItems: 'center',
|
374 | },
|
375 | track: {
|
376 | borderRadius: TRACK_SIZE / 2,
|
377 | },
|
378 | trackHorizontal: {
|
379 | height: TRACK_SIZE,
|
380 | },
|
381 | trackVertical: {
|
382 | flex: 1,
|
383 | width: TRACK_SIZE,
|
384 | },
|
385 | thumb: {
|
386 | position: 'absolute',
|
387 | width: THUMB_SIZE,
|
388 | height: THUMB_SIZE,
|
389 | borderRadius: THUMB_SIZE / 2,
|
390 | },
|
391 | touchArea: {
|
392 | position: 'absolute',
|
393 | backgroundColor: 'transparent',
|
394 | top: 0,
|
395 | left: 0,
|
396 | right: 0,
|
397 | bottom: 0,
|
398 | },
|
399 | debugThumbTouchArea: {
|
400 | position: 'absolute',
|
401 | backgroundColor: 'green',
|
402 | opacity: 0.5,
|
403 | },
|
404 | });
|
405 | export { Slider };
|
406 | export default withTheme(Slider, 'Slider');
|