diff --git a/packages/events/EventPluginHub.js b/packages/events/EventPluginHub.js index 4c51f5a10e..2eb53d0471 100644 --- a/packages/events/EventPluginHub.js +++ b/packages/events/EventPluginHub.js @@ -8,8 +8,15 @@ import ReactErrorUtils from 'shared/ReactErrorUtils'; import invariant from 'fbjs/lib/invariant'; -import EventPluginRegistry from './EventPluginRegistry'; -import EventPluginUtils from './EventPluginUtils'; +import { + injectEventPluginOrder, + injectEventPluginsByName, + plugins, +} from './EventPluginRegistry'; +import { + executeDispatchesInOrder, + getFiberCurrentPropsFromNode, +} from './EventPluginUtils'; import accumulateInto from './accumulateInto'; import forEachAccumulated from './forEachAccumulated'; @@ -28,7 +35,7 @@ var eventQueue = null; */ var executeDispatchesAndRelease = function(event, simulated) { if (event) { - EventPluginUtils.executeDispatchesInOrder(event, simulated); + executeDispatchesInOrder(event, simulated); if (!event.isPersistent()) { event.constructor.release(event); @@ -91,131 +98,127 @@ function shouldPreventMouseEvent(name, type, props) { * * @public */ -var EventPluginHub = { - /** - * Methods for injecting dependencies. - */ - injection: { - /** - * @param {array} InjectedEventPluginOrder - * @public - */ - injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder, - /** - * @param {object} injectedNamesToPlugins Map from names to plugin modules. - */ - injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName, - }, +/** + * Methods for injecting dependencies. + */ +export const injection = { + /** + * @param {array} InjectedEventPluginOrder + * @public + */ + injectEventPluginOrder, /** - * @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. + * @param {object} injectedNamesToPlugins Map from names to plugin modules. */ - getListener: function(inst, registrationName) { - var listener; - - // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not - // live here; needs to be moved to a better place soon - const stateNode = inst.stateNode; - if (!stateNode) { - // Work in progress (ex: onload events in incremental mode). - return null; - } - const props = EventPluginUtils.getFiberCurrentPropsFromNode(stateNode); - if (!props) { - // Work in progress. - return null; - } - listener = props[registrationName]; - if (shouldPreventMouseEvent(registrationName, inst.type, props)) { - return null; - } - invariant( - !listener || typeof listener === 'function', - 'Expected `%s` listener to be a function, instead got a value of `%s` type.', - registrationName, - typeof listener, - ); - return listener; - }, - - /** - * Allows registered plugins an opportunity to extract events from top-level - * native browser events. - * - * @return {*} An accumulation of synthetic events. - * @internal - */ - extractEvents: function( - topLevelType, - targetInst, - nativeEvent, - nativeEventTarget, - ) { - var events; - var plugins = EventPluginRegistry.plugins; - for (var i = 0; i < plugins.length; i++) { - // Not every plugin in the ordering may be loaded at runtime. - var possiblePlugin = plugins[i]; - if (possiblePlugin) { - var extractedEvents = possiblePlugin.extractEvents( - topLevelType, - targetInst, - nativeEvent, - nativeEventTarget, - ); - if (extractedEvents) { - events = accumulateInto(events, extractedEvents); - } - } - } - return events; - }, - - /** - * Enqueues a synthetic event that should be dispatched when - * `processEventQueue` is invoked. - * - * @param {*} events An accumulation of synthetic events. - * @internal - */ - enqueueEvents: function(events) { - if (events) { - eventQueue = accumulateInto(eventQueue, events); - } - }, - - /** - * Dispatches all synthetic events on the event queue. - * - * @internal - */ - processEventQueue: function(simulated) { - // Set `eventQueue` to null before processing it so that we can tell if more - // events get enqueued while processing. - var processingEventQueue = eventQueue; - eventQueue = null; - if (simulated) { - forEachAccumulated( - processingEventQueue, - executeDispatchesAndReleaseSimulated, - ); - } else { - forEachAccumulated( - processingEventQueue, - executeDispatchesAndReleaseTopLevel, - ); - } - invariant( - !eventQueue, - 'processEventQueue(): Additional events were enqueued while processing ' + - 'an event queue. Support for this has not yet been implemented.', - ); - // This would be a good time to rethrow if any of the event handlers threw. - ReactErrorUtils.rethrowCaughtError(); - }, + injectEventPluginsByName, }; -export default EventPluginHub; +/** + * @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. + */ +export 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 + const stateNode = inst.stateNode; + if (!stateNode) { + // Work in progress (ex: onload events in incremental mode). + return null; + } + const props = getFiberCurrentPropsFromNode(stateNode); + if (!props) { + // Work in progress. + return null; + } + listener = props[registrationName]; + if (shouldPreventMouseEvent(registrationName, inst.type, props)) { + return null; + } + invariant( + !listener || typeof listener === 'function', + 'Expected `%s` listener to be a function, instead got a value of `%s` type.', + registrationName, + typeof listener, + ); + return listener; +} + +/** + * Allows registered plugins an opportunity to extract events from top-level + * native browser events. + * + * @return {*} An accumulation of synthetic events. + * @internal + */ +export function extractEvents( + topLevelType, + targetInst, + nativeEvent, + nativeEventTarget, +) { + var events; + for (var i = 0; i < plugins.length; i++) { + // Not every plugin in the ordering may be loaded at runtime. + var possiblePlugin = plugins[i]; + if (possiblePlugin) { + var extractedEvents = possiblePlugin.extractEvents( + topLevelType, + targetInst, + nativeEvent, + nativeEventTarget, + ); + if (extractedEvents) { + events = accumulateInto(events, extractedEvents); + } + } + } + return events; +} + +/** + * Enqueues a synthetic event that should be dispatched when + * `processEventQueue` is invoked. + * + * @param {*} events An accumulation of synthetic events. + * @internal + */ +export function enqueueEvents(events) { + if (events) { + eventQueue = accumulateInto(eventQueue, events); + } +} + +/** + * Dispatches all synthetic events on the event queue. + * + * @internal + */ +export function processEventQueue(simulated) { + // Set `eventQueue` to null before processing it so that we can tell if more + // events get enqueued while processing. + var processingEventQueue = eventQueue; + eventQueue = null; + if (simulated) { + forEachAccumulated( + processingEventQueue, + executeDispatchesAndReleaseSimulated, + ); + } else { + forEachAccumulated( + processingEventQueue, + executeDispatchesAndReleaseTopLevel, + ); + } + invariant( + !eventQueue, + 'processEventQueue(): Additional events were enqueued while processing ' + + 'an event queue. Support for this has not yet been implemented.', + ); + // This would be a good time to rethrow if any of the event handlers threw. + ReactErrorUtils.rethrowCaughtError(); +} diff --git a/packages/events/EventPluginRegistry.js b/packages/events/EventPluginRegistry.js index 8c43349c09..aef6a7354e 100644 --- a/packages/events/EventPluginRegistry.js +++ b/packages/events/EventPluginRegistry.js @@ -48,7 +48,7 @@ function recomputePluginOrdering(): void { 'the plugin ordering, `%s`.', pluginName, ); - if (EventPluginRegistry.plugins[pluginIndex]) { + if (plugins[pluginIndex]) { continue; } invariant( @@ -57,7 +57,7 @@ function recomputePluginOrdering(): void { 'method, but `%s` does not.', pluginName, ); - EventPluginRegistry.plugins[pluginIndex] = pluginModule; + plugins[pluginIndex] = pluginModule; var publishedEvents = pluginModule.eventTypes; for (var eventName in publishedEvents) { invariant( @@ -88,12 +88,12 @@ function publishEventForPlugin( eventName: string, ): boolean { invariant( - !EventPluginRegistry.eventNameDispatchConfigs.hasOwnProperty(eventName), + !eventNameDispatchConfigs.hasOwnProperty(eventName), 'EventPluginHub: More than one plugin attempted to publish the same ' + 'event name, `%s`.', eventName, ); - EventPluginRegistry.eventNameDispatchConfigs[eventName] = dispatchConfig; + eventNameDispatchConfigs[eventName] = dispatchConfig; var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames; if (phasedRegistrationNames) { @@ -132,23 +132,21 @@ function publishRegistrationName( eventName: string, ): void { invariant( - !EventPluginRegistry.registrationNameModules[registrationName], + !registrationNameModules[registrationName], 'EventPluginHub: More than one plugin attempted to publish the same ' + 'registration name, `%s`.', registrationName, ); - EventPluginRegistry.registrationNameModules[registrationName] = pluginModule; - EventPluginRegistry.registrationNameDependencies[registrationName] = + registrationNameModules[registrationName] = pluginModule; + registrationNameDependencies[registrationName] = pluginModule.eventTypes[eventName].dependencies; if (__DEV__) { var lowerCasedName = registrationName.toLowerCase(); - EventPluginRegistry.possibleRegistrationNames[ - lowerCasedName - ] = registrationName; + possibleRegistrationNames[lowerCasedName] = registrationName; if (registrationName === 'onDoubleClick') { - EventPluginRegistry.possibleRegistrationNames.ondblclick = registrationName; + possibleRegistrationNames.ondblclick = registrationName; } } } @@ -158,95 +156,92 @@ function publishRegistrationName( * * @see {EventPluginHub} */ -var EventPluginRegistry = { - /** - * Ordered list of injected plugins. - */ - plugins: [], - /** - * Mapping from event name to dispatch config - */ - eventNameDispatchConfigs: {}, +/** + * Ordered list of injected plugins. + */ +export const plugins = []; - /** - * Mapping from registration name to plugin module - */ - registrationNameModules: {}, +/** + * Mapping from event name to dispatch config + */ +export const eventNameDispatchConfigs = {}; - /** - * Mapping from registration name to event name - */ - registrationNameDependencies: {}, +/** + * Mapping from registration name to plugin module + */ +export const registrationNameModules = {}; - /** - * Mapping from lowercase registration names to the properly cased version, - * used to warn in the case of missing event handlers. Available - * only in __DEV__. - * @type {Object} - */ - possibleRegistrationNames: __DEV__ ? {} : (null: any), - // Trust the developer to only use possibleRegistrationNames in __DEV__ +/** + * Mapping from registration name to event name + */ +export const registrationNameDependencies = {}; - /** - * 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} - */ - injectEventPluginOrder: function( - injectedEventPluginOrder: EventPluginOrder, - ): void { - invariant( - !eventPluginOrder, - 'EventPluginRegistry: Cannot inject event plugin ordering more than ' + - 'once. You are likely trying to load more than one copy of React.', - ); - // Clone the ordering so it cannot be dynamically mutated. - eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder); +/** + * Mapping from lowercase registration names to the properly cased version, + * used to warn in the case of missing event handlers. Available + * only in __DEV__. + * @type {Object} + */ +export const possibleRegistrationNames = __DEV__ ? {} : (null: any); +// Trust the developer to only use possibleRegistrationNames in __DEV__ + +/** + * 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} + */ +export function injectEventPluginOrder( + injectedEventPluginOrder: EventPluginOrder, +): void { + invariant( + !eventPluginOrder, + 'EventPluginRegistry: Cannot inject event plugin ordering more than ' + + 'once. You are likely trying to load more than one copy of React.', + ); + // Clone the ordering so it cannot be dynamically mutated. + eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder); + recomputePluginOrdering(); +} + +/** + * 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} + */ +export function injectEventPluginsByName( + injectedNamesToPlugins: NamesToPlugins, +): void { + var isOrderingDirty = false; + for (var pluginName in injectedNamesToPlugins) { + if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) { + continue; + } + var pluginModule = injectedNamesToPlugins[pluginName]; + if ( + !namesToPlugins.hasOwnProperty(pluginName) || + namesToPlugins[pluginName] !== pluginModule + ) { + invariant( + !namesToPlugins[pluginName], + 'EventPluginRegistry: Cannot inject two different event plugins ' + + 'using the same name, `%s`.', + pluginName, + ); + namesToPlugins[pluginName] = pluginModule; + isOrderingDirty = true; + } + } + if (isOrderingDirty) { recomputePluginOrdering(); - }, - - /** - * 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} - */ - injectEventPluginsByName: function( - injectedNamesToPlugins: NamesToPlugins, - ): void { - var isOrderingDirty = false; - for (var pluginName in injectedNamesToPlugins) { - if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) { - continue; - } - var pluginModule = injectedNamesToPlugins[pluginName]; - if ( - !namesToPlugins.hasOwnProperty(pluginName) || - namesToPlugins[pluginName] !== pluginModule - ) { - invariant( - !namesToPlugins[pluginName], - 'EventPluginRegistry: Cannot inject two different event plugins ' + - 'using the same name, `%s`.', - pluginName, - ); - namesToPlugins[pluginName] = pluginModule; - isOrderingDirty = true; - } - } - if (isOrderingDirty) { - recomputePluginOrdering(); - } - }, -}; - -export default EventPluginRegistry; + } +} diff --git a/packages/events/EventPluginUtils.js b/packages/events/EventPluginUtils.js index 199ff4c1c9..12e71021c9 100644 --- a/packages/events/EventPluginUtils.js +++ b/packages/events/EventPluginUtils.js @@ -9,23 +9,20 @@ import ReactErrorUtils from 'shared/ReactErrorUtils'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; -/** - * Injected dependencies: - */ +export let getFiberCurrentPropsFromNode = null; +export let getInstanceFromNode = null; +export let getNodeFromInstance = null; -/** - * - `ComponentTree`: [required] Module that can convert between React instances - * and actual node references. - */ -var ComponentTree; -var injection = { +export const injection = { injectComponentTree: function(Injected) { - ComponentTree = Injected; + ({ + getFiberCurrentPropsFromNode, + getInstanceFromNode, + getNodeFromInstance, + } = Injected); if (__DEV__) { warning( - Injected && - Injected.getNodeFromInstance && - Injected.getInstanceFromNode, + getNodeFromInstance && getInstanceFromNode, 'EventPluginUtils.injection.injectComponentTree(...): Injected ' + 'module is missing getNodeFromInstance or getInstanceFromNode.', ); @@ -33,7 +30,7 @@ var injection = { }, }; -function isEndish(topLevelType) { +export function isEndish(topLevelType) { return ( topLevelType === 'topMouseUp' || topLevelType === 'topTouchEnd' || @@ -41,10 +38,10 @@ function isEndish(topLevelType) { ); } -function isMoveish(topLevelType) { +export function isMoveish(topLevelType) { return topLevelType === 'topMouseMove' || topLevelType === 'topTouchMove'; } -function isStartish(topLevelType) { +export function isStartish(topLevelType) { return topLevelType === 'topMouseDown' || topLevelType === 'topTouchStart'; } @@ -80,7 +77,7 @@ if (__DEV__) { */ function executeDispatch(event, simulated, listener, inst) { var type = event.type || 'unknown-event'; - event.currentTarget = EventPluginUtils.getNodeFromInstance(inst); + event.currentTarget = getNodeFromInstance(inst); ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError( type, listener, @@ -93,7 +90,7 @@ function executeDispatch(event, simulated, listener, inst) { /** * Standard/simple iteration through an event's collected dispatches. */ -function executeDispatchesInOrder(event, simulated) { +export function executeDispatchesInOrder(event, simulated) { var dispatchListeners = event._dispatchListeners; var dispatchInstances = event._dispatchInstances; if (__DEV__) { @@ -153,7 +150,7 @@ function executeDispatchesInOrderStopAtTrueImpl(event) { /** * @see executeDispatchesInOrderStopAtTrueImpl */ -function executeDispatchesInOrderStopAtTrue(event) { +export function executeDispatchesInOrderStopAtTrue(event) { var ret = executeDispatchesInOrderStopAtTrueImpl(event); event._dispatchInstances = null; event._dispatchListeners = null; @@ -169,7 +166,7 @@ function executeDispatchesInOrderStopAtTrue(event) { * * @return {*} The return value of executing the single dispatch. */ -function executeDirectDispatch(event) { +export function executeDirectDispatch(event) { if (__DEV__) { validateEventDispatches(event); } @@ -180,7 +177,7 @@ function executeDirectDispatch(event) { 'executeDirectDispatch(...): Invalid `event`.', ); event.currentTarget = dispatchListener - ? EventPluginUtils.getNodeFromInstance(dispatchInstance) + ? getNodeFromInstance(dispatchInstance) : null; var res = dispatchListener ? dispatchListener(event) : null; event.currentTarget = null; @@ -193,34 +190,6 @@ function executeDirectDispatch(event) { * @param {SyntheticEvent} event * @return {boolean} True iff number of dispatches accumulated is greater than 0. */ -function hasDispatches(event) { +export function hasDispatches(event) { return !!event._dispatchListeners; } - -/** - * General utilities that are useful in creating custom Event Plugins. - */ -var EventPluginUtils = { - isEndish: isEndish, - isMoveish: isMoveish, - isStartish: isStartish, - - executeDirectDispatch: executeDirectDispatch, - executeDispatchesInOrder: executeDispatchesInOrder, - executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue, - hasDispatches: hasDispatches, - - getFiberCurrentPropsFromNode: function(node) { - return ComponentTree.getFiberCurrentPropsFromNode(node); - }, - getInstanceFromNode: function(node) { - return ComponentTree.getInstanceFromNode(node); - }, - getNodeFromInstance: function(node) { - return ComponentTree.getNodeFromInstance(node); - }, - - injection: injection, -}; - -export default EventPluginUtils; diff --git a/packages/events/EventPropagators.js b/packages/events/EventPropagators.js index 79c91f4895..f54440e3d3 100644 --- a/packages/events/EventPropagators.js +++ b/packages/events/EventPropagators.js @@ -12,14 +12,12 @@ import { } from 'shared/ReactTreeTraversal'; import warning from 'fbjs/lib/warning'; -import EventPluginHub from './EventPluginHub'; +import {getListener} from './EventPluginHub'; import accumulateInto from './accumulateInto'; import forEachAccumulated from './forEachAccumulated'; type PropagationPhases = 'bubbled' | 'captured'; -var getListener = EventPluginHub.getListener; - /** * Some event types have a notion of different registration names for different * "phases" of propagation. This finds listeners by a given phase. @@ -30,6 +28,16 @@ function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) { return getListener(inst, registrationName); } +/** + * 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. + */ + /** * Tags a `SyntheticEvent` with dispatched listeners. Creating this function * here, allows us to not have to bind or create functions for each event. @@ -104,38 +112,18 @@ function accumulateDirectDispatchesSingle(event) { } } -function accumulateTwoPhaseDispatches(events) { +export function accumulateTwoPhaseDispatches(events) { forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle); } -function accumulateTwoPhaseDispatchesSkipTarget(events) { +export function accumulateTwoPhaseDispatchesSkipTarget(events) { forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget); } -function accumulateEnterLeaveDispatches(leave, enter, from, to) { +export function accumulateEnterLeaveDispatches(leave, enter, from, to) { traverseEnterLeave(from, to, accumulateDispatches, leave, enter); } -function accumulateDirectDispatches(events) { +export function accumulateDirectDispatches(events) { forEachAccumulated(events, accumulateDirectDispatchesSingle); } - -/** - * 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. - * - * @constructor EventPropagators - */ -var EventPropagators = { - accumulateTwoPhaseDispatches: accumulateTwoPhaseDispatches, - accumulateTwoPhaseDispatchesSkipTarget: accumulateTwoPhaseDispatchesSkipTarget, - accumulateDirectDispatches: accumulateDirectDispatches, - accumulateEnterLeaveDispatches: accumulateEnterLeaveDispatches, -}; - -export default EventPropagators; diff --git a/packages/events/ReactControlledComponent.js b/packages/events/ReactControlledComponent.js index b604e455c7..59a7b623e3 100644 --- a/packages/events/ReactControlledComponent.js +++ b/packages/events/ReactControlledComponent.js @@ -7,7 +7,10 @@ import invariant from 'fbjs/lib/invariant'; -import EventPluginUtils from './EventPluginUtils'; +import { + getInstanceFromNode, + getFiberCurrentPropsFromNode, +} from './EventPluginUtils'; // Use to restore controlled state after a change event has fired. @@ -27,7 +30,7 @@ var restoreQueue = null; function restoreStateOfTarget(target) { // We perform this translation at the end of the event loop so that we // always receive the correct fiber here - var internalInstance = EventPluginUtils.getInstanceFromNode(target); + var internalInstance = getInstanceFromNode(target); if (!internalInstance) { // Unmounted return; @@ -38,9 +41,7 @@ function restoreStateOfTarget(target) { 'Fiber needs to be injected to handle a fiber target for controlled ' + 'events. This error is likely caused by a bug in React. Please file an issue.', ); - const props = EventPluginUtils.getFiberCurrentPropsFromNode( - internalInstance.stateNode, - ); + const props = getFiberCurrentPropsFromNode(internalInstance.stateNode); fiberHostComponent.restoreControlledState( internalInstance.stateNode, internalInstance.type, @@ -48,37 +49,33 @@ function restoreStateOfTarget(target) { ); } -var ReactControlledComponent = { - injection: ReactControlledComponentInjection, +export const injection = ReactControlledComponentInjection; - enqueueStateRestore(target) { - if (restoreTarget) { - if (restoreQueue) { - restoreQueue.push(target); - } else { - restoreQueue = [target]; - } +export function enqueueStateRestore(target) { + if (restoreTarget) { + if (restoreQueue) { + restoreQueue.push(target); } else { - restoreTarget = target; + restoreQueue = [target]; } - }, + } else { + restoreTarget = target; + } +} - restoreStateIfNeeded() { - if (!restoreTarget) { - return; +export function restoreStateIfNeeded() { + if (!restoreTarget) { + return; + } + var target = restoreTarget; + var queuedTargets = restoreQueue; + restoreTarget = null; + restoreQueue = null; + + restoreStateOfTarget(target); + if (queuedTargets) { + for (var i = 0; i < queuedTargets.length; i++) { + restoreStateOfTarget(queuedTargets[i]); } - var target = restoreTarget; - var queuedTargets = restoreQueue; - restoreTarget = null; - restoreQueue = null; - - restoreStateOfTarget(target); - if (queuedTargets) { - for (var i = 0; i < queuedTargets.length; i++) { - restoreStateOfTarget(queuedTargets[i]); - } - } - }, -}; - -export default ReactControlledComponent; + } +} diff --git a/packages/events/ReactEventEmitterMixin.js b/packages/events/ReactEventEmitterMixin.js index 302457743b..43b0e6eca2 100644 --- a/packages/events/ReactEventEmitterMixin.js +++ b/packages/events/ReactEventEmitterMixin.js @@ -5,32 +5,32 @@ * LICENSE file in the root directory of this source tree. */ -import EventPluginHub from './EventPluginHub'; +import { + enqueueEvents, + processEventQueue, + extractEvents, +} from './EventPluginHub'; function runEventQueueInBatch(events) { - EventPluginHub.enqueueEvents(events); - EventPluginHub.processEventQueue(false); + enqueueEvents(events); + processEventQueue(false); } -var ReactEventEmitterMixin = { - /** - * Streams a fired top-level event to `EventPluginHub` where plugins have the - * opportunity to create `ReactEvent`s to be dispatched. - */ - handleTopLevel: function( +/** + * Streams a fired top-level event to `EventPluginHub` where plugins have the + * opportunity to create `ReactEvent`s to be dispatched. + */ +export function handleTopLevel( + topLevelType, + targetInst, + nativeEvent, + nativeEventTarget, +) { + var events = extractEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget, - ) { - var events = EventPluginHub.extractEvents( - topLevelType, - targetInst, - nativeEvent, - nativeEventTarget, - ); - runEventQueueInBatch(events); - }, -}; - -export default ReactEventEmitterMixin; + ); + runEventQueueInBatch(events); +} diff --git a/packages/events/ReactGenericBatching.js b/packages/events/ReactGenericBatching.js index b38807ddae..42b27fd473 100644 --- a/packages/events/ReactGenericBatching.js +++ b/packages/events/ReactGenericBatching.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import ReactControlledComponent from './ReactControlledComponent'; +import {restoreStateIfNeeded} from './ReactControlledComponent'; // Used as a way to call batchedUpdates when we don't have a reference to // the renderer. Such as when we're dispatching events or if third party @@ -18,30 +18,24 @@ var fiberBatchedUpdates = function(fn, bookkeeping) { return fn(bookkeeping); }; -function batchedUpdates(fn, bookkeeping) { - // If we have Fiber loaded, we need to wrap this in a batching call so that - // Fiber can apply its default priority for this call. - return fiberBatchedUpdates(fn, bookkeeping); -} - var isNestingBatched = false; -function batchedUpdatesWithControlledComponents(fn, bookkeeping) { +export function batchedUpdates(fn, bookkeeping) { if (isNestingBatched) { // If we are currently inside another batch, we need to wait until it // fully completes before restoring state. Therefore, we add the target to // a queue of work. - return batchedUpdates(fn, bookkeeping); + return fiberBatchedUpdates(fn, bookkeeping); } isNestingBatched = true; try { - return batchedUpdates(fn, bookkeeping); + return fiberBatchedUpdates(fn, bookkeeping); } finally { // Here we wait until all updates have propagated, which is important // when using controlled components within layers: // https://github.com/facebook/react/issues/1698 // Then we restore state of any controlled component. isNestingBatched = false; - ReactControlledComponent.restoreStateIfNeeded(); + restoreStateIfNeeded(); } } @@ -51,9 +45,4 @@ var ReactGenericBatchingInjection = { }, }; -var ReactGenericBatching = { - batchedUpdates: batchedUpdatesWithControlledComponents, - injection: ReactGenericBatchingInjection, -}; - -export default ReactGenericBatching; +export const injection = ReactGenericBatchingInjection; diff --git a/packages/events/ResponderEventPlugin.js b/packages/events/ResponderEventPlugin.js index 8fecc297e0..4dfbc01d62 100644 --- a/packages/events/ResponderEventPlugin.js +++ b/packages/events/ResponderEventPlugin.js @@ -7,20 +7,24 @@ import {getLowestCommonAncestor, isAncestor} from 'shared/ReactTreeTraversal'; -import EventPluginUtils from './EventPluginUtils'; -import EventPropagators from './EventPropagators'; +import { + isStartish, + isMoveish, + isEndish, + executeDirectDispatch, + hasDispatches, + executeDispatchesInOrderStopAtTrue, + getInstanceFromNode, +} from './EventPluginUtils'; +import { + accumulateDirectDispatches, + accumulateTwoPhaseDispatches, + accumulateTwoPhaseDispatchesSkipTarget, +} from './EventPropagators'; import ResponderSyntheticEvent from './ResponderSyntheticEvent'; import ResponderTouchHistoryStore from './ResponderTouchHistoryStore'; import accumulate from './accumulate'; -var isStartish = EventPluginUtils.isStartish; -var isMoveish = EventPluginUtils.isMoveish; -var isEndish = EventPluginUtils.isEndish; -var executeDirectDispatch = EventPluginUtils.executeDirectDispatch; -var hasDispatches = EventPluginUtils.hasDispatches; -var executeDispatchesInOrderStopAtTrue = - EventPluginUtils.executeDispatchesInOrderStopAtTrue; - /** * Instance of element that should respond to touch/move types of interactions, * as indicated explicitly by relevant callbacks. @@ -340,9 +344,9 @@ function setResponderAndExtractTransfer( ); shouldSetEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; if (skipOverBubbleShouldSetFrom) { - EventPropagators.accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent); + accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent); } else { - EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent); + accumulateTwoPhaseDispatches(shouldSetEvent); } var wantsResponderInst = executeDispatchesInOrderStopAtTrue(shouldSetEvent); if (!shouldSetEvent.isPersistent()) { @@ -361,7 +365,7 @@ function setResponderAndExtractTransfer( ); grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; - EventPropagators.accumulateDirectDispatches(grantEvent); + accumulateDirectDispatches(grantEvent); var blockHostResponder = executeDirectDispatch(grantEvent) === true; if (responderInst) { var terminationRequestEvent = ResponderSyntheticEvent.getPooled( @@ -372,7 +376,7 @@ function setResponderAndExtractTransfer( ); terminationRequestEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; - EventPropagators.accumulateDirectDispatches(terminationRequestEvent); + accumulateDirectDispatches(terminationRequestEvent); var shouldSwitch = !hasDispatches(terminationRequestEvent) || executeDirectDispatch(terminationRequestEvent); @@ -388,7 +392,7 @@ function setResponderAndExtractTransfer( nativeEventTarget, ); terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; - EventPropagators.accumulateDirectDispatches(terminateEvent); + accumulateDirectDispatches(terminateEvent); extracted = accumulate(extracted, [grantEvent, terminateEvent]); changeResponder(wantsResponderInst, blockHostResponder); } else { @@ -399,7 +403,7 @@ function setResponderAndExtractTransfer( nativeEventTarget, ); rejectEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; - EventPropagators.accumulateDirectDispatches(rejectEvent); + accumulateDirectDispatches(rejectEvent); extracted = accumulate(extracted, rejectEvent); } } else { @@ -447,7 +451,7 @@ function noResponderTouches(nativeEvent) { var target = activeTouch.target; if (target !== null && target !== undefined && target !== 0) { // Is the original touch location inside of the current responder? - var targetInst = EventPluginUtils.getInstanceFromNode(target); + var targetInst = getInstanceFromNode(target); if (isAncestor(responderInst, targetInst)) { return false; } @@ -525,7 +529,7 @@ var ResponderEventPlugin = { nativeEventTarget, ); gesture.touchHistory = ResponderTouchHistoryStore.touchHistory; - EventPropagators.accumulateDirectDispatches(gesture); + accumulateDirectDispatches(gesture); extracted = accumulate(extracted, gesture); } @@ -547,7 +551,7 @@ var ResponderEventPlugin = { nativeEventTarget, ); finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; - EventPropagators.accumulateDirectDispatches(finalEvent); + accumulateDirectDispatches(finalEvent); extracted = accumulate(extracted, finalEvent); changeResponder(null); } diff --git a/packages/events/ResponderTouchHistoryStore.js b/packages/events/ResponderTouchHistoryStore.js index f1d4339deb..0c703a5970 100644 --- a/packages/events/ResponderTouchHistoryStore.js +++ b/packages/events/ResponderTouchHistoryStore.js @@ -10,9 +10,7 @@ import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; -import EventPluginUtils from './EventPluginUtils'; - -const {isEndish, isMoveish, isStartish} = EventPluginUtils; +import {isEndish, isMoveish, isStartish} from './EventPluginUtils'; /** * Tracks the position and time of each active touch by `touch.identifier`. We diff --git a/packages/events/__tests__/EventPluginRegistry-test.js b/packages/events/__tests__/EventPluginRegistry-test.js index 52dee2db01..6273e77799 100644 --- a/packages/events/__tests__/EventPluginRegistry-test.js +++ b/packages/events/__tests__/EventPluginRegistry-test.js @@ -16,7 +16,7 @@ describe('EventPluginRegistry', () => { beforeEach(() => { jest.resetModuleRegistry(); // TODO: can we express this test with only public API? - EventPluginRegistry = require('events/EventPluginRegistry').default; + EventPluginRegistry = require('events/EventPluginRegistry'); createPlugin = function(properties) { return Object.assign({extractEvents: function() {}}, properties); diff --git a/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js b/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js index b2080fbcec..647109a907 100644 --- a/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js +++ b/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js @@ -58,13 +58,12 @@ describe('ReactBrowserEventEmitter', () => { LISTENER.mockClear(); // TODO: can we express this test with only public API? - EventPluginHub = require('events/EventPluginHub').default; - EventPluginRegistry = require('events/EventPluginRegistry').default; + EventPluginHub = require('events/EventPluginHub'); + EventPluginRegistry = require('events/EventPluginRegistry'); React = require('react'); ReactDOM = require('react-dom'); - ReactDOMComponentTree = require('../client/ReactDOMComponentTree').default; - ReactBrowserEventEmitter = require('../events/ReactBrowserEventEmitter') - .default; + ReactDOMComponentTree = require('../client/ReactDOMComponentTree'); + ReactBrowserEventEmitter = require('../events/ReactBrowserEventEmitter'); ReactTestUtils = require('react-dom/test-utils'); TapEventPlugin = require('../events/TapEventPlugin').default; diff --git a/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js b/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js index d998817e7f..366cedd08d 100644 --- a/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js @@ -34,7 +34,7 @@ describe('ReactDOMComponentTree', () => { React = require('react'); ReactDOM = require('react-dom'); // TODO: can we express this test with only public API? - ReactDOMComponentTree = require('../client/ReactDOMComponentTree').default; + ReactDOMComponentTree = require('../client/ReactDOMComponentTree'); ReactDOMServer = require('react-dom/server'); }); diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js index 4b5e127c26..17494318f0 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -35,7 +35,7 @@ describe('ReactDOMServer', () => { ReactDOMServer = require('react-dom/server'); // TODO: can we express this test with only public API? - var DOMProperty = require('../shared/DOMProperty').default; + var DOMProperty = require('../shared/DOMProperty'); ROOT_ATTRIBUTE_NAME = DOMProperty.ROOT_ATTRIBUTE_NAME; }); diff --git a/packages/react-dom/src/__tests__/ReactTreeTraversal-test.js b/packages/react-dom/src/__tests__/ReactTreeTraversal-test.js index b02290587d..7afd0987d5 100644 --- a/packages/react-dom/src/__tests__/ReactTreeTraversal-test.js +++ b/packages/react-dom/src/__tests__/ReactTreeTraversal-test.js @@ -11,7 +11,7 @@ var React = require('react'); // TODO: can we express this test with only public API? -var ReactDOMComponentTree = require('../client/ReactDOMComponentTree').default; +var ReactDOMComponentTree = require('../client/ReactDOMComponentTree'); var ReactTestUtils = require('react-dom/test-utils'); /** diff --git a/packages/react-dom/src/client/DOMPropertyOperations.js b/packages/react-dom/src/client/DOMPropertyOperations.js index 434dcbf0d1..309ef65350 100644 --- a/packages/react-dom/src/client/DOMPropertyOperations.js +++ b/packages/react-dom/src/client/DOMPropertyOperations.js @@ -5,17 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import DOMProperty from '../shared/DOMProperty'; +import { + ATTRIBUTE_NAME_CHAR, + ATTRIBUTE_NAME_START_CHAR, + ID_ATTRIBUTE_NAME, + ROOT_ATTRIBUTE_NAME, + getPropertyInfo, + shouldSetAttribute, +} from '../shared/DOMProperty'; import warning from 'fbjs/lib/warning'; // isAttributeNameSafe() is currently duplicated in DOMMarkupOperations. // TODO: Find a better place for this. var VALID_ATTRIBUTE_NAME_REGEX = new RegExp( - '^[' + - DOMProperty.ATTRIBUTE_NAME_START_CHAR + - '][' + - DOMProperty.ATTRIBUTE_NAME_CHAR + - ']*$', + '^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$', ); var illegalAttributeNameCache = {}; var validatedAttributeNameCache = {}; @@ -54,11 +57,11 @@ function shouldIgnoreValue(propertyInfo, value) { */ export function setAttributeForID(node, id) { - node.setAttribute(DOMProperty.ID_ATTRIBUTE_NAME, id); + node.setAttribute(ID_ATTRIBUTE_NAME, id); } export function setAttributeForRoot(node) { - node.setAttribute(DOMProperty.ROOT_ATTRIBUTE_NAME, ''); + node.setAttribute(ROOT_ATTRIBUTE_NAME, ''); } /** @@ -68,7 +71,7 @@ export function setAttributeForRoot(node) { */ export function getValueForProperty(node, name, expected) { if (__DEV__) { - var propertyInfo = DOMProperty.getPropertyInfo(name); + var propertyInfo = getPropertyInfo(name); if (propertyInfo) { var mutationMethod = propertyInfo.mutationMethod; if (mutationMethod || propertyInfo.mustUseProperty) { @@ -151,9 +154,9 @@ export function getValueForAttribute(node, name, expected) { * @param {*} value */ export function setValueForProperty(node, name, value) { - var propertyInfo = DOMProperty.getPropertyInfo(name); + var propertyInfo = getPropertyInfo(name); - if (propertyInfo && DOMProperty.shouldSetAttribute(name, value)) { + if (propertyInfo && shouldSetAttribute(name, value)) { var mutationMethod = propertyInfo.mutationMethod; if (mutationMethod) { mutationMethod(node, value); @@ -184,7 +187,7 @@ export function setValueForProperty(node, name, value) { setValueForAttribute( node, name, - DOMProperty.shouldSetAttribute(name, value) ? value : null, + shouldSetAttribute(name, value) ? value : null, ); return; } @@ -228,7 +231,7 @@ export function deleteValueForAttribute(node, name) { * @param {string} name */ export function deleteValueForProperty(node, name) { - var propertyInfo = DOMProperty.getPropertyInfo(name); + var propertyInfo = getPropertyInfo(name); if (propertyInfo) { var mutationMethod = propertyInfo.mutationMethod; if (mutationMethod) { diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 0c113073d6..d89497a079 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -19,11 +19,11 @@ import * as ReactPortal from 'react-reconciler/src/ReactPortal'; // TODO: direct imports like some-package/src/* are bad. Fix me. import {injectInternals} from 'react-reconciler/src/ReactFiberDevToolsHook'; import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'; -import ReactGenericBatching from 'events/ReactGenericBatching'; -import ReactControlledComponent from 'events/ReactControlledComponent'; -import EventPluginHub from 'events/EventPluginHub'; -import EventPluginRegistry from 'events/EventPluginRegistry'; -import EventPropagators from 'events/EventPropagators'; +import * as ReactGenericBatching from 'events/ReactGenericBatching'; +import * as ReactControlledComponent from 'events/ReactControlledComponent'; +import * as EventPluginHub from 'events/EventPluginHub'; +import * as EventPluginRegistry from 'events/EventPluginRegistry'; +import * as EventPropagators from 'events/EventPropagators'; import * as ReactInstanceMap from 'shared/ReactInstanceMap'; import ReactFeatureFlags from 'shared/ReactFeatureFlags'; import ReactVersion from 'shared/ReactVersion'; @@ -34,12 +34,12 @@ import invariant from 'fbjs/lib/invariant'; import lowPriorityWarning from 'shared/lowPriorityWarning'; import warning from 'fbjs/lib/warning'; -import ReactDOMComponentTree from './ReactDOMComponentTree'; +import * as ReactDOMComponentTree from './ReactDOMComponentTree'; import * as ReactDOMFiberComponent from './ReactDOMFiberComponent'; import * as ReactInputSelection from './ReactInputSelection'; import validateDOMNesting from './validateDOMNesting'; -import ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter'; -import ReactDOMEventListener from '../events/ReactDOMEventListener'; +import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter'; +import * as ReactDOMEventListener from '../events/ReactDOMEventListener'; import {getChildNamespace} from '../shared/DOMNamespaces'; import { ELEMENT_NODE, @@ -48,9 +48,7 @@ import { DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, } from '../shared/HTMLNodeType'; -import DOMProperty from '../shared/DOMProperty'; - -var {ROOT_ATTRIBUTE_NAME} = DOMProperty; +import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty'; var { createElement, createTextNode, diff --git a/packages/react-dom/src/client/ReactDOMClientInjection.js b/packages/react-dom/src/client/ReactDOMClientInjection.js index 6373557a5a..cb4d2315f4 100644 --- a/packages/react-dom/src/client/ReactDOMClientInjection.js +++ b/packages/react-dom/src/client/ReactDOMClientInjection.js @@ -5,22 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import EventPluginHub from 'events/EventPluginHub'; -import EventPluginUtils from 'events/EventPluginUtils'; +import * as EventPluginHub from 'events/EventPluginHub'; +import * as EventPluginUtils from 'events/EventPluginUtils'; -import ReactDOMComponentTree from './ReactDOMComponentTree'; +import * as ReactDOMComponentTree from './ReactDOMComponentTree'; import BeforeInputEventPlugin from '../events/BeforeInputEventPlugin'; import ChangeEventPlugin from '../events/ChangeEventPlugin'; import DOMEventPluginOrder from '../events/DOMEventPluginOrder'; import EnterLeaveEventPlugin from '../events/EnterLeaveEventPlugin'; -import ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter'; -import ReactDOMEventListener from '../events/ReactDOMEventListener'; +import {handleTopLevel} from '../events/ReactBrowserEventEmitter'; +import {setHandleTopLevel} from '../events/ReactDOMEventListener'; import SelectEventPlugin from '../events/SelectEventPlugin'; import SimpleEventPlugin from '../events/SimpleEventPlugin'; -ReactDOMEventListener.setHandleTopLevel( - ReactBrowserEventEmitter.handleTopLevel, -); +setHandleTopLevel(handleTopLevel); /** * Inject modules for resolving DOM hierarchy and plugin ordering. diff --git a/packages/react-dom/src/client/ReactDOMComponentTree.js b/packages/react-dom/src/client/ReactDOMComponentTree.js index f4e9a969d9..687b510101 100644 --- a/packages/react-dom/src/client/ReactDOMComponentTree.js +++ b/packages/react-dom/src/client/ReactDOMComponentTree.js @@ -12,7 +12,7 @@ var randomKey = Math.random().toString(36).slice(2); var internalInstanceKey = '__reactInternalInstance$' + randomKey; var internalEventHandlersKey = '__reactEventHandlers$' + randomKey; -function precacheFiberNode(hostInst, node) { +export function precacheFiberNode(hostInst, node) { node[internalInstanceKey] = hostInst; } @@ -20,7 +20,7 @@ function precacheFiberNode(hostInst, node) { * Given a DOM node, return the closest ReactDOMComponent or * ReactDOMTextComponent instance ancestor. */ -function getClosestInstanceFromNode(node) { +export function getClosestInstanceFromNode(node) { if (node[internalInstanceKey]) { return node[internalInstanceKey]; } @@ -55,7 +55,7 @@ function getClosestInstanceFromNode(node) { * Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent * instance, or null if the node was not rendered by this React. */ -function getInstanceFromNode(node) { +export function getInstanceFromNode(node) { var inst = node[internalInstanceKey]; if (inst) { if (inst.tag === HostComponent || inst.tag === HostText) { @@ -71,7 +71,7 @@ function getInstanceFromNode(node) { * Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding * DOM node. */ -function getNodeFromInstance(inst) { +export function getNodeFromInstance(inst) { if (inst.tag === HostComponent || inst.tag === HostText) { // In Fiber this, is just the state node right now. We assume it will be // a host component or host text. @@ -83,21 +83,10 @@ function getNodeFromInstance(inst) { invariant(false, 'getNodeFromInstance: Invalid argument.'); } -function getFiberCurrentPropsFromNode(node) { +export function getFiberCurrentPropsFromNode(node) { return node[internalEventHandlersKey] || null; } -function updateFiberProps(node, props) { +export function updateFiberProps(node, props) { node[internalEventHandlersKey] = props; } - -var ReactDOMComponentTree = { - getClosestInstanceFromNode: getClosestInstanceFromNode, - getInstanceFromNode: getInstanceFromNode, - getNodeFromInstance: getNodeFromInstance, - precacheFiberNode: precacheFiberNode, - getFiberCurrentPropsFromNode, - updateFiberProps, -}; - -export default ReactDOMComponentTree; diff --git a/packages/react-dom/src/client/ReactDOMFB.js b/packages/react-dom/src/client/ReactDOMFB.js index 69a6b11cbd..36688af625 100644 --- a/packages/react-dom/src/client/ReactDOMFB.js +++ b/packages/react-dom/src/client/ReactDOMFB.js @@ -15,8 +15,8 @@ import * as ReactFiberErrorLogger import ReactErrorUtils from 'shared/ReactErrorUtils'; import ReactDOM from './ReactDOM'; -import ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter'; -import ReactDOMComponentTree from './ReactDOMComponentTree'; +import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter'; +import * as ReactDOMComponentTree from './ReactDOMComponentTree'; import TapEventPlugin from '../events/TapEventPlugin'; Object.assign( diff --git a/packages/react-dom/src/client/ReactDOMFiberComponent.js b/packages/react-dom/src/client/ReactDOMFiberComponent.js index b20d99aedf..7919058472 100644 --- a/packages/react-dom/src/client/ReactDOMFiberComponent.js +++ b/packages/react-dom/src/client/ReactDOMFiberComponent.js @@ -10,7 +10,7 @@ // TODO: direct imports like some-package/src/* are bad. Fix me. import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber'; -import EventPluginRegistry from 'events/EventPluginRegistry'; +import {registrationNameModules} from 'events/EventPluginRegistry'; import emptyFunction from 'fbjs/lib/emptyFunction'; import warning from 'fbjs/lib/warning'; @@ -22,10 +22,10 @@ import * as ReactDOMFiberTextarea from './ReactDOMFiberTextarea'; import * as inputValueTracking from './inputValueTracking'; import setInnerHTML from './setInnerHTML'; import setTextContent from './setTextContent'; -import ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter'; +import {listenTo, trapBubbledEvent} from '../events/ReactBrowserEventEmitter'; import * as CSSPropertyOperations from '../shared/CSSPropertyOperations'; import {Namespaces, getIntrinsicNamespace} from '../shared/DOMNamespaces'; -import DOMProperty from '../shared/DOMProperty'; +import {getPropertyInfo, shouldSetAttribute} from '../shared/DOMProperty'; import assertValidProps from '../shared/assertValidProps'; import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} from '../shared/HTMLNodeType'; import isCustomComponent from '../shared/isCustomComponent'; @@ -46,9 +46,6 @@ var { var didWarnInvalidHydration = false; var didWarnShadyDOM = false; -var listenTo = ReactBrowserEventEmitter.listenTo; -var registrationNameModules = EventPluginRegistry.registrationNameModules; - var DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML'; var SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning'; var SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning'; @@ -467,7 +464,7 @@ export function setInitialProperties( switch (tag) { case 'iframe': case 'object': - ReactBrowserEventEmitter.trapBubbledEvent('topLoad', 'load', domElement); + trapBubbledEvent('topLoad', 'load', domElement); props = rawProps; break; case 'video': @@ -475,62 +472,34 @@ export function setInitialProperties( // Create listener for each media event for (var event in mediaEvents) { if (mediaEvents.hasOwnProperty(event)) { - ReactBrowserEventEmitter.trapBubbledEvent( - event, - mediaEvents[event], - domElement, - ); + trapBubbledEvent(event, mediaEvents[event], domElement); } } props = rawProps; break; case 'source': - ReactBrowserEventEmitter.trapBubbledEvent( - 'topError', - 'error', - domElement, - ); + trapBubbledEvent('topError', 'error', domElement); props = rawProps; break; case 'img': case 'image': - ReactBrowserEventEmitter.trapBubbledEvent( - 'topError', - 'error', - domElement, - ); - ReactBrowserEventEmitter.trapBubbledEvent('topLoad', 'load', domElement); + trapBubbledEvent('topError', 'error', domElement); + trapBubbledEvent('topLoad', 'load', domElement); props = rawProps; break; case 'form': - ReactBrowserEventEmitter.trapBubbledEvent( - 'topReset', - 'reset', - domElement, - ); - ReactBrowserEventEmitter.trapBubbledEvent( - 'topSubmit', - 'submit', - domElement, - ); + trapBubbledEvent('topReset', 'reset', domElement); + trapBubbledEvent('topSubmit', 'submit', domElement); props = rawProps; break; case 'details': - ReactBrowserEventEmitter.trapBubbledEvent( - 'topToggle', - 'toggle', - domElement, - ); + trapBubbledEvent('topToggle', 'toggle', domElement); props = rawProps; break; case 'input': ReactDOMFiberInput.initWrapperState(domElement, rawProps); props = ReactDOMFiberInput.getHostProps(domElement, rawProps); - ReactBrowserEventEmitter.trapBubbledEvent( - 'topInvalid', - 'invalid', - domElement, - ); + trapBubbledEvent('topInvalid', 'invalid', domElement); // For controlled components we always need to ensure we're listening // to onChange. Even if there is no listener. ensureListeningTo(rootContainerElement, 'onChange'); @@ -542,11 +511,7 @@ export function setInitialProperties( case 'select': ReactDOMFiberSelect.initWrapperState(domElement, rawProps); props = ReactDOMFiberSelect.getHostProps(domElement, rawProps); - ReactBrowserEventEmitter.trapBubbledEvent( - 'topInvalid', - 'invalid', - domElement, - ); + trapBubbledEvent('topInvalid', 'invalid', domElement); // For controlled components we always need to ensure we're listening // to onChange. Even if there is no listener. ensureListeningTo(rootContainerElement, 'onChange'); @@ -554,11 +519,7 @@ export function setInitialProperties( case 'textarea': ReactDOMFiberTextarea.initWrapperState(domElement, rawProps); props = ReactDOMFiberTextarea.getHostProps(domElement, rawProps); - ReactBrowserEventEmitter.trapBubbledEvent( - 'topInvalid', - 'invalid', - domElement, - ); + trapBubbledEvent('topInvalid', 'invalid', domElement); // For controlled components we always need to ensure we're listening // to onChange. Even if there is no listener. ensureListeningTo(rootContainerElement, 'onChange'); @@ -871,63 +832,35 @@ export function diffHydratedProperties( switch (tag) { case 'iframe': case 'object': - ReactBrowserEventEmitter.trapBubbledEvent('topLoad', 'load', domElement); + trapBubbledEvent('topLoad', 'load', domElement); break; case 'video': case 'audio': // Create listener for each media event for (var event in mediaEvents) { if (mediaEvents.hasOwnProperty(event)) { - ReactBrowserEventEmitter.trapBubbledEvent( - event, - mediaEvents[event], - domElement, - ); + trapBubbledEvent(event, mediaEvents[event], domElement); } } break; case 'source': - ReactBrowserEventEmitter.trapBubbledEvent( - 'topError', - 'error', - domElement, - ); + trapBubbledEvent('topError', 'error', domElement); break; case 'img': case 'image': - ReactBrowserEventEmitter.trapBubbledEvent( - 'topError', - 'error', - domElement, - ); - ReactBrowserEventEmitter.trapBubbledEvent('topLoad', 'load', domElement); + trapBubbledEvent('topError', 'error', domElement); + trapBubbledEvent('topLoad', 'load', domElement); break; case 'form': - ReactBrowserEventEmitter.trapBubbledEvent( - 'topReset', - 'reset', - domElement, - ); - ReactBrowserEventEmitter.trapBubbledEvent( - 'topSubmit', - 'submit', - domElement, - ); + trapBubbledEvent('topReset', 'reset', domElement); + trapBubbledEvent('topSubmit', 'submit', domElement); break; case 'details': - ReactBrowserEventEmitter.trapBubbledEvent( - 'topToggle', - 'toggle', - domElement, - ); + trapBubbledEvent('topToggle', 'toggle', domElement); break; case 'input': ReactDOMFiberInput.initWrapperState(domElement, rawProps); - ReactBrowserEventEmitter.trapBubbledEvent( - 'topInvalid', - 'invalid', - domElement, - ); + trapBubbledEvent('topInvalid', 'invalid', domElement); // For controlled components we always need to ensure we're listening // to onChange. Even if there is no listener. ensureListeningTo(rootContainerElement, 'onChange'); @@ -937,22 +870,14 @@ export function diffHydratedProperties( break; case 'select': ReactDOMFiberSelect.initWrapperState(domElement, rawProps); - ReactBrowserEventEmitter.trapBubbledEvent( - 'topInvalid', - 'invalid', - domElement, - ); + trapBubbledEvent('topInvalid', 'invalid', domElement); // For controlled components we always need to ensure we're listening // to onChange. Even if there is no listener. ensureListeningTo(rootContainerElement, 'onChange'); break; case 'textarea': ReactDOMFiberTextarea.initWrapperState(domElement, rawProps); - ReactBrowserEventEmitter.trapBubbledEvent( - 'topInvalid', - 'invalid', - domElement, - ); + trapBubbledEvent('topInvalid', 'invalid', domElement); // For controlled components we always need to ensure we're listening // to onChange. Even if there is no listener. ensureListeningTo(rootContainerElement, 'onChange'); @@ -1069,8 +994,8 @@ export function diffHydratedProperties( if (nextProp !== serverValue) { warnForPropDifference(propKey, serverValue, nextProp); } - } else if (DOMProperty.shouldSetAttribute(propKey, nextProp)) { - if ((propertyInfo = DOMProperty.getPropertyInfo(propKey))) { + } else if (shouldSetAttribute(propKey, nextProp)) { + if ((propertyInfo = getPropertyInfo(propKey))) { // $FlowFixMe - Should be inferred as not undefined. extraAttributeNames.delete(propertyInfo.attributeName); serverValue = DOMPropertyOperations.getValueForProperty( diff --git a/packages/react-dom/src/client/ReactDOMFiberInput.js b/packages/react-dom/src/client/ReactDOMFiberInput.js index 8715db9e19..bad8f1645f 100644 --- a/packages/react-dom/src/client/ReactDOMFiberInput.js +++ b/packages/react-dom/src/client/ReactDOMFiberInput.js @@ -14,7 +14,7 @@ import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; import * as DOMPropertyOperations from './DOMPropertyOperations'; -import ReactDOMComponentTree from './ReactDOMComponentTree'; +import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree'; import ReactControlledValuePropTypes from '../shared/ReactControlledValuePropTypes'; @@ -317,9 +317,7 @@ function updateNamedCousins(rootNode, props) { // and the same name are rendered into the same form (same as #1939). // That's probably okay; we don't support it just as we don't support // mixing React radio buttons with non-React ones. - var otherProps = ReactDOMComponentTree.getFiberCurrentPropsFromNode( - otherNode, - ); + var otherProps = getFiberCurrentPropsFromNode(otherNode); invariant( otherProps, 'ReactDOMInput: Mixing React and non-React radio inputs with the ' + diff --git a/packages/react-dom/src/events/BeforeInputEventPlugin.js b/packages/react-dom/src/events/BeforeInputEventPlugin.js index 2488652491..1a068a5d13 100644 --- a/packages/react-dom/src/events/BeforeInputEventPlugin.js +++ b/packages/react-dom/src/events/BeforeInputEventPlugin.js @@ -7,10 +7,10 @@ import type {TopLevelTypes} from './BrowserEventConstants'; -import EventPropagators from 'events/EventPropagators'; +import {accumulateTwoPhaseDispatches} from 'events/EventPropagators'; import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'; -import FallbackCompositionState from './FallbackCompositionState'; +import * as FallbackCompositionState from './FallbackCompositionState'; import SyntheticCompositionEvent from './SyntheticCompositionEvent'; import SyntheticInputEvent from './SyntheticInputEvent'; @@ -263,7 +263,7 @@ function extractCompositionEvent( } } - EventPropagators.accumulateTwoPhaseDispatches(event); + accumulateTwoPhaseDispatches(event); return event; } @@ -422,7 +422,7 @@ function extractBeforeInputEvent( ); event.data = chars; - EventPropagators.accumulateTwoPhaseDispatches(event); + accumulateTwoPhaseDispatches(event); return event; } diff --git a/packages/react-dom/src/events/ChangeEventPlugin.js b/packages/react-dom/src/events/ChangeEventPlugin.js index 7df69980e6..7ab525f2e0 100644 --- a/packages/react-dom/src/events/ChangeEventPlugin.js +++ b/packages/react-dom/src/events/ChangeEventPlugin.js @@ -5,17 +5,17 @@ * LICENSE file in the root directory of this source tree. */ -import EventPluginHub from 'events/EventPluginHub'; -import EventPropagators from 'events/EventPropagators'; -import ReactControlledComponent from 'events/ReactControlledComponent'; -import ReactGenericBatching from 'events/ReactGenericBatching'; +import {enqueueEvents, processEventQueue} from 'events/EventPluginHub'; +import {accumulateTwoPhaseDispatches} from 'events/EventPropagators'; +import {enqueueStateRestore} from 'events/ReactControlledComponent'; +import {batchedUpdates} from 'events/ReactGenericBatching'; import SyntheticEvent from 'events/SyntheticEvent'; import isTextInputElement from 'shared/isTextInputElement'; import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'; import getEventTarget from './getEventTarget'; import isEventSupported from './isEventSupported'; -import ReactDOMComponentTree from '../client/ReactDOMComponentTree'; +import {getNodeFromInstance} from '../client/ReactDOMComponentTree'; import * as inputValueTracking from '../client/inputValueTracking'; var eventTypes = { @@ -46,8 +46,8 @@ function createAndAccumulateChangeEvent(inst, nativeEvent, target) { ); event.type = 'change'; // Flag this event loop as needing state restore. - ReactControlledComponent.enqueueStateRestore(target); - EventPropagators.accumulateTwoPhaseDispatches(event); + enqueueStateRestore(target); + accumulateTwoPhaseDispatches(event); return event; } /** @@ -84,16 +84,16 @@ function manualDispatchChangeEvent(nativeEvent) { // components don't work properly in conjunction with event bubbling because // the component is rerendered and the value reverted before all the event // handlers can run. See https://github.com/facebook/react/issues/708. - ReactGenericBatching.batchedUpdates(runEventInBatch, event); + batchedUpdates(runEventInBatch, event); } function runEventInBatch(event) { - EventPluginHub.enqueueEvents(event); - EventPluginHub.processEventQueue(false); + enqueueEvents(event); + processEventQueue(false); } function getInstIfValueChanged(targetInst) { - const targetNode = ReactDOMComponentTree.getNodeFromInstance(targetInst); + const targetNode = getNodeFromInstance(targetInst); if (inputValueTracking.updateValueIfChanged(targetNode)) { return targetInst; } @@ -262,9 +262,7 @@ var ChangeEventPlugin = { nativeEvent, nativeEventTarget, ) { - var targetNode = targetInst - ? ReactDOMComponentTree.getNodeFromInstance(targetInst) - : window; + var targetNode = targetInst ? getNodeFromInstance(targetInst) : window; var getTargetInstFunc, handleEventFunc; if (shouldUseChangeEvent(targetNode)) { diff --git a/packages/react-dom/src/events/EnterLeaveEventPlugin.js b/packages/react-dom/src/events/EnterLeaveEventPlugin.js index 4bfe5dab76..f93bea1d8e 100644 --- a/packages/react-dom/src/events/EnterLeaveEventPlugin.js +++ b/packages/react-dom/src/events/EnterLeaveEventPlugin.js @@ -5,10 +5,13 @@ * LICENSE file in the root directory of this source tree. */ -import EventPropagators from 'events/EventPropagators'; +import {accumulateEnterLeaveDispatches} from 'events/EventPropagators'; import SyntheticMouseEvent from './SyntheticMouseEvent'; -import ReactDOMComponentTree from '../client/ReactDOMComponentTree'; +import { + getClosestInstanceFromNode, + getNodeFromInstance, +} from '../client/ReactDOMComponentTree'; var eventTypes = { mouseEnter: { @@ -67,9 +70,7 @@ var EnterLeaveEventPlugin = { if (topLevelType === 'topMouseOut') { from = targetInst; var related = nativeEvent.relatedTarget || nativeEvent.toElement; - to = related - ? ReactDOMComponentTree.getClosestInstanceFromNode(related) - : null; + to = related ? getClosestInstanceFromNode(related) : null; } else { // Moving to a node from outside the window. from = null; @@ -81,12 +82,8 @@ var EnterLeaveEventPlugin = { return null; } - var fromNode = from == null - ? win - : ReactDOMComponentTree.getNodeFromInstance(from); - var toNode = to == null - ? win - : ReactDOMComponentTree.getNodeFromInstance(to); + var fromNode = from == null ? win : getNodeFromInstance(from); + var toNode = to == null ? win : getNodeFromInstance(to); var leave = SyntheticMouseEvent.getPooled( eventTypes.mouseLeave, @@ -108,7 +105,7 @@ var EnterLeaveEventPlugin = { enter.target = toNode; enter.relatedTarget = fromNode; - EventPropagators.accumulateEnterLeaveDispatches(leave, enter, from, to); + accumulateEnterLeaveDispatches(leave, enter, from, to); return [leave, enter]; }, diff --git a/packages/react-dom/src/events/FallbackCompositionState.js b/packages/react-dom/src/events/FallbackCompositionState.js index 79985f724a..eecdd9058c 100644 --- a/packages/react-dom/src/events/FallbackCompositionState.js +++ b/packages/react-dom/src/events/FallbackCompositionState.js @@ -24,52 +24,51 @@ var compositionState = { _fallbackText: null, }; -var FallbackCompositionState = { - initialize(nativeEventTarget) { - compositionState._root = nativeEventTarget; - compositionState._startText = FallbackCompositionState.getText(); - return true; - }, - reset() { - compositionState._root = null; - compositionState._startText = null; - compositionState._fallbackText = null; - }, - getData() { - if (compositionState._fallbackText) { - return compositionState._fallbackText; - } +export function initialize(nativeEventTarget) { + compositionState._root = nativeEventTarget; + compositionState._startText = getText(); + return true; +} - var start; - var startValue = compositionState._startText; - var startLength = startValue.length; - var end; - var endValue = FallbackCompositionState.getText(); - var endLength = endValue.length; +export function reset() { + compositionState._root = null; + compositionState._startText = null; + compositionState._fallbackText = null; +} - for (start = 0; start < startLength; start++) { - if (startValue[start] !== endValue[start]) { - break; - } - } - - var minEnd = startLength - start; - for (end = 1; end <= minEnd; end++) { - if (startValue[startLength - end] !== endValue[endLength - end]) { - break; - } - } - - var sliceTail = end > 1 ? 1 - end : undefined; - compositionState._fallbackText = endValue.slice(start, sliceTail); +export function getData() { + if (compositionState._fallbackText) { return compositionState._fallbackText; - }, - getText() { - if ('value' in compositionState._root) { - return compositionState._root.value; - } - return compositionState._root[getTextContentAccessor()]; - }, -}; + } -export default FallbackCompositionState; + var start; + var startValue = compositionState._startText; + var startLength = startValue.length; + var end; + var endValue = getText(); + var endLength = endValue.length; + + for (start = 0; start < startLength; start++) { + if (startValue[start] !== endValue[start]) { + break; + } + } + + var minEnd = startLength - start; + for (end = 1; end <= minEnd; end++) { + if (startValue[startLength - end] !== endValue[endLength - end]) { + break; + } + } + + var sliceTail = end > 1 ? 1 - end : undefined; + compositionState._fallbackText = endValue.slice(start, sliceTail); + return compositionState._fallbackText; +} + +export function getText() { + if ('value' in compositionState._root) { + return compositionState._root.value; + } + return compositionState._root[getTextContentAccessor()]; +} diff --git a/packages/react-dom/src/events/ReactBrowserEventEmitter.js b/packages/react-dom/src/events/ReactBrowserEventEmitter.js index 4358837995..f9a5ee52f4 100644 --- a/packages/react-dom/src/events/ReactBrowserEventEmitter.js +++ b/packages/react-dom/src/events/ReactBrowserEventEmitter.js @@ -5,13 +5,19 @@ * LICENSE file in the root directory of this source tree. */ -import EventPluginRegistry from 'events/EventPluginRegistry'; -import ReactEventEmitterMixin from 'events/ReactEventEmitterMixin'; +import {registrationNameDependencies} from 'events/EventPluginRegistry'; -import ReactDOMEventListener from './ReactDOMEventListener'; +import { + setEnabled, + isEnabled, + trapBubbledEvent, + trapCapturedEvent, +} from './ReactDOMEventListener'; import isEventSupported from './isEventSupported'; import BrowserEventConstants from './BrowserEventConstants'; +export * from 'events/ReactEventEmitterMixin'; + var {topLevelTypes} = BrowserEventConstants; /** @@ -88,153 +94,83 @@ function getListeningForDocument(mountAt) { return alreadyListeningTo[mountAt[topListenersIDKey]]; } -var ReactBrowserEventEmitter = Object.assign({}, ReactEventEmitterMixin, { - /** - * Sets whether or not any created callbacks should be enabled. - * - * @param {boolean} enabled True if callbacks should be enabled. - */ - setEnabled: function(enabled) { - if (ReactDOMEventListener) { - ReactDOMEventListener.setEnabled(enabled); - } - }, +/** + * We listen for bubbled touch events on the document object. + * + * Firefox v8.01 (and possibly others) exhibited strange behavior when + * mounting `onmousemove` events at some node that was not the document + * element. The symptoms were that if your mouse is not moving over something + * contained within that mount point (for example on the background) the + * top-level listeners for `onmousemove` won't be called. However, if you + * register the `mousemove` on the document object, then it will of course + * catch all `mousemove`s. This along with iOS quirks, justifies restricting + * top-level listeners to the document object only, at least for these + * movement types of events and possibly all events. + * + * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html + * + * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but + * they bubble to document. + * + * @param {string} registrationName Name of listener (e.g. `onClick`). + * @param {object} contentDocumentHandle Document which owns the container + */ +export function listenTo(registrationName, contentDocumentHandle) { + var mountAt = contentDocumentHandle; + var isListening = getListeningForDocument(mountAt); + var dependencies = registrationNameDependencies[registrationName]; - /** - * @return {boolean} True if callbacks are enabled. - */ - isEnabled: function() { - return !!(ReactDOMEventListener && ReactDOMEventListener.isEnabled()); - }, - - /** - * We listen for bubbled touch events on the document object. - * - * Firefox v8.01 (and possibly others) exhibited strange behavior when - * mounting `onmousemove` events at some node that was not the document - * element. The symptoms were that if your mouse is not moving over something - * contained within that mount point (for example on the background) the - * top-level listeners for `onmousemove` won't be called. However, if you - * register the `mousemove` on the document object, then it will of course - * catch all `mousemove`s. This along with iOS quirks, justifies restricting - * top-level listeners to the document object only, at least for these - * movement types of events and possibly all events. - * - * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html - * - * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but - * they bubble to document. - * - * @param {string} registrationName Name of listener (e.g. `onClick`). - * @param {object} contentDocumentHandle Document which owns the container - */ - listenTo: function(registrationName, contentDocumentHandle) { - var mountAt = contentDocumentHandle; - var isListening = getListeningForDocument(mountAt); - var dependencies = - EventPluginRegistry.registrationNameDependencies[registrationName]; - - for (var i = 0; i < dependencies.length; i++) { - var dependency = dependencies[i]; - if ( - !(isListening.hasOwnProperty(dependency) && isListening[dependency]) - ) { - if (dependency === 'topWheel') { - if (isEventSupported('wheel')) { - ReactDOMEventListener.trapBubbledEvent( - 'topWheel', - 'wheel', - mountAt, - ); - } else if (isEventSupported('mousewheel')) { - ReactDOMEventListener.trapBubbledEvent( - 'topWheel', - 'mousewheel', - mountAt, - ); - } else { - // Firefox needs to capture a different mouse scroll event. - // @see http://www.quirksmode.org/dom/events/tests/scroll.html - ReactDOMEventListener.trapBubbledEvent( - 'topWheel', - 'DOMMouseScroll', - mountAt, - ); - } - } else if (dependency === 'topScroll') { - ReactDOMEventListener.trapCapturedEvent( - 'topScroll', - 'scroll', - mountAt, - ); - } else if (dependency === 'topFocus' || dependency === 'topBlur') { - ReactDOMEventListener.trapCapturedEvent('topFocus', 'focus', mountAt); - ReactDOMEventListener.trapCapturedEvent('topBlur', 'blur', mountAt); - - // to make sure blur and focus event listeners are only attached once - isListening.topBlur = true; - isListening.topFocus = true; - } else if (dependency === 'topCancel') { - if (isEventSupported('cancel', true)) { - ReactDOMEventListener.trapCapturedEvent( - 'topCancel', - 'cancel', - mountAt, - ); - } - isListening.topCancel = true; - } else if (dependency === 'topClose') { - if (isEventSupported('close', true)) { - ReactDOMEventListener.trapCapturedEvent( - 'topClose', - 'close', - mountAt, - ); - } - isListening.topClose = true; - } else if (topLevelTypes.hasOwnProperty(dependency)) { - ReactDOMEventListener.trapBubbledEvent( - dependency, - topLevelTypes[dependency], - mountAt, - ); + for (var i = 0; i < dependencies.length; i++) { + var dependency = dependencies[i]; + if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) { + if (dependency === 'topWheel') { + if (isEventSupported('wheel')) { + trapBubbledEvent('topWheel', 'wheel', mountAt); + } else if (isEventSupported('mousewheel')) { + trapBubbledEvent('topWheel', 'mousewheel', mountAt); + } else { + // Firefox needs to capture a different mouse scroll event. + // @see http://www.quirksmode.org/dom/events/tests/scroll.html + trapBubbledEvent('topWheel', 'DOMMouseScroll', mountAt); } + } else if (dependency === 'topScroll') { + trapCapturedEvent('topScroll', 'scroll', mountAt); + } else if (dependency === 'topFocus' || dependency === 'topBlur') { + trapCapturedEvent('topFocus', 'focus', mountAt); + trapCapturedEvent('topBlur', 'blur', mountAt); - isListening[dependency] = true; + // to make sure blur and focus event listeners are only attached once + isListening.topBlur = true; + isListening.topFocus = true; + } else if (dependency === 'topCancel') { + if (isEventSupported('cancel', true)) { + trapCapturedEvent('topCancel', 'cancel', mountAt); + } + isListening.topCancel = true; + } else if (dependency === 'topClose') { + if (isEventSupported('close', true)) { + trapCapturedEvent('topClose', 'close', mountAt); + } + isListening.topClose = true; + } else if (topLevelTypes.hasOwnProperty(dependency)) { + trapBubbledEvent(dependency, topLevelTypes[dependency], mountAt); } + + isListening[dependency] = true; } - }, + } +} - isListeningToAllDependencies: function(registrationName, mountAt) { - var isListening = getListeningForDocument(mountAt); - var dependencies = - EventPluginRegistry.registrationNameDependencies[registrationName]; - for (var i = 0; i < dependencies.length; i++) { - var dependency = dependencies[i]; - if ( - !(isListening.hasOwnProperty(dependency) && isListening[dependency]) - ) { - return false; - } +export function isListeningToAllDependencies(registrationName, mountAt) { + var isListening = getListeningForDocument(mountAt); + var dependencies = registrationNameDependencies[registrationName]; + for (var i = 0; i < dependencies.length; i++) { + var dependency = dependencies[i]; + if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) { + return false; } - return true; - }, + } + return true; +} - trapBubbledEvent: function(topLevelType, handlerBaseName, handle) { - return ReactDOMEventListener.trapBubbledEvent( - topLevelType, - handlerBaseName, - handle, - ); - }, - - trapCapturedEvent: function(topLevelType, handlerBaseName, handle) { - return ReactDOMEventListener.trapCapturedEvent( - topLevelType, - handlerBaseName, - handle, - ); - }, -}); - -export default ReactBrowserEventEmitter; +export {setEnabled, isEnabled, trapBubbledEvent, trapCapturedEvent}; diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js index 4b728314f8..df136f67ec 100644 --- a/packages/react-dom/src/events/ReactDOMEventListener.js +++ b/packages/react-dom/src/events/ReactDOMEventListener.js @@ -5,13 +5,13 @@ * LICENSE file in the root directory of this source tree. */ -import ReactGenericBatching from 'events/ReactGenericBatching'; +import {batchedUpdates} from 'events/ReactGenericBatching'; import {isFiberMounted} from 'shared/ReactFiberTreeReflection'; import {HostRoot} from 'shared/ReactTypeOfWork'; import EventListener from 'fbjs/lib/EventListener'; import getEventTarget from './getEventTarget'; -import ReactDOMComponentTree from '../client/ReactDOMComponentTree'; +import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; var CALLBACK_BOOKKEEPING_POOL_SIZE = 10; var callbackBookkeepingPool = []; @@ -80,12 +80,12 @@ function handleTopLevelImpl(bookKeeping) { break; } bookKeeping.ancestors.push(ancestor); - ancestor = ReactDOMComponentTree.getClosestInstanceFromNode(root); + ancestor = getClosestInstanceFromNode(root); } while (ancestor); for (var i = 0; i < bookKeeping.ancestors.length; i++) { targetInst = bookKeeping.ancestors[i]; - ReactDOMEventListener._handleTopLevel( + _handleTopLevel( bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, @@ -94,99 +94,94 @@ function handleTopLevelImpl(bookKeeping) { } } -var ReactDOMEventListener = { - _enabled: true, - _handleTopLevel: null, +// TODO: can we stop exporting these? +export let _enabled = true; +export let _handleTopLevel: null; - setHandleTopLevel: function(handleTopLevel) { - ReactDOMEventListener._handleTopLevel = handleTopLevel; - }, +export function setHandleTopLevel(handleTopLevel) { + _handleTopLevel = handleTopLevel; +} - setEnabled: function(enabled) { - ReactDOMEventListener._enabled = !!enabled; - }, +export function setEnabled(enabled) { + _enabled = !!enabled; +} - isEnabled: function() { - return ReactDOMEventListener._enabled; - }, +export function isEnabled() { + return _enabled; +} - /** - * Traps top-level events by using event bubbling. - * - * @param {string} topLevelType Record from `BrowserEventConstants`. - * @param {string} handlerBaseName Event name (e.g. "click"). - * @param {object} element Element on which to attach listener. - * @return {?object} An object with a remove function which will forcefully - * remove the listener. - * @internal - */ - trapBubbledEvent: function(topLevelType, handlerBaseName, element) { - if (!element) { - return null; - } - return EventListener.listen( - element, - handlerBaseName, - ReactDOMEventListener.dispatchEvent.bind(null, topLevelType), - ); - }, +/** + * Traps top-level events by using event bubbling. + * + * @param {string} topLevelType Record from `BrowserEventConstants`. + * @param {string} handlerBaseName Event name (e.g. "click"). + * @param {object} element Element on which to attach listener. + * @return {?object} An object with a remove function which will forcefully + * remove the listener. + * @internal + */ +export function trapBubbledEvent(topLevelType, handlerBaseName, element) { + if (!element) { + return null; + } + return EventListener.listen( + element, + handlerBaseName, + dispatchEvent.bind(null, topLevelType), + ); +} - /** - * Traps a top-level event by using event capturing. - * - * @param {string} topLevelType Record from `BrowserEventConstants`. - * @param {string} handlerBaseName Event name (e.g. "click"). - * @param {object} element Element on which to attach listener. - * @return {?object} An object with a remove function which will forcefully - * remove the listener. - * @internal - */ - trapCapturedEvent: function(topLevelType, handlerBaseName, element) { - if (!element) { - return null; - } - return EventListener.capture( - element, - handlerBaseName, - ReactDOMEventListener.dispatchEvent.bind(null, topLevelType), - ); - }, +/** + * Traps a top-level event by using event capturing. + * + * @param {string} topLevelType Record from `BrowserEventConstants`. + * @param {string} handlerBaseName Event name (e.g. "click"). + * @param {object} element Element on which to attach listener. + * @return {?object} An object with a remove function which will forcefully + * remove the listener. + * @internal + */ +export function trapCapturedEvent(topLevelType, handlerBaseName, element) { + if (!element) { + return null; + } + return EventListener.capture( + element, + handlerBaseName, + dispatchEvent.bind(null, topLevelType), + ); +} - dispatchEvent: function(topLevelType, nativeEvent) { - if (!ReactDOMEventListener._enabled) { - return; - } +export function dispatchEvent(topLevelType, nativeEvent) { + if (!_enabled) { + return; + } - var nativeEventTarget = getEventTarget(nativeEvent); - var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode( - nativeEventTarget, - ); - if ( - targetInst !== null && - typeof targetInst.tag === 'number' && - !isFiberMounted(targetInst) - ) { - // If we get an event (ex: img onload) before committing that - // component's mount, ignore it for now (that is, treat it as if it was an - // event on a non-React tree). We might also consider queueing events and - // dispatching them after the mount. - targetInst = null; - } + var nativeEventTarget = getEventTarget(nativeEvent); + var targetInst = getClosestInstanceFromNode(nativeEventTarget); + if ( + targetInst !== null && + typeof targetInst.tag === 'number' && + !isFiberMounted(targetInst) + ) { + // If we get an event (ex: img onload) before committing that + // component's mount, ignore it for now (that is, treat it as if it was an + // event on a non-React tree). We might also consider queueing events and + // dispatching them after the mount. + targetInst = null; + } - var bookKeeping = getTopLevelCallbackBookKeeping( - topLevelType, - nativeEvent, - targetInst, - ); + var bookKeeping = getTopLevelCallbackBookKeeping( + topLevelType, + nativeEvent, + targetInst, + ); - try { - // Event queue being processed in the same cycle allows - // `preventDefault`. - ReactGenericBatching.batchedUpdates(handleTopLevelImpl, bookKeeping); - } finally { - releaseTopLevelCallbackBookKeeping(bookKeeping); - } - }, -}; - -export default ReactDOMEventListener; + try { + // Event queue being processed in the same cycle allows + // `preventDefault`. + batchedUpdates(handleTopLevelImpl, bookKeeping); + } finally { + releaseTopLevelCallbackBookKeeping(bookKeeping); + } +} diff --git a/packages/react-dom/src/events/SelectEventPlugin.js b/packages/react-dom/src/events/SelectEventPlugin.js index eca274787d..df59211990 100644 --- a/packages/react-dom/src/events/SelectEventPlugin.js +++ b/packages/react-dom/src/events/SelectEventPlugin.js @@ -5,15 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import EventPropagators from 'events/EventPropagators'; +import {accumulateTwoPhaseDispatches} from 'events/EventPropagators'; import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'; import SyntheticEvent from 'events/SyntheticEvent'; import isTextInputElement from 'shared/isTextInputElement'; import getActiveElement from 'fbjs/lib/getActiveElement'; import shallowEqual from 'fbjs/lib/shallowEqual'; -import ReactBrowserEventEmitter from './ReactBrowserEventEmitter'; -import ReactDOMComponentTree from '../client/ReactDOMComponentTree'; +import {isListeningToAllDependencies} from './ReactBrowserEventEmitter'; +import {getNodeFromInstance} from '../client/ReactDOMComponentTree'; import * as ReactInputSelection from '../client/ReactInputSelection'; import {DOCUMENT_NODE} from '../shared/HTMLNodeType'; @@ -46,11 +46,6 @@ var activeElementInst = null; var lastSelection = null; var mouseDown = false; -// Track whether all listeners exists for this plugin. If none exist, we do -// not extract events. See #3639. -var isListeningToAllDependencies = - ReactBrowserEventEmitter.isListeningToAllDependencies; - /** * Get an object which is a unique representation of the current selection. * @@ -114,7 +109,7 @@ function constructSelectEvent(nativeEvent, nativeEventTarget) { syntheticEvent.type = 'select'; syntheticEvent.target = activeElement; - EventPropagators.accumulateTwoPhaseDispatches(syntheticEvent); + accumulateTwoPhaseDispatches(syntheticEvent); return syntheticEvent; } @@ -150,13 +145,13 @@ var SelectEventPlugin = { : nativeEventTarget.nodeType === DOCUMENT_NODE ? nativeEventTarget : nativeEventTarget.ownerDocument; + // Track whether all listeners exists for this plugin. If none exist, we do + // not extract events. See #3639. if (!doc || !isListeningToAllDependencies('onSelect', doc)) { return null; } - var targetNode = targetInst - ? ReactDOMComponentTree.getNodeFromInstance(targetInst) - : window; + var targetNode = targetInst ? getNodeFromInstance(targetInst) : window; switch (topLevelType) { // Track the input node that has focus. diff --git a/packages/react-dom/src/events/SimpleEventPlugin.js b/packages/react-dom/src/events/SimpleEventPlugin.js index df6ceb1d36..a044ffb504 100644 --- a/packages/react-dom/src/events/SimpleEventPlugin.js +++ b/packages/react-dom/src/events/SimpleEventPlugin.js @@ -15,7 +15,7 @@ import type { import type {Fiber} from 'react-reconciler/src/ReactFiber'; import type {EventTypes, PluginModule} from 'events/PluginModuleType'; -import EventPropagators from 'events/EventPropagators'; +import {accumulateTwoPhaseDispatches} from 'events/EventPropagators'; import SyntheticEvent from 'events/SyntheticEvent'; import warning from 'fbjs/lib/warning'; @@ -274,7 +274,7 @@ var SimpleEventPlugin: PluginModule = { nativeEvent, nativeEventTarget, ); - EventPropagators.accumulateTwoPhaseDispatches(event); + accumulateTwoPhaseDispatches(event); return event; }, }; diff --git a/packages/react-dom/src/events/TapEventPlugin.js b/packages/react-dom/src/events/TapEventPlugin.js index 46f2254abd..8b99d5871d 100644 --- a/packages/react-dom/src/events/TapEventPlugin.js +++ b/packages/react-dom/src/events/TapEventPlugin.js @@ -7,15 +7,12 @@ * @flow */ -import EventPluginUtils from 'events/EventPluginUtils'; -import EventPropagators from 'events/EventPropagators'; +import {isStartish, isEndish} from 'events/EventPluginUtils'; +import {accumulateTwoPhaseDispatches} from 'events/EventPropagators'; import TouchEventUtils from 'fbjs/lib/TouchEventUtils'; import SyntheticUIEvent from './SyntheticUIEvent'; -var isStartish = EventPluginUtils.isStartish; -var isEndish = EventPluginUtils.isEndish; - /** * We are extending the Flow 'Touch' declaration to enable using bracket * notation to access properties. @@ -144,7 +141,7 @@ var TapEventPlugin = { startCoords.x = 0; startCoords.y = 0; } - EventPropagators.accumulateTwoPhaseDispatches(event); + accumulateTwoPhaseDispatches(event); return event; }, }; diff --git a/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js b/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js index add3f7fa45..54751f8a03 100644 --- a/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js +++ b/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js @@ -49,7 +49,7 @@ describe('BeforeInputEventPlugin', function() { this.ReactDOM = require('react-dom'); // TODO: can we express this test with only public API? - this.ReactDOMComponentTree = require('../../client/ReactDOMComponentTree').default; + this.ReactDOMComponentTree = require('../../client/ReactDOMComponentTree'); this.SyntheticCompositionEvent = require('../SyntheticCompositionEvent').default; this.SyntheticInputEvent = require('../SyntheticInputEvent').default; this.BeforeInputEventPlugin = require('../BeforeInputEventPlugin').default; diff --git a/packages/react-dom/src/events/__tests__/FallbackCompositionState-test.js b/packages/react-dom/src/events/__tests__/FallbackCompositionState-test.js index 82afb8984a..36fdbdad9d 100644 --- a/packages/react-dom/src/events/__tests__/FallbackCompositionState-test.js +++ b/packages/react-dom/src/events/__tests__/FallbackCompositionState-test.js @@ -16,7 +16,7 @@ describe('FallbackCompositionState', () => { beforeEach(() => { // TODO: can we express this test with only public API? - FallbackCompositionState = require('../FallbackCompositionState').default; + FallbackCompositionState = require('../FallbackCompositionState'); }); function getInput() { diff --git a/packages/react-dom/src/events/__tests__/SelectEventPlugin-test.js b/packages/react-dom/src/events/__tests__/SelectEventPlugin-test.js index c22ccd97d7..51795d9b48 100644 --- a/packages/react-dom/src/events/__tests__/SelectEventPlugin-test.js +++ b/packages/react-dom/src/events/__tests__/SelectEventPlugin-test.js @@ -30,8 +30,7 @@ describe('SelectEventPlugin', () => { ReactDOM = require('react-dom'); ReactTestUtils = require('react-dom/test-utils'); // TODO: can we express this test with only public API? - ReactDOMComponentTree = require('../../client/ReactDOMComponentTree') - .default; + ReactDOMComponentTree = require('../../client/ReactDOMComponentTree'); SelectEventPlugin = require('../SelectEventPlugin').default; }); diff --git a/packages/react-dom/src/server/DOMMarkupOperations.js b/packages/react-dom/src/server/DOMMarkupOperations.js index e96994bd4c..919ae9238a 100644 --- a/packages/react-dom/src/server/DOMMarkupOperations.js +++ b/packages/react-dom/src/server/DOMMarkupOperations.js @@ -5,7 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import DOMProperty from '../shared/DOMProperty'; +import { + ATTRIBUTE_NAME_CHAR, + ATTRIBUTE_NAME_START_CHAR, + ID_ATTRIBUTE_NAME, + ROOT_ATTRIBUTE_NAME, + getPropertyInfo, + shouldAttributeAcceptBooleanValue, + shouldSetAttribute, +} from '../shared/DOMProperty'; import quoteAttributeValueForBrowser from '../shared/quoteAttributeValueForBrowser'; import warning from 'fbjs/lib/warning'; @@ -13,11 +21,7 @@ import warning from 'fbjs/lib/warning'; // isAttributeNameSafe() is currently duplicated in DOMPropertyOperations. // TODO: Find a better place for this. var VALID_ATTRIBUTE_NAME_REGEX = new RegExp( - '^[' + - DOMProperty.ATTRIBUTE_NAME_START_CHAR + - '][' + - DOMProperty.ATTRIBUTE_NAME_CHAR + - ']*$', + '^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$', ); var illegalAttributeNameCache = {}; var validatedAttributeNameCache = {}; @@ -54,70 +58,65 @@ function shouldIgnoreValue(propertyInfo, value) { /** * Operations for dealing with DOM properties. */ -var DOMMarkupOperations = { - /** - * Creates markup for the ID property. - * - * @param {string} id Unescaped ID. - * @return {string} Markup string. - */ - createMarkupForID: function(id) { - return ( - DOMProperty.ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id) - ); - }, - createMarkupForRoot: function() { - return DOMProperty.ROOT_ATTRIBUTE_NAME + '=""'; - }, +/** + * Creates markup for the ID property. + * + * @param {string} id Unescaped ID. + * @return {string} Markup string. + */ +export function createMarkupForID(id) { + return ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id); +} - /** - * Creates markup for a property. - * - * @param {string} name - * @param {*} value - * @return {?string} Markup string, or null if the property was invalid. - */ - createMarkupForProperty: function(name, value) { - var propertyInfo = DOMProperty.getPropertyInfo(name); - if (propertyInfo) { - if (shouldIgnoreValue(propertyInfo, value)) { - return ''; - } - var attributeName = propertyInfo.attributeName; - if ( - propertyInfo.hasBooleanValue || - (propertyInfo.hasOverloadedBooleanValue && value === true) - ) { - return attributeName + '=""'; - } else if ( - typeof value !== 'boolean' || - DOMProperty.shouldAttributeAcceptBooleanValue(name) - ) { - return attributeName + '=' + quoteAttributeValueForBrowser(value); - } - } else if (DOMProperty.shouldSetAttribute(name, value)) { - if (value == null) { - return ''; - } - return name + '=' + quoteAttributeValueForBrowser(value); +export function createMarkupForRoot() { + return ROOT_ATTRIBUTE_NAME + '=""'; +} + +/** + * Creates markup for a property. + * + * @param {string} name + * @param {*} value + * @return {?string} Markup string, or null if the property was invalid. + */ +export function createMarkupForProperty(name, value) { + var propertyInfo = getPropertyInfo(name); + if (propertyInfo) { + if (shouldIgnoreValue(propertyInfo, value)) { + return ''; } - return null; - }, - - /** - * Creates markup for a custom property. - * - * @param {string} name - * @param {*} value - * @return {string} Markup string, or empty string if the property was invalid. - */ - createMarkupForCustomAttribute: function(name, value) { - if (!isAttributeNameSafe(name) || value == null) { + var attributeName = propertyInfo.attributeName; + if ( + propertyInfo.hasBooleanValue || + (propertyInfo.hasOverloadedBooleanValue && value === true) + ) { + return attributeName + '=""'; + } else if ( + typeof value !== 'boolean' || + shouldAttributeAcceptBooleanValue(name) + ) { + return attributeName + '=' + quoteAttributeValueForBrowser(value); + } + } else if (shouldSetAttribute(name, value)) { + if (value == null) { return ''; } return name + '=' + quoteAttributeValueForBrowser(value); - }, -}; + } + return null; +} -export default DOMMarkupOperations; +/** + * Creates markup for a custom property. + * + * @param {string} name + * @param {*} value + * @return {string} Markup string, or empty string if the property was invalid. + */ +export function createMarkupForCustomAttribute(name, value) { + if (!isAttributeNameSafe(name) || value == null) { + return ''; + } + return name + '=' + quoteAttributeValueForBrowser(value); +} diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 385094ef27..60c1f8f675 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -20,7 +20,11 @@ import checkPropTypes from 'prop-types/checkPropTypes'; import describeComponentFrame from 'shared/describeComponentFrame'; import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState'; -import DOMMarkupOperations from './DOMMarkupOperations'; +import { + createMarkupForCustomAttribute, + createMarkupForProperty, + createMarkupForRoot, +} from './DOMMarkupOperations'; import { Namespaces, getIntrinsicNamespace, @@ -319,13 +323,10 @@ function createOpenTagMarkup( var markup = null; if (isCustomComponent(tagLowercase, props)) { if (!RESERVED_PROPS.hasOwnProperty(propKey)) { - markup = DOMMarkupOperations.createMarkupForCustomAttribute( - propKey, - propValue, - ); + markup = createMarkupForCustomAttribute(propKey, propValue); } } else { - markup = DOMMarkupOperations.createMarkupForProperty(propKey, propValue); + markup = createMarkupForProperty(propKey, propValue); } if (markup) { ret += ' ' + markup; @@ -339,7 +340,7 @@ function createOpenTagMarkup( } if (isRootElement) { - ret += ' ' + DOMMarkupOperations.createMarkupForRoot(); + ret += ' ' + createMarkupForRoot(); } return ret; } diff --git a/packages/react-dom/src/shared/DOMProperty.js b/packages/react-dom/src/shared/DOMProperty.js index 78c3680d95..e39f77204f 100644 --- a/packages/react-dom/src/shared/DOMProperty.js +++ b/packages/react-dom/src/shared/DOMProperty.js @@ -68,7 +68,7 @@ var DOMPropertyInjection = { for (var propName in Properties) { invariant( - !DOMProperty.properties.hasOwnProperty(propName), + !properties.hasOwnProperty(propName), "injectDOMPropertyConfig(...): You're trying to inject DOM property " + "'%s' which has already been injected. You may be accidentally " + 'injecting the same DOM property config twice, or you may be ' + @@ -129,135 +129,114 @@ var DOMPropertyInjection = { // without case-sensitivity. This allows the whitelist to pick up // `allowfullscreen`, which should be written using the property configuration // for `allowFullscreen` - DOMProperty.properties[propName] = propertyInfo; + properties[propName] = propertyInfo; } }, }; /* eslint-disable max-len */ -var ATTRIBUTE_NAME_START_CHAR = +export const ATTRIBUTE_NAME_START_CHAR = ':A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD'; /* eslint-enable max-len */ +export const ATTRIBUTE_NAME_CHAR = + ATTRIBUTE_NAME_START_CHAR + '\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040'; + +export const ID_ATTRIBUTE_NAME = 'data-reactid'; +export const ROOT_ATTRIBUTE_NAME = 'data-reactroot'; /** - * DOMProperty exports lookup objects that can be used like functions: + * Map from property "standard name" to an object with info about how to set + * the property in the DOM. Each object contains: * - * > DOMProperty.isValid['id'] - * true - * > DOMProperty.isValid['foobar'] - * undefined - * - * Although this may be confusing, it performs better in general. - * - * @see http://jsperf.com/key-exists - * @see http://jsperf.com/key-missing + * attributeName: + * Used when rendering markup or with `*Attribute()`. + * attributeNamespace + * propertyName: + * Used on DOM node instances. (This includes properties that mutate due to + * external factors.) + * mutationMethod: + * If non-null, used instead of the property or `setAttribute()` after + * initial render. + * mustUseProperty: + * Whether the property must be accessed and mutated as an object property. + * hasBooleanValue: + * Whether the property should be removed when set to a falsey value. + * hasNumericValue: + * Whether the property must be numeric or parse as a numeric and should be + * removed when set to a falsey value. + * hasPositiveNumericValue: + * Whether the property must be positive numeric or parse as a positive + * numeric and should be removed when set to a falsey value. + * hasOverloadedBooleanValue: + * Whether the property can be used as a flag as well as with a value. + * Removed when strictly equal to false; present without a value when + * strictly equal to true; present with a value otherwise. */ -var DOMProperty = { - ID_ATTRIBUTE_NAME: 'data-reactid', - ROOT_ATTRIBUTE_NAME: 'data-reactroot', +export const properties = {}; - ATTRIBUTE_NAME_START_CHAR: ATTRIBUTE_NAME_START_CHAR, - ATTRIBUTE_NAME_CHAR: ATTRIBUTE_NAME_START_CHAR + - '\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040', - - /** - * Map from property "standard name" to an object with info about how to set - * the property in the DOM. Each object contains: - * - * attributeName: - * Used when rendering markup or with `*Attribute()`. - * attributeNamespace - * propertyName: - * Used on DOM node instances. (This includes properties that mutate due to - * external factors.) - * mutationMethod: - * If non-null, used instead of the property or `setAttribute()` after - * initial render. - * mustUseProperty: - * Whether the property must be accessed and mutated as an object property. - * hasBooleanValue: - * Whether the property should be removed when set to a falsey value. - * hasNumericValue: - * Whether the property must be numeric or parse as a numeric and should be - * removed when set to a falsey value. - * hasPositiveNumericValue: - * Whether the property must be positive numeric or parse as a positive - * numeric and should be removed when set to a falsey value. - * hasOverloadedBooleanValue: - * Whether the property can be used as a flag as well as with a value. - * Removed when strictly equal to false; present without a value when - * strictly equal to true; present with a value otherwise. - */ - properties: {}, - - /** - * Checks whether a property name is a writeable attribute. - * @method - */ - shouldSetAttribute: function(name, value) { - if (DOMProperty.isReservedProp(name)) { - return false; - } - if ( - (name[0] === 'o' || name[0] === 'O') && - (name[1] === 'n' || name[1] === 'N') && - name.length > 2 - ) { - return false; - } - if (value === null) { +/** + * Checks whether a property name is a writeable attribute. + * @method + */ +export function shouldSetAttribute(name, value) { + if (isReservedProp(name)) { + return false; + } + if ( + (name[0] === 'o' || name[0] === 'O') && + (name[1] === 'n' || name[1] === 'N') && + name.length > 2 + ) { + return false; + } + if (value === null) { + return true; + } + switch (typeof value) { + case 'boolean': + return shouldAttributeAcceptBooleanValue(name); + case 'undefined': + case 'number': + case 'string': + case 'object': return true; - } - switch (typeof value) { - case 'boolean': - return DOMProperty.shouldAttributeAcceptBooleanValue(name); - case 'undefined': - case 'number': - case 'string': - case 'object': - return true; - default: - // function, symbol - return false; - } - }, + default: + // function, symbol + return false; + } +} - getPropertyInfo(name) { - return DOMProperty.properties.hasOwnProperty(name) - ? DOMProperty.properties[name] - : null; - }, +export function getPropertyInfo(name) { + return properties.hasOwnProperty(name) ? properties[name] : null; +} - shouldAttributeAcceptBooleanValue(name) { - if (DOMProperty.isReservedProp(name)) { - return true; - } - let propertyInfo = DOMProperty.getPropertyInfo(name); - if (propertyInfo) { - return ( - propertyInfo.hasBooleanValue || - propertyInfo.hasStringBooleanValue || - propertyInfo.hasOverloadedBooleanValue - ); - } - var prefix = name.toLowerCase().slice(0, 5); - return prefix === 'data-' || prefix === 'aria-'; - }, +export function shouldAttributeAcceptBooleanValue(name) { + if (isReservedProp(name)) { + return true; + } + let propertyInfo = getPropertyInfo(name); + if (propertyInfo) { + return ( + propertyInfo.hasBooleanValue || + propertyInfo.hasStringBooleanValue || + propertyInfo.hasOverloadedBooleanValue + ); + } + var prefix = name.toLowerCase().slice(0, 5); + return prefix === 'data-' || prefix === 'aria-'; +} - /** - * Checks to see if a property name is within the list of properties - * reserved for internal React operations. These properties should - * not be set on an HTML element. - * - * @private - * @param {string} name - * @return {boolean} If the name is within reserved props - */ - isReservedProp(name) { - return RESERVED_PROPS.hasOwnProperty(name); - }, +/** + * Checks to see if a property name is within the list of properties + * reserved for internal React operations. These properties should + * not be set on an HTML element. + * + * @private + * @param {string} name + * @return {boolean} If the name is within reserved props + */ +export function isReservedProp(name) { + return RESERVED_PROPS.hasOwnProperty(name); +} - injection: DOMPropertyInjection, -}; - -export default DOMProperty; +export const injection = DOMPropertyInjection; diff --git a/packages/react-dom/src/shared/HTMLDOMPropertyConfig.js b/packages/react-dom/src/shared/HTMLDOMPropertyConfig.js index 24f639214d..d2da706a44 100644 --- a/packages/react-dom/src/shared/HTMLDOMPropertyConfig.js +++ b/packages/react-dom/src/shared/HTMLDOMPropertyConfig.js @@ -5,16 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import DOMProperty from './DOMProperty'; +import {injection} from './DOMProperty'; -var MUST_USE_PROPERTY = DOMProperty.injection.MUST_USE_PROPERTY; -var HAS_BOOLEAN_VALUE = DOMProperty.injection.HAS_BOOLEAN_VALUE; -var HAS_NUMERIC_VALUE = DOMProperty.injection.HAS_NUMERIC_VALUE; -var HAS_POSITIVE_NUMERIC_VALUE = - DOMProperty.injection.HAS_POSITIVE_NUMERIC_VALUE; -var HAS_OVERLOADED_BOOLEAN_VALUE = - DOMProperty.injection.HAS_OVERLOADED_BOOLEAN_VALUE; -var HAS_STRING_BOOLEAN_VALUE = DOMProperty.injection.HAS_STRING_BOOLEAN_VALUE; +var MUST_USE_PROPERTY = injection.MUST_USE_PROPERTY; +var HAS_BOOLEAN_VALUE = injection.HAS_BOOLEAN_VALUE; +var HAS_NUMERIC_VALUE = injection.HAS_NUMERIC_VALUE; +var HAS_POSITIVE_NUMERIC_VALUE = injection.HAS_POSITIVE_NUMERIC_VALUE; +var HAS_OVERLOADED_BOOLEAN_VALUE = injection.HAS_OVERLOADED_BOOLEAN_VALUE; +var HAS_STRING_BOOLEAN_VALUE = injection.HAS_STRING_BOOLEAN_VALUE; var HTMLDOMPropertyConfig = { // When adding attributes to this list, be sure to also add them to diff --git a/packages/react-dom/src/shared/ReactDOMInjection.js b/packages/react-dom/src/shared/ReactDOMInjection.js index 1f7d8ff988..4d0708e6d3 100644 --- a/packages/react-dom/src/shared/ReactDOMInjection.js +++ b/packages/react-dom/src/shared/ReactDOMInjection.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import DOMProperty from './DOMProperty'; +import * as DOMProperty from './DOMProperty'; import HTMLDOMPropertyConfig from './HTMLDOMPropertyConfig'; import SVGDOMPropertyConfig from './SVGDOMPropertyConfig'; diff --git a/packages/react-dom/src/shared/ReactDOMInvalidARIAHook.js b/packages/react-dom/src/shared/ReactDOMInvalidARIAHook.js index 4cddd87fb2..12e141987b 100644 --- a/packages/react-dom/src/shared/ReactDOMInvalidARIAHook.js +++ b/packages/react-dom/src/shared/ReactDOMInvalidARIAHook.js @@ -8,15 +8,13 @@ import warning from 'fbjs/lib/warning'; import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState'; -import DOMProperty from './DOMProperty'; +import {ATTRIBUTE_NAME_CHAR} from './DOMProperty'; import isCustomComponent from './isCustomComponent'; import validAriaProperties from './validAriaProperties'; var warnedProperties = {}; -var rARIA = new RegExp('^(aria)-[' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$'); -var rARIACamel = new RegExp( - '^(aria)[A-Z][' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$', -); +var rARIA = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$'); +var rARIACamel = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$'); var hasOwnProperty = Object.prototype.hasOwnProperty; diff --git a/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js b/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js index 3e69fa6cbe..7dd26a57bf 100644 --- a/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js +++ b/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js @@ -5,11 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import EventPluginRegistry from 'events/EventPluginRegistry'; +import { + registrationNameModules, + plugins, + possibleRegistrationNames, +} from 'events/EventPluginRegistry'; import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState'; import warning from 'fbjs/lib/warning'; -import DOMProperty from './DOMProperty'; +import { + ATTRIBUTE_NAME_CHAR, + isReservedProp, + shouldAttributeAcceptBooleanValue, + shouldSetAttribute, +} from './DOMProperty'; import isCustomComponent from './isCustomComponent'; import possibleStandardNames from './possibleStandardNames'; @@ -22,34 +31,29 @@ if (__DEV__) { var warnedProperties = {}; var hasOwnProperty = Object.prototype.hasOwnProperty; var EVENT_NAME_REGEX = /^on[A-Z]/; - var rARIA = new RegExp('^(aria)-[' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$'); - var rARIACamel = new RegExp( - '^(aria)[A-Z][' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$', - ); + var rARIA = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$'); + var rARIACamel = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$'); var validateProperty = function(tagName, name, value) { if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) { return true; } - if (EventPluginRegistry.registrationNameModules.hasOwnProperty(name)) { + if (registrationNameModules.hasOwnProperty(name)) { return true; } - if ( - EventPluginRegistry.plugins.length === 0 && - EVENT_NAME_REGEX.test(name) - ) { + if (plugins.length === 0 && EVENT_NAME_REGEX.test(name)) { // If no event plugins have been injected, we might be in a server environment. // Don't check events in this case. return true; } var lowerCasedName = name.toLowerCase(); - var registrationName = EventPluginRegistry.possibleRegistrationNames.hasOwnProperty( + var registrationName = possibleRegistrationNames.hasOwnProperty( lowerCasedName, ) - ? EventPluginRegistry.possibleRegistrationNames[lowerCasedName] + ? possibleRegistrationNames[lowerCasedName] : null; if (registrationName != null) { @@ -140,7 +144,7 @@ if (__DEV__) { return true; } - const isReserved = DOMProperty.isReservedProp(name); + const isReserved = isReservedProp(name); // Known attributes should match the casing specified in the property config. if (possibleStandardNames.hasOwnProperty(lowerCasedName)) { @@ -176,7 +180,7 @@ if (__DEV__) { if ( typeof value === 'boolean' && - !DOMProperty.shouldAttributeAcceptBooleanValue(name) + !shouldAttributeAcceptBooleanValue(name) ) { if (value) { warning( @@ -220,7 +224,7 @@ if (__DEV__) { } // Warn when a known attribute is a bad type - if (!DOMProperty.shouldSetAttribute(name, value)) { + if (!shouldSetAttribute(name, value)) { warnedProperties[name] = true; return false; } diff --git a/packages/react-dom/src/shared/SVGDOMPropertyConfig.js b/packages/react-dom/src/shared/SVGDOMPropertyConfig.js index 2b4f15078c..0abd29d883 100644 --- a/packages/react-dom/src/shared/SVGDOMPropertyConfig.js +++ b/packages/react-dom/src/shared/SVGDOMPropertyConfig.js @@ -5,9 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import DOMProperty from './DOMProperty'; +import {injection} from './DOMProperty'; -var {HAS_STRING_BOOLEAN_VALUE} = DOMProperty.injection; +var {HAS_STRING_BOOLEAN_VALUE} = injection; var NS = { xlink: 'http://www.w3.org/1999/xlink', diff --git a/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js b/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js index 9066de23de..763a4f38d0 100644 --- a/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js +++ b/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js @@ -6,7 +6,7 @@ */ import ReactDOM from 'react-dom'; -import EventPluginUtils from 'events/EventPluginUtils'; +import * as EventPluginUtils from 'events/EventPluginUtils'; import ResponderEventPlugin from 'events/ResponderEventPlugin'; import ResponderTouchHistoryStore from 'events/ResponderTouchHistoryStore'; diff --git a/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js b/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js index 56f2ce912d..734fa9977f 100644 --- a/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js +++ b/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js @@ -9,7 +9,10 @@ import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes'; -import EventPropagators from 'events/EventPropagators'; +import { + accumulateTwoPhaseDispatches, + accumulateDirectDispatches, +} from 'events/EventPropagators'; import SyntheticEvent from 'events/SyntheticEvent'; import invariant from 'fbjs/lib/invariant'; @@ -42,9 +45,9 @@ const ReactNativeBridgeEventPlugin = { nativeEventTarget, ); if (bubbleDispatchConfig) { - EventPropagators.accumulateTwoPhaseDispatches(event); + accumulateTwoPhaseDispatches(event); } else if (directDispatchConfig) { - EventPropagators.accumulateDirectDispatches(event); + accumulateDirectDispatches(event); } else { return null; } diff --git a/packages/react-native-renderer/src/ReactNativeComponentTree.js b/packages/react-native-renderer/src/ReactNativeComponentTree.js index 59da83a876..59406eba0f 100644 --- a/packages/react-native-renderer/src/ReactNativeComponentTree.js +++ b/packages/react-native-renderer/src/ReactNativeComponentTree.js @@ -10,11 +10,11 @@ import invariant from 'fbjs/lib/invariant'; var instanceCache = {}; var instanceProps = {}; -function precacheFiberNode(hostInst, tag) { +export function precacheFiberNode(hostInst, tag) { instanceCache[tag] = hostInst; } -function uncacheFiberNode(tag) { +export function uncacheFiberNode(tag) { delete instanceCache[tag]; delete instanceProps[tag]; } @@ -29,22 +29,16 @@ function getTagFromInstance(inst) { return tag; } -function getFiberCurrentPropsFromNode(stateNode) { +export { + getInstanceFromTag as getClosestInstanceFromNode, + getInstanceFromTag as getInstanceFromNode, + getTagFromInstance as getNodeFromInstance, +}; + +export function getFiberCurrentPropsFromNode(stateNode) { return instanceProps[stateNode._nativeTag] || null; } -function updateFiberProps(tag, props) { +export function updateFiberProps(tag, props) { instanceProps[tag] = props; } - -var ReactNativeComponentTree = { - getClosestInstanceFromNode: getInstanceFromTag, - getInstanceFromNode: getInstanceFromTag, - getNodeFromInstance: getTagFromInstance, - precacheFiberNode, - uncacheFiberNode, - getFiberCurrentPropsFromNode, - updateFiberProps, -}; - -export default ReactNativeComponentTree; diff --git a/packages/react-native-renderer/src/ReactNativeEventEmitter.js b/packages/react-native-renderer/src/ReactNativeEventEmitter.js index a639275249..2fa4ba4124 100644 --- a/packages/react-native-renderer/src/ReactNativeEventEmitter.js +++ b/packages/react-native-renderer/src/ReactNativeEventEmitter.js @@ -7,15 +7,18 @@ * @flow */ -import EventPluginHub from 'events/EventPluginHub'; -import EventPluginRegistry from 'events/EventPluginRegistry'; -import ReactEventEmitterMixin from 'events/ReactEventEmitterMixin'; -import ReactGenericBatching from 'events/ReactGenericBatching'; +import {getListener} from 'events/EventPluginHub'; +import {registrationNameModules} from 'events/EventPluginRegistry'; +import {batchedUpdates} from 'events/ReactGenericBatching'; +import {handleTopLevel} from 'events/ReactEventEmitterMixin'; import warning from 'fbjs/lib/warning'; -import ReactNativeComponentTree from './ReactNativeComponentTree'; +import {getInstanceFromNode} from './ReactNativeComponentTree'; import ReactNativeTagHandles from './ReactNativeTagHandles'; +export * from 'events/ReactEventEmitterMixin'; +export {getListener, registrationNameModules as registrationNames}; + /** * Version of `ReactBrowserEventEmitter` that works on the receiving side of a * serialized worker boundary. @@ -74,123 +77,101 @@ var removeTouchesAtIndices = function( return rippedOut; }; -var ReactNativeEventEmitter = { - ...ReactEventEmitterMixin, +/** + * Internal version of `receiveEvent` in terms of normalized (non-tag) + * `rootNodeID`. + * + * @see receiveEvent. + * + * @param {rootNodeID} rootNodeID React root node ID that event occurred on. + * @param {TopLevelType} topLevelType Top level type of event. + * @param {?object} nativeEventParam Object passed from native. + */ +export function _receiveRootNodeIDEvent( + rootNodeID: number, + topLevelType: string, + nativeEventParam: ?Object, +) { + var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT; + var inst = getInstanceFromNode(rootNodeID); + batchedUpdates(function() { + handleTopLevel(topLevelType, inst, nativeEvent, nativeEvent.target); + }); + // React Native doesn't use ReactControlledComponent but if it did, here's + // where it would do it. +} - registrationNames: EventPluginRegistry.registrationNameModules, +/** + * Publicly exposed method on module for native objc to invoke when a top + * level event is extracted. + * @param {rootNodeID} rootNodeID React root node ID that event occurred on. + * @param {TopLevelType} topLevelType Top level type of event. + * @param {object} nativeEventParam Object passed from native. + */ +export function receiveEvent( + rootNodeID: number, + topLevelType: string, + nativeEventParam: Object, +) { + _receiveRootNodeIDEvent(rootNodeID, topLevelType, nativeEventParam); +} - getListener: EventPluginHub.getListener, +/** + * Simple multi-wrapper around `receiveEvent` that is intended to receive an + * efficient representation of `Touch` objects, and other information that + * can be used to construct W3C compliant `Event` and `Touch` lists. + * + * This may create dispatch behavior that differs than web touch handling. We + * loop through each of the changed touches and receive it as a single event. + * So two `touchStart`/`touchMove`s that occur simultaneously are received as + * two separate touch event dispatches - when they arguably should be one. + * + * This implementation reuses the `Touch` objects themselves as the `Event`s + * since we dispatch an event for each touch (though that might not be spec + * compliant). The main purpose of reusing them is to save allocations. + * + * TODO: Dispatch multiple changed touches in one event. The bubble path + * could be the first common ancestor of all the `changedTouches`. + * + * One difference between this behavior and W3C spec: cancelled touches will + * not appear in `.touches`, or in any future `.touches`, though they may + * still be "actively touching the surface". + * + * Web desktop polyfills only need to construct a fake touch event with + * identifier 0, also abandoning traditional click handlers. + */ +export function receiveTouches( + eventTopLevelType: string, + touches: Array, + changedIndices: Array, +) { + var changedTouches = eventTopLevelType === 'topTouchEnd' || + eventTopLevelType === 'topTouchCancel' + ? removeTouchesAtIndices(touches, changedIndices) + : touchSubsequence(touches, changedIndices); - /** - * Internal version of `receiveEvent` in terms of normalized (non-tag) - * `rootNodeID`. - * - * @see receiveEvent. - * - * @param {rootNodeID} rootNodeID React root node ID that event occurred on. - * @param {TopLevelType} topLevelType Top level type of event. - * @param {?object} nativeEventParam Object passed from native. - */ - _receiveRootNodeIDEvent: function( - rootNodeID: number, - topLevelType: string, - nativeEventParam: ?Object, - ) { - var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT; - var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID); - ReactGenericBatching.batchedUpdates(function() { - ReactNativeEventEmitter.handleTopLevel( - topLevelType, - inst, - nativeEvent, - nativeEvent.target, - ); - }); - // React Native doesn't use ReactControlledComponent but if it did, here's - // where it would do it. - }, - - /** - * Publicly exposed method on module for native objc to invoke when a top - * level event is extracted. - * @param {rootNodeID} rootNodeID React root node ID that event occurred on. - * @param {TopLevelType} topLevelType Top level type of event. - * @param {object} nativeEventParam Object passed from native. - */ - receiveEvent: function( - rootNodeID: number, - topLevelType: string, - nativeEventParam: Object, - ) { - ReactNativeEventEmitter._receiveRootNodeIDEvent( - rootNodeID, - topLevelType, - nativeEventParam, - ); - }, - - /** - * Simple multi-wrapper around `receiveEvent` that is intended to receive an - * efficient representation of `Touch` objects, and other information that - * can be used to construct W3C compliant `Event` and `Touch` lists. - * - * This may create dispatch behavior that differs than web touch handling. We - * loop through each of the changed touches and receive it as a single event. - * So two `touchStart`/`touchMove`s that occur simultaneously are received as - * two separate touch event dispatches - when they arguably should be one. - * - * This implementation reuses the `Touch` objects themselves as the `Event`s - * since we dispatch an event for each touch (though that might not be spec - * compliant). The main purpose of reusing them is to save allocations. - * - * TODO: Dispatch multiple changed touches in one event. The bubble path - * could be the first common ancestor of all the `changedTouches`. - * - * One difference between this behavior and W3C spec: cancelled touches will - * not appear in `.touches`, or in any future `.touches`, though they may - * still be "actively touching the surface". - * - * Web desktop polyfills only need to construct a fake touch event with - * identifier 0, also abandoning traditional click handlers. - */ - receiveTouches: function( - eventTopLevelType: string, - touches: Array, - changedIndices: Array, - ) { - var changedTouches = eventTopLevelType === 'topTouchEnd' || - eventTopLevelType === 'topTouchCancel' - ? removeTouchesAtIndices(touches, changedIndices) - : touchSubsequence(touches, changedIndices); - - for (var jj = 0; jj < changedTouches.length; jj++) { - var touch = changedTouches[jj]; - // Touch objects can fulfill the role of `DOM` `Event` objects if we set - // the `changedTouches`/`touches`. This saves allocations. - touch.changedTouches = changedTouches; - touch.touches = touches; - var nativeEvent = touch; - var rootNodeID = null; - var target = nativeEvent.target; - if (target !== null && target !== undefined) { - if (target < ReactNativeTagHandles.tagsStartAt) { - if (__DEV__) { - warning( - false, - 'A view is reporting that a touch occurred on tag zero.', - ); - } - } else { - rootNodeID = target; + for (var jj = 0; jj < changedTouches.length; jj++) { + var touch = changedTouches[jj]; + // Touch objects can fulfill the role of `DOM` `Event` objects if we set + // the `changedTouches`/`touches`. This saves allocations. + touch.changedTouches = changedTouches; + touch.touches = touches; + var nativeEvent = touch; + var rootNodeID = null; + var target = nativeEvent.target; + if (target !== null && target !== undefined) { + if (target < ReactNativeTagHandles.tagsStartAt) { + if (__DEV__) { + warning( + false, + 'A view is reporting that a touch occurred on tag zero.', + ); } + } else { + rootNodeID = target; } - ReactNativeEventEmitter._receiveRootNodeIDEvent( - rootNodeID, - eventTopLevelType, - nativeEvent, - ); } - }, -}; - -export default ReactNativeEventEmitter; + // $FlowFixMe Shouldn't we *not* call it if rootNodeID is null? + _receiveRootNodeIDEvent(rootNodeID, eventTopLevelType, nativeEvent); + } +} diff --git a/packages/react-native-renderer/src/ReactNativeFiberInspector.js b/packages/react-native-renderer/src/ReactNativeFiberInspector.js index d31ab1f721..0a2d890c41 100644 --- a/packages/react-native-renderer/src/ReactNativeFiberInspector.js +++ b/packages/react-native-renderer/src/ReactNativeFiberInspector.js @@ -20,9 +20,8 @@ import invariant from 'fbjs/lib/invariant'; // Module provided by RN: import UIManager from 'UIManager'; -import ReactNativeComponentTree from './ReactNativeComponentTree'; +import {getClosestInstanceFromNode} from './ReactNativeComponentTree'; -const {getClosestInstanceFromNode} = ReactNativeComponentTree; let getInspectorDataForViewTag; if (__DEV__) { diff --git a/packages/react-native-renderer/src/ReactNativeFiberRenderer.js b/packages/react-native-renderer/src/ReactNativeFiberRenderer.js index ccc0529202..bda0389722 100644 --- a/packages/react-native-renderer/src/ReactNativeFiberRenderer.js +++ b/packages/react-native-renderer/src/ReactNativeFiberRenderer.js @@ -20,16 +20,14 @@ import deepFreezeAndThrowOnMutationInDev import * as ReactNativeViewConfigRegistry from './ReactNativeViewConfigRegistry'; import * as ReactNativeAttributePayload from './ReactNativeAttributePayload'; -import ReactNativeComponentTree from './ReactNativeComponentTree'; -import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent'; -import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling'; -import ReactNativeTagHandles from './ReactNativeTagHandles'; - -const { +import { precacheFiberNode, uncacheFiberNode, updateFiberProps, -} = ReactNativeComponentTree; +} from './ReactNativeComponentTree'; +import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent'; +import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling'; +import ReactNativeTagHandles from './ReactNativeTagHandles'; export type Container = number; export type Instance = { diff --git a/packages/react-native-renderer/src/ReactNativeInjection.js b/packages/react-native-renderer/src/ReactNativeInjection.js index c3ca7ac8d7..579525c038 100644 --- a/packages/react-native-renderer/src/ReactNativeInjection.js +++ b/packages/react-native-renderer/src/ReactNativeInjection.js @@ -16,15 +16,15 @@ // Module provided by RN: import 'InitializeCore'; -import EventPluginHub from 'events/EventPluginHub'; -import EventPluginUtils from 'events/EventPluginUtils'; +import * as EventPluginHub from 'events/EventPluginHub'; +import * as EventPluginUtils from 'events/EventPluginUtils'; import ResponderEventPlugin from 'events/ResponderEventPlugin'; // Module provided by RN: import RCTEventEmitter from 'RCTEventEmitter'; import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin'; -import ReactNativeComponentTree from './ReactNativeComponentTree'; -import ReactNativeEventEmitter from './ReactNativeEventEmitter'; +import * as ReactNativeComponentTree from './ReactNativeComponentTree'; +import * as ReactNativeEventEmitter from './ReactNativeEventEmitter'; import ReactNativeEventPluginOrder from './ReactNativeEventPluginOrder'; import ReactNativeGlobalResponderHandler from './ReactNativeGlobalResponderHandler'; diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js index 975a3bbf1b..4cfbb8a494 100644 --- a/packages/react-native-renderer/src/ReactNativeRenderer.js +++ b/packages/react-native-renderer/src/ReactNativeRenderer.js @@ -17,7 +17,7 @@ import * as ReactFiberErrorLogger from 'react-reconciler/src/ReactFiberErrorLogger'; import * as ReactPortal from 'react-reconciler/src/ReactPortal'; import {injectInternals} from 'react-reconciler/src/ReactFiberDevToolsHook'; -import ReactGenericBatching from 'events/ReactGenericBatching'; +import * as ReactGenericBatching from 'events/ReactGenericBatching'; import TouchHistoryMath from 'events/TouchHistoryMath'; import * as ReactGlobalSharedState from 'shared/ReactGlobalSharedState'; import ReactVersion from 'shared/ReactVersion'; @@ -28,7 +28,7 @@ import {showDialog} from './ReactNativeFiberErrorDialog'; import NativeMethodsMixin from './NativeMethodsMixin'; import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin'; import ReactNativeComponent from './ReactNativeComponent'; -import ReactNativeComponentTree from './ReactNativeComponentTree'; +import * as ReactNativeComponentTree from './ReactNativeComponentTree'; import ReactNativeFiberRenderer from './ReactNativeFiberRenderer'; import ReactNativePropRegistry from './ReactNativePropRegistry'; import {getInspectorDataForViewTag} from './ReactNativeFiberInspector'; diff --git a/packages/react-rt-renderer/src/ReactNativeRT.js b/packages/react-rt-renderer/src/ReactNativeRT.js index c6b89257d0..81f48bd081 100644 --- a/packages/react-rt-renderer/src/ReactNativeRT.js +++ b/packages/react-rt-renderer/src/ReactNativeRT.js @@ -18,10 +18,10 @@ import { } from 'react-native-renderer/src/ReactNativeFiberErrorDialog'; import * as ReactPortal from 'react-reconciler/src/ReactPortal'; import {injectInternals} from 'react-reconciler/src/ReactFiberDevToolsHook'; -import ReactGenericBatching from 'events/ReactGenericBatching'; +import * as ReactGenericBatching from 'events/ReactGenericBatching'; import ReactVersion from 'shared/ReactVersion'; -import ReactNativeRTComponentTree from './ReactNativeRTComponentTree'; +import {getFiberFromTag} from './ReactNativeRTComponentTree'; import ReactNativeRTFiberRenderer from './ReactNativeRTFiberRenderer'; import ReactNativeRTFiberInspector from './ReactNativeRTFiberInspector'; @@ -84,7 +84,7 @@ const ReactNativeRTFiber: ReactNativeRTType = { }; injectInternals({ - findFiberByHostInstance: ReactNativeRTComponentTree.getFiberFromTag, + findFiberByHostInstance: getFiberFromTag, findHostInstanceByFiber: ReactNativeRTFiberRenderer.findHostInstance, getInspectorDataForViewTag: ReactNativeRTFiberInspector.getInspectorDataForViewTag, // This is an enum because we may add more (e.g. profiler build) diff --git a/packages/react-rt-renderer/src/ReactNativeRTComponentTree.js b/packages/react-rt-renderer/src/ReactNativeRTComponentTree.js index cee2f95c6d..1da2c0d78e 100644 --- a/packages/react-rt-renderer/src/ReactNativeRTComponentTree.js +++ b/packages/react-rt-renderer/src/ReactNativeRTComponentTree.js @@ -12,33 +12,23 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber'; var instanceCache: {[key: number]: Fiber} = {}; var instanceProps: {[key: number]: Object} = {}; -function precacheFiberNode(fiber: Fiber, tag: number): void { +export function precacheFiberNode(fiber: Fiber, tag: number): void { instanceCache[tag] = fiber; } -function getFiberFromTag(tag: number): null | Fiber { +export function getFiberFromTag(tag: number): null | Fiber { return instanceCache[tag] || null; } -function uncacheFiberNode(tag: number): void { +export function uncacheFiberNode(tag: number): void { delete instanceCache[tag]; delete instanceProps[tag]; } -function getFiberCurrentPropsFromTag(tag: number): null | Object { +export function getFiberCurrentPropsFromTag(tag: number): null | Object { return instanceProps[tag] || null; } -function updateFiberProps(tag: number, props: Object): void { +export function updateFiberProps(tag: number, props: Object): void { instanceProps[tag] = props; } - -var ReactNativeRTComponentTree = { - precacheFiberNode, - uncacheFiberNode, - getFiberFromTag, - getFiberCurrentPropsFromTag, - updateFiberProps, -}; - -export default ReactNativeRTComponentTree; diff --git a/packages/react-rt-renderer/src/ReactNativeRTEventEmitter.js b/packages/react-rt-renderer/src/ReactNativeRTEventEmitter.js index 2f57650ecb..aa13763fda 100644 --- a/packages/react-rt-renderer/src/ReactNativeRTEventEmitter.js +++ b/packages/react-rt-renderer/src/ReactNativeRTEventEmitter.js @@ -7,11 +7,11 @@ * @flow */ -import ReactGenericBatching from 'events/ReactGenericBatching'; +import {batchedUpdates} from 'events/ReactGenericBatching'; // Module provided by RN: import BatchedBridge from 'BatchedBridge'; -import ReactNativeRTComponentTree from './ReactNativeRTComponentTree'; +import {getFiberCurrentPropsFromTag} from './ReactNativeRTComponentTree'; var ReactNativeRTEventEmitter = { /** @@ -27,7 +27,7 @@ var ReactNativeRTEventEmitter = { nativeEventParam: Object, ) { var nativeEvent = nativeEventParam; - var props = ReactNativeRTComponentTree.getFiberCurrentPropsFromTag(tag); + var props = getFiberCurrentPropsFromTag(tag); if (props == null) { return; } @@ -35,7 +35,7 @@ var ReactNativeRTEventEmitter = { if (typeof eventHandler !== 'function') { return; } - ReactGenericBatching.batchedUpdates(function() { + batchedUpdates(function() { eventHandler(nativeEvent); }); }, diff --git a/packages/react-rt-renderer/src/ReactNativeRTFiberInspector.js b/packages/react-rt-renderer/src/ReactNativeRTFiberInspector.js index 7816832cf9..ba2613bdf8 100644 --- a/packages/react-rt-renderer/src/ReactNativeRTFiberInspector.js +++ b/packages/react-rt-renderer/src/ReactNativeRTFiberInspector.js @@ -16,9 +16,8 @@ import {HostComponent} from 'shared/ReactTypeOfWork'; import emptyObject from 'fbjs/lib/emptyObject'; import invariant from 'fbjs/lib/invariant'; -import ReactNativeRTComponentTree from './ReactNativeRTComponentTree'; +import {getFiberFromTag} from './ReactNativeRTComponentTree'; -const {getFiberFromTag} = ReactNativeRTComponentTree; let getInspectorDataForViewTag; if (__DEV__) { diff --git a/packages/react-rt-renderer/src/ReactNativeRTFiberRenderer.js b/packages/react-rt-renderer/src/ReactNativeRTFiberRenderer.js index 6a23c8308d..0331bc4fef 100644 --- a/packages/react-rt-renderer/src/ReactNativeRTFiberRenderer.js +++ b/packages/react-rt-renderer/src/ReactNativeRTFiberRenderer.js @@ -13,7 +13,10 @@ import invariant from 'fbjs/lib/invariant'; // Module provided by RN: import RTManager from 'RTManager'; -import ReactNativeRTComponentTree from './ReactNativeRTComponentTree'; +import { + precacheFiberNode, + updateFiberProps, +} from './ReactNativeRTComponentTree'; import ReactNativeRTTagHandles from './ReactNativeRTTagHandles'; export type Container = number; @@ -21,8 +24,6 @@ export type Instance = number; export type Props = Object; export type TextInstance = number; -const {precacheFiberNode, updateFiberProps} = ReactNativeRTComponentTree; - function processProps(instance: number, props: Props): Object { const propsPayload = {}; for (var key in props) { diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index f1c5ac5d4d..cc778cdeb5 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -11,7 +11,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber'; import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot'; import ReactFiberReconciler from 'react-reconciler'; -import ReactGenericBatching from 'events/ReactGenericBatching'; +import {batchedUpdates} from 'events/ReactGenericBatching'; import {findCurrentFiberUsingSlowPath} from 'shared/ReactFiberTreeReflection'; import emptyObject from 'fbjs/lib/emptyObject'; import { @@ -633,7 +633,7 @@ var ReactTestRendererFiber = { }, /* eslint-disable camelcase */ - unstable_batchedUpdates: ReactGenericBatching.batchedUpdates, + unstable_batchedUpdates: batchedUpdates, /* eslint-enable camelcase */ }; diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index daf69690c6..5b4fa94cc9 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -25,28 +25,28 @@ "gzip": 6870 }, "react-dom.development.js (UMD_DEV)": { - "size": 596493, - "gzip": 134889 + "size": 589183, + "gzip": 133823 }, "react-dom.production.min.js (UMD_PROD)": { - "size": 100233, - "gzip": 31848 + "size": 96544, + "gzip": 31188 }, "react-dom.development.js (NODE_DEV)": { - "size": 577466, - "gzip": 130187 + "size": 570160, + "gzip": 129121 }, "react-dom.production.min.js (NODE_PROD)": { - "size": 98586, - "gzip": 30972 + "size": 94731, + "gzip": 30346 }, "ReactDOM-dev.js (FB_DEV)": { - "size": 578317, - "gzip": 130605 + "size": 571472, + "gzip": 129612 }, "ReactDOM-prod.js (FB_PROD)": { - "size": 412447, - "gzip": 91219 + "size": 405312, + "gzip": 90089 }, "react-dom-test-utils.development.js (NODE_DEV)": { "size": 37876, @@ -61,60 +61,60 @@ "gzip": 10182 }, "react-dom-unstable-native-dependencies.development.js (UMD_DEV)": { - "size": 86323, - "gzip": 20929 + "size": 66641, + "gzip": 16671 }, "react-dom-unstable-native-dependencies.production.min.js (UMD_PROD)": { - "size": 14157, - "gzip": 4583 + "size": 11349, + "gzip": 3900 }, "react-dom-unstable-native-dependencies.development.js (NODE_DEV)": { - "size": 81826, - "gzip": 19703 + "size": 62184, + "gzip": 15356 }, "react-dom-unstable-native-dependencies.production.min.js (NODE_PROD)": { - "size": 13707, - "gzip": 4458 + "size": 10892, + "gzip": 3771 }, "ReactDOMUnstableNativeDependencies-dev.js (FB_DEV)": { - "size": 81123, - "gzip": 19537 + "size": 61607, + "gzip": 15197 }, "ReactDOMUnstableNativeDependencies-prod.js (FB_PROD)": { - "size": 65265, - "gzip": 15254 + "size": 52139, + "gzip": 12586 }, "react-dom-server.browser.development.js (UMD_DEV)": { - "size": 107196, - "gzip": 27472 + "size": 100894, + "gzip": 26193 }, "react-dom-server.browser.production.min.js (UMD_PROD)": { - "size": 15632, - "gzip": 6077 + "size": 14713, + "gzip": 5827 }, "react-dom-server.browser.development.js (NODE_DEV)": { - "size": 96125, - "gzip": 24739 + "size": 89851, + "gzip": 23452 }, "react-dom-server.browser.production.min.js (NODE_PROD)": { - "size": 15101, - "gzip": 5920 + "size": 14188, + "gzip": 5707 }, "ReactDOMServer-dev.js (FB_DEV)": { - "size": 95444, - "gzip": 24574 + "size": 89268, + "gzip": 23281 }, "ReactDOMServer-prod.js (FB_PROD)": { - "size": 45835, - "gzip": 12001 + "size": 45175, + "gzip": 11989 }, "react-dom-server.node.development.js (NODE_DEV)": { - "size": 98291, - "gzip": 25249 + "size": 92017, + "gzip": 23960 }, "react-dom-server.node.production.min.js (NODE_PROD)": { - "size": 15925, - "gzip": 6228 + "size": 15013, + "gzip": 6016 }, "react-art.development.js (UMD_DEV)": { "size": 361363, @@ -141,40 +141,40 @@ "gzip": 43024 }, "ReactNativeRenderer-dev.js (RN_DEV)": { - "size": 268852, - "gzip": 46028 + "size": 264248, + "gzip": 45270 }, "ReactNativeRenderer-prod.js (RN_PROD)": { - "size": 206992, - "gzip": 35199 + "size": 202770, + "gzip": 34504 }, "ReactRTRenderer-dev.js (RN_DEV)": { - "size": 202141, - "gzip": 33933 + "size": 195970, + "gzip": 32882 }, "ReactRTRenderer-prod.js (RN_PROD)": { - "size": 150536, - "gzip": 24872 + "size": 143566, + "gzip": 23595 }, "ReactCSRenderer-dev.js (RN_DEV)": { - "size": 189508, - "gzip": 31550 + "size": 189548, + "gzip": 31563 }, "ReactCSRenderer-prod.js (RN_PROD)": { - "size": 139346, - "gzip": 22600 + "size": 139382, + "gzip": 22608 }, "react-test-renderer.development.js (NODE_DEV)": { - "size": 290743, - "gzip": 60340 + "size": 284199, + "gzip": 58944 }, "react-test-renderer.production.min.js (NODE_PROD)": { - "size": 50251, - "gzip": 15366 + "size": 47325, + "gzip": 14489 }, "ReactTestRenderer-dev.js (FB_DEV)": { - "size": 288988, - "gzip": 60118 + "size": 282462, + "gzip": 58722 }, "react-test-renderer-shallow.development.js (NODE_DEV)": { "size": 10657,