GT2/GT2-iOS/node_modules/react-native-gesture-handler/Swipeable.js

316 lines
8.8 KiB
JavaScript

// @flow
// Similarily to the DrawerLayout component this deserves to be put in a
// separate repo. ALthough, keeping it here for the time being will allow us
// to move faster and fix possible issues quicker
import React, { Component } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import { AnimatedEvent } from 'react-native/Libraries/Animated/src/AnimatedEvent';
import {
PanGestureHandler,
TapGestureHandler,
State,
} from 'react-native-gesture-handler';
const DRAG_TOSS = 0.05;
// Math.sign polyfill for iOS 8.x
if (!Math.sign) {
Math.sign = function(x) {
return ((x > 0) - (x < 0)) || +x;
};
}
export type PropType = {
children: any,
friction: number,
leftThreshold?: number,
rightThreshold?: number,
overshootLeft?: boolean,
overshootRight?: boolean,
onSwipeableLeftOpen?: Function,
onSwipeableRightOpen?: Function,
onSwipeableOpen?: Function,
onSwipeableClose?: Function,
renderLeftActions?: (
progressAnimatedValue: any,
dragAnimatedValue: any
) => any,
renderRightActions?: (
progressAnimatedValue: any,
dragAnimatedValue: any
) => any,
useNativeAnimations: boolean,
};
type StateType = {
dragX: Animated.Value,
rowTranslation: Animated.Value,
rowState: number,
leftWidth: number | typeof undefined,
rightOffset: number | typeof undefined,
rowWidth: number | typeof undefined,
};
export default class Swipeable extends Component<PropType, StateType> {
static defaultProps = {
friction: 1,
useNativeAnimations: true,
};
_onGestureEvent: ?AnimatedEvent;
_transX: ?Animated.Interpolation;
_showLeftAction: ?Animated.Interpolation;
_leftActionTranslate: ?Animated.Interpolation;
_showRightAction: ?Animated.Interpolation;
_rightActionTranslate: ?Animated.Interpolation;
constructor(props: PropType) {
super(props);
const dragX = new Animated.Value(0);
this.state = {
dragX,
rowTranslation: new Animated.Value(0),
rowState: 0,
leftWidth: undefined,
rightOffset: undefined,
rowWidth: undefined,
};
this._updateAnimatedEvent(props, this.state);
this._onGestureEvent = Animated.event(
[{ nativeEvent: { translationX: dragX } }],
{ useNativeDriver: props.useNativeAnimations }
);
}
componentWillUpdate(props: PropType, state: StateType) {
if (
this.props.friction !== props.friction ||
this.props.overshootLeft !== props.overshootLeft ||
this.props.overshootRight !== props.overshootRight ||
this.state.leftWidth !== state.leftWidth ||
this.state.rightOffset !== state.rightOffset ||
this.state.rowWidth !== state.rowWidth
) {
this._updateAnimatedEvent(props, state);
}
}
_updateAnimatedEvent = (props: PropType, state: StateType) => {
const { friction, useNativeAnimations } = props;
const { dragX, rowTranslation, leftWidth = 0, rowWidth = 0 } = state;
const { rightOffset = rowWidth } = state;
const rightWidth = Math.max(0, rowWidth - rightOffset);
const {
overshootLeft = leftWidth > 0,
overshootRight = rightWidth > 0,
} = props;
const transX = Animated.add(
rowTranslation,
dragX.interpolate({
inputRange: [0, friction],
outputRange: [0, 1],
})
).interpolate({
inputRange: [-rightWidth - 1, -rightWidth, leftWidth, leftWidth + 1],
outputRange: [
-rightWidth - (overshootRight ? 1 : 0),
-rightWidth,
leftWidth,
leftWidth + (overshootLeft ? 1 : 0),
],
});
this._transX = transX;
this._showLeftAction = transX.interpolate({
inputRange: [-1, 0, leftWidth],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
});
this._leftActionTranslate = this._showLeftAction.interpolate({
inputRange: [0, Number.MIN_VALUE],
outputRange: [-10000, 0],
extrapolate: 'clamp',
});
this._showRightAction = transX.interpolate({
inputRange: [-rightWidth, 0, 1],
outputRange: [1, 0, 0],
extrapolate: 'clamp',
});
this._rightActionTranslate = this._showRightAction.interpolate({
inputRange: [0, Number.MIN_VALUE],
outputRange: [-10000, 0],
extrapolate: 'clamp',
});
};
_onTapHandlerStateChange = ({ nativeEvent }) => {
if (nativeEvent.oldState === State.ACTIVE) {
this.close();
}
};
_onHandlerStateChange = ({ nativeEvent }) => {
if (nativeEvent.oldState === State.ACTIVE) {
this._handleRelease(nativeEvent);
}
};
_handleRelease = nativeEvent => {
const { velocityX, translationX: dragX } = nativeEvent;
const { leftWidth = 0, rowWidth = 0, rowState } = this.state;
const { rightOffset = rowWidth } = this.state;
const rightWidth = rowWidth - rightOffset;
const {
friction,
leftThreshold = leftWidth / 2,
rightThreshold = rightWidth / 2,
} = this.props;
const startOffsetX = this._currentOffset() + dragX / friction;
const translationX = (dragX + DRAG_TOSS * velocityX) / friction;
let toValue = 0;
if (rowState === 0) {
if (translationX > leftThreshold) {
toValue = leftWidth;
} else if (translationX < -rightThreshold) {
toValue = -rightWidth;
}
} else if (rowState === 1) {
// swiped to left
if (translationX > -leftThreshold) {
toValue = leftWidth;
}
} else {
// swiped to right
if (translationX < rightThreshold) {
toValue = -rightWidth;
}
}
this._animateRow(startOffsetX, toValue, velocityX / friction);
};
_animateRow = (fromValue, toValue, velocityX) => {
const { dragX, rowTranslation } = this.state;
dragX.setValue(0);
rowTranslation.setValue(fromValue);
this.setState({ rowState: Math.sign(toValue) });
Animated.spring(rowTranslation, {
velocity: velocityX,
bounciness: 0,
toValue,
useNativeDriver: this.props.useNativeAnimations,
}).start(({ finished }) => {
if (finished) {
if (toValue > 0 && this.props.onSwipeableLeftOpen) {
this.props.onSwipeableLeftOpen();
} else if (toValue < 0 && this.props.onSwipeableRightOpen) {
this.props.onSwipeableRightOpen();
}
if (toValue === 0) {
this.props.onSwipeableClose && this.props.onSwipeableClose();
} else {
this.props.onSwipeableOpen && this.props.onSwipeableOpen();
}
}
});
};
_onRowLayout = ({ nativeEvent }) => {
this.setState({ rowWidth: nativeEvent.layout.width });
};
_currentOffset = () => {
const { leftWidth = 0, rowWidth = 0, rowState } = this.state;
const { rightOffset = rowWidth } = this.state;
const rightWidth = rowWidth - rightOffset;
if (rowState === 1) {
return leftWidth;
} else if (rowState === -1) {
return -rightWidth;
}
return 0;
};
close = () => {
this._animateRow(this._currentOffset(), 0);
};
render() {
const { rowState } = this.state;
const { children, renderLeftActions, renderRightActions } = this.props;
const left = renderLeftActions && (
<Animated.View
style={[
styles.leftActions,
{ transform: [{ translateX: this._leftActionTranslate }] },
]}>
{renderLeftActions(this._showLeftAction, this._transX)}
<View
onLayout={({ nativeEvent }) =>
this.setState({ leftWidth: nativeEvent.layout.x })}
/>
</Animated.View>
);
const right = renderRightActions && (
<Animated.View
style={[
styles.rightActions,
{ transform: [{ translateX: this._rightActionTranslate }] },
]}>
{renderRightActions(this._showRightAction, this._transX)}
<View
onLayout={({ nativeEvent }) =>
this.setState({ rightOffset: nativeEvent.layout.x })}
/>
</Animated.View>
);
return (
<PanGestureHandler
{...this.props}
minDeltaX={10}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View onLayout={this._onRowLayout} style={styles.container}>
{left}
{right}
<TapGestureHandler
enabled={rowState !== 0}
onHandlerStateChange={this._onTapHandlerStateChange}>
<Animated.View
pointerEvents={rowState === 0 ? 'auto' : 'box-only'}
style={{
transform: [{ translateX: this._transX }],
}}>
{children}
</Animated.View>
</TapGestureHandler>
</Animated.View>
</PanGestureHandler>
);
}
}
const styles = StyleSheet.create({
container: {
overflow: 'hidden',
},
leftActions: {
...StyleSheet.absoluteFillObject,
flexDirection: 'row',
},
rightActions: {
...StyleSheet.absoluteFillObject,
flexDirection: 'row-reverse',
},
});