Fork legacy-events folder into react-dom and react-native (#19228)

This commit is contained in:
Dan Abramov 2020-07-01 16:37:13 +01:00 committed by GitHub
parent cd0275dcb2
commit e3f4eb7272
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 2421 additions and 106 deletions

View File

@ -1,5 +0,0 @@
{
"private": true,
"name": "legacy-events",
"version": "0.0.0"
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View File

@ -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 {

View File

@ -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,

View File

@ -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';
/**

View File

@ -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,

View File

@ -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.

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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',

View File

@ -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';

View File

@ -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';

View File

@ -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;

View File

@ -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 {

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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;

View File

@ -8,7 +8,7 @@
import {
registrationNameModules,
possibleRegistrationNames,
} from 'legacy-events/EventPluginRegistry';
} from '../legacy-events/EventPluginRegistry';
import {
ATTRIBUTE_NAME_CHAR,

View File

@ -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,

View File

@ -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:

View File

@ -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};

View File

@ -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,

View File

@ -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 {

View File

@ -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 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 {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';

View File

@ -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,

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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')

View File

@ -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 {

View File

@ -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> | 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> | 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();
}

View File

@ -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<AnyNativeEvent>
| ModernPluginModule<AnyNativeEvent>,
...,
};
type EventPluginOrder = null | Array<PluginName>;
/**
* 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<AnyNativeEvent>
| ModernPluginModule<AnyNativeEvent>,
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<AnyNativeEvent>
| ModernPluginModule<AnyNativeEvent>,
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<AnyNativeEvent>],
): 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,
);
}
}
}

View File

@ -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;
}

View File

@ -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<NativeEvent> = {
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<DispatchQueueItemPhaseEntry>;
export type DispatchQueueItem = {|
event: ReactSyntheticEvent,
capture: DispatchQueueItemPhase,
bubble: DispatchQueueItemPhase,
|};
export type DispatchQueue = Array<DispatchQueueItem>;
export type ModernPluginModule<NativeEvent> = {
eventTypes: EventTypes,
extractEvents: (
dispatchQueue: DispatchQueue,
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeTarget: NativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: number,
container: null | EventTarget,
) => void,
};

View File

@ -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;
}

View File

@ -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<TopLevelType>,
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 | null> | Fiber,
_dispatchListeners?: null | Array<Function> | Function,
_targetInst: Fiber,
type: string,
currentTarget: null | EventTarget,
|};

View File

@ -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> | 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;

View File

@ -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;

View File

@ -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];

View File

@ -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<TouchRecord> = [];
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<Touch>,
touches: Array<Touch>,
...
};
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;

View File

@ -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;

View File

@ -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;

View File

@ -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<T>(
current: ?(T | Array<T>),
next: T | Array<T>,
): T | Array<T> {
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;

View File

@ -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<T>(
current: ?(Array<T> | T),
next: T | Array<T>,
): T | Array<T> {
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;

View File

@ -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<T>(
arr: ?(Array<T> | 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;

View File

@ -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';

View File

@ -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',