Add flow types to PanResponder (#21291)

Summary:
This PR adds flow types to the `PanResponder` module. It is part of my effort to flowtype the `Swipable*` classes.

A new `touchHistory` field had to be added to `SyntheticEvent` as well.
Pull Request resolved: https://github.com/facebook/react-native/pull/21291

Reviewed By: TheSavior

Differential Revision: D10013265

Pulled By: RSNara

fbshipit-source-id: 3cd65a0eae41c756d1605e6771588d820f040e2a
This commit is contained in:
empyrical
2018-09-27 14:02:36 -07:00
committed by Facebook Github Bot
parent 57041ee44f
commit 3f79b2a4e9
3 changed files with 195 additions and 61 deletions

View File

@@ -25,6 +25,9 @@ const View = require('View');
const createReactClass = require('create-react-class');
const emptyFunction = require('fbjs/lib/emptyFunction');
import type {LayoutEvent, PressEvent} from 'CoreEventTypes';
import type {GestureState} from 'PanResponder';
const IS_RTL = I18nManager.isRTL;
// NOTE: Eventually convert these consts to an input object of configurations
@@ -204,7 +207,7 @@ const SwipeableRow = createReactClass({
this._animateToClosedPosition();
},
_onSwipeableViewLayout(event: Object): void {
_onSwipeableViewLayout(event: LayoutEvent): void {
this.setState({
isSwipeableViewRendered: true,
rowHeight: event.nativeEvent.layout.height,
@@ -212,16 +215,19 @@ const SwipeableRow = createReactClass({
},
_handleMoveShouldSetPanResponderCapture(
event: Object,
gestureState: Object,
event: PressEvent,
gestureState: GestureState,
): boolean {
// Decides whether a swipe is responded to by this component or its child
return gestureState.dy < 10 && this._isValidSwipe(gestureState);
},
_handlePanResponderGrant(event: Object, gestureState: Object): void {},
_handlePanResponderGrant(
event: PressEvent,
gestureState: GestureState,
): void {},
_handlePanResponderMove(event: Object, gestureState: Object): void {
_handlePanResponderMove(event: PressEvent, gestureState: GestureState): void {
if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) {
return;
}
@@ -235,22 +241,24 @@ const SwipeableRow = createReactClass({
}
},
_isSwipingRightFromClosed(gestureState: Object): boolean {
_isSwipingRightFromClosed(gestureState: GestureState): boolean {
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0;
},
_swipeFullSpeed(gestureState: Object): void {
_swipeFullSpeed(gestureState: GestureState): void {
this.state.currentLeft.setValue(this._previousLeft + gestureState.dx);
},
_swipeSlowSpeed(gestureState: Object): void {
_swipeSlowSpeed(gestureState: GestureState): void {
this.state.currentLeft.setValue(
this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR,
);
},
_isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
_isSwipingExcessivelyRightFromClosedPosition(
gestureState: GestureState,
): boolean {
/**
* We want to allow a BIT of right swipe, to allow users to know that
* swiping is available, but swiping right does not do anything
@@ -264,8 +272,8 @@ const SwipeableRow = createReactClass({
},
_onPanResponderTerminationRequest(
event: Object,
gestureState: Object,
event: PressEvent,
gestureState: GestureState,
): boolean {
return false;
},
@@ -338,7 +346,7 @@ const SwipeableRow = createReactClass({
},
// Ignore swipes due to user's finger moving slightly when tapping
_isValidSwipe(gestureState: Object): boolean {
_isValidSwipe(gestureState: GestureState): boolean {
if (
this.props.preventSwipeRight &&
this._previousLeft === CLOSED_LEFT_POSITION &&
@@ -350,7 +358,7 @@ const SwipeableRow = createReactClass({
return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
},
_shouldAnimateRemainder(gestureState: Object): boolean {
_shouldAnimateRemainder(gestureState: GestureState): boolean {
/**
* If user has swiped past a certain distance, animate the rest of the way
* if they let go
@@ -361,7 +369,7 @@ const SwipeableRow = createReactClass({
);
},
_handlePanResponderEnd(event: Object, gestureState: Object): void {
_handlePanResponderEnd(event: PressEvent, gestureState: GestureState): void {
const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx;
if (this._isSwipingRightFromClosed(gestureState)) {
this.props.onOpen();

View File

@@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
@@ -12,6 +13,8 @@
const InteractionManager = require('./InteractionManager');
const TouchHistoryMath = require('./TouchHistoryMath');
import type {PressEvent} from 'CoreEventTypes';
const currentCentroidXOfTouchesChangedAfter =
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
const currentCentroidYOfTouchesChangedAfter =
@@ -121,6 +124,93 @@ const currentCentroidY = TouchHistoryMath.currentCentroidY;
* [PanResponder example in RNTester](https://github.com/facebook/react-native/blob/master/RNTester/js/PanResponderExample.js)
*/
export type GestureState = {|
/**
* ID of the gestureState - persisted as long as there at least one touch on screen
*/
stateID: number,
/**
* The latest screen coordinates of the recently-moved touch
*/
moveX: number,
/**
* The latest screen coordinates of the recently-moved touch
*/
moveY: number,
/**
* The screen coordinates of the responder grant
*/
x0: number,
/**
* The screen coordinates of the responder grant
*/
y0: number,
/**
* Accumulated distance of the gesture since the touch started
*/
dx: number,
/**
* Accumulated distance of the gesture since the touch started
*/
dy: number,
/**
* Current velocity of the gesture
*/
vx: number,
/**
* Current velocity of the gesture
*/
vy: number,
/**
* Number of touches currently on screen
*/
numberActiveTouches: number,
/**
* All `gestureState` accounts for timeStamps up until this value
*
* @private
*/
_accountsForMovesUpTo: number,
|};
type ActiveCallback = (
event: PressEvent,
gestureState: GestureState,
) => boolean;
type PassiveCallback = (event: PressEvent, gestureState: GestureState) => mixed;
type PanResponderConfig = $ReadOnly<{|
onMoveShouldSetPanResponder?: ?ActiveCallback,
onMoveShouldSetPanResponderCapture?: ?ActiveCallback,
onStartShouldSetPanResponder?: ?ActiveCallback,
onStartShouldSetPanResponderCapture?: ?ActiveCallback,
/**
* The body of `onResponderGrant` returns a bool, but the vast majority of
* callsites return void and this TODO notice is found in it:
* TODO: t7467124 investigate if this can be removed
*/
onPanResponderGrant?: ?(PassiveCallback | ActiveCallback),
onPanResponderReject?: ?PassiveCallback,
onPanResponderStart?: ?PassiveCallback,
onPanResponderEnd?: ?PassiveCallback,
onPanResponderRelease?: ?PassiveCallback,
onPanResponderMove?: ?PassiveCallback,
onPanResponderTerminate?: ?PassiveCallback,
onPanResponderTerminationRequest?: ?ActiveCallback,
onShouldBlockNativeResponder?: ?ActiveCallback,
|}>;
const PanResponder = {
/**
*
@@ -185,7 +275,7 @@ const PanResponder = {
* - vx/vy: Velocity.
*/
_initializeGestureState: function(gestureState) {
_initializeGestureState(gestureState: GestureState) {
gestureState.moveX = 0;
gestureState.moveY = 0;
gestureState.x0 = 0;
@@ -223,7 +313,10 @@ const PanResponder = {
* typical responder callback pattern (without using `PanResponder`), but
* avoids more dispatches than necessary.
*/
_updateGestureStateOnMove: function(gestureState, touchHistory) {
_updateGestureStateOnMove(
gestureState: GestureState,
touchHistory: $PropertyType<PressEvent, 'touchHistory'>,
) {
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
touchHistory,
@@ -290,40 +383,50 @@ const PanResponder = {
* accordingly. (numberActiveTouches) may not be totally accurate unless you
* are the responder.
*/
create: function(config) {
create(config: PanResponderConfig) {
const interactionState = {
handle: (null: ?number),
};
const gestureState = {
const gestureState: GestureState = {
// Useful for debugging
stateID: Math.random(),
moveX: 0,
moveY: 0,
x0: 0,
y0: 0,
dx: 0,
dy: 0,
vx: 0,
vy: 0,
numberActiveTouches: 0,
_accountsForMovesUpTo: 0,
};
PanResponder._initializeGestureState(gestureState);
const panHandlers = {
onStartShouldSetResponder: function(e) {
return config.onStartShouldSetPanResponder === undefined
onStartShouldSetResponder(event: PressEvent): boolean {
return config.onStartShouldSetPanResponder == null
? false
: config.onStartShouldSetPanResponder(e, gestureState);
: config.onStartShouldSetPanResponder(event, gestureState);
},
onMoveShouldSetResponder: function(e) {
return config.onMoveShouldSetPanResponder === undefined
onMoveShouldSetResponder(event: PressEvent): boolean {
return config.onMoveShouldSetPanResponder == null
? false
: config.onMoveShouldSetPanResponder(e, gestureState);
: config.onMoveShouldSetPanResponder(event, gestureState);
},
onStartShouldSetResponderCapture: function(e) {
onStartShouldSetResponderCapture(event: PressEvent): boolean {
// TODO: Actually, we should reinitialize the state any time
// touches.length increases from 0 active to > 0 active.
if (e.nativeEvent.touches.length === 1) {
if (event.nativeEvent.touches.length === 1) {
PanResponder._initializeGestureState(gestureState);
}
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
return config.onStartShouldSetPanResponderCapture !== undefined
? config.onStartShouldSetPanResponderCapture(e, gestureState)
gestureState.numberActiveTouches =
event.touchHistory.numberActiveTouches;
return config.onStartShouldSetPanResponderCapture != null
? config.onStartShouldSetPanResponderCapture(event, gestureState)
: false;
},
onMoveShouldSetResponderCapture: function(e) {
const touchHistory = e.touchHistory;
onMoveShouldSetResponderCapture(event: PressEvent): boolean {
const touchHistory = event.touchHistory;
// Responder system incorrectly dispatches should* to current responder
// Filter out any touch moves past the first one - we would have
// already processed multi-touch geometry during the first event.
@@ -335,56 +438,56 @@ const PanResponder = {
}
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
return config.onMoveShouldSetPanResponderCapture
? config.onMoveShouldSetPanResponderCapture(e, gestureState)
? config.onMoveShouldSetPanResponderCapture(event, gestureState)
: false;
},
onResponderGrant: function(e) {
onResponderGrant(event: PressEvent): boolean {
if (!interactionState.handle) {
interactionState.handle = InteractionManager.createInteractionHandle();
}
gestureState.x0 = currentCentroidX(e.touchHistory);
gestureState.y0 = currentCentroidY(e.touchHistory);
gestureState.x0 = currentCentroidX(event.touchHistory);
gestureState.y0 = currentCentroidY(event.touchHistory);
gestureState.dx = 0;
gestureState.dy = 0;
if (config.onPanResponderGrant) {
config.onPanResponderGrant(e, gestureState);
config.onPanResponderGrant(event, gestureState);
}
// TODO: t7467124 investigate if this can be removed
return config.onShouldBlockNativeResponder === undefined
return config.onShouldBlockNativeResponder == null
? true
: config.onShouldBlockNativeResponder();
: config.onShouldBlockNativeResponder(event, gestureState);
},
onResponderReject: function(e) {
onResponderReject(event: PressEvent): void {
clearInteractionHandle(
interactionState,
config.onPanResponderReject,
e,
event,
gestureState,
);
},
onResponderRelease: function(e) {
onResponderRelease(event: PressEvent): void {
clearInteractionHandle(
interactionState,
config.onPanResponderRelease,
e,
event,
gestureState,
);
PanResponder._initializeGestureState(gestureState);
},
onResponderStart: function(e) {
const touchHistory = e.touchHistory;
onResponderStart(event: PressEvent): void {
const touchHistory = event.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
if (config.onPanResponderStart) {
config.onPanResponderStart(e, gestureState);
config.onPanResponderStart(event, gestureState);
}
},
onResponderMove: function(e) {
const touchHistory = e.touchHistory;
onResponderMove(event: PressEvent): void {
const touchHistory = event.touchHistory;
// Guard against the dispatch of two touch moves when there are two
// simultaneously changed touches.
if (
@@ -397,35 +500,35 @@ const PanResponder = {
// already processed multi-touch geometry during the first event.
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
if (config.onPanResponderMove) {
config.onPanResponderMove(e, gestureState);
config.onPanResponderMove(event, gestureState);
}
},
onResponderEnd: function(e) {
const touchHistory = e.touchHistory;
onResponderEnd(event: PressEvent): void {
const touchHistory = event.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
clearInteractionHandle(
interactionState,
config.onPanResponderEnd,
e,
event,
gestureState,
);
},
onResponderTerminate: function(e) {
onResponderTerminate(event: PressEvent): void {
clearInteractionHandle(
interactionState,
config.onPanResponderTerminate,
e,
event,
gestureState,
);
PanResponder._initializeGestureState(gestureState);
},
onResponderTerminationRequest: function(e) {
return config.onPanResponderTerminationRequest === undefined
onResponderTerminationRequest(event: PressEvent): boolean {
return config.onPanResponderTerminationRequest == null
? true
: config.onPanResponderTerminationRequest(e, gestureState);
: config.onPanResponderTerminationRequest(event, gestureState);
},
};
return {
@@ -439,9 +542,9 @@ const PanResponder = {
function clearInteractionHandle(
interactionState: {handle: ?number},
callback: Function,
event: Object,
gestureState: Object,
callback: ?(ActiveCallback | PassiveCallback),
event: PressEvent,
gestureState: GestureState,
) {
if (interactionState.handle) {
InteractionManager.clearInteractionHandle(interactionState.handle);

View File

@@ -29,6 +29,29 @@ export type SyntheticEvent<T> = $ReadOnly<{|
type: ?string,
|}>;
export type ResponderSyntheticEvent<T> = $ReadOnly<{|
...SyntheticEvent<T>,
touchHistory: $ReadOnly<{|
indexOfSingleActiveTouch: number,
mostRecentTimeStamp: number,
numberActiveTouches: number,
touchBank: $ReadOnlyArray<
$ReadOnly<{|
touchActive: boolean,
startPageX: number,
startPageY: number,
startTimeStamp: number,
currentPageX: number,
currentPageY: number,
currentTimeStamp: number,
previousPageX: number,
previousPageY: number,
previousTimeStamp: number,
|}>,
>,
|}>,
|}>;
export type Layout = $ReadOnly<{|
x: number,
y: number,
@@ -57,7 +80,7 @@ export type TextLayoutEvent = SyntheticEvent<
|}>,
>;
export type PressEvent = SyntheticEvent<
export type PressEvent = ResponderSyntheticEvent<
$ReadOnly<{|
changedTouches: $ReadOnlyArray<$PropertyType<PressEvent, 'nativeEvent'>>,
force: number,