/* @flow */ import * as React from 'react'; import PropTypes from 'prop-types'; import { Animated, StyleSheet, View } from 'react-native'; import { PagerRendererPropType } from './PropTypes'; import type { PagerRendererProps } from './TypeDefinitions'; type Props<T> = PagerRendererProps<T> & { swipeDistanceThreshold?: number, swipeVelocityThreshold?: number, GestureHandler: any, }; const DefaultTransitionSpec = { timing: Animated.spring, tension: 68, friction: 12, }; export default class PagerExperimental<T: *> extends React.Component<Props<T>> { static propTypes = { ...PagerRendererPropType, swipeDistanceThreshold: PropTypes.number, swipeVelocityThreshold: PropTypes.number, GestureHandler: PropTypes.object, }; static defaultProps = { canJumpToTab: () => true, }; componentDidUpdate(prevProps: Props<T>) { if ( prevProps.navigationState.routes.length !== this.props.navigationState.routes.length || prevProps.layout.width !== this.props.layout.width ) { this._transitionTo(this.props.navigationState.index, undefined, false); } else if ( prevProps.navigationState.index !== this.props.navigationState.index && this.props.navigationState.index !== this._pendingIndex ) { this._transitionTo(this.props.navigationState.index); } } _handleHandlerStateChange = event => { const { GestureHandler } = this.props; if (event.nativeEvent.state === GestureHandler.State.BEGAN) { this.props.onSwipeStart && this.props.onSwipeStart(); } else if (event.nativeEvent.state === GestureHandler.State.END) { this.props.onSwipeEnd && this.props.onSwipeEnd(); const { navigationState, layout, swipeDistanceThreshold = layout.width / 1.75, swipeVelocityThreshold = 150, } = this.props; const { translationX, translationY, velocityX, velocityY, } = event.nativeEvent; const currentIndex = typeof this._pendingIndex === 'number' ? this._pendingIndex : navigationState.index; let nextIndex = currentIndex; if ( Math.abs(translationX) > Math.abs(translationY) && Math.abs(velocityX) > Math.abs(velocityY) && (Math.abs(translationX) > swipeDistanceThreshold || Math.abs(velocityX) > swipeVelocityThreshold) ) { nextIndex = Math.round( Math.min( Math.max(0, currentIndex - translationX / Math.abs(translationX)), navigationState.routes.length - 1 ) ); } if ( !isFinite(nextIndex) || !this.props.canJumpToTab(this.props.navigationState.routes[nextIndex]) ) { nextIndex = currentIndex; } this._transitionTo(nextIndex, velocityX); } }; _transitionTo = ( index: number, velocity?: number, animated?: boolean = true ) => { const offset = -index * this.props.layout.width; if (this.props.animationEnabled === false || animated === false) { this.props.panX.setValue(0); this.props.offsetX.setValue(offset); return; } const { timing, ...transitionConfig } = DefaultTransitionSpec; const { useNativeDriver } = this.props; Animated.parallel([ timing(this.props.panX, { ...transitionConfig, toValue: 0, velocity, useNativeDriver, }), timing(this.props.offsetX, { ...transitionConfig, toValue: offset, velocity, useNativeDriver, }), ]).start(({ finished }) => { if (finished) { const route = this.props.navigationState.routes[index]; this.props.jumpTo(route.key); this.props.onAnimationEnd && this.props.onAnimationEnd(); this._pendingIndex = null; } }); this._pendingIndex = index; }; _pendingIndex: ?number; render() { const { GestureHandler, panX, offsetX, layout, navigationState, swipeEnabled, children, } = this.props; const { width } = layout; const { routes } = navigationState; const maxTranslate = width * (routes.length - 1); const translateX = routes.length > 1 ? Animated.add(panX, offsetX).interpolate({ inputRange: [-maxTranslate, 0], outputRange: [-maxTranslate, 0], extrapolate: 'clamp', }) : 0; return ( <GestureHandler.PanGestureHandler enabled={layout.width !== 0 && swipeEnabled !== false} minDeltaX={10} onGestureEvent={Animated.event( [{ nativeEvent: { translationX: this.props.panX } }], { useNativeDriver: this.props.useNativeDriver } )} onHandlerStateChange={this._handleHandlerStateChange} > <Animated.View style={[ styles.sheet, width ? { width: routes.length * width, transform: [{ translateX }] } : null, ]} > {React.Children.map(children, (child, i) => { const route = navigationState.routes[i]; const focused = i === navigationState.index; return ( <View key={route.key} testID={this.props.getTestID({ route })} accessibilityElementsHidden={!focused} importantForAccessibility={ focused ? 'auto' : 'no-hide-descendants' } style={ width ? { width } : focused ? StyleSheet.absoluteFill : null } > {focused || width ? child : null} </View> ); })} </Animated.View> </GestureHandler.PanGestureHandler> ); } } const styles = StyleSheet.create({ sheet: { flex: 1, flexDirection: 'row', alignItems: 'stretch', }, });