react-native-mock-tmp-build
Version:
A fully mocked and test-friendly version of react native
386 lines (362 loc) • 18.1 kB
JavaScript
var _TouchHistoryMath=require('./TouchHistoryMath');var _TouchHistoryMath2=_interopRequireDefault(_TouchHistoryMath);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{'default':obj};}
var currentCentroidXOfTouchesChangedAfter=
_TouchHistoryMath2['default'].currentCentroidXOfTouchesChangedAfter;
var currentCentroidYOfTouchesChangedAfter=
_TouchHistoryMath2['default'].currentCentroidYOfTouchesChangedAfter;
var previousCentroidXOfTouchesChangedAfter=
_TouchHistoryMath2['default'].previousCentroidXOfTouchesChangedAfter;
var previousCentroidYOfTouchesChangedAfter=
_TouchHistoryMath2['default'].previousCentroidYOfTouchesChangedAfter;
var currentCentroidX=_TouchHistoryMath2['default'].currentCentroidX;
var currentCentroidY=_TouchHistoryMath2['default'].currentCentroidY;
/**
* `PanResponder` reconciles several touches into a single gesture. It makes
* single-touch gestures resilient to extra touches, and can be used to
* recognize simple multi-touch gestures.
*
* It provides a predictable wrapper of the responder handlers provided by the
* [gesture responder system](/react-native/docs/gesture-responder-system.html).
* For each handler, it provides a new `gestureState` object alongside the
* native event object:
*
* ```
* onPanResponderMove: (event, gestureState) => {}
* ```
*
* A native event is a synthetic touch event with the following form:
*
* - `nativeEvent`
* + `changedTouches` - Array of all touch events that have changed since the last event
* + `identifier` - The ID of the touch
* + `locationX` - The X position of the touch, relative to the element
* + `locationY` - The Y position of the touch, relative to the element
* + `pageX` - The X position of the touch, relative to the root element
* + `pageY` - The Y position of the touch, relative to the root element
* + `target` - The node id of the element receiving the touch event
* + `timestamp` - A time identifier for the touch, useful for velocity calculation
* + `touches` - Array of all current touches on the screen
*
* A `gestureState` object has the following:
*
* - `stateID` - ID of the gestureState- persisted as long as there at least
* one touch on screen
* - `moveX` - the latest screen coordinates of the recently-moved touch
* - `moveY` - the latest screen coordinates of the recently-moved touch
* - `x0` - the screen coordinates of the responder grant
* - `y0` - the screen coordinates of the responder grant
* - `dx` - accumulated distance of the gesture since the touch started
* - `dy` - accumulated distance of the gesture since the touch started
* - `vx` - current velocity of the gesture
* - `vy` - current velocity of the gesture
* - `numberActiveTouches` - Number of touches currently on screen
*
* ### Basic Usage
*
* ```
* componentWillMount: function() {
* this._panResponder = PanResponder.create({
* // Ask to be the responder:
* onStartShouldSetPanResponder: (evt, gestureState) => true,
* onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
* onMoveShouldSetPanResponder: (evt, gestureState) => true,
* onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
*
* onPanResponderGrant: (evt, gestureState) => {
* // The guesture has started. Show visual feedback so the user knows
* // what is happening!
*
* // gestureState.{x,y}0 will be set to zero now
* },
* onPanResponderMove: (evt, gestureState) => {
* // The most recent move distance is gestureState.move{X,Y}
*
* // The accumulated gesture distance since becoming responder is
* // gestureState.d{x,y}
* },
* onPanResponderTerminationRequest: (evt, gestureState) => true,
* onPanResponderRelease: (evt, gestureState) => {
* // The user has released all touches while this view is the
* // responder. This typically means a gesture has succeeded
* },
* onPanResponderTerminate: (evt, gestureState) => {
* // Another component has become the responder, so this gesture
* // should be cancelled
* },
* onShouldBlockNativeResponder: (evt, gestureState) => {
* // Returns whether this component should block native components from becoming the JS
* // responder. Returns true by default. Is currently only supported on android.
* return true;
* },
* });
* },
*
* render: function() {
* return (
* <View {...this._panResponder.panHandlers} />
* );
* },
*
* ```
*
* ### Working Example
*
* To see it in action, try the
* [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js)
*/
var PanResponder={
/**
*
* A graphical explanation of the touch data flow:
*
* +----------------------------+ +--------------------------------+
* | ResponderTouchHistoryStore | |TouchHistoryMath |
* +----------------------------+ +----------+---------------------+
* |Global store of touchHistory| |Allocation-less math util |
* |including activeness, start | |on touch history (centroids |
* |position, prev/cur position.| |and multitouch movement etc) |
* | | | |
* +----^-----------------------+ +----^---------------------------+
* | |
* | (records relevant history |
* | of touches relevant for |
* | implementing higher level |
* | gestures) |
* | |
* +----+-----------------------+ +----|---------------------------+
* | ResponderEventPlugin | | | Your App/Component |
* +----------------------------+ +----|---------------------------+
* |Negotiates which view gets | Low level | | High level |
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
* |Also records history into | touchHistory| | Pan | multitouch + |
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
* +----------------------------+ attached to | | | distance and |
* each event | +---------+ velocity. |
* | |
* | |
* +--------------------------------+
*
*
*
* Gesture that calculates cumulative movement over time in a way that just
* "does the right thing" for multiple touches. The "right thing" is very
* nuanced. When moving two touches in opposite directions, the cumulative
* distance is zero in each dimension. When two touches move in parallel five
* pixels in the same direction, the cumulative distance is five, not ten. If
* two touches start, one moves five in a direction, then stops and the other
* touch moves fives in the same direction, the cumulative distance is ten.
*
* This logic requires a kind of processing of time "clusters" of touch events
* so that two touch moves that essentially occur in parallel but move every
* other frame respectively, are considered part of the same movement.
*
* Explanation of some of the non-obvious fields:
*
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
* invalid. If a move event has been observed, `(moveX, moveY)` is the
* centroid of the most recently moved "cluster" of active touches.
* (Currently all move have the same timeStamp, but later we should add some
* threshold for what is considered to be "moving"). If a palm is
* accidentally counted as a touch, but a finger is moving greatly, the palm
* will move slightly, but we only want to count the single moving touch.
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
* responder.
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
* distance. Accounts for touch moves that are clustered together in time,
* moving the same direction. Only valid when currently responder (otherwise,
* it only represents the drag distance below the threshold).
* - vx/vy: Velocity.
*/
_initializeGestureState:function(){function _initializeGestureState(gestureState){
var newGestureState=gestureState;
newGestureState.moveX=0;
newGestureState.moveY=0;
newGestureState.x0=0;
newGestureState.y0=0;
newGestureState.dx=0;
newGestureState.dy=0;
newGestureState.vx=0;
newGestureState.vy=0;
newGestureState.numberActiveTouches=0;
newGestureState._accountsForMovesUpTo=0;
}return _initializeGestureState;}(),
/**
* This is nuanced and is necessary. It is incorrect to continuously take all
* active *and* recently moved touches, find the centroid, and track how that
* result changes over time. Instead, we must take all recently moved
* touches, and calculate how the centroid has changed just for those
* recently moved touches, and append that change to an accumulator. This is
* to (at least) handle the case where the user is moving three fingers, and
* then one of the fingers stops but the other two continue.
*
* This is very different than taking all of the recently moved touches and
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
* changes* in the centroid of recently moved touches.
*
* There is also some nuance with how we handle multiple moved touches in a
* single event. With the way `ReactNativeEventEmitter` dispatches touches as
* individual events, multiple touches generate two 'move' events, each of
* them triggering `onResponderMove`. But with the way `PanResponder` works,
* all of the gesture inference is performed on the first dispatch, since it
* looks at all of the touches (even the ones for which there hasn't been a
* native dispatch yet). Therefore, `PanResponder` does not call
* `onResponderMove` passed the first dispatch. This diverges from the
* typical responder callback pattern (without using `PanResponder`), but
* avoids more dispatches than necessary.
*/
_updateGestureStateOnMove:function(){function _updateGestureStateOnMove(gestureState,touchHistory){
var newGestureState=gestureState;
newGestureState.numberActiveTouches=touchHistory.numberActiveTouches;
newGestureState.moveX=currentCentroidXOfTouchesChangedAfter(
touchHistory,
newGestureState._accountsForMovesUpTo);
newGestureState.moveY=currentCentroidYOfTouchesChangedAfter(
touchHistory,
newGestureState._accountsForMovesUpTo);
var movedAfter=newGestureState._accountsForMovesUpTo;
var prevX=previousCentroidXOfTouchesChangedAfter(touchHistory,movedAfter);
var x=currentCentroidXOfTouchesChangedAfter(touchHistory,movedAfter);
var prevY=previousCentroidYOfTouchesChangedAfter(touchHistory,movedAfter);
var y=currentCentroidYOfTouchesChangedAfter(touchHistory,movedAfter);
var nextDX=newGestureState.dx+(x-prevX);
var nextDY=newGestureState.dy+(y-prevY);
// TODO: This must be filtered intelligently.
var dt=
touchHistory.mostRecentTimeStamp-newGestureState._accountsForMovesUpTo;
newGestureState.vx=(nextDX-newGestureState.dx)/dt;
newGestureState.vy=(nextDY-newGestureState.dy)/dt;
newGestureState.dx=nextDX;
newGestureState.dy=nextDY;
newGestureState._accountsForMovesUpTo=touchHistory.mostRecentTimeStamp;
}return _updateGestureStateOnMove;}(),
/**
* @param {object} config Enhanced versions of all of the responder callbacks
* that provide not only the typical `ResponderSyntheticEvent`, but also the
* `PanResponder` gesture state. Simply replace the word `Responder` with
* `PanResponder` in each of the typical `onResponder*` callbacks. For
* example, the `config` object would look like:
*
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
* - `onPanResponderReject: (e, gestureState) => {...}`
* - `onPanResponderGrant: (e, gestureState) => {...}`
* - `onPanResponderStart: (e, gestureState) => {...}`
* - `onPanResponderEnd: (e, gestureState) => {...}`
* - `onPanResponderRelease: (e, gestureState) => {...}`
* - `onPanResponderMove: (e, gestureState) => {...}`
* - `onPanResponderTerminate: (e, gestureState) => {...}`
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
*
* In general, for events that have capture equivalents, we update the
* gestureState once in the capture phase and can use it in the bubble phase
* as well.
*
* Be careful with onStartShould* callbacks. They only reflect updated
* `gestureState` for start/end events that bubble/capture to the Node.
* Once the node is the responder, you can rely on every start/end event
* being processed by the gesture and `gestureState` being updated
* accordingly. (numberActiveTouches) may not be totally accurate unless you
* are the responder.
*/
create:function(){function create(config){
var gestureState={
// Useful for debugging
stateID:Math.random()};
PanResponder._initializeGestureState(gestureState);
var panHandlers={
onStartShouldSetResponder:function(){function onStartShouldSetResponder(e){
return config.onStartShouldSetPanResponder===undefined?false:
config.onStartShouldSetPanResponder(e,gestureState);
}return onStartShouldSetResponder;}(),
onMoveShouldSetResponder:function(){function onMoveShouldSetResponder(e){
return config.onMoveShouldSetPanResponder===undefined?false:
config.onMoveShouldSetPanResponder(e,gestureState);
}return onMoveShouldSetResponder;}(),
onStartShouldSetResponderCapture:function(){function onStartShouldSetResponderCapture(e){
// TODO: Actually, we should reinitialize the state any time
// touches.length increases from 0 active to > 0 active.
if(e.nativeEvent.touches.length===1){
PanResponder._initializeGestureState(gestureState);
}
gestureState.numberActiveTouches=e.touchHistory.numberActiveTouches;
return config.onStartShouldSetPanResponderCapture!==undefined?
config.onStartShouldSetPanResponderCapture(e,gestureState):false;
}return onStartShouldSetResponderCapture;}(),
onMoveShouldSetResponderCapture:function(){function onMoveShouldSetResponderCapture(e){
var touchHistory=e.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.
if(gestureState._accountsForMovesUpTo===touchHistory.mostRecentTimeStamp){
return false;
}
PanResponder._updateGestureStateOnMove(gestureState,touchHistory);
return config.onMoveShouldSetPanResponderCapture?
config.onMoveShouldSetPanResponderCapture(e,gestureState):false;
}return onMoveShouldSetResponderCapture;}(),
onResponderGrant:function(){function onResponderGrant(e){
gestureState.x0=currentCentroidX(e.touchHistory);
gestureState.y0=currentCentroidY(e.touchHistory);
gestureState.dx=0;
gestureState.dy=0;
if(config.onPanResponderGrant){
config.onPanResponderGrant(e,gestureState);
}
// TODO: t7467124 investigate if this can be removed
return config.onShouldBlockNativeResponder===undefined?true:
config.onShouldBlockNativeResponder();
}return onResponderGrant;}(),
onResponderReject:function(){function onResponderReject(e){
if(config.onPanResponderReject){
config.onPanResponderReject(e,gestureState);
}
}return onResponderReject;}(),
onResponderRelease:function(){function onResponderRelease(e){
if(config.onPanResponderRelease){
config.onPanResponderRelease(e,gestureState);
}
PanResponder._initializeGestureState(gestureState);
}return onResponderRelease;}(),
onResponderStart:function(){function onResponderStart(e){
var touchHistory=e.touchHistory;
gestureState.numberActiveTouches=touchHistory.numberActiveTouches;
if(config.onPanResponderStart){
config.onPanResponderStart(e,gestureState);
}
}return onResponderStart;}(),
onResponderMove:function(){function onResponderMove(e){
var touchHistory=e.touchHistory;
// Guard against the dispatch of two touch moves when there are two
// simultaneously changed touches.
if(gestureState._accountsForMovesUpTo===touchHistory.mostRecentTimeStamp){
return;
}
// Filter out any touch moves past the first one - we would have
// already processed multi-touch geometry during the first event.
PanResponder._updateGestureStateOnMove(gestureState,touchHistory);
if(config.onPanResponderMove){
config.onPanResponderMove(e,gestureState);
}
}return onResponderMove;}(),
onResponderEnd:function(){function onResponderEnd(e){
var touchHistory=e.touchHistory;
gestureState.numberActiveTouches=touchHistory.numberActiveTouches;
if(config.onPanResponderEnd){
config.onPanResponderEnd(e,gestureState);
}
}return onResponderEnd;}(),
onResponderTerminate:function(){function onResponderTerminate(e){
if(config.onPanResponderTerminate){
config.onPanResponderTerminate(e,gestureState);
}
PanResponder._initializeGestureState(gestureState);
}return onResponderTerminate;}(),
onResponderTerminationRequest:function(){function onResponderTerminationRequest(e){
return config.onPanResponderTerminationRequest===undefined?true:
config.onPanResponderTerminationRequest(e,gestureState);
}return onResponderTerminationRequest;}()};
return{panHandlers:panHandlers};
}return create;}()};
module.exports=PanResponder;