2017-12-10 21:51:33 +01:00
/ * * @ l i c e n s e R e a c t v 1 6 . 2 . 0
2017-10-14 18:40:54 +02:00
* react - dom - unstable - native - dependencies . development . js
*
* Copyright ( c ) 2013 - present , Facebook , Inc .
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree .
* /
2017-12-10 21:51:33 +01:00
2017-10-14 18:40:54 +02:00
'use strict' ;
2017-12-10 21:51:33 +01:00
if ( process . env . NODE _ENV !== "production" ) {
( function ( ) {
2017-10-14 18:40:54 +02:00
'use strict' ;
2017-12-10 21:51:33 +01:00
var ReactDOM = require ( 'react-dom' ) ;
2017-10-14 18:40:54 +02:00
var invariant = require ( 'fbjs/lib/invariant' ) ;
2017-12-10 21:51:33 +01:00
var warning = require ( 'fbjs/lib/warning' ) ;
var _assign = require ( 'object-assign' ) ;
2017-10-14 18:40:54 +02:00
var emptyFunction = require ( 'fbjs/lib/emptyFunction' ) ;
/ * *
2017-12-10 21:51:33 +01:00
* WARNING : DO NOT manually require this module .
* This is a replacement for ` invariant(...) ` used by the error code system
* and will _only _ be required by the corresponding babel pass .
* It always throws .
2017-10-14 18:40:54 +02:00
* /
{
// In DEV mode, we swap out invokeGuardedCallback for a special version
// that plays more nicely with the browser's DevTools. The idea is to preserve
// "Pause on exceptions" behavior. Because React wraps all user-provided
// functions in invokeGuardedCallback, and the production version of
// invokeGuardedCallback uses a try-catch, all user exceptions are treated
// like caught exceptions, and the DevTools won't pause unless the developer
// takes the extra step of enabling pause on caught exceptions. This is
// untintuitive, though, because even though React has caught the error, from
// the developer's perspective, the error is uncaught.
//
// To preserve the expected "Pause on exceptions" behavior, we don't use a
// try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake
// DOM node, and call the user-provided callback from inside an event handler
// for that fake event. If the callback throws, the error is "captured" using
// a global event handler. But because the error happens in a different
// event loop context, it does not interrupt the normal program flow.
// Effectively, this gives us try-catch behavior without actually using
// try-catch. Neat!
// Check that the browser supports the APIs we need to implement our special
// DEV version of invokeGuardedCallback
if ( typeof window !== 'undefined' && typeof window . dispatchEvent === 'function' && typeof document !== 'undefined' && typeof document . createEvent === 'function' ) {
var fakeNode = document . createElement ( 'react' ) ;
2017-12-10 21:51:33 +01:00
2017-10-14 18:40:54 +02:00
}
}
2017-12-10 21:51:33 +01:00
var getFiberCurrentPropsFromNode = null ;
var getInstanceFromNode = null ;
var getNodeFromInstance = null ;
2017-10-14 18:40:54 +02:00
var injection = {
injectComponentTree : function ( Injected ) {
2017-12-10 21:51:33 +01:00
getFiberCurrentPropsFromNode = Injected . getFiberCurrentPropsFromNode ;
getInstanceFromNode = Injected . getInstanceFromNode ;
getNodeFromInstance = Injected . getNodeFromInstance ;
2017-10-14 18:40:54 +02:00
{
2017-12-10 21:51:33 +01:00
warning ( getNodeFromInstance && getInstanceFromNode , 'EventPluginUtils.injection.injectComponentTree(...): Injected ' + 'module is missing getNodeFromInstance or getInstanceFromNode.' ) ;
2017-10-14 18:40:54 +02:00
}
}
} ;
function isEndish ( topLevelType ) {
return topLevelType === 'topMouseUp' || topLevelType === 'topTouchEnd' || topLevelType === 'topTouchCancel' ;
}
function isMoveish ( topLevelType ) {
return topLevelType === 'topMouseMove' || topLevelType === 'topTouchMove' ;
}
function isStartish ( topLevelType ) {
return topLevelType === 'topMouseDown' || topLevelType === 'topTouchStart' ;
}
var validateEventDispatches ;
{
validateEventDispatches = function ( event ) {
var dispatchListeners = event . _dispatchListeners ;
var dispatchInstances = event . _dispatchInstances ;
var listenersIsArr = Array . isArray ( dispatchListeners ) ;
var listenersLen = listenersIsArr ? dispatchListeners . length : dispatchListeners ? 1 : 0 ;
var instancesIsArr = Array . isArray ( dispatchInstances ) ;
var instancesLen = instancesIsArr ? dispatchInstances . length : dispatchInstances ? 1 : 0 ;
warning ( instancesIsArr === listenersIsArr && instancesLen === listenersLen , 'EventPluginUtils: Invalid `event`.' ) ;
} ;
}
/ * *
* Standard / simple iteration through an event ' s collected dispatches .
* /
2017-12-10 21:51:33 +01:00
2017-10-14 18:40:54 +02:00
/ * *
* Standard / simple iteration through an event ' s collected dispatches , but stops
* at the first dispatch execution returning true , and returns that id .
*
* @ return { ? string } id of the first dispatch execution who ' s listener returns
* true , or null if no listener returned true .
* /
function executeDispatchesInOrderStopAtTrueImpl ( event ) {
var dispatchListeners = event . _dispatchListeners ;
var dispatchInstances = event . _dispatchInstances ;
{
validateEventDispatches ( event ) ;
}
if ( Array . isArray ( dispatchListeners ) ) {
for ( var i = 0 ; i < dispatchListeners . length ; i ++ ) {
if ( event . isPropagationStopped ( ) ) {
break ;
}
// Listeners and Instances are two parallel arrays that are always in sync.
if ( dispatchListeners [ i ] ( event , dispatchInstances [ i ] ) ) {
return dispatchInstances [ i ] ;
}
}
} else if ( dispatchListeners ) {
if ( dispatchListeners ( event , dispatchInstances ) ) {
return dispatchInstances ;
}
}
return null ;
}
/ * *
* @ see executeDispatchesInOrderStopAtTrueImpl
* /
function executeDispatchesInOrderStopAtTrue ( event ) {
var ret = executeDispatchesInOrderStopAtTrueImpl ( event ) ;
event . _dispatchInstances = null ;
event . _dispatchListeners = null ;
return ret ;
}
/ * *
* Execution of a "direct" dispatch - there must be at most one dispatch
* accumulated on the event or it is considered an error . It doesn ' t really make
* sense for an event with multiple dispatches ( bubbled ) to keep track of the
* return values at each dispatch execution , but it does tend to make sense when
* dealing with "direct" dispatches .
*
* @ return { * } The return value of executing the single dispatch .
* /
function executeDirectDispatch ( event ) {
{
validateEventDispatches ( event ) ;
}
var dispatchListener = event . _dispatchListeners ;
var dispatchInstance = event . _dispatchInstances ;
! ! Array . isArray ( dispatchListener ) ? invariant ( false , 'executeDirectDispatch(...): Invalid `event`.' ) : void 0 ;
2017-12-10 21:51:33 +01:00
event . currentTarget = dispatchListener ? getNodeFromInstance ( dispatchInstance ) : null ;
2017-10-14 18:40:54 +02:00
var res = dispatchListener ? dispatchListener ( event ) : null ;
event . currentTarget = null ;
event . _dispatchListeners = null ;
event . _dispatchInstances = null ;
return res ;
}
/ * *
* @ param { SyntheticEvent } event
* @ return { boolean } True iff number of dispatches accumulated is greater than 0.
* /
function hasDispatches ( event ) {
return ! ! event . _dispatchListeners ;
}
2017-12-10 21:51:33 +01:00
// Before we know whether it is functional or class
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
// Root of a host tree. Could be nested inside another node.
// A subtree. Could be an entry point to a different renderer.
var HostComponent = 5 ;
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
function getParent ( inst ) {
do {
inst = inst [ 'return' ] ;
// TODO: If this is a HostRoot we might want to bail out.
// That is depending on if we want nested subtrees (layers) to bubble
// events to their parent. We could also go through parentNode on the
// host node but that wouldn't work for React Native and doesn't let us
// do the portal feature.
} while ( inst && inst . tag !== HostComponent ) ;
if ( inst ) {
return inst ;
}
return null ;
}
2017-10-14 18:40:54 +02:00
/ * *
2017-12-10 21:51:33 +01:00
* Return the lowest common ancestor of A and B , or null if they are in
* different trees .
2017-10-14 18:40:54 +02:00
* /
2017-12-10 21:51:33 +01:00
function getLowestCommonAncestor ( instA , instB ) {
var depthA = 0 ;
for ( var tempA = instA ; tempA ; tempA = getParent ( tempA ) ) {
depthA ++ ;
}
var depthB = 0 ;
for ( var tempB = instB ; tempB ; tempB = getParent ( tempB ) ) {
depthB ++ ;
}
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
// If A is deeper, crawl up.
while ( depthA - depthB > 0 ) {
instA = getParent ( instA ) ;
depthA -- ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
// If B is deeper, crawl up.
while ( depthB - depthA > 0 ) {
instB = getParent ( instB ) ;
depthB -- ;
}
// Walk in lockstep until we find a match.
var depth = depthA ;
while ( depth -- ) {
if ( instA === instB || instA === instB . alternate ) {
return instA ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
instA = getParent ( instA ) ;
instB = getParent ( instB ) ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
return null ;
2017-10-14 18:40:54 +02:00
}
/ * *
2017-12-10 21:51:33 +01:00
* Return if A is an ancestor of B .
2017-10-14 18:40:54 +02:00
* /
2017-12-10 21:51:33 +01:00
function isAncestor ( instA , instB ) {
while ( instB ) {
if ( instA === instB || instA === instB . alternate ) {
return true ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
instB = getParent ( instB ) ;
2017-10-14 18:40:54 +02:00
}
return false ;
}
/ * *
2017-12-10 21:51:33 +01:00
* Return the parent instance of the passed - in instance .
2017-10-14 18:40:54 +02:00
* /
2017-12-10 21:51:33 +01:00
function getParentInstance ( inst ) {
return getParent ( inst ) ;
}
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
/ * *
* Simulates the traversal of a two - phase , capture / bubble event dispatch .
* /
function traverseTwoPhase ( inst , fn , arg ) {
var path = [ ] ;
while ( inst ) {
path . push ( inst ) ;
inst = getParent ( inst ) ;
}
var i ;
for ( i = path . length ; i -- > 0 ; ) {
fn ( path [ i ] , 'captured' , arg ) ;
}
for ( i = 0 ; i < path . length ; i ++ ) {
fn ( path [ i ] , 'bubbled' , arg ) ;
2017-10-14 18:40:54 +02:00
}
}
2017-12-10 21:51:33 +01:00
/ * *
* Traverses the ID hierarchy and invokes the supplied ` cb ` on any IDs that
* should would receive a ` mouseEnter ` or ` mouseLeave ` event .
*
* Does not invoke the callback on the nearest common ancestor because nothing
* "entered" or "left" that element .
* /
2017-10-14 18:40:54 +02:00
/ * *
* Registers plugins so that they can extract and dispatch events .
*
* @ see { EventPluginHub }
* /
2017-12-10 21:51:33 +01:00
/ * *
* Ordered list of injected plugins .
* /
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
/ * *
* Mapping from event name to dispatch config
* /
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
/ * *
* Mapping from registration name to plugin module
* /
/ * *
* Mapping from registration name to event name
* /
/ * *
* Mapping from lowercase registration names to the properly cased version ,
* used to warn in the case of missing event handlers . Available
* only in true .
* @ type { Object }
* /
// Trust the developer to only use possibleRegistrationNames in true
/ * *
* Injects an ordering of plugins ( by plugin name ) . This allows the ordering
* to be decoupled from injection of the actual plugins so that ordering is
* always deterministic regardless of packaging , on - the - fly injection , etc .
*
* @ param { array } InjectedEventPluginOrder
* @ internal
* @ see { EventPluginHub . injection . injectEventPluginOrder }
* /
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
/ * *
* Injects plugins to be used by ` EventPluginHub ` . The plugin names must be
* in the ordering injected by ` injectEventPluginOrder ` .
*
* Plugins can be injected as part of page initialization or on - the - fly .
*
* @ param { object } injectedNamesToPlugins Map from names to plugin modules .
* @ internal
* @ see { EventPluginHub . injection . injectEventPluginsByName }
* /
2017-10-14 18:40:54 +02:00
/ * *
* Accumulates items that must not be null or undefined into the first one . This
* is used to conserve memory by avoiding array allocations , and thus sacrifices
* API cleanness . Since ` current ` can be null before being passed in and not
* null after this function , make sure to assign it back to ` current ` :
*
* ` a = accumulateInto(a, b); `
*
* This API should be sparingly used . Try ` accumulate ` for something cleaner .
*
* @ return { * | array < * > } An accumulation of items .
* /
function accumulateInto ( current , next ) {
! ( next != null ) ? invariant ( false , 'accumulateInto(...): Accumulated items must not be null or undefined.' ) : void 0 ;
if ( current == null ) {
return next ;
}
// Both are not empty. Warning: Never call x.concat(y) when you are not
// certain that x is an Array (x could be a string with concat method).
if ( Array . isArray ( current ) ) {
if ( Array . isArray ( next ) ) {
current . push . apply ( current , next ) ;
return current ;
}
current . push ( next ) ;
return current ;
}
if ( Array . isArray ( next ) ) {
// A bit too dangerous to mutate `next`.
return [ current ] . concat ( next ) ;
}
return [ current , next ] ;
}
/ * *
* @ param { array } arr an "accumulation" of items which is either an Array or
* a single item . Useful when paired with the ` accumulate ` module . This is a
* simple utility that allows us to reason about a collection of items , but
* handling the case when there is exactly one item ( and we do not need to
* allocate an array ) .
* @ param { function } cb Callback invoked with each element or a collection .
* @ param { ? } [ scope ] Scope used as ` this ` in a callback .
* /
function forEachAccumulated ( arr , cb , scope ) {
if ( Array . isArray ( arr ) ) {
arr . forEach ( cb , scope ) ;
} else if ( arr ) {
cb . call ( scope , arr ) ;
}
}
function isInteractive ( tag ) {
return tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea' ;
}
function shouldPreventMouseEvent ( name , type , props ) {
switch ( name ) {
case 'onClick' :
case 'onClickCapture' :
case 'onDoubleClick' :
case 'onDoubleClickCapture' :
case 'onMouseDown' :
case 'onMouseDownCapture' :
case 'onMouseMove' :
case 'onMouseMoveCapture' :
case 'onMouseUp' :
case 'onMouseUpCapture' :
return ! ! ( props . disabled && isInteractive ( type ) ) ;
default :
return false ;
}
}
/ * *
* This is a unified interface for event plugins to be installed and configured .
*
* Event plugins can implement the following properties :
*
* ` extractEvents ` { function ( string , DOMEventTarget , string , object ) : * }
* Required . When a top - level event is fired , this method is expected to
* extract synthetic events that will in turn be queued and dispatched .
*
* ` eventTypes ` { object }
* Optional , plugins that fire events must publish a mapping of registration
* names that are used to register listeners . Values of this mapping must
* be objects that contain ` registrationName ` or ` phasedRegistrationNames ` .
*
* ` executeDispatch ` { function ( object , function , string ) }
* Optional , allows plugins to override how an event gets dispatched . By
* default , the listener is simply invoked .
*
* Each plugin that is injected into ` EventsPluginHub ` is immediately operable .
*
* @ public
* /
/ * *
2017-12-10 21:51:33 +01:00
* Methods for injecting dependencies .
2017-10-14 18:40:54 +02:00
* /
/ * *
2017-12-10 21:51:33 +01:00
* @ param { object } inst The instance , which is the source of events .
* @ param { string } registrationName Name of listener ( e . g . ` onClick ` ) .
* @ return { ? function } The stored callback .
2017-10-14 18:40:54 +02:00
* /
2017-12-10 21:51:33 +01:00
function getListener ( inst , registrationName ) {
var listener ;
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
// live here; needs to be moved to a better place soon
var stateNode = inst . stateNode ;
if ( ! stateNode ) {
// Work in progress (ex: onload events in incremental mode).
return null ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
var props = getFiberCurrentPropsFromNode ( stateNode ) ;
if ( ! props ) {
// Work in progress.
return null ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
listener = props [ registrationName ] ;
if ( shouldPreventMouseEvent ( registrationName , inst . type , props ) ) {
return null ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
! ( ! listener || typeof listener === 'function' ) ? invariant ( false , 'Expected `%s` listener to be a function, instead got a value of `%s` type.' , registrationName , typeof listener ) : void 0 ;
return listener ;
2017-10-14 18:40:54 +02:00
}
/ * *
2017-12-10 21:51:33 +01:00
* Allows registered plugins an opportunity to extract events from top - level
* native browser events .
*
* @ return { * } An accumulation of synthetic events .
* @ internal
2017-10-14 18:40:54 +02:00
* /
/ * *
2017-12-10 21:51:33 +01:00
* Enqueues a synthetic event that should be dispatched when
* ` processEventQueue ` is invoked .
*
* @ param { * } events An accumulation of synthetic events .
* @ internal
2017-10-14 18:40:54 +02:00
* /
2017-12-10 21:51:33 +01:00
2017-10-14 18:40:54 +02:00
/ * *
2017-12-10 21:51:33 +01:00
* Dispatches all synthetic events on the event queue .
2017-10-14 18:40:54 +02:00
*
2017-12-10 21:51:33 +01:00
* @ internal
2017-10-14 18:40:54 +02:00
* /
/ * *
* Some event types have a notion of different registration names for different
* "phases" of propagation . This finds listeners by a given phase .
* /
function listenerAtPhase ( inst , event , propagationPhase ) {
var registrationName = event . dispatchConfig . phasedRegistrationNames [ propagationPhase ] ;
return getListener ( inst , registrationName ) ;
}
2017-12-10 21:51:33 +01:00
/ * *
* A small set of propagation patterns , each of which will accept a small amount
* of information , and generate a set of "dispatch ready event objects" - which
* are sets of events that have already been annotated with a set of dispatched
* listener functions / ids . The API is designed this way to discourage these
* propagation strategies from actually executing the dispatches , since we
* always want to collect the entire set of dispatches before executing even a
* single one .
* /
2017-10-14 18:40:54 +02:00
/ * *
* Tags a ` SyntheticEvent ` with dispatched listeners . Creating this function
* here , allows us to not have to bind or create functions for each event .
* Mutating the event ' s members allows us to not have to create a wrapping
* "dispatch" object that pairs the event with the listener .
* /
function accumulateDirectionalDispatches ( inst , phase , event ) {
{
2017-12-10 21:51:33 +01:00
warning ( inst , 'Dispatching inst must not be null' ) ;
2017-10-14 18:40:54 +02:00
}
var listener = listenerAtPhase ( inst , event , phase ) ;
if ( listener ) {
2017-12-10 21:51:33 +01:00
event . _dispatchListeners = accumulateInto ( event . _dispatchListeners , listener ) ;
event . _dispatchInstances = accumulateInto ( event . _dispatchInstances , inst ) ;
2017-10-14 18:40:54 +02:00
}
}
/ * *
* Collect dispatches ( must be entirely collected before dispatching - see unit
* tests ) . Lazily allocate the array to conserve memory . We must loop through
* each event and perform the traversal for each one . We cannot perform a
* single traversal for the entire collection of events because each event may
* have a different target .
* /
function accumulateTwoPhaseDispatchesSingle ( event ) {
if ( event && event . dispatchConfig . phasedRegistrationNames ) {
2017-12-10 21:51:33 +01:00
traverseTwoPhase ( event . _targetInst , accumulateDirectionalDispatches , event ) ;
2017-10-14 18:40:54 +02:00
}
}
/ * *
* Same as ` accumulateTwoPhaseDispatchesSingle ` , but skips over the targetID .
* /
function accumulateTwoPhaseDispatchesSingleSkipTarget ( event ) {
if ( event && event . dispatchConfig . phasedRegistrationNames ) {
var targetInst = event . _targetInst ;
2017-12-10 21:51:33 +01:00
var parentInst = targetInst ? getParentInstance ( targetInst ) : null ;
traverseTwoPhase ( parentInst , accumulateDirectionalDispatches , event ) ;
2017-10-14 18:40:54 +02:00
}
}
/ * *
* Accumulates without regard to direction , does not look for phased
* registration names . Same as ` accumulateDirectDispatchesSingle ` but without
* requiring that the ` dispatchMarker ` be the same as the dispatched ID .
* /
function accumulateDispatches ( inst , ignoredDirection , event ) {
if ( inst && event && event . dispatchConfig . registrationName ) {
var registrationName = event . dispatchConfig . registrationName ;
var listener = getListener ( inst , registrationName ) ;
if ( listener ) {
2017-12-10 21:51:33 +01:00
event . _dispatchListeners = accumulateInto ( event . _dispatchListeners , listener ) ;
event . _dispatchInstances = accumulateInto ( event . _dispatchInstances , inst ) ;
2017-10-14 18:40:54 +02:00
}
}
}
/ * *
* Accumulates dispatches on an ` SyntheticEvent ` , but only for the
* ` dispatchMarker ` .
* @ param { SyntheticEvent } event
* /
function accumulateDirectDispatchesSingle ( event ) {
if ( event && event . dispatchConfig . registrationName ) {
accumulateDispatches ( event . _targetInst , null , event ) ;
}
}
function accumulateTwoPhaseDispatches ( events ) {
2017-12-10 21:51:33 +01:00
forEachAccumulated ( events , accumulateTwoPhaseDispatchesSingle ) ;
2017-10-14 18:40:54 +02:00
}
function accumulateTwoPhaseDispatchesSkipTarget ( events ) {
2017-12-10 21:51:33 +01:00
forEachAccumulated ( events , accumulateTwoPhaseDispatchesSingleSkipTarget ) ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
2017-10-14 18:40:54 +02:00
function accumulateDirectDispatches ( events ) {
2017-12-10 21:51:33 +01:00
forEachAccumulated ( events , accumulateDirectDispatchesSingle ) ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
/* eslint valid-typeof: 0 */
2017-10-14 18:40:54 +02:00
var didWarnForAddedNewProperty = false ;
var isProxySupported = typeof Proxy === 'function' ;
var EVENT _POOL _SIZE = 10 ;
var shouldBeReleasedProperties = [ 'dispatchConfig' , '_targetInst' , 'nativeEvent' , 'isDefaultPrevented' , 'isPropagationStopped' , '_dispatchListeners' , '_dispatchInstances' ] ;
/ * *
* @ interface Event
* @ see http : //www.w3.org/TR/DOM-Level-3-Events/
* /
var EventInterface = {
type : null ,
target : null ,
// currentTarget is set when dispatching; no use in copying it here
currentTarget : emptyFunction . thatReturnsNull ,
eventPhase : null ,
bubbles : null ,
cancelable : null ,
timeStamp : function ( event ) {
return event . timeStamp || Date . now ( ) ;
} ,
defaultPrevented : null ,
isTrusted : null
} ;
/ * *
* Synthetic events are dispatched by event plugins , typically in response to a
* top - level event delegation handler .
*
* These systems should generally use pooling to reduce the frequency of garbage
* collection . The system should check ` isPersistent ` to determine whether the
* event should be released into the pool after being dispatched . Users that
* need a persisted event should invoke ` persist ` .
*
* Synthetic events ( and subclasses ) implement the DOM Level 3 Events API by
* normalizing browser quirks . Subclasses do not necessarily have to implement a
* DOM interface ; custom application - specific events can also subclass this .
*
* @ param { object } dispatchConfig Configuration used to dispatch this event .
* @ param { * } targetInst Marker identifying the event target .
* @ param { object } nativeEvent Native browser event .
* @ param { DOMEventTarget } nativeEventTarget Target node .
* /
function SyntheticEvent ( dispatchConfig , targetInst , nativeEvent , nativeEventTarget ) {
{
// these have a getter/setter for warnings
delete this . nativeEvent ;
delete this . preventDefault ;
delete this . stopPropagation ;
}
this . dispatchConfig = dispatchConfig ;
this . _targetInst = targetInst ;
this . nativeEvent = nativeEvent ;
var Interface = this . constructor . Interface ;
for ( var propName in Interface ) {
if ( ! Interface . hasOwnProperty ( propName ) ) {
continue ;
}
{
delete this [ propName ] ; // this has a getter/setter for warnings
}
var normalize = Interface [ propName ] ;
if ( normalize ) {
this [ propName ] = normalize ( nativeEvent ) ;
} else {
if ( propName === 'target' ) {
this . target = nativeEventTarget ;
} else {
this [ propName ] = nativeEvent [ propName ] ;
}
}
}
var defaultPrevented = nativeEvent . defaultPrevented != null ? nativeEvent . defaultPrevented : nativeEvent . returnValue === false ;
if ( defaultPrevented ) {
this . isDefaultPrevented = emptyFunction . thatReturnsTrue ;
} else {
this . isDefaultPrevented = emptyFunction . thatReturnsFalse ;
}
this . isPropagationStopped = emptyFunction . thatReturnsFalse ;
return this ;
}
2017-12-10 21:51:33 +01:00
_assign ( SyntheticEvent . prototype , {
2017-10-14 18:40:54 +02:00
preventDefault : function ( ) {
this . defaultPrevented = true ;
var event = this . nativeEvent ;
if ( ! event ) {
return ;
}
if ( event . preventDefault ) {
event . preventDefault ( ) ;
} else if ( typeof event . returnValue !== 'unknown' ) {
event . returnValue = false ;
}
this . isDefaultPrevented = emptyFunction . thatReturnsTrue ;
} ,
stopPropagation : function ( ) {
var event = this . nativeEvent ;
if ( ! event ) {
return ;
}
if ( event . stopPropagation ) {
event . stopPropagation ( ) ;
} else if ( typeof event . cancelBubble !== 'unknown' ) {
// The ChangeEventPlugin registers a "propertychange" event for
// IE. This event does not support bubbling or cancelling, and
// any references to cancelBubble throw "Member not found". A
// typeof check of "unknown" circumvents this issue (and is also
// IE specific).
event . cancelBubble = true ;
}
this . isPropagationStopped = emptyFunction . thatReturnsTrue ;
} ,
/ * *
* We release all dispatched ` SyntheticEvent ` s after each event loop , adding
* them back into the pool . This allows a way to hold onto a reference that
* won ' t be added back into the pool .
* /
persist : function ( ) {
this . isPersistent = emptyFunction . thatReturnsTrue ;
} ,
/ * *
* Checks if this event should be released back into the pool .
*
* @ return { boolean } True if this should not be released , false otherwise .
* /
isPersistent : emptyFunction . thatReturnsFalse ,
/ * *
* ` PooledClass ` looks for ` destructor ` on each instance it releases .
* /
destructor : function ( ) {
var Interface = this . constructor . Interface ;
for ( var propName in Interface ) {
{
Object . defineProperty ( this , propName , getPooledWarningPropertyDefinition ( propName , Interface [ propName ] ) ) ;
}
}
for ( var i = 0 ; i < shouldBeReleasedProperties . length ; i ++ ) {
this [ shouldBeReleasedProperties [ i ] ] = null ;
}
{
Object . defineProperty ( this , 'nativeEvent' , getPooledWarningPropertyDefinition ( 'nativeEvent' , null ) ) ;
Object . defineProperty ( this , 'preventDefault' , getPooledWarningPropertyDefinition ( 'preventDefault' , emptyFunction ) ) ;
Object . defineProperty ( this , 'stopPropagation' , getPooledWarningPropertyDefinition ( 'stopPropagation' , emptyFunction ) ) ;
}
}
} ) ;
SyntheticEvent . Interface = EventInterface ;
/ * *
* Helper to reduce boilerplate when creating subclasses .
*
* @ param { function } Class
* @ param { ? object } Interface
* /
SyntheticEvent . augmentClass = function ( Class , Interface ) {
var Super = this ;
var E = function ( ) { } ;
E . prototype = Super . prototype ;
var prototype = new E ( ) ;
2017-12-10 21:51:33 +01:00
_assign ( prototype , Class . prototype ) ;
2017-10-14 18:40:54 +02:00
Class . prototype = prototype ;
Class . prototype . constructor = Class ;
2017-12-10 21:51:33 +01:00
Class . Interface = _assign ( { } , Super . Interface , Interface ) ;
2017-10-14 18:40:54 +02:00
Class . augmentClass = Super . augmentClass ;
addEventPoolingTo ( Class ) ;
} ;
/ * * P r o x y i n g a f t e r e v e r y t h i n g s e t o n S y n t h e t i c E v e n t
2017-12-10 21:51:33 +01:00
* to resolve Proxy issue on some WebKit browsers
* in which some Event properties are set to undefined ( GH # 10010 )
* /
2017-10-14 18:40:54 +02:00
{
if ( isProxySupported ) {
/*eslint-disable no-func-assign */
SyntheticEvent = new Proxy ( SyntheticEvent , {
construct : function ( target , args ) {
return this . apply ( target , Object . create ( target . prototype ) , args ) ;
} ,
apply : function ( constructor , that , args ) {
return new Proxy ( constructor . apply ( that , args ) , {
set : function ( target , prop , value ) {
if ( prop !== 'isPersistent' && ! target . constructor . Interface . hasOwnProperty ( prop ) && shouldBeReleasedProperties . indexOf ( prop ) === - 1 ) {
2017-12-10 21:51:33 +01:00
warning ( didWarnForAddedNewProperty || target . isPersistent ( ) , "This synthetic event is reused for performance reasons. If you're " + "seeing this, you're adding a new property in the synthetic event object. " + 'The property is never released. See ' + 'https://fb.me/react-event-pooling for more information.' ) ;
2017-10-14 18:40:54 +02:00
didWarnForAddedNewProperty = true ;
}
target [ prop ] = value ;
return true ;
}
} ) ;
}
} ) ;
/*eslint-enable no-func-assign */
}
}
addEventPoolingTo ( SyntheticEvent ) ;
/ * *
2017-12-10 21:51:33 +01:00
* Helper to nullify syntheticEvent instance properties when destructing
*
* @ param { String } propName
* @ param { ? object } getVal
* @ return { object } defineProperty object
* /
2017-10-14 18:40:54 +02:00
function getPooledWarningPropertyDefinition ( propName , getVal ) {
var isFunction = typeof getVal === 'function' ;
return {
configurable : true ,
set : set ,
get : get
} ;
function set ( val ) {
var action = isFunction ? 'setting the method' : 'setting the property' ;
warn ( action , 'This is effectively a no-op' ) ;
return val ;
}
function get ( ) {
var action = isFunction ? 'accessing the method' : 'accessing the property' ;
var result = isFunction ? 'This is a no-op function' : 'This is set to null' ;
warn ( action , result ) ;
return getVal ;
}
function warn ( action , result ) {
var warningCondition = false ;
2017-12-10 21:51:33 +01:00
warning ( warningCondition , "This synthetic event is reused for performance reasons. If you're seeing this, " + "you're %s `%s` on a released/nullified synthetic event. %s. " + 'If you must keep the original synthetic event around, use event.persist(). ' + 'See https://fb.me/react-event-pooling for more information.' , action , propName , result ) ;
2017-10-14 18:40:54 +02:00
}
}
function getPooledEvent ( dispatchConfig , targetInst , nativeEvent , nativeInst ) {
var EventConstructor = this ;
if ( EventConstructor . eventPool . length ) {
var instance = EventConstructor . eventPool . pop ( ) ;
EventConstructor . call ( instance , dispatchConfig , targetInst , nativeEvent , nativeInst ) ;
return instance ;
}
return new EventConstructor ( dispatchConfig , targetInst , nativeEvent , nativeInst ) ;
}
function releasePooledEvent ( event ) {
var EventConstructor = this ;
! ( event instanceof EventConstructor ) ? invariant ( false , 'Trying to release an event instance into a pool of a different type.' ) : void 0 ;
event . destructor ( ) ;
if ( EventConstructor . eventPool . length < EVENT _POOL _SIZE ) {
EventConstructor . eventPool . push ( event ) ;
}
}
function addEventPoolingTo ( EventConstructor ) {
EventConstructor . eventPool = [ ] ;
EventConstructor . getPooled = getPooledEvent ;
EventConstructor . release = releasePooledEvent ;
}
2017-12-10 21:51:33 +01:00
var SyntheticEvent$1 = SyntheticEvent ;
2017-10-14 18:40:54 +02:00
/ * *
* ` touchHistory ` isn ' t actually on the native event , but putting it in the
* interface will ensure that it is cleaned up when pooled / destroyed . The
* ` ResponderEventPlugin ` will populate it appropriately .
* /
var ResponderEventInterface = {
touchHistory : function ( nativeEvent ) {
return null ; // Actually doesn't even look at the native event.
}
} ;
/ * *
* @ param { object } dispatchConfig Configuration used to dispatch this event .
* @ param { string } dispatchMarker Marker identifying the event target .
* @ param { object } nativeEvent Native event .
* @ extends { SyntheticEvent }
* /
function ResponderSyntheticEvent ( dispatchConfig , dispatchMarker , nativeEvent , nativeEventTarget ) {
2017-12-10 21:51:33 +01:00
return SyntheticEvent$1 . call ( this , dispatchConfig , dispatchMarker , nativeEvent , nativeEventTarget ) ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
SyntheticEvent$1 . augmentClass ( ResponderSyntheticEvent , ResponderEventInterface ) ;
2017-10-14 18:40:54 +02:00
/ * *
* Tracks the position and time of each active touch by ` touch.identifier ` . We
* should typically only see IDs in the range of 1 - 20 because IDs get recycled
* when touches end and start again .
* /
var MAX _TOUCH _BANK = 20 ;
var touchBank = [ ] ;
var touchHistory = {
touchBank : touchBank ,
numberActiveTouches : 0 ,
// If there is only one active touch, we remember its location. This prevents
// us having to loop through all of the touches all the time in the most
// common case.
indexOfSingleActiveTouch : - 1 ,
mostRecentTimeStamp : 0
} ;
function timestampForTouch ( touch ) {
// The legacy internal implementation provides "timeStamp", which has been
// renamed to "timestamp". Let both work for now while we iron it out
// TODO (evv): rename timeStamp to timestamp in internal code
return touch . timeStamp || touch . timestamp ;
}
/ * *
* TODO : Instead of making gestures recompute filtered velocity , we could
* include a built in velocity computation that can be reused globally .
* /
function createTouchRecord ( touch ) {
return {
touchActive : true ,
startPageX : touch . pageX ,
startPageY : touch . pageY ,
startTimeStamp : timestampForTouch ( touch ) ,
currentPageX : touch . pageX ,
currentPageY : touch . pageY ,
currentTimeStamp : timestampForTouch ( touch ) ,
previousPageX : touch . pageX ,
previousPageY : touch . pageY ,
previousTimeStamp : timestampForTouch ( touch )
} ;
}
function resetTouchRecord ( touchRecord , touch ) {
touchRecord . touchActive = true ;
touchRecord . startPageX = touch . pageX ;
touchRecord . startPageY = touch . pageY ;
touchRecord . startTimeStamp = timestampForTouch ( touch ) ;
touchRecord . currentPageX = touch . pageX ;
touchRecord . currentPageY = touch . pageY ;
touchRecord . currentTimeStamp = timestampForTouch ( touch ) ;
touchRecord . previousPageX = touch . pageX ;
touchRecord . previousPageY = touch . pageY ;
touchRecord . previousTimeStamp = timestampForTouch ( touch ) ;
}
function getTouchIdentifier ( _ref ) {
var identifier = _ref . identifier ;
! ( identifier != null ) ? invariant ( false , 'Touch object is missing identifier.' ) : void 0 ;
{
2017-12-10 21:51:33 +01:00
warning ( identifier <= MAX _TOUCH _BANK , 'Touch identifier %s is greater than maximum supported %s which causes ' + 'performance issues backfilling array locations for all of the indices.' , identifier , MAX _TOUCH _BANK ) ;
2017-10-14 18:40:54 +02:00
}
return identifier ;
}
function recordTouchStart ( touch ) {
var identifier = getTouchIdentifier ( touch ) ;
var touchRecord = touchBank [ identifier ] ;
if ( touchRecord ) {
resetTouchRecord ( touchRecord , touch ) ;
} else {
touchBank [ identifier ] = createTouchRecord ( touch ) ;
}
touchHistory . mostRecentTimeStamp = timestampForTouch ( touch ) ;
}
function recordTouchMove ( touch ) {
var touchRecord = touchBank [ getTouchIdentifier ( touch ) ] ;
if ( touchRecord ) {
touchRecord . touchActive = true ;
touchRecord . previousPageX = touchRecord . currentPageX ;
touchRecord . previousPageY = touchRecord . currentPageY ;
touchRecord . previousTimeStamp = touchRecord . currentTimeStamp ;
touchRecord . currentPageX = touch . pageX ;
touchRecord . currentPageY = touch . pageY ;
touchRecord . currentTimeStamp = timestampForTouch ( touch ) ;
touchHistory . mostRecentTimeStamp = timestampForTouch ( touch ) ;
} else {
console . error ( 'Cannot record touch move without a touch start.\n' + 'Touch Move: %s\n' , 'Touch Bank: %s' , printTouch ( touch ) , printTouchBank ( ) ) ;
}
}
function recordTouchEnd ( touch ) {
var touchRecord = touchBank [ getTouchIdentifier ( touch ) ] ;
if ( touchRecord ) {
touchRecord . touchActive = false ;
touchRecord . previousPageX = touchRecord . currentPageX ;
touchRecord . previousPageY = touchRecord . currentPageY ;
touchRecord . previousTimeStamp = touchRecord . currentTimeStamp ;
touchRecord . currentPageX = touch . pageX ;
touchRecord . currentPageY = touch . pageY ;
touchRecord . currentTimeStamp = timestampForTouch ( touch ) ;
touchHistory . mostRecentTimeStamp = timestampForTouch ( touch ) ;
} else {
console . error ( 'Cannot record touch end without a touch start.\n' + 'Touch End: %s\n' , 'Touch Bank: %s' , printTouch ( touch ) , printTouchBank ( ) ) ;
}
}
function printTouch ( touch ) {
return JSON . stringify ( {
identifier : touch . identifier ,
pageX : touch . pageX ,
pageY : touch . pageY ,
timestamp : timestampForTouch ( touch )
} ) ;
}
function printTouchBank ( ) {
var printed = JSON . stringify ( touchBank . slice ( 0 , MAX _TOUCH _BANK ) ) ;
if ( touchBank . length > MAX _TOUCH _BANK ) {
printed += ' (original size: ' + touchBank . length + ')' ;
}
return printed ;
}
var ResponderTouchHistoryStore = {
recordTouchTrack : function ( topLevelType , nativeEvent ) {
2017-12-10 21:51:33 +01:00
if ( isMoveish ( topLevelType ) ) {
2017-10-14 18:40:54 +02:00
nativeEvent . changedTouches . forEach ( recordTouchMove ) ;
2017-12-10 21:51:33 +01:00
} else if ( isStartish ( topLevelType ) ) {
2017-10-14 18:40:54 +02:00
nativeEvent . changedTouches . forEach ( recordTouchStart ) ;
touchHistory . numberActiveTouches = nativeEvent . touches . length ;
if ( touchHistory . numberActiveTouches === 1 ) {
touchHistory . indexOfSingleActiveTouch = nativeEvent . touches [ 0 ] . identifier ;
}
2017-12-10 21:51:33 +01:00
} else if ( isEndish ( topLevelType ) ) {
2017-10-14 18:40:54 +02:00
nativeEvent . changedTouches . forEach ( recordTouchEnd ) ;
touchHistory . numberActiveTouches = nativeEvent . touches . length ;
if ( touchHistory . numberActiveTouches === 1 ) {
for ( var i = 0 ; i < touchBank . length ; i ++ ) {
var touchTrackToCheck = touchBank [ i ] ;
if ( touchTrackToCheck != null && touchTrackToCheck . touchActive ) {
touchHistory . indexOfSingleActiveTouch = i ;
break ;
}
}
{
var activeRecord = touchBank [ touchHistory . indexOfSingleActiveTouch ] ;
2017-12-10 21:51:33 +01:00
warning ( activeRecord != null && activeRecord . touchActive , 'Cannot find single active touch.' ) ;
2017-10-14 18:40:54 +02:00
}
}
}
} ,
touchHistory : touchHistory
} ;
/ * *
* Accumulates items that must not be null or undefined .
*
* This is used to conserve memory by avoiding array allocations .
*
* @ return { * | array < * > } An accumulation of items .
* /
function accumulate ( current , next ) {
! ( next != null ) ? invariant ( false , 'accumulate(...): Accumulated items must be not be null or undefined.' ) : void 0 ;
if ( current == null ) {
return next ;
}
// Both are not empty. Warning: Never call x.concat(y) when you are not
// certain that x is an Array (x could be a string with concat method).
if ( Array . isArray ( current ) ) {
return current . concat ( next ) ;
}
if ( Array . isArray ( next ) ) {
return [ current ] . concat ( next ) ;
}
return [ current , next ] ;
}
/ * *
* Instance of element that should respond to touch / move types of interactions ,
* as indicated explicitly by relevant callbacks .
* /
var responderInst = null ;
/ * *
* Count of current touches . A textInput should become responder iff the
* selection changes while there is a touch on the screen .
* /
var trackedTouchCount = 0 ;
/ * *
* Last reported number of active touches .
* /
var previousActiveTouches = 0 ;
var changeResponder = function ( nextResponderInst , blockHostResponder ) {
var oldResponderInst = responderInst ;
responderInst = nextResponderInst ;
if ( ResponderEventPlugin . GlobalResponderHandler !== null ) {
ResponderEventPlugin . GlobalResponderHandler . onChange ( oldResponderInst , nextResponderInst , blockHostResponder ) ;
}
} ;
var eventTypes = {
/ * *
* On a ` touchStart ` / ` mouseDown ` , is it desired that this element become the
* responder ?
* /
startShouldSetResponder : {
phasedRegistrationNames : {
bubbled : 'onStartShouldSetResponder' ,
captured : 'onStartShouldSetResponderCapture'
}
} ,
/ * *
* On a ` scroll ` , is it desired that this element become the responder ? This
* is usually not needed , but should be used to retroactively infer that a
* ` touchStart ` had occurred during momentum scroll . During a momentum scroll ,
* a touch start will be immediately followed by a scroll event if the view is
* currently scrolling .
*
* TODO : This shouldn ' t bubble .
* /
scrollShouldSetResponder : {
phasedRegistrationNames : {
bubbled : 'onScrollShouldSetResponder' ,
captured : 'onScrollShouldSetResponderCapture'
}
} ,
/ * *
* On text selection change , should this element become the responder ? This
* is needed for text inputs or other views with native selection , so the
* JS view can claim the responder .
*
* TODO : This shouldn ' t bubble .
* /
selectionChangeShouldSetResponder : {
phasedRegistrationNames : {
bubbled : 'onSelectionChangeShouldSetResponder' ,
captured : 'onSelectionChangeShouldSetResponderCapture'
}
} ,
/ * *
* On a ` touchMove ` / ` mouseMove ` , is it desired that this element become the
* responder ?
* /
moveShouldSetResponder : {
phasedRegistrationNames : {
bubbled : 'onMoveShouldSetResponder' ,
captured : 'onMoveShouldSetResponderCapture'
}
} ,
/ * *
* Direct responder events dispatched directly to responder . Do not bubble .
* /
responderStart : { registrationName : 'onResponderStart' } ,
responderMove : { registrationName : 'onResponderMove' } ,
responderEnd : { registrationName : 'onResponderEnd' } ,
responderRelease : { registrationName : 'onResponderRelease' } ,
responderTerminationRequest : {
registrationName : 'onResponderTerminationRequest'
} ,
responderGrant : { registrationName : 'onResponderGrant' } ,
responderReject : { registrationName : 'onResponderReject' } ,
responderTerminate : { registrationName : 'onResponderTerminate' }
} ;
/ * *
*
* Responder System :
* -- -- -- -- -- -- -- --
*
* - A global , solitary "interaction lock" on a view .
* - If a node becomes the responder , it should convey visual feedback
* immediately to indicate so , either by highlighting or moving accordingly .
* - To be the responder means , that touches are exclusively important to that
* responder view , and no other view .
* - While touches are still occurring , the responder lock can be transferred to
* a new view , but only to increasingly "higher" views ( meaning ancestors of
* the current responder ) .
*
* Responder being granted :
* -- -- -- -- -- -- -- -- -- -- -- --
*
* - Touch starts , moves , and scrolls can cause an ID to become the responder .
* - We capture / bubble ` startShouldSetResponder ` / ` moveShouldSetResponder ` to
* the "appropriate place" .
* - If nothing is currently the responder , the "appropriate place" is the
* initiating event ' s ` targetID ` .
* - If something * is * already the responder , the "appropriate place" is the
* first common ancestor of the event target and the current ` responderInst ` .
* - Some negotiation happens : See the timing diagram below .
* - Scrolled views automatically become responder . The reasoning is that a
* platform scroll view that isn ' t built on top of the responder system has
* began scrolling , and the active responder must now be notified that the
* interaction is no longer locked to it - the system has taken over .
*
* - Responder being released :
* As soon as no more touches that * started * inside of descendants of the
* * current * responderInst , an ` onResponderRelease ` event is dispatched to the
* current responder , and the responder lock is released .
*
* TODO :
* - on "end" , a callback hook for ` onResponderEndShouldRemainResponder ` that
* determines if the responder lock should remain .
* - If a view shouldn ' t "remain" the responder , any active touches should by
* default be considered "dead" and do not influence future negotiations or
* bubble paths . It should be as if those touches do not exist .
* -- For multitouch : Usually a translate - z will choose to "remain" responder
* after one out of many touches ended . For translate - y , usually the view
* doesn ' t wish to "remain" responder after one of many touches end .
* - Consider building this on top of a ` stopPropagation ` model similar to
* ` W3C ` events .
* - Ensure that ` onResponderTerminate ` is called on touch cancels , whether or
* not ` onResponderTerminationRequest ` returns ` true ` or ` false ` .
*
* /
/ * N e g o t i a t i o n P e r f o r m e d
+ -- -- -- -- -- -- -- -- -- -- -- - +
/ \
Process low level events to + Current Responder + wantsResponderID
determine who to perform negot - | ( if any exists at all ) |
iation / transition | Otherwise just pass through |
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- -- -- -- +
Bubble to find first ID | |
to return true : wantsResponderID | |
| |
+ -- -- -- -- -- -- - + | |
| onTouchStart | | |
+ -- -- -- + -- -- -- + none | |
| return | |
+ -- -- -- -- -- - v -- -- -- -- -- -- - + true | + -- -- -- -- -- -- -- -- -- -- -- -- + |
| onStartShouldSetResponder | -- -- - > | onResponderStart ( cur ) | < -- -- -- -- -- - +
+ -- -- -- -- -- - + -- -- -- -- -- -- - + | + -- -- -- -- -- -- -- -- -- -- -- -- + | |
| | | + -- -- -- -- + -- -- -- - +
| returned true for | false : REJECT + -- -- -- -- > | onResponderReject
| wantsResponderID | | | + -- -- -- -- -- -- -- -- +
| ( now attempt | + -- -- -- -- -- -- -- -- -- + -- -- - + |
| handoff ) | | onResponder | |
+ -- -- -- -- -- -- -- -- -- - > | TerminationRequest | |
| + -- -- -- -- -- -- -- -- -- + -- -- - + |
| | | + -- -- -- -- -- -- -- -- +
| true : GRANT + -- -- -- -- > | onResponderGrant |
| | + -- -- -- -- + -- -- -- - +
| + -- -- -- -- -- -- -- -- -- -- -- -- + | |
| | onResponderTerminate | < -- -- -- -- -- - +
| + -- -- -- -- -- -- -- -- -- + -- -- - + |
| | | + -- -- -- -- -- -- -- -- +
| + -- -- -- -- > | onResponderStart |
| | + -- -- -- -- -- -- -- -- +
Bubble to find first ID | |
to return true : wantsResponderID | |
| |
+ -- -- -- -- -- -- - + | |
| onTouchMove | | |
+ -- -- -- + -- -- -- + none | |
| return | |
+ -- -- -- -- -- - v -- -- -- -- -- -- - + true | + -- -- -- -- -- -- -- -- -- -- -- -- + |
| onMoveShouldSetResponder | -- -- - > | onResponderMove ( cur ) | < -- -- -- -- -- - +
+ -- -- -- -- -- - + -- -- -- -- -- -- - + | + -- -- -- -- -- -- -- -- -- -- -- -- + | |
| | | + -- -- -- -- + -- -- -- - +
| returned true for | false : REJECT + -- -- -- -- > | onResponderRejec |
| wantsResponderID | | | + -- -- -- -- -- -- -- -- +
| ( now attempt | + -- -- -- -- -- -- -- -- -- + -- -- - + |
| handoff ) | | onResponder | |
+ -- -- -- -- -- -- -- -- -- - > | TerminationRequest | |
| + -- -- -- -- -- -- -- -- -- + -- -- - + |
| | | + -- -- -- -- -- -- -- -- +
| true : GRANT + -- -- -- -- > | onResponderGrant |
| | + -- -- -- -- + -- -- -- - +
| + -- -- -- -- -- -- -- -- -- -- -- -- + | |
| | onResponderTerminate | < -- -- -- -- -- - +
| + -- -- -- -- -- -- -- -- -- + -- -- - + |
| | | + -- -- -- -- -- -- -- -- +
| + -- -- -- -- > | onResponderMove |
| | + -- -- -- -- -- -- -- -- +
| |
| |
Some active touch started | |
inside current responder | + -- -- -- -- -- -- -- -- -- -- -- -- + |
+ -- -- -- -- -- -- -- -- -- -- -- -- - > | onResponderEnd | |
| | + -- -- -- -- -- -- -- -- -- -- -- -- + |
+ -- - + -- -- -- -- - + | |
| onTouchEnd | | |
+ -- - + -- -- -- -- - + | |
| | + -- -- -- -- -- -- -- -- -- -- -- -- + |
+ -- -- -- -- -- -- -- -- -- -- -- -- - > | onResponderEnd | |
No active touches started | + -- -- -- -- -- - + -- -- -- -- -- -- + |
inside current responder | | |
| v |
| + -- -- -- -- -- -- -- -- -- -- -- -- + |
| | onResponderRelease | |
| + -- -- -- -- -- -- -- -- -- -- -- -- + |
| |
+ + * /
/ * *
* A note about event ordering in the ` EventPluginHub ` .
*
* Suppose plugins are injected in the following order :
*
* ` [R, S, C] `
*
* To help illustrate the example , assume ` S ` is ` SimpleEventPlugin ` ( for
* ` onClick ` etc ) and ` R ` is ` ResponderEventPlugin ` .
*
* "Deferred-Dispatched Events" :
*
* - The current event plugin system will traverse the list of injected plugins ,
* in order , and extract events by collecting the plugin ' s return value of
* ` extractEvents() ` .
* - These events that are returned from ` extractEvents ` are " deferred
* dispatched events " .
* - When returned from ` extractEvents ` , deferred - dispatched events contain an
* "accumulation" of deferred dispatches .
* - These deferred dispatches are accumulated / collected before they are
* returned , but processed at a later time by the ` EventPluginHub ` ( hence the
* name deferred ) .
*
* In the process of returning their deferred - dispatched events , event plugins
* themselves can dispatch events on - demand without returning them from
* ` extractEvents ` . Plugins might want to do this , so that they can use event
* dispatching as a tool that helps them decide which events should be extracted
* in the first place .
*
* "On-Demand-Dispatched Events" :
*
* - On - demand - dispatched events are not returned from ` extractEvents ` .
* - On - demand - dispatched events are dispatched during the process of returning
* the deferred - dispatched events .
* - They should not have side effects .
* - They should be avoided , and / or eventually be replaced with another
* abstraction that allows event plugins to perform multiple "rounds" of event
* extraction .
*
* Therefore , the sequence of event dispatches becomes :
*
* - ` R ` s on - demand events ( if any ) ( dispatched by ` R ` on - demand )
* - ` S ` s on - demand events ( if any ) ( dispatched by ` S ` on - demand )
* - ` C ` s on - demand events ( if any ) ( dispatched by ` C ` on - demand )
* - ` R ` s extracted events ( if any ) ( dispatched by ` EventPluginHub ` )
* - ` S ` s extracted events ( if any ) ( dispatched by ` EventPluginHub ` )
* - ` C ` s extracted events ( if any ) ( dispatched by ` EventPluginHub ` )
*
* In the case of ` ResponderEventPlugin ` : If the ` startShouldSetResponder `
* on - demand dispatch returns ` true ` ( and some other details are satisfied ) the
* ` onResponderGrant ` deferred dispatched event is returned from
* ` extractEvents ` . The sequence of dispatch executions in this case
* will appear as follows :
*
* - ` startShouldSetResponder ` ( ` ResponderEventPlugin ` dispatches on - demand )
* - ` touchStartCapture ` ( ` EventPluginHub ` dispatches as usual )
* - ` touchStart ` ( ` EventPluginHub ` dispatches as usual )
* - ` responderGrant/Reject ` ( ` EventPluginHub ` dispatches as usual )
* /
function setResponderAndExtractTransfer ( topLevelType , targetInst , nativeEvent , nativeEventTarget ) {
2017-12-10 21:51:33 +01:00
var shouldSetEventType = isStartish ( topLevelType ) ? eventTypes . startShouldSetResponder : isMoveish ( topLevelType ) ? eventTypes . moveShouldSetResponder : topLevelType === 'topSelectionChange' ? eventTypes . selectionChangeShouldSetResponder : eventTypes . scrollShouldSetResponder ;
2017-10-14 18:40:54 +02:00
// TODO: stop one short of the current responder.
2017-12-10 21:51:33 +01:00
var bubbleShouldSetFrom = ! responderInst ? targetInst : getLowestCommonAncestor ( responderInst , targetInst ) ;
2017-10-14 18:40:54 +02:00
// When capturing/bubbling the "shouldSet" event, we want to skip the target
// (deepest ID) if it happens to be the current responder. The reasoning:
// It's strange to get an `onMoveShouldSetResponder` when you're *already*
// the responder.
var skipOverBubbleShouldSetFrom = bubbleShouldSetFrom === responderInst ;
2017-12-10 21:51:33 +01:00
var shouldSetEvent = ResponderSyntheticEvent . getPooled ( shouldSetEventType , bubbleShouldSetFrom , nativeEvent , nativeEventTarget ) ;
shouldSetEvent . touchHistory = ResponderTouchHistoryStore . touchHistory ;
2017-10-14 18:40:54 +02:00
if ( skipOverBubbleShouldSetFrom ) {
2017-12-10 21:51:33 +01:00
accumulateTwoPhaseDispatchesSkipTarget ( shouldSetEvent ) ;
2017-10-14 18:40:54 +02:00
} else {
2017-12-10 21:51:33 +01:00
accumulateTwoPhaseDispatches ( shouldSetEvent ) ;
2017-10-14 18:40:54 +02:00
}
2017-12-10 21:51:33 +01:00
var wantsResponderInst = executeDispatchesInOrderStopAtTrue ( shouldSetEvent ) ;
2017-10-14 18:40:54 +02:00
if ( ! shouldSetEvent . isPersistent ( ) ) {
shouldSetEvent . constructor . release ( shouldSetEvent ) ;
}
if ( ! wantsResponderInst || wantsResponderInst === responderInst ) {
return null ;
}
var extracted ;
2017-12-10 21:51:33 +01:00
var grantEvent = ResponderSyntheticEvent . getPooled ( eventTypes . responderGrant , wantsResponderInst , nativeEvent , nativeEventTarget ) ;
grantEvent . touchHistory = ResponderTouchHistoryStore . touchHistory ;
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
accumulateDirectDispatches ( grantEvent ) ;
var blockHostResponder = executeDirectDispatch ( grantEvent ) === true ;
2017-10-14 18:40:54 +02:00
if ( responderInst ) {
2017-12-10 21:51:33 +01:00
var terminationRequestEvent = ResponderSyntheticEvent . getPooled ( eventTypes . responderTerminationRequest , responderInst , nativeEvent , nativeEventTarget ) ;
terminationRequestEvent . touchHistory = ResponderTouchHistoryStore . touchHistory ;
accumulateDirectDispatches ( terminationRequestEvent ) ;
var shouldSwitch = ! hasDispatches ( terminationRequestEvent ) || executeDirectDispatch ( terminationRequestEvent ) ;
2017-10-14 18:40:54 +02:00
if ( ! terminationRequestEvent . isPersistent ( ) ) {
terminationRequestEvent . constructor . release ( terminationRequestEvent ) ;
}
if ( shouldSwitch ) {
2017-12-10 21:51:33 +01:00
var terminateEvent = ResponderSyntheticEvent . getPooled ( eventTypes . responderTerminate , responderInst , nativeEvent , nativeEventTarget ) ;
terminateEvent . touchHistory = ResponderTouchHistoryStore . touchHistory ;
accumulateDirectDispatches ( terminateEvent ) ;
extracted = accumulate ( extracted , [ grantEvent , terminateEvent ] ) ;
2017-10-14 18:40:54 +02:00
changeResponder ( wantsResponderInst , blockHostResponder ) ;
} else {
2017-12-10 21:51:33 +01:00
var rejectEvent = ResponderSyntheticEvent . getPooled ( eventTypes . responderReject , wantsResponderInst , nativeEvent , nativeEventTarget ) ;
rejectEvent . touchHistory = ResponderTouchHistoryStore . touchHistory ;
accumulateDirectDispatches ( rejectEvent ) ;
extracted = accumulate ( extracted , rejectEvent ) ;
2017-10-14 18:40:54 +02:00
}
} else {
2017-12-10 21:51:33 +01:00
extracted = accumulate ( extracted , grantEvent ) ;
2017-10-14 18:40:54 +02:00
changeResponder ( wantsResponderInst , blockHostResponder ) ;
}
return extracted ;
}
/ * *
* A transfer is a negotiation between a currently set responder and the next
* element to claim responder status . Any start event could trigger a transfer
* of responderInst . Any move event could trigger a transfer .
*
* @ param { string } topLevelType Record from ` BrowserEventConstants ` .
* @ return { boolean } True if a transfer of responder could possibly occur .
* /
function canTriggerTransfer ( topLevelType , topLevelInst , nativeEvent ) {
return topLevelInst && (
// responderIgnoreScroll: We are trying to migrate away from specifically
// tracking native scroll events here and responderIgnoreScroll indicates we
// will send topTouchCancel to handle canceling touch events instead
2017-12-10 21:51:33 +01:00
topLevelType === 'topScroll' && ! nativeEvent . responderIgnoreScroll || trackedTouchCount > 0 && topLevelType === 'topSelectionChange' || isStartish ( topLevelType ) || isMoveish ( topLevelType ) ) ;
2017-10-14 18:40:54 +02:00
}
/ * *
* Returns whether or not this touch end event makes it such that there are no
* longer any touches that started inside of the current ` responderInst ` .
*
* @ param { NativeEvent } nativeEvent Native touch end event .
* @ return { boolean } Whether or not this touch end event ends the responder .
* /
function noResponderTouches ( nativeEvent ) {
var touches = nativeEvent . touches ;
if ( ! touches || touches . length === 0 ) {
return true ;
}
for ( var i = 0 ; i < touches . length ; i ++ ) {
var activeTouch = touches [ i ] ;
var target = activeTouch . target ;
if ( target !== null && target !== undefined && target !== 0 ) {
// Is the original touch location inside of the current responder?
2017-12-10 21:51:33 +01:00
var targetInst = getInstanceFromNode ( target ) ;
if ( isAncestor ( responderInst , targetInst ) ) {
2017-10-14 18:40:54 +02:00
return false ;
}
}
}
return true ;
}
var ResponderEventPlugin = {
/* For unit testing only */
_getResponder : function ( ) {
return responderInst ;
} ,
eventTypes : eventTypes ,
/ * *
* We must be resilient to ` targetInst ` being ` null ` on ` touchMove ` or
* ` touchEnd ` . On certain platforms , this means that a native scroll has
* assumed control and the original touch targets are destroyed .
* /
extractEvents : function ( topLevelType , targetInst , nativeEvent , nativeEventTarget ) {
2017-12-10 21:51:33 +01:00
if ( isStartish ( topLevelType ) ) {
2017-10-14 18:40:54 +02:00
trackedTouchCount += 1 ;
2017-12-10 21:51:33 +01:00
} else if ( isEndish ( topLevelType ) ) {
2017-10-14 18:40:54 +02:00
if ( trackedTouchCount >= 0 ) {
trackedTouchCount -= 1 ;
} else {
console . error ( 'Ended a touch event which was not counted in `trackedTouchCount`.' ) ;
return null ;
}
}
2017-12-10 21:51:33 +01:00
ResponderTouchHistoryStore . recordTouchTrack ( topLevelType , nativeEvent ) ;
2017-10-14 18:40:54 +02:00
var extracted = canTriggerTransfer ( topLevelType , targetInst , nativeEvent ) ? setResponderAndExtractTransfer ( topLevelType , targetInst , nativeEvent , nativeEventTarget ) : null ;
// Responder may or may not have transferred on a new touch start/move.
// Regardless, whoever is the responder after any potential transfer, we
// direct all touch start/move/ends to them in the form of
// `onResponderMove/Start/End`. These will be called for *every* additional
// finger that move/start/end, dispatched directly to whoever is the
// current responder at that moment, until the responder is "released".
//
// These multiple individual change touch events are are always bookended
// by `onResponderGrant`, and one of
// (`onResponderRelease/onResponderTerminate`).
2017-12-10 21:51:33 +01:00
var isResponderTouchStart = responderInst && isStartish ( topLevelType ) ;
var isResponderTouchMove = responderInst && isMoveish ( topLevelType ) ;
var isResponderTouchEnd = responderInst && isEndish ( topLevelType ) ;
2017-10-14 18:40:54 +02:00
var incrementalTouch = isResponderTouchStart ? eventTypes . responderStart : isResponderTouchMove ? eventTypes . responderMove : isResponderTouchEnd ? eventTypes . responderEnd : null ;
if ( incrementalTouch ) {
2017-12-10 21:51:33 +01:00
var gesture = ResponderSyntheticEvent . getPooled ( incrementalTouch , responderInst , nativeEvent , nativeEventTarget ) ;
gesture . touchHistory = ResponderTouchHistoryStore . touchHistory ;
accumulateDirectDispatches ( gesture ) ;
extracted = accumulate ( extracted , gesture ) ;
2017-10-14 18:40:54 +02:00
}
var isResponderTerminate = responderInst && topLevelType === 'topTouchCancel' ;
2017-12-10 21:51:33 +01:00
var isResponderRelease = responderInst && ! isResponderTerminate && isEndish ( topLevelType ) && noResponderTouches ( nativeEvent ) ;
2017-10-14 18:40:54 +02:00
var finalTouch = isResponderTerminate ? eventTypes . responderTerminate : isResponderRelease ? eventTypes . responderRelease : null ;
if ( finalTouch ) {
2017-12-10 21:51:33 +01:00
var finalEvent = ResponderSyntheticEvent . getPooled ( finalTouch , responderInst , nativeEvent , nativeEventTarget ) ;
finalEvent . touchHistory = ResponderTouchHistoryStore . touchHistory ;
accumulateDirectDispatches ( finalEvent ) ;
extracted = accumulate ( extracted , finalEvent ) ;
2017-10-14 18:40:54 +02:00
changeResponder ( null ) ;
}
2017-12-10 21:51:33 +01:00
var numberActiveTouches = ResponderTouchHistoryStore . touchHistory . numberActiveTouches ;
2017-10-14 18:40:54 +02:00
if ( ResponderEventPlugin . GlobalInteractionHandler && numberActiveTouches !== previousActiveTouches ) {
ResponderEventPlugin . GlobalInteractionHandler . onChange ( numberActiveTouches ) ;
}
previousActiveTouches = numberActiveTouches ;
return extracted ;
} ,
GlobalResponderHandler : null ,
GlobalInteractionHandler : null ,
injection : {
/ * *
* @ param { { onChange : ( ReactID , ReactID ) => void } GlobalResponderHandler
* Object that handles any change in responder . Use this to inject
* integration with an existing touch handling system etc .
* /
injectGlobalResponderHandler : function ( GlobalResponderHandler ) {
ResponderEventPlugin . GlobalResponderHandler = GlobalResponderHandler ;
} ,
/ * *
* @ param { { onChange : ( numberActiveTouches ) => void } GlobalInteractionHandler
* Object that handles any change in the number of active touches .
* /
injectGlobalInteractionHandler : function ( GlobalInteractionHandler ) {
ResponderEventPlugin . GlobalInteractionHandler = GlobalInteractionHandler ;
}
}
} ;
2017-12-10 21:51:33 +01:00
// This is used by react-native-web.
var injectComponentTree = injection . injectComponentTree ;
2017-10-14 18:40:54 +02:00
// Inject react-dom's ComponentTree into this module.
2017-12-10 21:51:33 +01:00
var ReactDOMComponentTree = ReactDOM . _ _SECRET _INTERNALS _DO _NOT _USE _OR _YOU _WILL _BE _FIRED . ReactDOMComponentTree ;
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
injectComponentTree ( ReactDOMComponentTree ) ;
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
var ReactDOMUnstableNativeDependencies = Object . freeze ( {
injectComponentTree : injectComponentTree ,
ResponderEventPlugin : ResponderEventPlugin ,
ResponderTouchHistoryStore : ResponderTouchHistoryStore
} ) ;
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
var unstableNativeDependencies = ReactDOMUnstableNativeDependencies ;
2017-10-14 18:40:54 +02:00
2017-12-10 21:51:33 +01:00
module . exports = unstableNativeDependencies ;
} ) ( ) ;
2017-10-14 18:40:54 +02:00
}