UNPKG

react-native

Version:

A framework for building native apps using React

1,504 lines (1,318 loc) • 965 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @noflow * @nolint * @providesModule ReactNativeRenderer-dev * @preventMunge * @generated SignedSource<<4fbb60a6e6520f92ffc104738fdd5ce8>> */ "use strict"; if (__DEV__) { (function () { "use strict"; /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ if ( typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === "function" ) { __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); } var React = require("react"); require("react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore"); var ReactNativePrivateInterface = require("react-native/Libraries/ReactPrivate/ReactNativePrivateInterface"); var Scheduler = require("scheduler"); var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; // by calls to these methods by a Babel plugin. // // In PROD (or in packages without access to React internals), // they are left as they are instead. function warn(format) { { { for ( var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++ ) { args[_key - 1] = arguments[_key]; } printWarning("warn", format, args); } } } function error(format) { { { for ( var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++ ) { args[_key2 - 1] = arguments[_key2]; } printWarning("error", format, args); } } } function printWarning(level, format, args) { // When changing this logic, you might want to also // update consoleWithStackDev.www.js as well. { var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; var stack = ReactDebugCurrentFrame.getStackAddendum(); if (stack !== "") { format += "%s"; args = args.concat([stack]); } // eslint-disable-next-line react-internal/safe-string-coercion var argsWithFormat = args.map(function (item) { return String(item); }); // Careful: RN currently depends on this prefix argsWithFormat.unshift("Warning: " + format); // We intentionally don't use spread (or .apply) directly because it // breaks IE9: https://github.com/facebook/react/issues/13610 // eslint-disable-next-line react-internal/no-production-logging Function.prototype.apply.call(console[level], console, argsWithFormat); } } var isArrayImpl = Array.isArray; // eslint-disable-next-line no-redeclare function isArray(a) { return isArrayImpl(a); } var hasError = false; var caughtError = null; var getFiberCurrentPropsFromNode$1 = null; var getInstanceFromNode = null; var getNodeFromInstance = null; function setComponentTree( getFiberCurrentPropsFromNodeImpl, getInstanceFromNodeImpl, getNodeFromInstanceImpl ) { getFiberCurrentPropsFromNode$1 = getFiberCurrentPropsFromNodeImpl; getInstanceFromNode = getInstanceFromNodeImpl; getNodeFromInstance = getNodeFromInstanceImpl; { if (!getNodeFromInstance || !getInstanceFromNode) { error( "Injected " + "module is missing getNodeFromInstance or getInstanceFromNode." ); } } } function validateEventDispatches(event) { { var dispatchListeners = event._dispatchListeners; var dispatchInstances = event._dispatchInstances; var listenersIsArr = isArray(dispatchListeners); var listenersLen = listenersIsArr ? dispatchListeners.length : dispatchListeners ? 1 : 0; var instancesIsArr = isArray(dispatchInstances); var instancesLen = instancesIsArr ? dispatchInstances.length : dispatchInstances ? 1 : 0; if ( instancesIsArr !== listenersIsArr || instancesLen !== listenersLen ) { error("EventPluginUtils: Invalid `event`."); } } } /** * Dispatch the event to the listener. * @param {SyntheticEvent} event SyntheticEvent to handle * @param {function} listener Application-level callback * @param {*} inst Internal component instance */ function executeDispatch(event, listener, inst) { event.currentTarget = getNodeFromInstance(inst); try { listener(event); } catch (error) { if (!hasError) { hasError = true; caughtError = error; } } event.currentTarget = null; } /** * Standard/simple iteration through an event's collected dispatches. */ function executeDispatchesInOrder(event) { var dispatchListeners = event._dispatchListeners; var dispatchInstances = event._dispatchInstances; { validateEventDispatches(event); } if (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. executeDispatch(event, dispatchListeners[i], dispatchInstances[i]); } } else if (dispatchListeners) { executeDispatch(event, dispatchListeners, dispatchInstances); } event._dispatchListeners = null; event._dispatchInstances = null; } /** * 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 (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; if (isArray(dispatchListener)) { throw new Error("Invalid `event`."); } event.currentTarget = dispatchListener ? getNodeFromInstance(dispatchInstance) : null; 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; } function rethrowCaughtError() { if (hasError) { var error = caughtError; hasError = false; caughtError = null; throw error; } } var assign = Object.assign; var EVENT_POOL_SIZE = 10; /** * @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: function () { return null; }, eventPhase: null, bubbles: null, cancelable: null, timeStamp: function (event) { return event.timeStamp || Date.now(); }, defaultPrevented: null, isTrusted: null }; function functionThatReturnsTrue() { return true; } function functionThatReturnsFalse() { return false; } /** * 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; delete this.isDefaultPrevented; delete this.isPropagationStopped; } this.dispatchConfig = dispatchConfig; this._targetInst = targetInst; this.nativeEvent = nativeEvent; this._dispatchListeners = null; this._dispatchInstances = null; 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 = functionThatReturnsTrue; } else { this.isDefaultPrevented = functionThatReturnsFalse; } this.isPropagationStopped = functionThatReturnsFalse; return this; } assign(SyntheticEvent.prototype, { 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 = functionThatReturnsTrue; }, 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 = functionThatReturnsTrue; }, /** * 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 = functionThatReturnsTrue; }, /** * Checks if this event should be released back into the pool. * * @return {boolean} True if this should not be released, false otherwise. */ isPersistent: functionThatReturnsFalse, /** * `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]) ); } } this.dispatchConfig = null; this._targetInst = null; this.nativeEvent = null; this.isDefaultPrevented = functionThatReturnsFalse; this.isPropagationStopped = functionThatReturnsFalse; this._dispatchListeners = null; this._dispatchInstances = null; { Object.defineProperty( this, "nativeEvent", getPooledWarningPropertyDefinition("nativeEvent", null) ); Object.defineProperty( this, "isDefaultPrevented", getPooledWarningPropertyDefinition( "isDefaultPrevented", functionThatReturnsFalse ) ); Object.defineProperty( this, "isPropagationStopped", getPooledWarningPropertyDefinition( "isPropagationStopped", functionThatReturnsFalse ) ); Object.defineProperty( this, "preventDefault", getPooledWarningPropertyDefinition("preventDefault", function () {}) ); Object.defineProperty( this, "stopPropagation", getPooledWarningPropertyDefinition( "stopPropagation", function () {} ) ); } } }); SyntheticEvent.Interface = EventInterface; /** * Helper to reduce boilerplate when creating subclasses. */ SyntheticEvent.extend = function (Interface) { var Super = this; var E = function () {}; E.prototype = Super.prototype; var prototype = new E(); function Class() { return Super.apply(this, arguments); } assign(prototype, Class.prototype); Class.prototype = prototype; Class.prototype.constructor = Class; Class.Interface = assign({}, Super.Interface, Interface); Class.extend = Super.extend; addEventPoolingTo(Class); return Class; }; addEventPoolingTo(SyntheticEvent); /** * Helper to nullify syntheticEvent instance properties when destructing * * @param {String} propName * @param {?object} getVal * @return {object} defineProperty object */ function getPooledWarningPropertyDefinition(propName, getVal) { 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) { { error( "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://react.dev/link/event-pooling for more information.", action, propName, result ); } } var isFunction = typeof getVal === "function"; return { configurable: true, set: set, get: get }; } function createOrGetPooledEvent( 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; if (!(event instanceof EventConstructor)) { throw new Error( "Trying to release an event instance into a pool of a different type." ); } event.destructor(); if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) { EventConstructor.eventPool.push(event); } } function addEventPoolingTo(EventConstructor) { EventConstructor.getPooled = createOrGetPooledEvent; EventConstructor.eventPool = []; EventConstructor.release = releasePooledEvent; } /** * `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 ResponderSyntheticEvent = SyntheticEvent.extend({ touchHistory: function (nativeEvent) { return null; // Actually doesn't even look at the native event. } }); var TOP_TOUCH_START = "topTouchStart"; var TOP_TOUCH_MOVE = "topTouchMove"; var TOP_TOUCH_END = "topTouchEnd"; var TOP_TOUCH_CANCEL = "topTouchCancel"; var TOP_SCROLL = "topScroll"; var TOP_SELECTION_CHANGE = "topSelectionChange"; function isStartish(topLevelType) { return topLevelType === TOP_TOUCH_START; } function isMoveish(topLevelType) { return topLevelType === TOP_TOUCH_MOVE; } function isEndish(topLevelType) { return ( topLevelType === TOP_TOUCH_END || topLevelType === TOP_TOUCH_CANCEL ); } var startDependencies = [TOP_TOUCH_START]; var moveDependencies = [TOP_TOUCH_MOVE]; var endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END]; /** * 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; if (identifier == null) { throw new Error("Touch object is missing identifier."); } { if (identifier > MAX_TOUCH_BANK) { error( "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 ); } } 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 { { warn( "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 { { warn( "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 instrumentationCallback; var ResponderTouchHistoryStore = { /** * Registers a listener which can be used to instrument every touch event. */ instrument: function (callback) { instrumentationCallback = callback; }, recordTouchTrack: function (topLevelType, nativeEvent) { if (instrumentationCallback != null) { instrumentationCallback(topLevelType, nativeEvent); } if (isMoveish(topLevelType)) { nativeEvent.changedTouches.forEach(recordTouchMove); } else if (isStartish(topLevelType)) { nativeEvent.changedTouches.forEach(recordTouchStart); touchHistory.numberActiveTouches = nativeEvent.touches.length; if (touchHistory.numberActiveTouches === 1) { touchHistory.indexOfSingleActiveTouch = nativeEvent.touches[0].identifier; } } else if (isEndish(topLevelType)) { 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]; if (activeRecord == null || !activeRecord.touchActive) { error("Cannot find single active touch."); } } } } }, 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) { if (next == null) { throw new Error("Accumulated items must not be null or undefined."); } 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 (isArray(current)) { /* $FlowFixMe[incompatible-return] if `current` is `T` and `T` an array, * `isArray` might refine to the array element type of `T` */ return current.concat(next); } if (isArray(next)) { /* $FlowFixMe[incompatible-return] unsound if `next` is `T` and `T` an array, * `isArray` might refine to the array element type of `T` */ return [current].concat(next); } return [current, next]; } /** * 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) { if (next == null) { throw new Error("Accumulated items must not be null or undefined."); } 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 (isArray(current)) { if (isArray(next)) { // $FlowFixMe[prop-missing] `isArray` does not ensure array is mutable // $FlowFixMe[method-unbinding] current.push.apply(current, next); return current; } // $FlowFixMe[prop-missing] `isArray` does not ensure array is mutable current.push(next); return current; } if (isArray(next)) { // A bit too dangerous to mutate `next`. /* $FlowFixMe[incompatible-return] unsound if `next` is `T` and `T` an array, * `isArray` might refine to the array element type of `T` */ 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)) { // $FlowFixMe[incompatible-call] if `T` is an array, `cb` cannot be called arr.forEach(cb, scope); } else if (arr) { cb.call(scope, arr); } } var FunctionComponent = 0; var ClassComponent = 1; var IndeterminateComponent = 2; // Before we know whether it is function or class var HostRoot = 3; // Root of a host tree. Could be nested inside another node. var HostPortal = 4; // A subtree. Could be an entry point to a different renderer. var HostComponent = 5; var HostText = 6; var Fragment = 7; var Mode = 8; var ContextConsumer = 9; var ContextProvider = 10; var ForwardRef = 11; var Profiler = 12; var SuspenseComponent = 13; var MemoComponent = 14; var SimpleMemoComponent = 15; var LazyComponent = 16; var IncompleteClassComponent = 17; var DehydratedFragment = 18; var SuspenseListComponent = 19; var ScopeComponent = 21; var OffscreenComponent = 22; var LegacyHiddenComponent = 23; var CacheComponent = 24; var TracingMarkerComponent = 25; var HostHoistable = 26; var HostSingleton = 27; /** * 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; function changeResponder(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" }, dependencies: startDependencies }, /** * 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" }, dependencies: [TOP_SCROLL] }, /** * 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" }, dependencies: [TOP_SELECTION_CHANGE] }, /** * On a `touchMove`/`mouseMove`, is it desired that this element become the * responder? */ moveShouldSetResponder: { phasedRegistrationNames: { bubbled: "onMoveShouldSetResponder", captured: "onMoveShouldSetResponderCapture" }, dependencies: moveDependencies }, /** * Direct responder events dispatched directly to responder. Do not bubble. */ responderStart: { registrationName: "onResponderStart", dependencies: startDependencies }, responderMove: { registrationName: "onResponderMove", dependencies: moveDependencies }, responderEnd: { registrationName: "onResponderEnd", dependencies: endDependencies }, responderRelease: { registrationName: "onResponderRelease", dependencies: endDependencies }, responderTerminationRequest: { registrationName: "onResponderTerminationRequest", dependencies: [] }, responderGrant: { registrationName: "onResponderGrant", dependencies: [] }, responderReject: { registrationName: "onResponderReject", dependencies: [] }, responderTerminate: { registrationName: "onResponderTerminate", dependencies: [] } }; // Start of inline: the below functions were inlined from // EventPropagator.js, as they deviated from ReactDOM's newer // implementations. function getParent$1(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; } /** * Return the lowest common ancestor of A and B, or null if they are in * different trees. */ function getLowestCommonAncestor(instA, instB) { var depthA = 0; for (var tempA = instA; tempA; tempA = getParent$1(tempA)) { depthA++; } var depthB = 0; for (var tempB = instB; tempB; tempB = getParent$1(tempB)) { depthB++; } // If A is deeper, crawl up. while (depthA - depthB > 0) { instA = getParent$1(instA); depthA--; } // If B is deeper, crawl up. while (depthB - depthA > 0) { instB = getParent$1(instB); depthB--; } // Walk in lockstep until we find a match. var depth = depthA; while (depth--) { if (instA === instB || instA === instB.alternate) { return instA; } instA = getParent$1(instA); instB = getParent$1(instB); } return null; } /** * Return if A is an ancestor of B. */ function isAncestor(instA, instB) { while (instB) { if (instA === instB || instA === instB.alternate) { return true; } instB = getParent$1(instB); } return false; } /** * Simulates the traversal of a two-phase, capture/bubble event dispatch. */ function traverseTwoPhase$1(inst, fn, arg) { var path = []; while (inst) { path.push(inst); inst = getParent$1(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); } } function getListener$1(inst, registrationName) { var stateNode = inst.stateNode; if (stateNode === null) { // Work in progress (ex: onload events in incremental mode). return null; } var props = getFiberCurrentPropsFromNode$1(stateNode); if (props === null) { // Work in progress. return null; } var listener = props[registrationName]; if (listener && typeof listener !== "function") { throw new Error( "Expected `" + registrationName + "` listener to be a function, instead got a value of `" + typeof listener + "` type." ); } return listener; } function listenerAtPhase$1(inst, event, propagationPhase) { var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase]; return getListener$1(inst, registrationName); } function accumulateDirectionalDispatches$1(inst, phase, event) { { if (!inst) { error("Dispatching inst must not be null"); } } var listener = listenerAtPhase$1(inst, event, phase); if (listener) { event._dispatchListeners = accumulateInto( event._dispatchListeners, listener ); event._dispatchInstances = accumulateInto( event._dispatchInstances, inst ); } } /** * 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$1(inst, ignoredDirection, event) { if (inst && event && event.dispatchConfig.registrationName) { var registrationName = event.dispatchConfig.registrationName; var listener = getListener$1(inst, registrationName); if (listener) { event._dispatchListeners = accumulateInto( event._dispatchListeners, listener ); event._dispatchInstances = accumulateInto( event._dispatchInstances, inst ); } } } /** * Accumulates dispatches on an `SyntheticEvent`, but only for the * `dispatchMarker`. * @param {SyntheticEvent} event */ function accumulateDirectDispatchesSingle$1(event) { if (event && event.dispatchConfig.registrationName) { accumulateDispatches$1(event._targetInst, null, event); } } function accumulateDirectDispatches$1(events) { forEachAccumulated(events, accumulateDirectDispatchesSingle$1); } function accumulateTwoPhaseDispatchesSingleSkipTarget(event) { if (event && event.dispatchConfig.phasedRegistrationNames) { var targetInst = event._targetInst; var parentInst = targetInst ? getParent$1(targetInst) : null; traverseTwoPhase$1( parentInst, accumulateDirectionalDispatches$1, event ); } } function accumulateTwoPhaseDispatchesSkipTarget(events) { forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget); } function accumulateTwoPhaseDispatchesSingle$1(event) { if (event && event.dispatchConfig.phasedRegistrationNames) { traverseTwoPhase$1( event._targetInst, accumulateDirectionalDispatches$1, event ); } } function accumulateTwoPhaseDispatches$1(events) { forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle$1); } // End of inline /** * * 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`. * */ /* Negotiation Performed +-----------------------+ / \ 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 `EventPluginRegistry`. * * 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