diff --git a/Libraries/Experimental/SwipeableRow/SwipeableRow.js b/Libraries/Experimental/SwipeableRow/SwipeableRow.js index 7bf67dae28cfe8..fb9cc1df409090 100644 --- a/Libraries/Experimental/SwipeableRow/SwipeableRow.js +++ b/Libraries/Experimental/SwipeableRow/SwipeableRow.js @@ -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(); diff --git a/Libraries/Interaction/PanResponder.js b/Libraries/Interaction/PanResponder.js index 1015ec20de165d..3275b9afe36410 100644 --- a/Libraries/Interaction/PanResponder.js +++ b/Libraries/Interaction/PanResponder.js @@ -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, + ) { 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); diff --git a/Libraries/Types/CoreEventTypes.js b/Libraries/Types/CoreEventTypes.js index 5e55adfb3b2946..1abd02571c2d3b 100644 --- a/Libraries/Types/CoreEventTypes.js +++ b/Libraries/Types/CoreEventTypes.js @@ -29,6 +29,29 @@ export type SyntheticEvent = $ReadOnly<{| type: ?string, |}>; +export type ResponderSyntheticEvent = $ReadOnly<{| + ...SyntheticEvent, + 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>, force: number,