diff --git a/packages/legacy-events/package.json b/packages/legacy-events/package.json deleted file mode 100644 index 8460cb438a8a..000000000000 --- a/packages/legacy-events/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "private": true, - "name": "legacy-events", - "version": "0.0.0" -} diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 1fe446e32a31..2016fbe9d6c9 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -42,7 +42,7 @@ import {canUseDOM} from 'shared/ExecutionEnvironment'; import { eventNameDispatchConfigs, injectEventPluginsByName, -} from 'legacy-events/EventPluginRegistry'; +} from '../legacy-events/EventPluginRegistry'; import ReactVersion from 'shared/ReactVersion'; import invariant from 'shared/invariant'; import { diff --git a/packages/react-dom/src/client/ReactDOMClientInjection.js b/packages/react-dom/src/client/ReactDOMClientInjection.js index 507e9c462596..c61d4b072c1b 100644 --- a/packages/react-dom/src/client/ReactDOMClientInjection.js +++ b/packages/react-dom/src/client/ReactDOMClientInjection.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {setComponentTree} from 'legacy-events/EventPluginUtils'; +import {setComponentTree} from '../legacy-events/EventPluginUtils'; import { getFiberCurrentPropsFromNode, @@ -29,7 +29,7 @@ import { injectEventPluginOrder, injectEventPluginsByName, injectEventPlugins, -} from 'legacy-events/EventPluginRegistry'; +} from '../legacy-events/EventPluginRegistry'; import {enableModernEventSystem} from 'shared/ReactFeatureFlags'; if (enableModernEventSystem) { diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.js index 89663a91918b..fc16a49d7c79 100644 --- a/packages/react-dom/src/client/ReactDOMComponent.js +++ b/packages/react-dom/src/client/ReactDOMComponent.js @@ -7,7 +7,7 @@ * @flow */ -import {registrationNameModules} from 'legacy-events/EventPluginRegistry'; +import {registrationNameModules} from '../legacy-events/EventPluginRegistry'; import {canUseDOM} from 'shared/ExecutionEnvironment'; import invariant from 'shared/invariant'; import { diff --git a/packages/react-dom/src/client/ReactDOMComponentTree.js b/packages/react-dom/src/client/ReactDOMComponentTree.js index bb841b78569e..9d2822f60dfe 100644 --- a/packages/react-dom/src/client/ReactDOMComponentTree.js +++ b/packages/react-dom/src/client/ReactDOMComponentTree.js @@ -17,7 +17,7 @@ import type { SuspenseInstance, Props, } from './ReactDOMHostConfig'; -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; import { HostComponent, diff --git a/packages/react-dom/src/client/ReactDOMEventHandle.js b/packages/react-dom/src/client/ReactDOMEventHandle.js index 2c8db49d6444..b477685c176f 100644 --- a/packages/react-dom/src/client/ReactDOMEventHandle.js +++ b/packages/react-dom/src/client/ReactDOMEventHandle.js @@ -7,7 +7,7 @@ * @flow */ -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; import type {EventPriority, ReactScopeInstance} from 'shared/ReactTypes'; import type { ReactDOMEventHandle, diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index a2ba69855c61..a7039702b8de 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -7,7 +7,7 @@ * @flow */ -import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; +import type {TopLevelType} from '../legacy-events/TopLevelEventTypes'; import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; import type { BoundingRect, diff --git a/packages/react-dom/src/events/DOMEventProperties.js b/packages/react-dom/src/events/DOMEventProperties.js index 8fda91636ed1..747fd4278e92 100644 --- a/packages/react-dom/src/events/DOMEventProperties.js +++ b/packages/react-dom/src/events/DOMEventProperties.js @@ -11,11 +11,11 @@ import type {EventPriority} from 'shared/ReactTypes'; import type { TopLevelType, DOMTopLevelEventType, -} from 'legacy-events/TopLevelEventTypes'; +} from '../legacy-events/TopLevelEventTypes'; import type { DispatchConfig, CustomDispatchConfig, -} from 'legacy-events/ReactSyntheticEventType'; +} from '../legacy-events/ReactSyntheticEventType'; import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes'; import { diff --git a/packages/react-dom/src/events/DOMLegacyEventPluginSystem.js b/packages/react-dom/src/events/DOMLegacyEventPluginSystem.js index 9489bcdb4284..fde6a1c65055 100644 --- a/packages/react-dom/src/events/DOMLegacyEventPluginSystem.js +++ b/packages/react-dom/src/events/DOMLegacyEventPluginSystem.js @@ -7,15 +7,15 @@ * @flow */ -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {AnyNativeEvent} from '../legacy-events/PluginModuleType'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; import type {ElementListenerMap} from '../client/ReactDOMComponentTree'; import type {EventSystemFlags} from './EventSystemFlags'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; -import type {LegacyPluginModule} from 'legacy-events/PluginModuleType'; -import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType'; -import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; -import forEachAccumulated from 'legacy-events/forEachAccumulated'; +import type {LegacyPluginModule} from '../legacy-events/PluginModuleType'; +import type {ReactSyntheticEvent} from '../legacy-events/ReactSyntheticEventType'; +import type {TopLevelType} from '../legacy-events/TopLevelEventTypes'; +import forEachAccumulated from '../legacy-events/forEachAccumulated'; import { HostRoot, @@ -23,10 +23,10 @@ import { HostText, } from 'react-reconciler/src/ReactWorkTags'; import {IS_FIRST_ANCESTOR, PLUGIN_EVENT_SYSTEM} from './EventSystemFlags'; -import {runEventsInBatch} from 'legacy-events/EventBatching'; -import {plugins} from 'legacy-events/EventPluginRegistry'; -import accumulateInto from 'legacy-events/accumulateInto'; -import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry'; +import {runEventsInBatch} from '../legacy-events/EventBatching'; +import {plugins} from '../legacy-events/EventPluginRegistry'; +import accumulateInto from '../legacy-events/accumulateInto'; +import {registrationNameDependencies} from '../legacy-events/EventPluginRegistry'; import getEventTarget from './getEventTarget'; import { diff --git a/packages/react-dom/src/events/DOMModernPluginEventSystem.js b/packages/react-dom/src/events/DOMModernPluginEventSystem.js index 73372e81cbde..5cebb608e4af 100644 --- a/packages/react-dom/src/events/DOMModernPluginEventSystem.js +++ b/packages/react-dom/src/events/DOMModernPluginEventSystem.js @@ -7,8 +7,8 @@ * @flow */ -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {AnyNativeEvent} from '../legacy-events/PluginModuleType'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; import type { ElementListenerMap, ElementListenerMapEntry, @@ -22,14 +22,14 @@ import type { DispatchQueueItem, DispatchQueueItemPhase, DispatchQueueItemPhaseEntry, -} from 'legacy-events/PluginModuleType'; +} from '../legacy-events/PluginModuleType'; import type { ReactSyntheticEvent, CustomDispatchConfig, -} from 'legacy-events/ReactSyntheticEventType'; +} from '../legacy-events/ReactSyntheticEventType'; -import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry'; -import {plugins} from 'legacy-events/EventPluginRegistry'; +import {registrationNameDependencies} from '../legacy-events/EventPluginRegistry'; +import {plugins} from '../legacy-events/EventPluginRegistry'; import { PLUGIN_EVENT_SYSTEM, LEGACY_FB_SUPPORT, diff --git a/packages/react-dom/src/events/DOMTopLevelEventTypes.js b/packages/react-dom/src/events/DOMTopLevelEventTypes.js index 3eddfb02cae0..69e0a1e319bd 100644 --- a/packages/react-dom/src/events/DOMTopLevelEventTypes.js +++ b/packages/react-dom/src/events/DOMTopLevelEventTypes.js @@ -7,12 +7,12 @@ * @flow */ -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; import { unsafeCastStringToDOMTopLevelType, unsafeCastDOMTopLevelTypeToString, -} from 'legacy-events/TopLevelEventTypes'; +} from '../legacy-events/TopLevelEventTypes'; import getVendorPrefixedEventName from './getVendorPrefixedEventName'; /** diff --git a/packages/react-dom/src/events/DeprecatedDOMEventResponderSystem.js b/packages/react-dom/src/events/DeprecatedDOMEventResponderSystem.js index 008772003507..878015ebc0a2 100644 --- a/packages/react-dom/src/events/DeprecatedDOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DeprecatedDOMEventResponderSystem.js @@ -12,7 +12,7 @@ import { PASSIVE_NOT_SUPPORTED, RESPONDER_EVENT_SYSTEM, } from './EventSystemFlags'; -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; +import type {AnyNativeEvent} from '../legacy-events/PluginModuleType'; import { HostComponent, ScopeComponent, @@ -25,7 +25,7 @@ import type { ReactDOMResponderContext, ReactDOMResponderEvent, } from '../shared/ReactDOMTypes'; -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; import { batchedEventUpdates, discreteUpdates, diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js index 826ec784b180..d906ea8b5413 100644 --- a/packages/react-dom/src/events/ReactDOMEventListener.js +++ b/packages/react-dom/src/events/ReactDOMEventListener.js @@ -7,11 +7,11 @@ * @flow */ -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; +import type {AnyNativeEvent} from '../legacy-events/PluginModuleType'; import type {EventPriority} from 'shared/ReactTypes'; import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig'; -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; // Intentionally not named imports because Rollup would use dynamic dispatch for // CommonJS interop named imports. diff --git a/packages/react-dom/src/events/ReactDOMEventReplaying.js b/packages/react-dom/src/events/ReactDOMEventReplaying.js index 7ece12abadb6..f544755937ed 100644 --- a/packages/react-dom/src/events/ReactDOMEventReplaying.js +++ b/packages/react-dom/src/events/ReactDOMEventReplaying.js @@ -7,9 +7,9 @@ * @flow */ -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; +import type {AnyNativeEvent} from '../legacy-events/PluginModuleType'; import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig'; -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; import type {ElementListenerMap} from '../client/ReactDOMComponentTree'; import type {EventSystemFlags} from './EventSystemFlags'; import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; @@ -36,7 +36,7 @@ import { getClosestInstanceFromNode, getEventListenerMap, } from '../client/ReactDOMComponentTree'; -import {unsafeCastDOMTopLevelTypeToString} from 'legacy-events/TopLevelEventTypes'; +import {unsafeCastDOMTopLevelTypeToString} from '../legacy-events/TopLevelEventTypes'; import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags'; let attemptSynchronousHydration: (fiber: Object) => void; diff --git a/packages/react-dom/src/events/SyntheticAnimationEvent.js b/packages/react-dom/src/events/SyntheticAnimationEvent.js index f284445f9052..e5a161cb6ff2 100644 --- a/packages/react-dom/src/events/SyntheticAnimationEvent.js +++ b/packages/react-dom/src/events/SyntheticAnimationEvent.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../legacy-events/SyntheticEvent'; /** * @interface Event diff --git a/packages/react-dom/src/events/SyntheticClipboardEvent.js b/packages/react-dom/src/events/SyntheticClipboardEvent.js index 0bab76e2d756..bcab0e926ca7 100644 --- a/packages/react-dom/src/events/SyntheticClipboardEvent.js +++ b/packages/react-dom/src/events/SyntheticClipboardEvent.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../legacy-events/SyntheticEvent'; /** * @interface Event diff --git a/packages/react-dom/src/events/SyntheticCompositionEvent.js b/packages/react-dom/src/events/SyntheticCompositionEvent.js index 14697893ded5..afe88b4028e3 100644 --- a/packages/react-dom/src/events/SyntheticCompositionEvent.js +++ b/packages/react-dom/src/events/SyntheticCompositionEvent.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../legacy-events/SyntheticEvent'; /** * @interface Event diff --git a/packages/react-dom/src/events/SyntheticInputEvent.js b/packages/react-dom/src/events/SyntheticInputEvent.js index 3842a75f937e..1e9817593535 100644 --- a/packages/react-dom/src/events/SyntheticInputEvent.js +++ b/packages/react-dom/src/events/SyntheticInputEvent.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../legacy-events/SyntheticEvent'; /** * @interface Event diff --git a/packages/react-dom/src/events/SyntheticTransitionEvent.js b/packages/react-dom/src/events/SyntheticTransitionEvent.js index f22a0147de8b..654d846d9a3f 100644 --- a/packages/react-dom/src/events/SyntheticTransitionEvent.js +++ b/packages/react-dom/src/events/SyntheticTransitionEvent.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../legacy-events/SyntheticEvent'; /** * @interface Event diff --git a/packages/react-dom/src/events/SyntheticUIEvent.js b/packages/react-dom/src/events/SyntheticUIEvent.js index 43fcef6602b3..488ab4119042 100644 --- a/packages/react-dom/src/events/SyntheticUIEvent.js +++ b/packages/react-dom/src/events/SyntheticUIEvent.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../legacy-events/SyntheticEvent'; const SyntheticUIEvent = SyntheticEvent.extend({ view: null, diff --git a/packages/react-dom/src/events/getEventModifierState.js b/packages/react-dom/src/events/getEventModifierState.js index b2f32b82b69f..8a3f2f29f643 100644 --- a/packages/react-dom/src/events/getEventModifierState.js +++ b/packages/react-dom/src/events/getEventModifierState.js @@ -12,7 +12,7 @@ * @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers */ -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; +import type {AnyNativeEvent} from '../legacy-events/PluginModuleType'; const modifierKeyToProp = { Alt: 'altKey', diff --git a/packages/react-dom/src/events/plugins/LegacyBeforeInputEventPlugin.js b/packages/react-dom/src/events/plugins/LegacyBeforeInputEventPlugin.js index 3c866c674933..1175fd56fccf 100644 --- a/packages/react-dom/src/events/plugins/LegacyBeforeInputEventPlugin.js +++ b/packages/react-dom/src/events/plugins/LegacyBeforeInputEventPlugin.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; +import type {TopLevelType} from '../../legacy-events/TopLevelEventTypes'; import {canUseDOM} from 'shared/ExecutionEnvironment'; diff --git a/packages/react-dom/src/events/plugins/LegacyChangeEventPlugin.js b/packages/react-dom/src/events/plugins/LegacyChangeEventPlugin.js index e9518bb1a2ef..a03c3819a42a 100644 --- a/packages/react-dom/src/events/plugins/LegacyChangeEventPlugin.js +++ b/packages/react-dom/src/events/plugins/LegacyChangeEventPlugin.js @@ -5,8 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {runEventsInBatch} from 'legacy-events/EventBatching'; -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import {runEventsInBatch} from '../../legacy-events/EventBatching'; +import SyntheticEvent from '../../legacy-events/SyntheticEvent'; import isTextInputElement from '../isTextInputElement'; import {canUseDOM} from 'shared/ExecutionEnvironment'; diff --git a/packages/react-dom/src/events/plugins/LegacySelectEventPlugin.js b/packages/react-dom/src/events/plugins/LegacySelectEventPlugin.js index 39406c2deb8b..289994ed2b78 100644 --- a/packages/react-dom/src/events/plugins/LegacySelectEventPlugin.js +++ b/packages/react-dom/src/events/plugins/LegacySelectEventPlugin.js @@ -6,7 +6,7 @@ */ import {canUseDOM} from 'shared/ExecutionEnvironment'; -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../../legacy-events/SyntheticEvent'; import isTextInputElement from '../isTextInputElement'; import shallowEqual from 'shared/shallowEqual'; @@ -30,7 +30,7 @@ import {hasSelectionCapabilities} from '../../client/ReactInputSelection'; import {DOCUMENT_NODE} from '../../shared/HTMLNodeType'; import {accumulateTwoPhaseDispatches} from '../DOMLegacyEventPluginSystem'; -import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry'; +import {registrationNameDependencies} from '../../legacy-events/EventPluginRegistry'; const skipSelectionChangeEvent = canUseDOM && 'documentMode' in document && document.documentMode <= 11; diff --git a/packages/react-dom/src/events/plugins/LegacySimpleEventPlugin.js b/packages/react-dom/src/events/plugins/LegacySimpleEventPlugin.js index 6bae4ab1cda1..b4e1aa89632c 100644 --- a/packages/react-dom/src/events/plugins/LegacySimpleEventPlugin.js +++ b/packages/react-dom/src/events/plugins/LegacySimpleEventPlugin.js @@ -10,12 +10,12 @@ import type { TopLevelType, DOMTopLevelEventType, -} from 'legacy-events/TopLevelEventTypes'; -import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType'; +} from '../../legacy-events/TopLevelEventTypes'; +import type {ReactSyntheticEvent} from '../../legacy-events/ReactSyntheticEventType'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; -import type {LegacyPluginModule} from 'legacy-events/PluginModuleType'; +import type {LegacyPluginModule} from '../../legacy-events/PluginModuleType'; -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../../legacy-events/SyntheticEvent'; import * as DOMTopLevelEventTypes from '../DOMTopLevelEventTypes'; import { diff --git a/packages/react-dom/src/events/plugins/ModernBeforeInputEventPlugin.js b/packages/react-dom/src/events/plugins/ModernBeforeInputEventPlugin.js index b32b6bbddc64..1bbf54bde966 100644 --- a/packages/react-dom/src/events/plugins/ModernBeforeInputEventPlugin.js +++ b/packages/react-dom/src/events/plugins/ModernBeforeInputEventPlugin.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; +import type {TopLevelType} from '../../legacy-events/TopLevelEventTypes'; import {canUseDOM} from 'shared/ExecutionEnvironment'; diff --git a/packages/react-dom/src/events/plugins/ModernChangeEventPlugin.js b/packages/react-dom/src/events/plugins/ModernChangeEventPlugin.js index 49f4f859f438..ee47a2280691 100644 --- a/packages/react-dom/src/events/plugins/ModernChangeEventPlugin.js +++ b/packages/react-dom/src/events/plugins/ModernChangeEventPlugin.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../../legacy-events/SyntheticEvent'; import isTextInputElement from '../isTextInputElement'; import {canUseDOM} from 'shared/ExecutionEnvironment'; diff --git a/packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js b/packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js index 74347b3a93da..9761593a28a8 100644 --- a/packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js +++ b/packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js @@ -6,7 +6,7 @@ */ import {canUseDOM} from 'shared/ExecutionEnvironment'; -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../../legacy-events/SyntheticEvent'; import isTextInputElement from '../isTextInputElement'; import shallowEqual from 'shared/shallowEqual'; diff --git a/packages/react-dom/src/events/plugins/ModernSimpleEventPlugin.js b/packages/react-dom/src/events/plugins/ModernSimpleEventPlugin.js index 746bf884fa5a..27db617a5b07 100644 --- a/packages/react-dom/src/events/plugins/ModernSimpleEventPlugin.js +++ b/packages/react-dom/src/events/plugins/ModernSimpleEventPlugin.js @@ -10,15 +10,15 @@ import type { TopLevelType, DOMTopLevelEventType, -} from 'legacy-events/TopLevelEventTypes'; +} from '../../legacy-events/TopLevelEventTypes'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type { ModernPluginModule, DispatchQueue, -} from 'legacy-events/PluginModuleType'; +} from '../../legacy-events/PluginModuleType'; import type {EventSystemFlags} from '../EventSystemFlags'; -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../../legacy-events/SyntheticEvent'; import * as DOMTopLevelEventTypes from '../DOMTopLevelEventTypes'; import { diff --git a/packages/legacy-events/EventBatching.js b/packages/react-dom/src/legacy-events/EventBatching.js similarity index 100% rename from packages/legacy-events/EventBatching.js rename to packages/react-dom/src/legacy-events/EventBatching.js diff --git a/packages/legacy-events/EventPluginRegistry.js b/packages/react-dom/src/legacy-events/EventPluginRegistry.js similarity index 100% rename from packages/legacy-events/EventPluginRegistry.js rename to packages/react-dom/src/legacy-events/EventPluginRegistry.js diff --git a/packages/legacy-events/EventPluginUtils.js b/packages/react-dom/src/legacy-events/EventPluginUtils.js similarity index 100% rename from packages/legacy-events/EventPluginUtils.js rename to packages/react-dom/src/legacy-events/EventPluginUtils.js diff --git a/packages/legacy-events/PluginModuleType.js b/packages/react-dom/src/legacy-events/PluginModuleType.js similarity index 100% rename from packages/legacy-events/PluginModuleType.js rename to packages/react-dom/src/legacy-events/PluginModuleType.js diff --git a/packages/legacy-events/ReactGenericBatching.js b/packages/react-dom/src/legacy-events/ReactGenericBatching.js similarity index 100% rename from packages/legacy-events/ReactGenericBatching.js rename to packages/react-dom/src/legacy-events/ReactGenericBatching.js diff --git a/packages/legacy-events/ReactSyntheticEventType.js b/packages/react-dom/src/legacy-events/ReactSyntheticEventType.js similarity index 100% rename from packages/legacy-events/ReactSyntheticEventType.js rename to packages/react-dom/src/legacy-events/ReactSyntheticEventType.js diff --git a/packages/legacy-events/ResponderEventPlugin.js b/packages/react-dom/src/legacy-events/ResponderEventPlugin.js similarity index 100% rename from packages/legacy-events/ResponderEventPlugin.js rename to packages/react-dom/src/legacy-events/ResponderEventPlugin.js diff --git a/packages/legacy-events/ResponderSyntheticEvent.js b/packages/react-dom/src/legacy-events/ResponderSyntheticEvent.js similarity index 100% rename from packages/legacy-events/ResponderSyntheticEvent.js rename to packages/react-dom/src/legacy-events/ResponderSyntheticEvent.js diff --git a/packages/legacy-events/ResponderTopLevelEventTypes.js b/packages/react-dom/src/legacy-events/ResponderTopLevelEventTypes.js similarity index 100% rename from packages/legacy-events/ResponderTopLevelEventTypes.js rename to packages/react-dom/src/legacy-events/ResponderTopLevelEventTypes.js diff --git a/packages/legacy-events/ResponderTouchHistoryStore.js b/packages/react-dom/src/legacy-events/ResponderTouchHistoryStore.js similarity index 100% rename from packages/legacy-events/ResponderTouchHistoryStore.js rename to packages/react-dom/src/legacy-events/ResponderTouchHistoryStore.js diff --git a/packages/legacy-events/SyntheticEvent.js b/packages/react-dom/src/legacy-events/SyntheticEvent.js similarity index 100% rename from packages/legacy-events/SyntheticEvent.js rename to packages/react-dom/src/legacy-events/SyntheticEvent.js diff --git a/packages/legacy-events/TopLevelEventTypes.js b/packages/react-dom/src/legacy-events/TopLevelEventTypes.js similarity index 100% rename from packages/legacy-events/TopLevelEventTypes.js rename to packages/react-dom/src/legacy-events/TopLevelEventTypes.js diff --git a/packages/legacy-events/accumulate.js b/packages/react-dom/src/legacy-events/accumulate.js similarity index 100% rename from packages/legacy-events/accumulate.js rename to packages/react-dom/src/legacy-events/accumulate.js diff --git a/packages/legacy-events/accumulateInto.js b/packages/react-dom/src/legacy-events/accumulateInto.js similarity index 100% rename from packages/legacy-events/accumulateInto.js rename to packages/react-dom/src/legacy-events/accumulateInto.js diff --git a/packages/legacy-events/forEachAccumulated.js b/packages/react-dom/src/legacy-events/forEachAccumulated.js similarity index 100% rename from packages/legacy-events/forEachAccumulated.js rename to packages/react-dom/src/legacy-events/forEachAccumulated.js diff --git a/packages/react-dom/src/shared/ReactDOMTypes.js b/packages/react-dom/src/shared/ReactDOMTypes.js index 086e10554316..f1f548c82eda 100644 --- a/packages/react-dom/src/shared/ReactDOMTypes.js +++ b/packages/react-dom/src/shared/ReactDOMTypes.js @@ -14,7 +14,7 @@ import type { EventPriority, ReactScopeInstance, } from 'shared/ReactTypes'; -import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {DOMTopLevelEventType} from '../legacy-events/TopLevelEventTypes'; type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch; diff --git a/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js b/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js index fd0f71dacb82..f1eb971e2bc8 100644 --- a/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js +++ b/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js @@ -8,7 +8,7 @@ import { registrationNameModules, possibleRegistrationNames, -} from 'legacy-events/EventPluginRegistry'; +} from '../legacy-events/EventPluginRegistry'; import { ATTRIBUTE_NAME_CHAR, diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js index ddc51a422fc1..b807463b4022 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtils.js +++ b/packages/react-dom/src/test-utils/ReactTestUtils.js @@ -15,13 +15,13 @@ import { HostComponent, HostText, } from 'react-reconciler/src/ReactWorkTags'; -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import SyntheticEvent from '../legacy-events/SyntheticEvent'; import invariant from 'shared/invariant'; import {ELEMENT_NODE} from '../shared/HTMLNodeType'; import * as DOMTopLevelEventTypes from '../events/DOMTopLevelEventTypes'; import act from './ReactTestUtilsAct'; -import forEachAccumulated from 'legacy-events/forEachAccumulated'; -import accumulateInto from 'legacy-events/accumulateInto'; +import forEachAccumulated from '../legacy-events/forEachAccumulated'; +import accumulateInto from '../legacy-events/accumulateInto'; import {enableModernEventSystem} from 'shared/ReactFeatureFlags'; import { rethrowCaughtError, diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js index d5f426bb5487..b5567d035698 100644 --- a/packages/react-native-renderer/src/ReactFabric.js +++ b/packages/react-native-renderer/src/ReactFabric.js @@ -27,7 +27,7 @@ import { } from 'react-reconciler/src/ReactFiberReconciler'; import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal'; -import {setBatchingImplementation} from 'legacy-events/ReactGenericBatching'; +import {setBatchingImplementation} from './legacy-events/ReactGenericBatching'; import ReactVersion from 'shared/ReactVersion'; // Module provided by RN: diff --git a/packages/react-native-renderer/src/ReactFabricEventEmitter.js b/packages/react-native-renderer/src/ReactFabricEventEmitter.js index f8f8f44ded3d..15798d8f777e 100644 --- a/packages/react-native-renderer/src/ReactFabricEventEmitter.js +++ b/packages/react-native-renderer/src/ReactFabricEventEmitter.js @@ -7,19 +7,19 @@ * @flow */ -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; +import type {AnyNativeEvent} from './legacy-events/PluginModuleType'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; -import type {LegacyPluginModule} from 'legacy-events/PluginModuleType'; -import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType'; -import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; +import type {LegacyPluginModule} from './legacy-events/PluginModuleType'; +import type {ReactSyntheticEvent} from './legacy-events/ReactSyntheticEventType'; +import type {TopLevelType} from './legacy-events/TopLevelEventTypes'; -import {registrationNameModules} from 'legacy-events/EventPluginRegistry'; -import {batchedUpdates} from 'legacy-events/ReactGenericBatching'; -import accumulateInto from 'legacy-events/accumulateInto'; +import {registrationNameModules} from './legacy-events/EventPluginRegistry'; +import {batchedUpdates} from './legacy-events/ReactGenericBatching'; +import accumulateInto from './legacy-events/accumulateInto'; -import {plugins} from 'legacy-events/EventPluginRegistry'; +import {plugins} from './legacy-events/EventPluginRegistry'; import getListener from './ReactNativeGetListener'; -import {runEventsInBatch} from 'legacy-events/EventBatching'; +import {runEventsInBatch} from './legacy-events/EventBatching'; export {getListener, registrationNameModules as registrationNames}; diff --git a/packages/react-native-renderer/src/ReactFabricInjection.js b/packages/react-native-renderer/src/ReactFabricInjection.js index a2502f700a04..60a269bf013d 100644 --- a/packages/react-native-renderer/src/ReactFabricInjection.js +++ b/packages/react-native-renderer/src/ReactFabricInjection.js @@ -14,9 +14,9 @@ import { getInstanceFromNode, getNodeFromInstance, } from './ReactFabricComponentTree'; -import {setComponentTree} from 'legacy-events/EventPluginUtils'; +import {setComponentTree} from './legacy-events/EventPluginUtils'; import ReactFabricGlobalResponderHandler from './ReactFabricGlobalResponderHandler'; -import ResponderEventPlugin from 'legacy-events/ResponderEventPlugin'; +import ResponderEventPlugin from './legacy-events/ResponderEventPlugin'; setComponentTree( getFiberCurrentPropsFromNode, diff --git a/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js b/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js index f64355b26e47..f63d21199fc6 100644 --- a/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js +++ b/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js @@ -7,16 +7,16 @@ * @flow */ -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; -import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; -import SyntheticEvent from 'legacy-events/SyntheticEvent'; +import type {AnyNativeEvent} from './legacy-events/PluginModuleType'; +import type {TopLevelType} from './legacy-events/TopLevelEventTypes'; +import SyntheticEvent from './legacy-events/SyntheticEvent'; import invariant from 'shared/invariant'; // Module provided by RN: import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; -import accumulateInto from 'legacy-events/accumulateInto'; +import accumulateInto from './legacy-events/accumulateInto'; import getListener from './ReactNativeGetListener'; -import forEachAccumulated from 'legacy-events/forEachAccumulated'; +import forEachAccumulated from './legacy-events/forEachAccumulated'; import {HostComponent} from 'react-reconciler/src/ReactWorkTags'; const { diff --git a/packages/react-native-renderer/src/ReactNativeEventEmitter.js b/packages/react-native-renderer/src/ReactNativeEventEmitter.js index 3b1d085455c7..2ba35aed39b9 100644 --- a/packages/react-native-renderer/src/ReactNativeEventEmitter.js +++ b/packages/react-native-renderer/src/ReactNativeEventEmitter.js @@ -7,18 +7,18 @@ * @flow */ -import type {AnyNativeEvent} from 'legacy-events/PluginModuleType'; +import type {AnyNativeEvent} from './legacy-events/PluginModuleType'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; -import type {LegacyPluginModule} from 'legacy-events/PluginModuleType'; -import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType'; -import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; - -import {registrationNameModules} from 'legacy-events/EventPluginRegistry'; -import {batchedUpdates} from 'legacy-events/ReactGenericBatching'; -import {runEventsInBatch} from 'legacy-events/EventBatching'; -import {plugins} from 'legacy-events/EventPluginRegistry'; +import type {LegacyPluginModule} from './legacy-events/PluginModuleType'; +import type {ReactSyntheticEvent} from './legacy-events/ReactSyntheticEventType'; +import type {TopLevelType} from './legacy-events/TopLevelEventTypes'; + +import {registrationNameModules} from './legacy-events/EventPluginRegistry'; +import {batchedUpdates} from './legacy-events/ReactGenericBatching'; +import {runEventsInBatch} from './legacy-events/EventBatching'; +import {plugins} from './legacy-events/EventPluginRegistry'; import getListener from './ReactNativeGetListener'; -import accumulateInto from 'legacy-events/accumulateInto'; +import accumulateInto from './legacy-events/accumulateInto'; import {getInstanceFromNode} from './ReactNativeComponentTree'; diff --git a/packages/react-native-renderer/src/ReactNativeGetListener.js b/packages/react-native-renderer/src/ReactNativeGetListener.js index 99d4429e8774..8a3476f9a6ad 100644 --- a/packages/react-native-renderer/src/ReactNativeGetListener.js +++ b/packages/react-native-renderer/src/ReactNativeGetListener.js @@ -9,7 +9,7 @@ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import invariant from 'shared/invariant'; -import {getFiberCurrentPropsFromNode} from 'legacy-events/EventPluginUtils'; +import {getFiberCurrentPropsFromNode} from './legacy-events/EventPluginUtils'; export default function getListener( inst: Fiber, diff --git a/packages/react-native-renderer/src/ReactNativeInjection.js b/packages/react-native-renderer/src/ReactNativeInjection.js index 7c88fa03f683..3fef7d5ff023 100644 --- a/packages/react-native-renderer/src/ReactNativeInjection.js +++ b/packages/react-native-renderer/src/ReactNativeInjection.js @@ -14,10 +14,10 @@ import { getInstanceFromNode, getNodeFromInstance, } from './ReactNativeComponentTree'; -import {setComponentTree} from 'legacy-events/EventPluginUtils'; +import {setComponentTree} from './legacy-events/EventPluginUtils'; import {receiveEvent, receiveTouches} from './ReactNativeEventEmitter'; import ReactNativeGlobalResponderHandler from './ReactNativeGlobalResponderHandler'; -import ResponderEventPlugin from 'legacy-events/ResponderEventPlugin'; +import ResponderEventPlugin from './legacy-events/ResponderEventPlugin'; // Module provided by RN: import {RCTEventEmitter} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; diff --git a/packages/react-native-renderer/src/ReactNativeInjectionShared.js b/packages/react-native-renderer/src/ReactNativeInjectionShared.js index bfb3b926db57..7470d98f3480 100644 --- a/packages/react-native-renderer/src/ReactNativeInjectionShared.js +++ b/packages/react-native-renderer/src/ReactNativeInjectionShared.js @@ -16,11 +16,11 @@ // Module provided by RN: import 'react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore'; -import ResponderEventPlugin from 'legacy-events/ResponderEventPlugin'; +import ResponderEventPlugin from './legacy-events/ResponderEventPlugin'; import { injectEventPluginOrder, injectEventPluginsByName, -} from 'legacy-events/EventPluginRegistry'; +} from './legacy-events/EventPluginRegistry'; import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin'; import ReactNativeEventPluginOrder from './ReactNativeEventPluginOrder'; diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js index c9734fb7ffa6..762fb55e4f86 100644 --- a/packages/react-native-renderer/src/ReactNativeRenderer.js +++ b/packages/react-native-renderer/src/ReactNativeRenderer.js @@ -30,7 +30,7 @@ import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal import { setBatchingImplementation, batchedUpdates, -} from 'legacy-events/ReactGenericBatching'; +} from './legacy-events/ReactGenericBatching'; import ReactVersion from 'shared/ReactVersion'; // Module provided by RN: import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; diff --git a/packages/react-native-renderer/src/__tests__/EventPluginRegistry-test.internal.js b/packages/react-native-renderer/src/__tests__/EventPluginRegistry-test.internal.js index 2f30399cd8f0..c4c8b226a59b 100644 --- a/packages/react-native-renderer/src/__tests__/EventPluginRegistry-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/EventPluginRegistry-test.internal.js @@ -19,7 +19,7 @@ describe('EventPluginRegistry', () => { // The public API surface of this is covered by other tests so // if `EventPluginRegistry` is ever deleted, these tests should be // safe to remove too. - EventPluginRegistry = require('legacy-events/EventPluginRegistry'); + EventPluginRegistry = require('react-native-renderer/src/legacy-events/EventPluginRegistry'); createPlugin = function(properties) { return Object.assign({extractEvents: function() {}}, properties); diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js index de2aebc93bf1..d6413064a02f 100644 --- a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js @@ -68,7 +68,8 @@ beforeEach(() => { .RCTEventEmitter; React = require('react'); ReactNative = require('react-native-renderer'); - ResponderEventPlugin = require('legacy-events/ResponderEventPlugin').default; + ResponderEventPlugin = require('react-native-renderer/src/legacy-events/ResponderEventPlugin') + .default; UIManager = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface') .UIManager; createReactNativeComponentClass = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface') diff --git a/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js b/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js index 6b60df8a1578..8265b2c2f491 100644 --- a/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js @@ -407,9 +407,9 @@ describe('ResponderEventPlugin', () => { beforeEach(() => { jest.resetModules(); - EventBatching = require('legacy-events/EventBatching'); - EventPluginUtils = require('legacy-events/EventPluginUtils'); - ResponderEventPlugin = require('legacy-events/ResponderEventPlugin') + EventBatching = require('react-native-renderer/src/legacy-events/EventBatching'); + EventPluginUtils = require('react-native-renderer/src/legacy-events/EventPluginUtils'); + ResponderEventPlugin = require('react-native-renderer/src/legacy-events/ResponderEventPlugin') .default; deleteAllListeners(GRANDPARENT_INST); @@ -1380,8 +1380,9 @@ describe('ResponderEventPlugin', () => { // ResponderEventPlugin uses `getLowestCommonAncestor` const React = require('react'); const ReactTestUtils = require('react-dom/test-utils'); - const getLowestCommonAncestor = require('legacy-events/ResponderEventPlugin') + const getLowestCommonAncestor = require('react-native-renderer/src/legacy-events/ResponderEventPlugin') .getLowestCommonAncestor; + // This works by accident and will likely break in the future. const ReactDOMComponentTree = require('react-dom/src/client/ReactDOMComponentTree'); class ChildComponent extends React.Component { diff --git a/packages/react-native-renderer/src/legacy-events/EventBatching.js b/packages/react-native-renderer/src/legacy-events/EventBatching.js new file mode 100644 index 000000000000..bffc978ad6e2 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/EventBatching.js @@ -0,0 +1,66 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @flow + */ + +import invariant from 'shared/invariant'; +import {rethrowCaughtError} from 'shared/ReactErrorUtils'; + +import type {ReactSyntheticEvent} from './ReactSyntheticEventType'; +import accumulateInto from './accumulateInto'; +import forEachAccumulated from './forEachAccumulated'; +import {executeDispatchesInOrder} from './EventPluginUtils'; + +/** + * Internal queue of events that have accumulated their dispatches and are + * waiting to have their dispatches executed. + */ +let eventQueue: ?(Array | ReactSyntheticEvent) = null; + +/** + * Dispatches an event and releases it back into the pool, unless persistent. + * + * @param {?object} event Synthetic event to be dispatched. + * @private + */ +const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) { + if (event) { + executeDispatchesInOrder(event); + + if (!event.isPersistent()) { + event.constructor.release(event); + } + } +}; +const executeDispatchesAndReleaseTopLevel = function(e) { + return executeDispatchesAndRelease(e); +}; + +export function runEventsInBatch( + events: Array | ReactSyntheticEvent | null, +) { + if (events !== null) { + eventQueue = accumulateInto(eventQueue, events); + } + + // Set `eventQueue` to null before processing it so that we can tell if more + // events get enqueued while processing. + const processingEventQueue = eventQueue; + eventQueue = null; + + if (!processingEventQueue) { + return; + } + + 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. + rethrowCaughtError(); +} diff --git a/packages/react-native-renderer/src/legacy-events/EventPluginRegistry.js b/packages/react-native-renderer/src/legacy-events/EventPluginRegistry.js new file mode 100644 index 000000000000..7c22ebbcbc65 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/EventPluginRegistry.js @@ -0,0 +1,270 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {DispatchConfig} from './ReactSyntheticEventType'; +import type { + AnyNativeEvent, + PluginName, + LegacyPluginModule, + ModernPluginModule, +} from './PluginModuleType'; + +import invariant from 'shared/invariant'; + +type NamesToPlugins = { + [key: PluginName]: + | LegacyPluginModule + | ModernPluginModule, + ..., +}; +type EventPluginOrder = null | Array; + +/** + * Injectable ordering of event plugins. + */ +let eventPluginOrder: EventPluginOrder = null; + +/** + * Injectable mapping from names to event plugin modules. + */ +const namesToPlugins: NamesToPlugins = {}; + +/** + * Recomputes the plugin list using the injected plugins and plugin ordering. + * + * @private + */ +function recomputePluginOrdering(): void { + if (!eventPluginOrder) { + // Wait until an `eventPluginOrder` is injected. + return; + } + for (const pluginName in namesToPlugins) { + const pluginModule = namesToPlugins[pluginName]; + const pluginIndex = eventPluginOrder.indexOf(pluginName); + invariant( + pluginIndex > -1, + 'EventPluginRegistry: Cannot inject event plugins that do not exist in ' + + 'the plugin ordering, `%s`.', + pluginName, + ); + if (plugins[pluginIndex]) { + continue; + } + invariant( + pluginModule.extractEvents, + 'EventPluginRegistry: Event plugins must implement an `extractEvents` ' + + 'method, but `%s` does not.', + pluginName, + ); + plugins[pluginIndex] = pluginModule; + const publishedEvents = pluginModule.eventTypes; + for (const eventName in publishedEvents) { + invariant( + publishEventForPlugin( + publishedEvents[eventName], + pluginModule, + eventName, + ), + 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', + eventName, + pluginName, + ); + } + } +} + +/** + * Publishes an event so that it can be dispatched by the supplied plugin. + * + * @param {object} dispatchConfig Dispatch configuration for the event. + * @param {object} PluginModule Plugin publishing the event. + * @return {boolean} True if the event was successfully published. + * @private + */ +function publishEventForPlugin( + dispatchConfig: DispatchConfig, + pluginModule: + | LegacyPluginModule + | ModernPluginModule, + eventName: string, +): boolean { + invariant( + !eventNameDispatchConfigs.hasOwnProperty(eventName), + 'EventPluginRegistry: More than one plugin attempted to publish the same ' + + 'event name, `%s`.', + eventName, + ); + eventNameDispatchConfigs[eventName] = dispatchConfig; + + const phasedRegistrationNames = dispatchConfig.phasedRegistrationNames; + if (phasedRegistrationNames) { + for (const phaseName in phasedRegistrationNames) { + if (phasedRegistrationNames.hasOwnProperty(phaseName)) { + const phasedRegistrationName = phasedRegistrationNames[phaseName]; + publishRegistrationName( + phasedRegistrationName, + pluginModule, + eventName, + ); + } + } + return true; + } else if (dispatchConfig.registrationName) { + publishRegistrationName( + dispatchConfig.registrationName, + pluginModule, + eventName, + ); + return true; + } + return false; +} + +/** + * Publishes a registration name that is used to identify dispatched events. + * + * @param {string} registrationName Registration name to add. + * @param {object} PluginModule Plugin publishing the event. + * @private + */ +function publishRegistrationName( + registrationName: string, + pluginModule: + | LegacyPluginModule + | ModernPluginModule, + eventName: string, +): void { + invariant( + !registrationNameModules[registrationName], + 'EventPluginRegistry: More than one plugin attempted to publish the same ' + + 'registration name, `%s`.', + registrationName, + ); + registrationNameModules[registrationName] = pluginModule; + registrationNameDependencies[registrationName] = + pluginModule.eventTypes[eventName].dependencies; + + if (__DEV__) { + const lowerCasedName = registrationName.toLowerCase(); + possibleRegistrationNames[lowerCasedName] = registrationName; + + if (registrationName === 'onDoubleClick') { + possibleRegistrationNames.ondblclick = registrationName; + } + } +} + +/** + * Registers plugins so that they can extract and dispatch events. + */ + +/** + * Ordered list of injected plugins. + */ +export const plugins = []; + +/** + * Mapping from event name to dispatch config + */ +export const eventNameDispatchConfigs = {}; + +/** + * Mapping from registration name to plugin module + */ +export const registrationNameModules = {}; + +/** + * Mapping from registration name to event name + */ +export const registrationNameDependencies = {}; + +/** + * 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 + */ +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 plugin event system. 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 + */ +export function injectEventPluginsByName( + injectedNamesToPlugins: NamesToPlugins, +): void { + let isOrderingDirty = false; + for (const pluginName in injectedNamesToPlugins) { + if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) { + continue; + } + const 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 function injectEventPlugins( + eventPlugins: [ModernPluginModule], +): void { + for (let i = 0; i < eventPlugins.length; i++) { + const pluginModule = eventPlugins[i]; + plugins.push(pluginModule); + const publishedEvents = pluginModule.eventTypes; + for (const eventName in publishedEvents) { + publishEventForPlugin( + publishedEvents[eventName], + pluginModule, + eventName, + ); + } + } +} diff --git a/packages/react-native-renderer/src/legacy-events/EventPluginUtils.js b/packages/react-native-renderer/src/legacy-events/EventPluginUtils.js new file mode 100644 index 000000000000..abba8e9a3dab --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/EventPluginUtils.js @@ -0,0 +1,172 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils'; +import invariant from 'shared/invariant'; + +export let getFiberCurrentPropsFromNode = null; +export let getInstanceFromNode = null; +export let getNodeFromInstance = null; + +export function setComponentTree( + getFiberCurrentPropsFromNodeImpl, + getInstanceFromNodeImpl, + getNodeFromInstanceImpl, +) { + getFiberCurrentPropsFromNode = getFiberCurrentPropsFromNodeImpl; + getInstanceFromNode = getInstanceFromNodeImpl; + getNodeFromInstance = getNodeFromInstanceImpl; + if (__DEV__) { + if (!getNodeFromInstance || !getInstanceFromNode) { + console.error( + 'EventPluginUtils.setComponentTree(...): Injected ' + + 'module is missing getNodeFromInstance or getInstanceFromNode.', + ); + } + } +} + +let validateEventDispatches; +if (__DEV__) { + validateEventDispatches = function(event) { + const dispatchListeners = event._dispatchListeners; + const dispatchInstances = event._dispatchInstances; + + const listenersIsArr = Array.isArray(dispatchListeners); + const listenersLen = listenersIsArr + ? dispatchListeners.length + : dispatchListeners + ? 1 + : 0; + + const instancesIsArr = Array.isArray(dispatchInstances); + const instancesLen = instancesIsArr + ? dispatchInstances.length + : dispatchInstances + ? 1 + : 0; + + if (instancesIsArr !== listenersIsArr || instancesLen !== listenersLen) { + console.error('EventPluginUtils: Invalid `event`.'); + } + }; +} + +/** + * Dispatch the event to the listener. + * @param {SyntheticEvent} event SyntheticEvent to handle + * @param {function} listener Application-level callback + * @param {*} inst Internal component instance + */ +export function executeDispatch(event, listener, inst) { + const type = event.type || 'unknown-event'; + event.currentTarget = getNodeFromInstance(inst); + invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event); + event.currentTarget = null; +} + +/** + * Standard/simple iteration through an event's collected dispatches. + */ +export function executeDispatchesInOrder(event) { + const dispatchListeners = event._dispatchListeners; + const dispatchInstances = event._dispatchInstances; + if (__DEV__) { + validateEventDispatches(event); + } + if (Array.isArray(dispatchListeners)) { + for (let i = 0; i < dispatchListeners.length; i++) { + if (event.isPropagationStopped()) { + break; + } + // Listeners and Instances are two parallel arrays that are always in sync. + executeDispatch(event, dispatchListeners[i], dispatchInstances[i]); + } + } else if (dispatchListeners) { + executeDispatch(event, dispatchListeners, dispatchInstances); + } + event._dispatchListeners = null; + event._dispatchInstances = null; +} + +/** + * Standard/simple iteration through an event's collected dispatches, but stops + * at the first dispatch execution returning true, and returns that id. + * + * @return {?string} id of the first dispatch execution who's listener returns + * true, or null if no listener returned true. + */ +function executeDispatchesInOrderStopAtTrueImpl(event) { + const dispatchListeners = event._dispatchListeners; + const dispatchInstances = event._dispatchInstances; + if (__DEV__) { + validateEventDispatches(event); + } + if (Array.isArray(dispatchListeners)) { + for (let i = 0; i < dispatchListeners.length; i++) { + if (event.isPropagationStopped()) { + break; + } + // Listeners and Instances are two parallel arrays that are always in sync. + if (dispatchListeners[i](event, dispatchInstances[i])) { + return dispatchInstances[i]; + } + } + } else if (dispatchListeners) { + if (dispatchListeners(event, dispatchInstances)) { + return dispatchInstances; + } + } + return null; +} + +/** + * @see executeDispatchesInOrderStopAtTrueImpl + */ +export function executeDispatchesInOrderStopAtTrue(event) { + const ret = executeDispatchesInOrderStopAtTrueImpl(event); + event._dispatchInstances = null; + event._dispatchListeners = null; + return ret; +} + +/** + * Execution of a "direct" dispatch - there must be at most one dispatch + * accumulated on the event or it is considered an error. It doesn't really make + * sense for an event with multiple dispatches (bubbled) to keep track of the + * return values at each dispatch execution, but it does tend to make sense when + * dealing with "direct" dispatches. + * + * @return {*} The return value of executing the single dispatch. + */ +export function executeDirectDispatch(event) { + if (__DEV__) { + validateEventDispatches(event); + } + const dispatchListener = event._dispatchListeners; + const dispatchInstance = event._dispatchInstances; + invariant( + !Array.isArray(dispatchListener), + 'executeDirectDispatch(...): Invalid `event`.', + ); + event.currentTarget = dispatchListener + ? getNodeFromInstance(dispatchInstance) + : null; + const res = dispatchListener ? dispatchListener(event) : null; + event.currentTarget = null; + event._dispatchListeners = null; + event._dispatchInstances = null; + return res; +} + +/** + * @param {SyntheticEvent} event + * @return {boolean} True iff number of dispatches accumulated is greater than 0. + */ +export function hasDispatches(event) { + return !!event._dispatchListeners; +} diff --git a/packages/react-native-renderer/src/legacy-events/PluginModuleType.js b/packages/react-native-renderer/src/legacy-events/PluginModuleType.js new file mode 100644 index 000000000000..7f29ccc642f4 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/PluginModuleType.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; +import type { + DispatchConfig, + ReactSyntheticEvent, +} from './ReactSyntheticEventType'; +import type {TopLevelType} from './TopLevelEventTypes'; + +export type EventTypes = {[key: string]: DispatchConfig, ...}; + +export type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | TouchEvent; + +export type PluginName = string; + +export type EventSystemFlags = number; + +export type LegacyPluginModule = { + eventTypes: EventTypes, + extractEvents: ( + topLevelType: TopLevelType, + targetInst: null | Fiber, + nativeTarget: NativeEvent, + nativeEventTarget: null | EventTarget, + eventSystemFlags?: number, + container?: null | EventTarget, + ) => ?ReactSyntheticEvent, + tapMoveThreshold?: number, +}; + +export type DispatchQueueItemPhaseEntry = {| + instance: null | Fiber, + listener: Function, + currentTarget: EventTarget, +|}; + +export type DispatchQueueItemPhase = Array; + +export type DispatchQueueItem = {| + event: ReactSyntheticEvent, + capture: DispatchQueueItemPhase, + bubble: DispatchQueueItemPhase, +|}; + +export type DispatchQueue = Array; + +export type ModernPluginModule = { + eventTypes: EventTypes, + extractEvents: ( + dispatchQueue: DispatchQueue, + topLevelType: TopLevelType, + targetInst: null | Fiber, + nativeTarget: NativeEvent, + nativeEventTarget: null | EventTarget, + eventSystemFlags: number, + container: null | EventTarget, + ) => void, +}; diff --git a/packages/react-native-renderer/src/legacy-events/ReactGenericBatching.js b/packages/react-native-renderer/src/legacy-events/ReactGenericBatching.js new file mode 100644 index 000000000000..2681c30cba52 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/ReactGenericBatching.js @@ -0,0 +1,83 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// 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 +// libraries need to call batchedUpdates. Eventually, this API will go away when +// everything is batched by default. We'll then have a similar API to opt-out of +// scheduled work and instead do synchronous work. + +// Defaults +let batchedUpdatesImpl = function(fn, bookkeeping) { + return fn(bookkeeping); +}; +let discreteUpdatesImpl = function(fn, a, b, c, d) { + return fn(a, b, c, d); +}; +let flushDiscreteUpdatesImpl = function() {}; +let batchedEventUpdatesImpl = batchedUpdatesImpl; + +let isInsideEventHandler = false; +let isBatchingEventUpdates = false; + +export function batchedUpdates(fn, bookkeeping) { + if (isInsideEventHandler) { + // If we are currently inside another batch, we need to wait until it + // fully completes before restoring state. + return fn(bookkeeping); + } + isInsideEventHandler = true; + try { + return batchedUpdatesImpl(fn, bookkeeping); + } finally { + isInsideEventHandler = false; + } +} + +export function batchedEventUpdates(fn, a, b) { + if (isBatchingEventUpdates) { + // If we are currently inside another batch, we need to wait until it + // fully completes before restoring state. + return fn(a, b); + } + isBatchingEventUpdates = true; + try { + return batchedEventUpdatesImpl(fn, a, b); + } finally { + isBatchingEventUpdates = false; + } +} + +export function discreteUpdates(fn, a, b, c, d) { + const prevIsInsideEventHandler = isInsideEventHandler; + isInsideEventHandler = true; + try { + return discreteUpdatesImpl(fn, a, b, c, d); + } finally { + isInsideEventHandler = prevIsInsideEventHandler; + if (!isInsideEventHandler) { + } + } +} + +export function flushDiscreteUpdatesIfNeeded(timeStamp: number) { + if (!isInsideEventHandler) { + flushDiscreteUpdatesImpl(); + } +} + +export function setBatchingImplementation( + _batchedUpdatesImpl, + _discreteUpdatesImpl, + _flushDiscreteUpdatesImpl, + _batchedEventUpdatesImpl, +) { + batchedUpdatesImpl = _batchedUpdatesImpl; + discreteUpdatesImpl = _discreteUpdatesImpl; + flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl; + batchedEventUpdatesImpl = _batchedEventUpdatesImpl; +} diff --git a/packages/react-native-renderer/src/legacy-events/ReactSyntheticEventType.js b/packages/react-native-renderer/src/legacy-events/ReactSyntheticEventType.js new file mode 100644 index 000000000000..1bc167afe006 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/ReactSyntheticEventType.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * Flow type for SyntheticEvent class that includes private properties + * @flow + */ + +import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; +import type {EventPriority} from 'shared/ReactTypes'; +import type {TopLevelType} from './TopLevelEventTypes'; + +export type DispatchConfig = {| + dependencies?: Array, + phasedRegistrationNames: {| + bubbled: null | string, + captured: null | string, + |}, + registrationName?: string, + eventPriority: EventPriority, +|}; + +export type CustomDispatchConfig = {| + phasedRegistrationNames: {| + bubbled: null, + captured: null, + |}, + registrationName?: string, + customEvent: true, +|}; + +export type ReactSyntheticEvent = {| + dispatchConfig: DispatchConfig | CustomDispatchConfig, + getPooled: ( + dispatchConfig: DispatchConfig | CustomDispatchConfig, + targetInst: Fiber, + nativeTarget: Event, + nativeEventTarget: EventTarget, + ) => ReactSyntheticEvent, + isPersistent: () => boolean, + isPropagationStopped: () => boolean, + _dispatchInstances?: null | Array | Fiber, + _dispatchListeners?: null | Array | Function, + _targetInst: Fiber, + type: string, + currentTarget: null | EventTarget, +|}; diff --git a/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js b/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js new file mode 100644 index 000000000000..78bef3bbcee9 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js @@ -0,0 +1,802 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + executeDirectDispatch, + hasDispatches, + executeDispatchesInOrderStopAtTrue, + getInstanceFromNode, + getFiberCurrentPropsFromNode, +} from './EventPluginUtils'; +import ResponderSyntheticEvent from './ResponderSyntheticEvent'; +import ResponderTouchHistoryStore from './ResponderTouchHistoryStore'; +import accumulate from './accumulate'; +import { + TOP_SCROLL, + TOP_SELECTION_CHANGE, + TOP_TOUCH_CANCEL, + isStartish, + isMoveish, + isEndish, + startDependencies, + moveDependencies, + endDependencies, +} from './ResponderTopLevelEventTypes'; +import accumulateInto from './accumulateInto'; +import forEachAccumulated from './forEachAccumulated'; +import {HostComponent} from 'react-reconciler/src/ReactWorkTags'; +import invariant from 'shared/invariant'; + +/** + * Instance of element that should respond to touch/move types of interactions, + * as indicated explicitly by relevant callbacks. + */ +let responderInst = null; + +/** + * Count of current touches. A textInput should become responder iff the + * selection changes while there is a touch on the screen. + */ +let trackedTouchCount = 0; + +const changeResponder = function(nextResponderInst, blockHostResponder) { + const oldResponderInst = responderInst; + responderInst = nextResponderInst; + if (ResponderEventPlugin.GlobalResponderHandler !== null) { + ResponderEventPlugin.GlobalResponderHandler.onChange( + oldResponderInst, + nextResponderInst, + blockHostResponder, + ); + } +}; + +const eventTypes = { + /** + * On a `touchStart`/`mouseDown`, is it desired that this element become the + * responder? + */ + startShouldSetResponder: { + phasedRegistrationNames: { + bubbled: 'onStartShouldSetResponder', + captured: 'onStartShouldSetResponderCapture', + }, + dependencies: startDependencies, + }, + + /** + * On a `scroll`, is it desired that this element become the responder? This + * is usually not needed, but should be used to retroactively infer that a + * `touchStart` had occurred during momentum scroll. During a momentum scroll, + * a touch start will be immediately followed by a scroll event if the view is + * currently scrolling. + * + * TODO: This shouldn't bubble. + */ + scrollShouldSetResponder: { + phasedRegistrationNames: { + bubbled: 'onScrollShouldSetResponder', + captured: 'onScrollShouldSetResponderCapture', + }, + dependencies: [TOP_SCROLL], + }, + + /** + * On text selection change, should this element become the responder? This + * is needed for text inputs or other views with native selection, so the + * JS view can claim the responder. + * + * TODO: This shouldn't bubble. + */ + selectionChangeShouldSetResponder: { + phasedRegistrationNames: { + bubbled: 'onSelectionChangeShouldSetResponder', + captured: 'onSelectionChangeShouldSetResponderCapture', + }, + dependencies: [TOP_SELECTION_CHANGE], + }, + + /** + * On a `touchMove`/`mouseMove`, is it desired that this element become the + * responder? + */ + moveShouldSetResponder: { + phasedRegistrationNames: { + bubbled: 'onMoveShouldSetResponder', + captured: 'onMoveShouldSetResponderCapture', + }, + dependencies: moveDependencies, + }, + + /** + * Direct responder events dispatched directly to responder. Do not bubble. + */ + responderStart: { + registrationName: 'onResponderStart', + dependencies: startDependencies, + }, + responderMove: { + registrationName: 'onResponderMove', + dependencies: moveDependencies, + }, + responderEnd: { + registrationName: 'onResponderEnd', + dependencies: endDependencies, + }, + responderRelease: { + registrationName: 'onResponderRelease', + dependencies: endDependencies, + }, + responderTerminationRequest: { + registrationName: 'onResponderTerminationRequest', + dependencies: [], + }, + responderGrant: { + registrationName: 'onResponderGrant', + dependencies: [], + }, + responderReject: { + registrationName: 'onResponderReject', + dependencies: [], + }, + responderTerminate: { + registrationName: 'onResponderTerminate', + dependencies: [], + }, +}; + +// Start of inline: the below functions were inlined from +// EventPropagator.js, as they deviated from ReactDOM's newer +// implementations. + +function getParent(inst) { + do { + inst = inst.return; + // TODO: If this is a HostRoot we might want to bail out. + // That is depending on if we want nested subtrees (layers) to bubble + // events to their parent. We could also go through parentNode on the + // host node but that wouldn't work for React Native and doesn't let us + // do the portal feature. + } while (inst && inst.tag !== HostComponent); + if (inst) { + return inst; + } + return null; +} + +/** + * Return the lowest common ancestor of A and B, or null if they are in + * different trees. + */ +export function getLowestCommonAncestor(instA, instB) { + let depthA = 0; + for (let tempA = instA; tempA; tempA = getParent(tempA)) { + depthA++; + } + let depthB = 0; + for (let tempB = instB; tempB; tempB = getParent(tempB)) { + depthB++; + } + + // If A is deeper, crawl up. + while (depthA - depthB > 0) { + instA = getParent(instA); + depthA--; + } + + // If B is deeper, crawl up. + while (depthB - depthA > 0) { + instB = getParent(instB); + depthB--; + } + + // Walk in lockstep until we find a match. + let depth = depthA; + while (depth--) { + if (instA === instB || instA === instB.alternate) { + return instA; + } + instA = getParent(instA); + instB = getParent(instB); + } + return null; +} + +/** + * Return if A is an ancestor of B. + */ +function isAncestor(instA, instB) { + while (instB) { + if (instA === instB || instA === instB.alternate) { + return true; + } + instB = getParent(instB); + } + return false; +} + +/** + * Simulates the traversal of a two-phase, capture/bubble event dispatch. + */ +function traverseTwoPhase(inst, fn, arg) { + const path = []; + while (inst) { + path.push(inst); + inst = getParent(inst); + } + let i; + for (i = path.length; i-- > 0; ) { + fn(path[i], 'captured', arg); + } + for (i = 0; i < path.length; i++) { + fn(path[i], 'bubbled', arg); + } +} + +function getListener(inst, registrationName) { + const stateNode = inst.stateNode; + if (stateNode === null) { + // Work in progress (ex: onload events in incremental mode). + return null; + } + const props = getFiberCurrentPropsFromNode(stateNode); + if (props === null) { + // Work in progress. + return null; + } + const listener = props[registrationName]; + invariant( + !listener || typeof listener === 'function', + 'Expected `%s` listener to be a function, instead got a value of `%s` type.', + registrationName, + typeof listener, + ); + return listener; +} + +function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) { + const registrationName = + event.dispatchConfig.phasedRegistrationNames[propagationPhase]; + return getListener(inst, registrationName); +} + +function accumulateDirectionalDispatches(inst, phase, event) { + if (__DEV__) { + if (!inst) { + console.error('Dispatching inst must not be null'); + } + } + const listener = listenerAtPhase(inst, event, phase); + if (listener) { + event._dispatchListeners = accumulateInto( + event._dispatchListeners, + listener, + ); + event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); + } +} + +/** + * Accumulates without regard to direction, does not look for phased + * registration names. Same as `accumulateDirectDispatchesSingle` but without + * requiring that the `dispatchMarker` be the same as the dispatched ID. + */ +function accumulateDispatches( + inst: Object, + ignoredDirection: ?boolean, + event: Object, +): void { + if (inst && event && event.dispatchConfig.registrationName) { + const registrationName = event.dispatchConfig.registrationName; + const listener = getListener(inst, registrationName); + if (listener) { + event._dispatchListeners = accumulateInto( + event._dispatchListeners, + listener, + ); + event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); + } + } +} + +/** + * Accumulates dispatches on an `SyntheticEvent`, but only for the + * `dispatchMarker`. + * @param {SyntheticEvent} event + */ +function accumulateDirectDispatchesSingle(event: Object) { + if (event && event.dispatchConfig.registrationName) { + accumulateDispatches(event._targetInst, null, event); + } +} + +function accumulateDirectDispatches(events: ?(Array | Object)) { + forEachAccumulated(events, accumulateDirectDispatchesSingle); +} + +function accumulateTwoPhaseDispatchesSingleSkipTarget(event) { + if (event && event.dispatchConfig.phasedRegistrationNames) { + const targetInst = event._targetInst; + const parentInst = targetInst ? getParent(targetInst) : null; + traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event); + } +} + +function accumulateTwoPhaseDispatchesSkipTarget(events) { + forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget); +} + +function accumulateTwoPhaseDispatchesSingle(event) { + if (event && event.dispatchConfig.phasedRegistrationNames) { + traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event); + } +} + +function accumulateTwoPhaseDispatches(events) { + forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle); +} +// End of inline + +/** + * + * Responder System: + * ---------------- + * + * - A global, solitary "interaction lock" on a view. + * - If a node becomes the responder, it should convey visual feedback + * immediately to indicate so, either by highlighting or moving accordingly. + * - To be the responder means, that touches are exclusively important to that + * responder view, and no other view. + * - While touches are still occurring, the responder lock can be transferred to + * a new view, but only to increasingly "higher" views (meaning ancestors of + * the current responder). + * + * Responder being granted: + * ------------------------ + * + * - Touch starts, moves, and scrolls can cause an ID to become the responder. + * - We capture/bubble `startShouldSetResponder`/`moveShouldSetResponder` to + * the "appropriate place". + * - If nothing is currently the responder, the "appropriate place" is the + * initiating event's `targetID`. + * - If something *is* already the responder, the "appropriate place" is the + * first common ancestor of the event target and the current `responderInst`. + * - Some negotiation happens: See the timing diagram below. + * - Scrolled views automatically become responder. The reasoning is that a + * platform scroll view that isn't built on top of the responder system has + * began scrolling, and the active responder must now be notified that the + * interaction is no longer locked to it - the system has taken over. + * + * - Responder being released: + * As soon as no more touches that *started* inside of descendants of the + * *current* responderInst, an `onResponderRelease` event is dispatched to the + * current responder, and the responder lock is released. + * + * TODO: + * - on "end", a callback hook for `onResponderEndShouldRemainResponder` that + * determines if the responder lock should remain. + * - If a view shouldn't "remain" the responder, any active touches should by + * default be considered "dead" and do not influence future negotiations or + * bubble paths. It should be as if those touches do not exist. + * -- For multitouch: Usually a translate-z will choose to "remain" responder + * after one out of many touches ended. For translate-y, usually the view + * doesn't wish to "remain" responder after one of many touches end. + * - Consider building this on top of a `stopPropagation` model similar to + * `W3C` events. + * - Ensure that `onResponderTerminate` is called on touch cancels, whether or + * not `onResponderTerminationRequest` returns `true` or `false`. + * + */ + +/* Negotiation Performed + +-----------------------+ + / \ +Process low level events to + Current Responder + wantsResponderID +determine who to perform negot-| (if any exists at all) | +iation/transition | Otherwise just pass through| +-------------------------------+----------------------------+------------------+ +Bubble to find first ID | | +to return true:wantsResponderID| | + | | + +-------------+ | | + | onTouchStart| | | + +------+------+ none | | + | return| | ++-----------v-------------+true| +------------------------+ | +|onStartShouldSetResponder|----->|onResponderStart (cur) |<-----------+ ++-----------+-------------+ | +------------------------+ | | + | | | +--------+-------+ + | returned true for| false:REJECT +-------->|onResponderReject + | wantsResponderID | | | +----------------+ + | (now attempt | +------------------+-----+ | + | handoff) | | onResponder | | + +------------------->| TerminationRequest| | + | +------------------+-----+ | + | | | +----------------+ + | true:GRANT +-------->|onResponderGrant| + | | +--------+-------+ + | +------------------------+ | | + | | onResponderTerminate |<-----------+ + | +------------------+-----+ | + | | | +----------------+ + | +-------->|onResponderStart| + | | +----------------+ +Bubble to find first ID | | +to return true:wantsResponderID| | + | | + +-------------+ | | + | onTouchMove | | | + +------+------+ none | | + | return| | ++-----------v-------------+true| +------------------------+ | +|onMoveShouldSetResponder |----->|onResponderMove (cur) |<-----------+ ++-----------+-------------+ | +------------------------+ | | + | | | +--------+-------+ + | returned true for| false:REJECT +-------->|onResponderRejec| + | wantsResponderID | | | +----------------+ + | (now attempt | +------------------+-----+ | + | handoff) | | onResponder | | + +------------------->| TerminationRequest| | + | +------------------+-----+ | + | | | +----------------+ + | true:GRANT +-------->|onResponderGrant| + | | +--------+-------+ + | +------------------------+ | | + | | onResponderTerminate |<-----------+ + | +------------------+-----+ | + | | | +----------------+ + | +-------->|onResponderMove | + | | +----------------+ + | | + | | + Some active touch started| | + inside current responder | +------------------------+ | + +------------------------->| onResponderEnd | | + | | +------------------------+ | + +---+---------+ | | + | onTouchEnd | | | + +---+---------+ | | + | | +------------------------+ | + +------------------------->| onResponderEnd | | + No active touches started| +-----------+------------+ | + inside current responder | | | + | v | + | +------------------------+ | + | | onResponderRelease | | + | +------------------------+ | + | | + + + */ + +/** + * A note about event ordering in the `EventPluginRegistry`. + * + * Suppose plugins are injected in the following order: + * + * `[R, S, C]` + * + * To help illustrate the example, assume `S` is `SimpleEventPlugin` (for + * `onClick` etc) and `R` is `ResponderEventPlugin`. + * + * "Deferred-Dispatched Events": + * + * - The current event plugin system will traverse the list of injected plugins, + * in order, and extract events by collecting the plugin's return value of + * `extractEvents()`. + * - These events that are returned from `extractEvents` are "deferred + * dispatched events". + * - When returned from `extractEvents`, deferred-dispatched events contain an + * "accumulation" of deferred dispatches. + * - These deferred dispatches are accumulated/collected before they are + * returned, but processed at a later time by the `EventPluginRegistry` (hence the + * name deferred). + * + * In the process of returning their deferred-dispatched events, event plugins + * themselves can dispatch events on-demand without returning them from + * `extractEvents`. Plugins might want to do this, so that they can use event + * dispatching as a tool that helps them decide which events should be extracted + * in the first place. + * + * "On-Demand-Dispatched Events": + * + * - On-demand-dispatched events are not returned from `extractEvents`. + * - On-demand-dispatched events are dispatched during the process of returning + * the deferred-dispatched events. + * - They should not have side effects. + * - They should be avoided, and/or eventually be replaced with another + * abstraction that allows event plugins to perform multiple "rounds" of event + * extraction. + * + * Therefore, the sequence of event dispatches becomes: + * + * - `R`s on-demand events (if any) (dispatched by `R` on-demand) + * - `S`s on-demand events (if any) (dispatched by `S` on-demand) + * - `C`s on-demand events (if any) (dispatched by `C` on-demand) + * - `R`s extracted events (if any) (dispatched by `EventPluginRegistry`) + * - `S`s extracted events (if any) (dispatched by `EventPluginRegistry`) + * - `C`s extracted events (if any) (dispatched by `EventPluginRegistry`) + * + * In the case of `ResponderEventPlugin`: If the `startShouldSetResponder` + * on-demand dispatch returns `true` (and some other details are satisfied) the + * `onResponderGrant` deferred dispatched event is returned from + * `extractEvents`. The sequence of dispatch executions in this case + * will appear as follows: + * + * - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand) + * - `touchStartCapture` (`EventPluginRegistry` dispatches as usual) + * - `touchStart` (`EventPluginRegistry` dispatches as usual) + * - `responderGrant/Reject` (`EventPluginRegistry` dispatches as usual) + */ + +function setResponderAndExtractTransfer( + topLevelType, + targetInst, + nativeEvent, + nativeEventTarget, +) { + const shouldSetEventType = isStartish(topLevelType) + ? eventTypes.startShouldSetResponder + : isMoveish(topLevelType) + ? eventTypes.moveShouldSetResponder + : topLevelType === TOP_SELECTION_CHANGE + ? eventTypes.selectionChangeShouldSetResponder + : eventTypes.scrollShouldSetResponder; + + // TODO: stop one short of the current responder. + const bubbleShouldSetFrom = !responderInst + ? targetInst + : getLowestCommonAncestor(responderInst, targetInst); + + // When capturing/bubbling the "shouldSet" event, we want to skip the target + // (deepest ID) if it happens to be the current responder. The reasoning: + // It's strange to get an `onMoveShouldSetResponder` when you're *already* + // the responder. + const skipOverBubbleShouldSetFrom = bubbleShouldSetFrom === responderInst; + const shouldSetEvent = ResponderSyntheticEvent.getPooled( + shouldSetEventType, + bubbleShouldSetFrom, + nativeEvent, + nativeEventTarget, + ); + shouldSetEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; + if (skipOverBubbleShouldSetFrom) { + accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent); + } else { + accumulateTwoPhaseDispatches(shouldSetEvent); + } + const wantsResponderInst = executeDispatchesInOrderStopAtTrue(shouldSetEvent); + if (!shouldSetEvent.isPersistent()) { + shouldSetEvent.constructor.release(shouldSetEvent); + } + + if (!wantsResponderInst || wantsResponderInst === responderInst) { + return null; + } + let extracted; + const grantEvent = ResponderSyntheticEvent.getPooled( + eventTypes.responderGrant, + wantsResponderInst, + nativeEvent, + nativeEventTarget, + ); + grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; + + accumulateDirectDispatches(grantEvent); + const blockHostResponder = executeDirectDispatch(grantEvent) === true; + if (responderInst) { + const terminationRequestEvent = ResponderSyntheticEvent.getPooled( + eventTypes.responderTerminationRequest, + responderInst, + nativeEvent, + nativeEventTarget, + ); + terminationRequestEvent.touchHistory = + ResponderTouchHistoryStore.touchHistory; + accumulateDirectDispatches(terminationRequestEvent); + const shouldSwitch = + !hasDispatches(terminationRequestEvent) || + executeDirectDispatch(terminationRequestEvent); + if (!terminationRequestEvent.isPersistent()) { + terminationRequestEvent.constructor.release(terminationRequestEvent); + } + + if (shouldSwitch) { + const terminateEvent = ResponderSyntheticEvent.getPooled( + eventTypes.responderTerminate, + responderInst, + nativeEvent, + nativeEventTarget, + ); + terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; + accumulateDirectDispatches(terminateEvent); + extracted = accumulate(extracted, [grantEvent, terminateEvent]); + changeResponder(wantsResponderInst, blockHostResponder); + } else { + const rejectEvent = ResponderSyntheticEvent.getPooled( + eventTypes.responderReject, + wantsResponderInst, + nativeEvent, + nativeEventTarget, + ); + rejectEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; + accumulateDirectDispatches(rejectEvent); + extracted = accumulate(extracted, rejectEvent); + } + } else { + extracted = accumulate(extracted, grantEvent); + changeResponder(wantsResponderInst, blockHostResponder); + } + return extracted; +} + +/** + * A transfer is a negotiation between a currently set responder and the next + * element to claim responder status. Any start event could trigger a transfer + * of responderInst. Any move event could trigger a transfer. + * + * @param {string} topLevelType Record from `BrowserEventConstants`. + * @return {boolean} True if a transfer of responder could possibly occur. + */ +function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) { + return ( + topLevelInst && + // responderIgnoreScroll: We are trying to migrate away from specifically + // tracking native scroll events here and responderIgnoreScroll indicates we + // will send topTouchCancel to handle canceling touch events instead + ((topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll) || + (trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE) || + isStartish(topLevelType) || + isMoveish(topLevelType)) + ); +} + +/** + * Returns whether or not this touch end event makes it such that there are no + * longer any touches that started inside of the current `responderInst`. + * + * @param {NativeEvent} nativeEvent Native touch end event. + * @return {boolean} Whether or not this touch end event ends the responder. + */ +function noResponderTouches(nativeEvent) { + const touches = nativeEvent.touches; + if (!touches || touches.length === 0) { + return true; + } + for (let i = 0; i < touches.length; i++) { + const activeTouch = touches[i]; + const target = activeTouch.target; + if (target !== null && target !== undefined && target !== 0) { + // Is the original touch location inside of the current responder? + const targetInst = getInstanceFromNode(target); + if (isAncestor(responderInst, targetInst)) { + return false; + } + } + } + return true; +} + +const ResponderEventPlugin = { + /* For unit testing only */ + _getResponder: function() { + return responderInst; + }, + + eventTypes: eventTypes, + + /** + * We must be resilient to `targetInst` being `null` on `touchMove` or + * `touchEnd`. On certain platforms, this means that a native scroll has + * assumed control and the original touch targets are destroyed. + */ + extractEvents: function( + topLevelType, + targetInst, + nativeEvent, + nativeEventTarget, + eventSystemFlags, + ) { + if (isStartish(topLevelType)) { + trackedTouchCount += 1; + } else if (isEndish(topLevelType)) { + if (trackedTouchCount >= 0) { + trackedTouchCount -= 1; + } else { + if (__DEV__) { + console.warn( + 'Ended a touch event which was not counted in `trackedTouchCount`.', + ); + } + return null; + } + } + + ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent); + + let extracted = canTriggerTransfer(topLevelType, targetInst, nativeEvent) + ? setResponderAndExtractTransfer( + topLevelType, + targetInst, + nativeEvent, + nativeEventTarget, + ) + : null; + // Responder may or may not have transferred on a new touch start/move. + // Regardless, whoever is the responder after any potential transfer, we + // direct all touch start/move/ends to them in the form of + // `onResponderMove/Start/End`. These will be called for *every* additional + // finger that move/start/end, dispatched directly to whoever is the + // current responder at that moment, until the responder is "released". + // + // These multiple individual change touch events are are always bookended + // by `onResponderGrant`, and one of + // (`onResponderRelease/onResponderTerminate`). + const isResponderTouchStart = responderInst && isStartish(topLevelType); + const isResponderTouchMove = responderInst && isMoveish(topLevelType); + const isResponderTouchEnd = responderInst && isEndish(topLevelType); + const incrementalTouch = isResponderTouchStart + ? eventTypes.responderStart + : isResponderTouchMove + ? eventTypes.responderMove + : isResponderTouchEnd + ? eventTypes.responderEnd + : null; + + if (incrementalTouch) { + const gesture = ResponderSyntheticEvent.getPooled( + incrementalTouch, + responderInst, + nativeEvent, + nativeEventTarget, + ); + gesture.touchHistory = ResponderTouchHistoryStore.touchHistory; + accumulateDirectDispatches(gesture); + extracted = accumulate(extracted, gesture); + } + + const isResponderTerminate = + responderInst && topLevelType === TOP_TOUCH_CANCEL; + const isResponderRelease = + responderInst && + !isResponderTerminate && + isEndish(topLevelType) && + noResponderTouches(nativeEvent); + const finalTouch = isResponderTerminate + ? eventTypes.responderTerminate + : isResponderRelease + ? eventTypes.responderRelease + : null; + if (finalTouch) { + const finalEvent = ResponderSyntheticEvent.getPooled( + finalTouch, + responderInst, + nativeEvent, + nativeEventTarget, + ); + finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; + accumulateDirectDispatches(finalEvent); + extracted = accumulate(extracted, finalEvent); + changeResponder(null); + } + + return extracted; + }, + + GlobalResponderHandler: null, + + injection: { + /** + * @param {{onChange: (ReactID, ReactID) => void} GlobalResponderHandler + * Object that handles any change in responder. Use this to inject + * integration with an existing touch handling system etc. + */ + injectGlobalResponderHandler(GlobalResponderHandler) { + ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler; + }, + }, +}; + +export default ResponderEventPlugin; diff --git a/packages/react-native-renderer/src/legacy-events/ResponderSyntheticEvent.js b/packages/react-native-renderer/src/legacy-events/ResponderSyntheticEvent.js new file mode 100644 index 000000000000..00a8c6876b20 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/ResponderSyntheticEvent.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import SyntheticEvent from './SyntheticEvent'; + +/** + * `touchHistory` isn't actually on the native event, but putting it in the + * interface will ensure that it is cleaned up when pooled/destroyed. The + * `ResponderEventPlugin` will populate it appropriately. + */ +const ResponderSyntheticEvent = SyntheticEvent.extend({ + touchHistory: function(nativeEvent) { + return null; // Actually doesn't even look at the native event. + }, +}); + +export default ResponderSyntheticEvent; diff --git a/packages/react-native-renderer/src/legacy-events/ResponderTopLevelEventTypes.js b/packages/react-native-renderer/src/legacy-events/ResponderTopLevelEventTypes.js new file mode 100644 index 000000000000..75d9736cfb98 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/ResponderTopLevelEventTypes.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export const TOP_TOUCH_START = 'topTouchStart'; +export const TOP_TOUCH_MOVE = 'topTouchMove'; +export const TOP_TOUCH_END = 'topTouchEnd'; +export const TOP_TOUCH_CANCEL = 'topTouchCancel'; +export const TOP_SCROLL = 'topScroll'; +export const TOP_SELECTION_CHANGE = 'topSelectionChange'; + +export function isStartish(topLevelType: mixed): boolean { + return topLevelType === TOP_TOUCH_START; +} + +export function isMoveish(topLevelType: mixed): boolean { + return topLevelType === TOP_TOUCH_MOVE; +} + +export function isEndish(topLevelType: mixed): boolean { + return topLevelType === TOP_TOUCH_END || topLevelType === TOP_TOUCH_CANCEL; +} + +export const startDependencies = [TOP_TOUCH_START]; +export const moveDependencies = [TOP_TOUCH_MOVE]; +export const endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END]; diff --git a/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js b/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js new file mode 100644 index 000000000000..643e2c130a4e --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js @@ -0,0 +1,222 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import invariant from 'shared/invariant'; + +import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes'; + +/** + * Tracks the position and time of each active touch by `touch.identifier`. We + * should typically only see IDs in the range of 1-20 because IDs get recycled + * when touches end and start again. + */ +type TouchRecord = {| + touchActive: boolean, + startPageX: number, + startPageY: number, + startTimeStamp: number, + currentPageX: number, + currentPageY: number, + currentTimeStamp: number, + previousPageX: number, + previousPageY: number, + previousTimeStamp: number, +|}; + +const MAX_TOUCH_BANK = 20; +const touchBank: Array = []; +const touchHistory = { + touchBank, + numberActiveTouches: 0, + // If there is only one active touch, we remember its location. This prevents + // us having to loop through all of the touches all the time in the most + // common case. + indexOfSingleActiveTouch: -1, + mostRecentTimeStamp: 0, +}; + +type Touch = { + identifier: ?number, + pageX: number, + pageY: number, + timestamp: number, + ... +}; +type TouchEvent = { + changedTouches: Array, + touches: Array, + ... +}; + +function timestampForTouch(touch: Touch): number { + // The legacy internal implementation provides "timeStamp", which has been + // renamed to "timestamp". Let both work for now while we iron it out + // TODO (evv): rename timeStamp to timestamp in internal code + return (touch: any).timeStamp || touch.timestamp; +} + +/** + * TODO: Instead of making gestures recompute filtered velocity, we could + * include a built in velocity computation that can be reused globally. + */ +function createTouchRecord(touch: Touch): TouchRecord { + return { + touchActive: true, + startPageX: touch.pageX, + startPageY: touch.pageY, + startTimeStamp: timestampForTouch(touch), + currentPageX: touch.pageX, + currentPageY: touch.pageY, + currentTimeStamp: timestampForTouch(touch), + previousPageX: touch.pageX, + previousPageY: touch.pageY, + previousTimeStamp: timestampForTouch(touch), + }; +} + +function resetTouchRecord(touchRecord: TouchRecord, touch: Touch): void { + touchRecord.touchActive = true; + touchRecord.startPageX = touch.pageX; + touchRecord.startPageY = touch.pageY; + touchRecord.startTimeStamp = timestampForTouch(touch); + touchRecord.currentPageX = touch.pageX; + touchRecord.currentPageY = touch.pageY; + touchRecord.currentTimeStamp = timestampForTouch(touch); + touchRecord.previousPageX = touch.pageX; + touchRecord.previousPageY = touch.pageY; + touchRecord.previousTimeStamp = timestampForTouch(touch); +} + +function getTouchIdentifier({identifier}: Touch): number { + invariant(identifier != null, 'Touch object is missing identifier.'); + if (__DEV__) { + if (identifier > MAX_TOUCH_BANK) { + console.error( + 'Touch identifier %s is greater than maximum supported %s which causes ' + + 'performance issues backfilling array locations for all of the indices.', + identifier, + MAX_TOUCH_BANK, + ); + } + } + return identifier; +} + +function recordTouchStart(touch: Touch): void { + const identifier = getTouchIdentifier(touch); + const touchRecord = touchBank[identifier]; + if (touchRecord) { + resetTouchRecord(touchRecord, touch); + } else { + touchBank[identifier] = createTouchRecord(touch); + } + touchHistory.mostRecentTimeStamp = timestampForTouch(touch); +} + +function recordTouchMove(touch: Touch): void { + const touchRecord = touchBank[getTouchIdentifier(touch)]; + if (touchRecord) { + touchRecord.touchActive = true; + touchRecord.previousPageX = touchRecord.currentPageX; + touchRecord.previousPageY = touchRecord.currentPageY; + touchRecord.previousTimeStamp = touchRecord.currentTimeStamp; + touchRecord.currentPageX = touch.pageX; + touchRecord.currentPageY = touch.pageY; + touchRecord.currentTimeStamp = timestampForTouch(touch); + touchHistory.mostRecentTimeStamp = timestampForTouch(touch); + } else { + if (__DEV__) { + console.warn( + 'Cannot record touch move without a touch start.\n' + + 'Touch Move: %s\n' + + 'Touch Bank: %s', + printTouch(touch), + printTouchBank(), + ); + } + } +} + +function recordTouchEnd(touch: Touch): void { + const touchRecord = touchBank[getTouchIdentifier(touch)]; + if (touchRecord) { + touchRecord.touchActive = false; + touchRecord.previousPageX = touchRecord.currentPageX; + touchRecord.previousPageY = touchRecord.currentPageY; + touchRecord.previousTimeStamp = touchRecord.currentTimeStamp; + touchRecord.currentPageX = touch.pageX; + touchRecord.currentPageY = touch.pageY; + touchRecord.currentTimeStamp = timestampForTouch(touch); + touchHistory.mostRecentTimeStamp = timestampForTouch(touch); + } else { + if (__DEV__) { + console.warn( + 'Cannot record touch end without a touch start.\n' + + 'Touch End: %s\n' + + 'Touch Bank: %s', + printTouch(touch), + printTouchBank(), + ); + } + } +} + +function printTouch(touch: Touch): string { + return JSON.stringify({ + identifier: touch.identifier, + pageX: touch.pageX, + pageY: touch.pageY, + timestamp: timestampForTouch(touch), + }); +} + +function printTouchBank(): string { + let printed = JSON.stringify(touchBank.slice(0, MAX_TOUCH_BANK)); + if (touchBank.length > MAX_TOUCH_BANK) { + printed += ' (original size: ' + touchBank.length + ')'; + } + return printed; +} + +const ResponderTouchHistoryStore = { + recordTouchTrack(topLevelType: string, nativeEvent: TouchEvent): void { + if (isMoveish(topLevelType)) { + nativeEvent.changedTouches.forEach(recordTouchMove); + } else if (isStartish(topLevelType)) { + nativeEvent.changedTouches.forEach(recordTouchStart); + touchHistory.numberActiveTouches = nativeEvent.touches.length; + if (touchHistory.numberActiveTouches === 1) { + touchHistory.indexOfSingleActiveTouch = + nativeEvent.touches[0].identifier; + } + } else if (isEndish(topLevelType)) { + nativeEvent.changedTouches.forEach(recordTouchEnd); + touchHistory.numberActiveTouches = nativeEvent.touches.length; + if (touchHistory.numberActiveTouches === 1) { + for (let i = 0; i < touchBank.length; i++) { + const touchTrackToCheck = touchBank[i]; + if (touchTrackToCheck != null && touchTrackToCheck.touchActive) { + touchHistory.indexOfSingleActiveTouch = i; + break; + } + } + if (__DEV__) { + const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch]; + if (activeRecord == null || !activeRecord.touchActive) { + console.error('Cannot find single active touch.'); + } + } + } + } + }, + + touchHistory, +}; + +export default ResponderTouchHistoryStore; diff --git a/packages/react-native-renderer/src/legacy-events/SyntheticEvent.js b/packages/react-native-renderer/src/legacy-events/SyntheticEvent.js new file mode 100644 index 000000000000..b9ada217b0e6 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/SyntheticEvent.js @@ -0,0 +1,366 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* eslint valid-typeof: 0 */ + +import invariant from 'shared/invariant'; +import {enableModernEventSystem} from 'shared/ReactFeatureFlags'; + +const EVENT_POOL_SIZE = 10; + +/** + * @interface Event + * @see http://www.w3.org/TR/DOM-Level-3-Events/ + */ +const EventInterface = { + type: null, + target: null, + // currentTarget is set when dispatching; no use in copying it here + currentTarget: function() { + return null; + }, + eventPhase: null, + bubbles: null, + cancelable: null, + timeStamp: function(event) { + return event.timeStamp || Date.now(); + }, + defaultPrevented: null, + isTrusted: null, +}; + +function functionThatReturnsTrue() { + return true; +} + +function functionThatReturnsFalse() { + return false; +} + +/** + * Synthetic events are dispatched by event plugins, typically in response to a + * top-level event delegation handler. + * + * These systems should generally use pooling to reduce the frequency of garbage + * collection. The system should check `isPersistent` to determine whether the + * event should be released into the pool after being dispatched. Users that + * need a persisted event should invoke `persist`. + * + * Synthetic events (and subclasses) implement the DOM Level 3 Events API by + * normalizing browser quirks. Subclasses do not necessarily have to implement a + * DOM interface; custom application-specific events can also subclass this. + * + * @param {object} dispatchConfig Configuration used to dispatch this event. + * @param {*} targetInst Marker identifying the event target. + * @param {object} nativeEvent Native browser event. + * @param {DOMEventTarget} nativeEventTarget Target node. + */ +function SyntheticEvent( + dispatchConfig, + targetInst, + nativeEvent, + nativeEventTarget, +) { + if (__DEV__) { + // these have a getter/setter for warnings + delete this.nativeEvent; + delete this.preventDefault; + delete this.stopPropagation; + delete this.isDefaultPrevented; + delete this.isPropagationStopped; + } + + this.dispatchConfig = dispatchConfig; + this._targetInst = targetInst; + this.nativeEvent = nativeEvent; + if (!enableModernEventSystem) { + this._dispatchListeners = null; + this._dispatchInstances = null; + } + + const Interface = this.constructor.Interface; + for (const propName in Interface) { + if (!Interface.hasOwnProperty(propName)) { + continue; + } + if (__DEV__) { + delete this[propName]; // this has a getter/setter for warnings + } + const normalize = Interface[propName]; + if (normalize) { + this[propName] = normalize(nativeEvent); + } else { + if (propName === 'target') { + this.target = nativeEventTarget; + } else { + this[propName] = nativeEvent[propName]; + } + } + } + + const defaultPrevented = + nativeEvent.defaultPrevented != null + ? nativeEvent.defaultPrevented + : nativeEvent.returnValue === false; + if (defaultPrevented) { + this.isDefaultPrevented = functionThatReturnsTrue; + } else { + this.isDefaultPrevented = functionThatReturnsFalse; + } + this.isPropagationStopped = functionThatReturnsFalse; + return this; +} + +Object.assign(SyntheticEvent.prototype, { + preventDefault: function() { + this.defaultPrevented = true; + const event = this.nativeEvent; + if (!event) { + return; + } + + if (event.preventDefault) { + event.preventDefault(); + } else if (typeof event.returnValue !== 'unknown') { + event.returnValue = false; + } + this.isDefaultPrevented = functionThatReturnsTrue; + }, + + stopPropagation: function() { + const event = this.nativeEvent; + if (!event) { + return; + } + + if (event.stopPropagation) { + event.stopPropagation(); + } else if (typeof event.cancelBubble !== 'unknown') { + // The ChangeEventPlugin registers a "propertychange" event for + // IE. This event does not support bubbling or cancelling, and + // any references to cancelBubble throw "Member not found". A + // typeof check of "unknown" circumvents this issue (and is also + // IE specific). + event.cancelBubble = true; + } + + this.isPropagationStopped = functionThatReturnsTrue; + }, + + /** + * We release all dispatched `SyntheticEvent`s after each event loop, adding + * them back into the pool. This allows a way to hold onto a reference that + * won't be added back into the pool. + */ + persist: function() { + // Modern event system doesn't use pooling. + if (!enableModernEventSystem) { + this.isPersistent = functionThatReturnsTrue; + } + }, + + /** + * Checks if this event should be released back into the pool. + * + * @return {boolean} True if this should not be released, false otherwise. + */ + isPersistent: enableModernEventSystem + ? functionThatReturnsTrue + : functionThatReturnsFalse, + + /** + * `PooledClass` looks for `destructor` on each instance it releases. + */ + destructor: function() { + // Modern event system doesn't use pooling. + if (!enableModernEventSystem) { + const Interface = this.constructor.Interface; + for (const propName in Interface) { + if (__DEV__) { + Object.defineProperty( + this, + propName, + getPooledWarningPropertyDefinition(propName, Interface[propName]), + ); + } else { + this[propName] = null; + } + } + this.dispatchConfig = null; + this._targetInst = null; + this.nativeEvent = null; + this.isDefaultPrevented = functionThatReturnsFalse; + this.isPropagationStopped = functionThatReturnsFalse; + this._dispatchListeners = null; + this._dispatchInstances = null; + if (__DEV__) { + Object.defineProperty( + this, + 'nativeEvent', + getPooledWarningPropertyDefinition('nativeEvent', null), + ); + Object.defineProperty( + this, + 'isDefaultPrevented', + getPooledWarningPropertyDefinition( + 'isDefaultPrevented', + functionThatReturnsFalse, + ), + ); + Object.defineProperty( + this, + 'isPropagationStopped', + getPooledWarningPropertyDefinition( + 'isPropagationStopped', + functionThatReturnsFalse, + ), + ); + Object.defineProperty( + this, + 'preventDefault', + getPooledWarningPropertyDefinition('preventDefault', () => {}), + ); + Object.defineProperty( + this, + 'stopPropagation', + getPooledWarningPropertyDefinition('stopPropagation', () => {}), + ); + } + } + }, +}); + +SyntheticEvent.Interface = EventInterface; + +/** + * Helper to reduce boilerplate when creating subclasses. + */ +SyntheticEvent.extend = function(Interface) { + const Super = this; + + const E = function() {}; + E.prototype = Super.prototype; + const prototype = new E(); + + function Class() { + return Super.apply(this, arguments); + } + Object.assign(prototype, Class.prototype); + Class.prototype = prototype; + Class.prototype.constructor = Class; + + Class.Interface = Object.assign({}, Super.Interface, Interface); + Class.extend = Super.extend; + addEventPoolingTo(Class); + + return Class; +}; + +addEventPoolingTo(SyntheticEvent); + +/** + * Helper to nullify syntheticEvent instance properties when destructing + * + * @param {String} propName + * @param {?object} getVal + * @return {object} defineProperty object + */ +function getPooledWarningPropertyDefinition(propName, getVal) { + const isFunction = typeof getVal === 'function'; + return { + configurable: true, + set: set, + get: get, + }; + + function set(val) { + const action = isFunction ? 'setting the method' : 'setting the property'; + warn(action, 'This is effectively a no-op'); + return val; + } + + function get() { + const action = isFunction + ? 'accessing the method' + : 'accessing the property'; + const result = isFunction + ? 'This is a no-op function' + : 'This is set to null'; + warn(action, result); + return getVal; + } + + function warn(action, result) { + if (__DEV__) { + console.error( + "This synthetic event is reused for performance reasons. If you're seeing this, " + + "you're %s `%s` on a released/nullified synthetic event. %s. " + + 'If you must keep the original synthetic event around, use event.persist(). ' + + 'See https://fb.me/react-event-pooling for more information.', + action, + propName, + result, + ); + } + } +} + +function createOrGetPooledEvent( + dispatchConfig, + targetInst, + nativeEvent, + nativeInst, +) { + const EventConstructor = this; + // Modern event system doesn't use pooling. + if (!enableModernEventSystem) { + if (EventConstructor.eventPool.length) { + const instance = EventConstructor.eventPool.pop(); + EventConstructor.call( + instance, + dispatchConfig, + targetInst, + nativeEvent, + nativeInst, + ); + return instance; + } + } + return new EventConstructor( + dispatchConfig, + targetInst, + nativeEvent, + nativeInst, + ); +} + +function releasePooledEvent(event) { + // Modern event system doesn't use pooling. + if (!enableModernEventSystem) { + const EventConstructor = this; + invariant( + event instanceof EventConstructor, + 'Trying to release an event instance into a pool of a different type.', + ); + event.destructor(); + if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) { + EventConstructor.eventPool.push(event); + } + } +} + +function addEventPoolingTo(EventConstructor) { + EventConstructor.getPooled = createOrGetPooledEvent; + + // Modern event system doesn't use pooling. + if (!enableModernEventSystem) { + EventConstructor.eventPool = []; + EventConstructor.release = releasePooledEvent; + } +} + +export default SyntheticEvent; diff --git a/packages/react-native-renderer/src/legacy-events/TopLevelEventTypes.js b/packages/react-native-renderer/src/legacy-events/TopLevelEventTypes.js new file mode 100644 index 000000000000..ce64f7a8e5d6 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/TopLevelEventTypes.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export type RNTopLevelEventType = + | 'topMouseDown' + | 'topMouseMove' + | 'topMouseUp' + | 'topScroll' + | 'topSelectionChange' + | 'topTouchCancel' + | 'topTouchEnd' + | 'topTouchMove' + | 'topTouchStart'; + +export opaque type DOMTopLevelEventType = string; + +// Do not use the below two methods directly! +// Instead use constants exported from DOMTopLevelEventTypes in ReactDOM. +// (It is the only module that is allowed to access these methods.) + +export function unsafeCastStringToDOMTopLevelType( + topLevelType: string, +): DOMTopLevelEventType { + return topLevelType; +} + +export function unsafeCastDOMTopLevelTypeToString( + topLevelType: DOMTopLevelEventType, +): string { + return topLevelType; +} + +export type TopLevelType = DOMTopLevelEventType | RNTopLevelEventType; diff --git a/packages/react-native-renderer/src/legacy-events/accumulate.js b/packages/react-native-renderer/src/legacy-events/accumulate.js new file mode 100644 index 000000000000..6fcf9f1fd80c --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/accumulate.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import invariant from 'shared/invariant'; + +/** + * Accumulates items that must not be null or undefined. + * + * This is used to conserve memory by avoiding array allocations. + * + * @return {*|array<*>} An accumulation of items. + */ +function accumulate( + current: ?(T | Array), + next: T | Array, +): T | Array { + invariant( + next != null, + 'accumulate(...): Accumulated items must not be null or undefined.', + ); + + if (current == null) { + return next; + } + + // Both are not empty. Warning: Never call x.concat(y) when you are not + // certain that x is an Array (x could be a string with concat method). + if (Array.isArray(current)) { + return current.concat(next); + } + + if (Array.isArray(next)) { + return [current].concat(next); + } + + return [current, next]; +} + +export default accumulate; diff --git a/packages/react-native-renderer/src/legacy-events/accumulateInto.js b/packages/react-native-renderer/src/legacy-events/accumulateInto.js new file mode 100644 index 000000000000..35625311da88 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/accumulateInto.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import invariant from 'shared/invariant'; + +/** + * Accumulates items that must not be null or undefined into the first one. This + * is used to conserve memory by avoiding array allocations, and thus sacrifices + * API cleanness. Since `current` can be null before being passed in and not + * null after this function, make sure to assign it back to `current`: + * + * `a = accumulateInto(a, b);` + * + * This API should be sparingly used. Try `accumulate` for something cleaner. + * + * @return {*|array<*>} An accumulation of items. + */ + +function accumulateInto( + current: ?(Array | T), + next: T | Array, +): T | Array { + invariant( + next != null, + 'accumulateInto(...): Accumulated items must not be null or undefined.', + ); + + if (current == null) { + return next; + } + + // Both are not empty. Warning: Never call x.concat(y) when you are not + // certain that x is an Array (x could be a string with concat method). + if (Array.isArray(current)) { + if (Array.isArray(next)) { + current.push.apply(current, next); + return current; + } + current.push(next); + return current; + } + + if (Array.isArray(next)) { + // A bit too dangerous to mutate `next`. + return [current].concat(next); + } + + return [current, next]; +} + +export default accumulateInto; diff --git a/packages/react-native-renderer/src/legacy-events/forEachAccumulated.js b/packages/react-native-renderer/src/legacy-events/forEachAccumulated.js new file mode 100644 index 000000000000..369be5605cc4 --- /dev/null +++ b/packages/react-native-renderer/src/legacy-events/forEachAccumulated.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +/** + * @param {array} arr an "accumulation" of items which is either an Array or + * a single item. Useful when paired with the `accumulate` module. This is a + * simple utility that allows us to reason about a collection of items, but + * handling the case when there is exactly one item (and we do not need to + * allocate an array). + * @param {function} cb Callback invoked with each element or a collection. + * @param {?} [scope] Scope used as `this` in a callback. + */ +function forEachAccumulated( + arr: ?(Array | T), + cb: (elem: T) => void, + scope: ?any, +) { + if (Array.isArray(arr)) { + arr.forEach(cb, scope); + } else if (arr) { + cb.call(scope, arr); + } +} + +export default forEachAccumulated; diff --git a/scripts/flow/react-native-host-hooks.js b/scripts/flow/react-native-host-hooks.js index 718fe20d9a71..c42159e0fc22 100644 --- a/scripts/flow/react-native-host-hooks.js +++ b/scripts/flow/react-native-host-hooks.js @@ -16,7 +16,7 @@ import type { ReactNativeBaseComponentViewConfig, ViewConfigGetter, } from 'react-native-renderer/src/ReactNativeTypes'; -import type {RNTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {RNTopLevelEventType} from 'react-native-renderer/src/legacy-events/TopLevelEventTypes'; import type {CapturedError} from 'react-reconciler/src/ReactCapturedValue'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; diff --git a/scripts/shared/pathsByLanguageVersion.js b/scripts/shared/pathsByLanguageVersion.js index dddd84835927..f1b19a8893a3 100644 --- a/scripts/shared/pathsByLanguageVersion.js +++ b/scripts/shared/pathsByLanguageVersion.js @@ -15,7 +15,6 @@ const esNextPaths = [ 'packages/dom-event-testing-library/**/*.js', 'packages/react-interactions/**/*.js', 'packages/react-interactions/**/*.js', - 'packages/legacy-events/**/*.js', 'packages/shared/**/*.js', // Shims and Flow environment 'scripts/flow/*.js',