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:
parent
d8b40cc541
commit
16f06bcb80
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue