react-native-gesture-handler
Version:
Declarative API exposing native platform touch and gesture system to React Native
364 lines (356 loc) • 11 kB
JavaScript
"use strict";
// 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 * as React from 'react';
import { Component } from 'react';
import { Animated, StyleSheet, View, I18nManager } from 'react-native';
import { PanGestureHandler } from '../handlers/PanGestureHandler';
import { TapGestureHandler } from '../handlers/TapGestureHandler';
import { State } from '../State';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const DRAG_TOSS = 0.05;
// Animated.AnimatedInterpolation has been converted to a generic type
// in @types/react-native 0.70. This way we can maintain compatibility
// with all versions of @types/react-native
/**
* @deprecated use Reanimated version of Swipeable instead
*
* This component allows for implementing swipeable rows or similar interaction.
*/
export default class Swipeable extends Component {
static defaultProps = {
friction: 1,
overshootFriction: 1,
useNativeAnimations: true
};
constructor(props) {
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
});
}
shouldComponentUpdate(props, state) {
if (this.props.friction !== props.friction || this.props.overshootLeft !== props.overshootLeft || this.props.overshootRight !== props.overshootRight || this.props.overshootFriction !== props.overshootFriction || this.state.leftWidth !== state.leftWidth || this.state.rightOffset !== state.rightOffset || this.state.rowWidth !== state.rowWidth) {
this.updateAnimatedEvent(props, state);
}
return true;
}
updateAnimatedEvent = (props, state) => {
const {
friction,
overshootFriction
} = 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 / overshootFriction : 0), -rightWidth, leftWidth, leftWidth + (overshootLeft ? 1 / overshootFriction : 0)]
});
this.transX = transX;
this.showLeftAction = leftWidth > 0 ? transX.interpolate({
inputRange: [-1, 0, leftWidth],
outputRange: [0, 0, 1]
}) : new Animated.Value(0);
this.leftActionTranslate = this.showLeftAction.interpolate({
inputRange: [0, Number.MIN_VALUE],
outputRange: [-10000, 0],
extrapolate: 'clamp'
});
this.showRightAction = rightWidth > 0 ? transX.interpolate({
inputRange: [-rightWidth, 0, 1],
outputRange: [1, 0, 0]
}) : new Animated.Value(0);
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 = ev => {
if (ev.nativeEvent.oldState === State.ACTIVE) {
this.handleRelease(ev);
}
if (ev.nativeEvent.state === State.ACTIVE) {
const {
velocityX,
translationX: dragX
} = ev.nativeEvent;
const {
rowState
} = this.state;
const {
friction
} = this.props;
const translationX = (dragX + DRAG_TOSS * velocityX) / friction;
const direction = rowState === -1 ? 'right' : rowState === 1 ? 'left' : translationX > 0 ? 'left' : 'right';
if (rowState === 0) {
this.props.onSwipeableOpenStartDrag?.(direction);
} else {
this.props.onSwipeableCloseStartDrag?.(direction);
}
}
};
handleRelease = ev => {
const {
velocityX,
translationX: dragX
} = ev.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, {
restSpeedThreshold: 1.7,
restDisplacementThreshold: 0.4,
velocity: velocityX,
bounciness: 0,
toValue,
useNativeDriver: this.props.useNativeAnimations,
...this.props.animationOptions
}).start(({
finished
}) => {
if (finished) {
if (toValue > 0) {
this.props.onSwipeableLeftOpen?.();
this.props.onSwipeableOpen?.('left', this);
} else if (toValue < 0) {
this.props.onSwipeableRightOpen?.();
this.props.onSwipeableOpen?.('right', this);
} else {
const closingDirection = fromValue > 0 ? 'left' : 'right';
this.props.onSwipeableClose?.(closingDirection, this);
}
}
});
if (toValue > 0) {
this.props.onSwipeableLeftWillOpen?.();
this.props.onSwipeableWillOpen?.('left');
} else if (toValue < 0) {
this.props.onSwipeableRightWillOpen?.();
this.props.onSwipeableWillOpen?.('right');
} else {
const closingDirection = fromValue > 0 ? 'left' : 'right';
this.props.onSwipeableWillClose?.(closingDirection);
}
};
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);
};
// eslint-disable-next-line @eslint-react/no-unused-class-component-members
openLeft = () => {
const {
leftWidth = 0
} = this.state;
this.animateRow(this.currentOffset(), leftWidth);
};
// eslint-disable-next-line @eslint-react/no-unused-class-component-members
openRight = () => {
const {
rowWidth = 0
} = this.state;
const {
rightOffset = rowWidth
} = this.state;
const rightWidth = rowWidth - rightOffset;
this.animateRow(this.currentOffset(), -rightWidth);
};
// eslint-disable-next-line @eslint-react/no-unused-class-component-members
reset = () => {
const {
dragX,
rowTranslation
} = this.state;
dragX.setValue(0);
rowTranslation.setValue(0);
this.setState({
rowState: 0
});
};
render() {
const {
rowState
} = this.state;
const {
children,
renderLeftActions,
renderRightActions,
dragOffsetFromLeftEdge = 10,
dragOffsetFromRightEdge = 10
} = this.props;
const left = renderLeftActions && /*#__PURE__*/_jsxs(Animated.View, {
style: [styles.leftActions,
// All those and below parameters can have ! since they are all
// asigned in constructor in `updateAnimatedEvent` but TS cannot spot
// it for some reason
{
transform: [{
translateX: this.leftActionTranslate
}]
}],
children: [renderLeftActions(this.showLeftAction, this.transX, this), /*#__PURE__*/_jsx(View, {
onLayout: ({
nativeEvent
}) => this.setState({
leftWidth: nativeEvent.layout.x
})
})]
});
const right = renderRightActions && /*#__PURE__*/_jsxs(Animated.View, {
style: [styles.rightActions, {
transform: [{
translateX: this.rightActionTranslate
}]
}],
children: [renderRightActions(this.showRightAction, this.transX, this), /*#__PURE__*/_jsx(View, {
onLayout: ({
nativeEvent
}) => this.setState({
rightOffset: nativeEvent.layout.x
})
})]
});
return /*#__PURE__*/_jsx(PanGestureHandler, {
activeOffsetX: [-dragOffsetFromRightEdge, dragOffsetFromLeftEdge],
touchAction: "pan-y",
...this.props,
onGestureEvent: this.onGestureEvent,
onHandlerStateChange: this.onHandlerStateChange,
children: /*#__PURE__*/_jsxs(Animated.View, {
onLayout: this.onRowLayout,
style: [styles.container, this.props.containerStyle],
children: [left, right, /*#__PURE__*/_jsx(TapGestureHandler, {
enabled: rowState !== 0,
touchAction: "pan-y",
onHandlerStateChange: this.onTapHandlerStateChange,
children: /*#__PURE__*/_jsx(Animated.View, {
pointerEvents: rowState === 0 ? 'auto' : 'box-only',
style: [{
transform: [{
translateX: this.transX
}]
}, this.props.childrenContainerStyle],
children: children
})
})]
})
});
}
}
const styles = StyleSheet.create({
container: {
overflow: 'hidden'
},
leftActions: {
...StyleSheet.absoluteFillObject,
flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row'
},
rightActions: {
...StyleSheet.absoluteFillObject,
flexDirection: I18nManager.isRTL ? 'row' : 'row-reverse'
}
});
//# sourceMappingURL=Swipeable.js.map