SwipeableRow: Remove PropTypes, convert to ES6 class (#21386)

Summary:
Part of: https://github.com/facebook/react-native/issues/21342

This PR removes prop types from `SwipeableRow`, and converts it from a `createReactClass` to an ES6 class.
Pull Request resolved: https://github.com/facebook/react-native/pull/21386

Differential Revision: D10100555

Pulled By: TheSavior

fbshipit-source-id: ab350546f4fa6f1ed3fdeae07e342890af6d9a22
This commit is contained in:
empyrical 2018-09-28 00:04:05 -07:00 committed by Facebook Github Bot
parent d8b40cc541
commit 16f06bcb80
1 changed files with 90 additions and 107 deletions

View File

@ -16,13 +16,8 @@ const PanResponder = require('PanResponder');
const React = require('React'); const React = require('React');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const StyleSheet = require('StyleSheet'); const StyleSheet = require('StyleSheet');
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
* found when Flow v0.54 was deployed. To see the error delete this comment and
* run Flow. */
const TimerMixin = require('react-timer-mixin');
const View = require('View'); const View = require('View');
const createReactClass = require('create-react-class');
const emptyFunction = require('fbjs/lib/emptyFunction'); const emptyFunction = require('fbjs/lib/emptyFunction');
import type {LayoutEvent, PressEvent} from 'CoreEventTypes'; import type {LayoutEvent, PressEvent} from 'CoreEventTypes';
@ -62,18 +57,38 @@ const RIGHT_SWIPE_THRESHOLD = 30 * SLOW_SPEED_SWIPE_FACTOR;
type Props = $ReadOnly<{| type Props = $ReadOnly<{|
children?: ?React.Node, children?: ?React.Node,
isOpen?: ?boolean, isOpen: boolean,
maxSwipeDistance?: ?number, maxSwipeDistance: number,
onClose?: ?Function, onClose: () => mixed,
onOpen?: ?Function, onOpen: () => mixed,
onSwipeEnd?: ?Function, onSwipeEnd: () => mixed,
onSwipeStart?: ?Function, onSwipeStart: () => mixed,
preventSwipeRight?: ?boolean, preventSwipeRight: boolean,
/**
* Should bounce the row on mount
*/
shouldBounceOnMount?: ?boolean, shouldBounceOnMount?: ?boolean,
/**
* A ReactElement that is unveiled when the user swipes
*/
slideoutView?: ?React.Node, slideoutView?: ?React.Node,
swipeThreshold?: ?number,
/**
* The minimum swipe distance required before fully animating the swipe. If
* the user swipes less than this distance, the item will return to its
* previous (open/close) position.
*/
swipeThreshold: number,
|}>; |}>;
type State = {|
currentLeft: Animated.Value,
isSwipeableViewRendered: boolean,
rowHeight: ?number,
|};
/** /**
* Creates a swipable row that allows taps on the main item and a custom View * Creates a swipable row that allows taps on the main item and a custom View
* on the item hidden behind the row. Typically this should be used in * on the item hidden behind the row. Typically this should be used in
@ -81,75 +96,44 @@ type Props = $ReadOnly<{|
* used in a normal ListView. See the renderRow for SwipeableListView to see how * used in a normal ListView. See the renderRow for SwipeableListView to see how
* to use this component separately. * to use this component separately.
*/ */
const SwipeableRow = createReactClass({ class SwipeableRow extends React.Component<Props, State> {
displayName: 'SwipeableRow', _timerID: ?TimeoutID = null;
_panResponder: {},
_previousLeft: CLOSED_LEFT_POSITION,
mixins: [TimerMixin], static defaultProps = {
isOpen: false,
preventSwipeRight: false,
maxSwipeDistance: 0,
onOpen: emptyFunction,
onClose: emptyFunction,
onSwipeEnd: emptyFunction,
onSwipeStart: emptyFunction,
swipeThreshold: 30,
};
propTypes: { state = {
children: PropTypes.any, currentLeft: new Animated.Value(this._previousLeft),
isOpen: PropTypes.bool,
preventSwipeRight: PropTypes.bool,
maxSwipeDistance: PropTypes.number.isRequired,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onSwipeEnd: PropTypes.func.isRequired,
onSwipeStart: PropTypes.func.isRequired,
// Should bounce the row on mount
shouldBounceOnMount: PropTypes.bool,
/** /**
* A ReactElement that is unveiled when the user swipes * In order to render component A beneath component B, A must be rendered
* before B. However, this will cause "flickering", aka we see A briefly
* then B. To counter this, _isSwipeableViewRendered flag is used to set
* component A to be transparent until component B is loaded.
*/ */
slideoutView: PropTypes.node.isRequired, isSwipeableViewRendered: false,
/** rowHeight: null,
* The minimum swipe distance required before fully animating the swipe. If };
* the user swipes less than this distance, the item will return to its
* previous (open/close) position.
*/
swipeThreshold: PropTypes.number.isRequired,
},
getInitialState(): Object { _panResponder = PanResponder.create({
return { onMoveShouldSetPanResponderCapture: this
currentLeft: new Animated.Value(this._previousLeft), ._handleMoveShouldSetPanResponderCapture,
/** onPanResponderGrant: this._handlePanResponderGrant,
* In order to render component A beneath component B, A must be rendered onPanResponderMove: this._handlePanResponderMove,
* before B. However, this will cause "flickering", aka we see A briefly onPanResponderRelease: this._handlePanResponderEnd,
* then B. To counter this, _isSwipeableViewRendered flag is used to set onPanResponderTerminationRequest: this._onPanResponderTerminationRequest,
* component A to be transparent until component B is loaded. onPanResponderTerminate: this._handlePanResponderEnd,
*/ onShouldBlockNativeResponder: (event, gestureState) => false,
isSwipeableViewRendered: false, });
rowHeight: (null: ?number),
};
},
getDefaultProps(): Object { _previousLeft = CLOSED_LEFT_POSITION;
return {
isOpen: false,
preventSwipeRight: false,
maxSwipeDistance: 0,
onOpen: emptyFunction,
onClose: emptyFunction,
onSwipeEnd: emptyFunction,
onSwipeStart: emptyFunction,
swipeThreshold: 30,
};
},
UNSAFE_componentWillMount(): void {
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponderCapture: this
._handleMoveShouldSetPanResponderCapture,
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderMove: this._handlePanResponderMove,
onPanResponderRelease: this._handlePanResponderEnd,
onPanResponderTerminationRequest: this._onPanResponderTerminationRequest,
onPanResponderTerminate: this._handlePanResponderEnd,
onShouldBlockNativeResponder: (event, gestureState) => false,
});
},
componentDidMount(): void { componentDidMount(): void {
if (this.props.shouldBounceOnMount) { if (this.props.shouldBounceOnMount) {
@ -157,13 +141,17 @@ const SwipeableRow = createReactClass({
* Do the on mount bounce after a delay because if we animate when other * Do the on mount bounce after a delay because if we animate when other
* components are loading, the animation will be laggy * components are loading, the animation will be laggy
*/ */
this.setTimeout(() => { this._timerID = setTimeout(() => {
this._animateBounceBack(ON_MOUNT_BOUNCE_DURATION); this._animateBounceBack(ON_MOUNT_BOUNCE_DURATION);
}, ON_MOUNT_BOUNCE_DELAY); }, ON_MOUNT_BOUNCE_DELAY);
} }
}, }
UNSAFE_componentWillReceiveProps(nextProps: Object): void { componentWillUnmount(): void {
this._timerID && clearTimeout(this._timerID);
}
UNSAFE_componentWillReceiveProps(nextProps: Props): void {
/** /**
* We do not need an "animateOpen(noCallback)" because this animation is * We do not need an "animateOpen(noCallback)" because this animation is
* handled internally by this component. * handled internally by this component.
@ -171,9 +159,9 @@ const SwipeableRow = createReactClass({
if (this.props.isOpen && !nextProps.isOpen) { if (this.props.isOpen && !nextProps.isOpen) {
this._animateToClosedPosition(); this._animateToClosedPosition();
} }
}, }
render(): React.Element<any> { render(): React.Node {
// The view hidden behind the main view // The view hidden behind the main view
let slideOutView; let slideOutView;
if (this.state.isSwipeableViewRendered && this.state.rowHeight) { if (this.state.isSwipeableViewRendered && this.state.rowHeight) {
@ -200,19 +188,19 @@ const SwipeableRow = createReactClass({
{swipeableView} {swipeableView}
</View> </View>
); );
}, }
close(): void { close(): void {
this.props.onClose(); this.props.onClose();
this._animateToClosedPosition(); this._animateToClosedPosition();
}, }
_onSwipeableViewLayout(event: LayoutEvent): void { _onSwipeableViewLayout(event: LayoutEvent): void {
this.setState({ this.setState({
isSwipeableViewRendered: true, isSwipeableViewRendered: true,
rowHeight: event.nativeEvent.layout.height, rowHeight: event.nativeEvent.layout.height,
}); });
}, }
_handleMoveShouldSetPanResponderCapture( _handleMoveShouldSetPanResponderCapture(
event: PressEvent, event: PressEvent,
@ -220,12 +208,12 @@ const SwipeableRow = createReactClass({
): boolean { ): boolean {
// Decides whether a swipe is responded to by this component or its child // Decides whether a swipe is responded to by this component or its child
return gestureState.dy < 10 && this._isValidSwipe(gestureState); return gestureState.dy < 10 && this._isValidSwipe(gestureState);
}, }
_handlePanResponderGrant( _handlePanResponderGrant(
event: PressEvent, event: PressEvent,
gestureState: GestureState, gestureState: GestureState,
): void {}, ): void {}
_handlePanResponderMove(event: PressEvent, gestureState: GestureState): void { _handlePanResponderMove(event: PressEvent, gestureState: GestureState): void {
if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) { if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) {
@ -239,22 +227,22 @@ const SwipeableRow = createReactClass({
} else { } else {
this._swipeFullSpeed(gestureState); this._swipeFullSpeed(gestureState);
} }
}, }
_isSwipingRightFromClosed(gestureState: GestureState): boolean { _isSwipingRightFromClosed(gestureState: GestureState): boolean {
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx; const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0; return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0;
}, }
_swipeFullSpeed(gestureState: GestureState): void { _swipeFullSpeed(gestureState: GestureState): void {
this.state.currentLeft.setValue(this._previousLeft + gestureState.dx); this.state.currentLeft.setValue(this._previousLeft + gestureState.dx);
}, }
_swipeSlowSpeed(gestureState: GestureState): void { _swipeSlowSpeed(gestureState: GestureState): void {
this.state.currentLeft.setValue( this.state.currentLeft.setValue(
this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR, this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR,
); );
}, }
_isSwipingExcessivelyRightFromClosedPosition( _isSwipingExcessivelyRightFromClosedPosition(
gestureState: GestureState, gestureState: GestureState,
@ -269,14 +257,14 @@ const SwipeableRow = createReactClass({
this._isSwipingRightFromClosed(gestureState) && this._isSwipingRightFromClosed(gestureState) &&
gestureStateDx > RIGHT_SWIPE_THRESHOLD gestureStateDx > RIGHT_SWIPE_THRESHOLD
); );
}, }
_onPanResponderTerminationRequest( _onPanResponderTerminationRequest(
event: PressEvent, event: PressEvent,
gestureState: GestureState, gestureState: GestureState,
): boolean { ): boolean {
return false; return false;
}, }
_animateTo( _animateTo(
toValue: number, toValue: number,
@ -291,14 +279,14 @@ const SwipeableRow = createReactClass({
this._previousLeft = toValue; this._previousLeft = toValue;
callback(); callback();
}); });
}, }
_animateToOpenPosition(): void { _animateToOpenPosition(): void {
const maxSwipeDistance = IS_RTL const maxSwipeDistance = IS_RTL
? -this.props.maxSwipeDistance ? -this.props.maxSwipeDistance
: this.props.maxSwipeDistance; : this.props.maxSwipeDistance;
this._animateTo(-maxSwipeDistance); this._animateTo(-maxSwipeDistance);
}, }
_animateToOpenPositionWith(speed: number, distMoved: number): void { _animateToOpenPositionWith(speed: number, distMoved: number): void {
/** /**
@ -320,15 +308,15 @@ const SwipeableRow = createReactClass({
? -this.props.maxSwipeDistance ? -this.props.maxSwipeDistance
: this.props.maxSwipeDistance; : this.props.maxSwipeDistance;
this._animateTo(-maxSwipeDistance, duration); this._animateTo(-maxSwipeDistance, duration);
}, }
_animateToClosedPosition(duration: number = SWIPE_DURATION): void { _animateToClosedPosition(duration: number = SWIPE_DURATION): void {
this._animateTo(CLOSED_LEFT_POSITION, duration); this._animateTo(CLOSED_LEFT_POSITION, duration);
}, }
_animateToClosedPositionDuringBounce(): void { _animateToClosedPositionDuringBounce(): void {
this._animateToClosedPosition(RIGHT_SWIPE_BOUNCE_BACK_DURATION); this._animateToClosedPosition(RIGHT_SWIPE_BOUNCE_BACK_DURATION);
}, }
_animateBounceBack(duration: number): void { _animateBounceBack(duration: number): void {
/** /**
@ -343,7 +331,7 @@ const SwipeableRow = createReactClass({
duration, duration,
this._animateToClosedPositionDuringBounce, this._animateToClosedPositionDuringBounce,
); );
}, }
// Ignore swipes due to user's finger moving slightly when tapping // Ignore swipes due to user's finger moving slightly when tapping
_isValidSwipe(gestureState: GestureState): boolean { _isValidSwipe(gestureState: GestureState): boolean {
@ -356,7 +344,7 @@ const SwipeableRow = createReactClass({
} }
return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD; return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
}, }
_shouldAnimateRemainder(gestureState: GestureState): boolean { _shouldAnimateRemainder(gestureState: GestureState): boolean {
/** /**
@ -367,7 +355,7 @@ const SwipeableRow = createReactClass({
Math.abs(gestureState.dx) > this.props.swipeThreshold || Math.abs(gestureState.dx) > this.props.swipeThreshold ||
gestureState.vx > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD gestureState.vx > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD
); );
}, }
_handlePanResponderEnd(event: PressEvent, gestureState: GestureState): void { _handlePanResponderEnd(event: PressEvent, gestureState: GestureState): void {
const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx; const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx;
@ -393,12 +381,7 @@ const SwipeableRow = createReactClass({
} }
this.props.onSwipeEnd(); this.props.onSwipeEnd();
}, }
});
// TODO: Delete this when `SwipeableRow` uses class syntax.
class TypedSwipeableRow extends React.Component<Props> {
close() {}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -411,4 +394,4 @@ const styles = StyleSheet.create({
}, },
}); });
module.exports = ((SwipeableRow: any): Class<TypedSwipeableRow>); module.exports = SwipeableRow;