Use browser event names for top-level event types in React DOM (#12629)

* Add TopLevelEventTypes

* Fix `ReactBrowserEventEmitter`

* Fix EventPluginUtils

* Fix TapEventPlugin

* Fix ResponderEventPlugin

* Update ReactDOMFiberComponent

* Fix BeforeInputEventPlugin

* Fix ChangeEventPlugin

* Fix EnterLeaveEventPlugin

* Add missing non top event type used in ChangeEventPlugin

* Fix SelectEventPlugin

* Fix SimpleEventPlugin

* Fix outstanding Flow issues and move TopLevelEventTypes

* Inline a list of all events in `ReactTestUtils`

* Fix tests

* Make it pretty

* Fix completly unrelated typo

* Don’t use map constructor because of IE11

* Update typings, revert changes to native code

* Make topLevelTypes in ResponderEventPlugin injectable and create DOM and ReactNative variant

* Set proper dependencies for DOMResponderEventPlugin

* Prettify

* Make some react dom tests no longer depend on internal API

* Use factories to create top level speific generic event modules

* Remove unused dependency

* Revert exposed module renaming, hide store creation, and inline dependency decleration

* Add Flow types to createResponderEventPlugin and its consumers

* Remove unused dependency

* Use opaque flow type for TopLevelType

* Add missing semis

* Use raw event names as top level identifer

* Upgrade baylon

This is required for parsing opaque flow types in our CI tests.

* Clean up flow types

* Revert Map changes of ReactBrowserEventEmitter

* Upgrade babel-* packages

Apparently local unit tests also have issues with parsing JavaScript
modules that contain opaque types (not sure why I didn't notice
earlier!?).

* Revert Map changes of SimpleEventPlugin

* Clean up ReactTestUtils

* Add missing semi

* Fix Flow issue

* Make TopLevelType clearer

* Favor for loops

* Explain the new DOMTopLevelEventTypes concept

* Use static injection for Responder plugin types

* Remove null check and rely on flow checks

* Add missing ResponderEventPlugin dependencies
This commit is contained in:
Philipp Spieß 2018-05-15 11:38:50 +02:00 committed by Dan Abramov
parent 1047980dca
commit e96dc14059
32 changed files with 1969 additions and 1000 deletions

View File

@ -15,7 +15,7 @@ function NumberInputs() {
`}
affectedBrowsers="IE Edge, IE 11">
<TestCase.Steps>
<li>Type any string (not an actual password</li>
<li>Type any string (not an actual password)</li>
</TestCase.Steps>
<TestCase.ExpectedResult>

View File

@ -36,7 +36,7 @@
"babel-plugin-transform-regenerator": "^6.26.0",
"babel-preset-react": "^6.5.0",
"babel-traverse": "^6.9.0",
"babylon": "6.15.0",
"babylon": "6.18.0",
"bundle-collapser": "^1.1.1",
"chalk": "^1.1.3",
"cli-table": "^0.3.1",

View File

@ -25,6 +25,7 @@ import type {PluginModule} from './PluginModuleType';
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {AnyNativeEvent} from './PluginModuleType';
import type {TopLevelType} from './TopLevelEventTypes';
/**
* Internal queue of events that have accumulated their dispatches and are
@ -165,7 +166,7 @@ export function getListener(inst: Fiber, registrationName: string) {
* @internal
*/
function extractEvents(
topLevelType: string,
topLevelType: TopLevelType,
targetInst: Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
@ -227,7 +228,7 @@ export function runEventsInBatch(
}
export function runExtractedEventsInBatch(
topLevelType: string,
topLevelType: TopLevelType,
targetInst: Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,

View File

@ -30,21 +30,6 @@ export const injection = {
},
};
export function isEndish(topLevelType) {
return (
topLevelType === 'topMouseUp' ||
topLevelType === 'topTouchEnd' ||
topLevelType === 'topTouchCancel'
);
}
export function isMoveish(topLevelType) {
return topLevelType === 'topMouseMove' || topLevelType === 'topTouchMove';
}
export function isStartish(topLevelType) {
return topLevelType === 'topMouseDown' || topLevelType === 'topTouchStart';
}
let validateEventDispatches;
if (__DEV__) {
validateEventDispatches = function(event) {

View File

@ -12,6 +12,7 @@ import type {
DispatchConfig,
ReactSyntheticEvent,
} from './ReactSyntheticEventType';
import type {TopLevelType} from './TopLevelEventTypes';
export type EventTypes = {[key: string]: DispatchConfig};
@ -22,7 +23,7 @@ export type PluginName = string;
export type PluginModule<NativeEvent> = {
eventTypes: EventTypes,
extractEvents: (
topLevelType: string,
topLevelType: TopLevelType,
targetInst: Fiber,
nativeTarget: NativeEvent,
nativeEventTarget: EventTarget,

View File

@ -9,9 +9,10 @@
*/
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {TopLevelType} from './TopLevelEventTypes';
export type DispatchConfig = {
dependencies: Array<string>,
dependencies: Array<TopLevelType>,
phasedRegistrationNames?: {
bubbled: string,
captured: string,

View File

@ -8,9 +8,6 @@
import {getLowestCommonAncestor, isAncestor} from 'shared/ReactTreeTraversal';
import {
isStartish,
isMoveish,
isEndish,
executeDirectDispatch,
hasDispatches,
executeDispatchesInOrderStopAtTrue,
@ -24,6 +21,17 @@ import {
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';
/**
* Instance of element that should respond to touch/move types of interactions,
@ -37,11 +45,6 @@ let responderInst = null;
*/
let trackedTouchCount = 0;
/**
* Last reported number of active touches.
*/
let previousActiveTouches = 0;
const changeResponder = function(nextResponderInst, blockHostResponder) {
const oldResponderInst = responderInst;
responderInst = nextResponderInst;
@ -64,6 +67,7 @@ const eventTypes = {
bubbled: 'onStartShouldSetResponder',
captured: 'onStartShouldSetResponderCapture',
},
dependencies: startDependencies,
},
/**
@ -80,6 +84,7 @@ const eventTypes = {
bubbled: 'onScrollShouldSetResponder',
captured: 'onScrollShouldSetResponderCapture',
},
dependencies: [TOP_SCROLL],
},
/**
@ -94,6 +99,7 @@ const eventTypes = {
bubbled: 'onSelectionChangeShouldSetResponder',
captured: 'onSelectionChangeShouldSetResponderCapture',
},
dependencies: [TOP_SELECTION_CHANGE],
},
/**
@ -105,21 +111,44 @@ const eventTypes = {
bubbled: 'onMoveShouldSetResponder',
captured: 'onMoveShouldSetResponderCapture',
},
dependencies: moveDependencies,
},
/**
* Direct responder events dispatched directly to responder. Do not bubble.
*/
responderStart: {registrationName: 'onResponderStart'},
responderMove: {registrationName: 'onResponderMove'},
responderEnd: {registrationName: 'onResponderEnd'},
responderRelease: {registrationName: 'onResponderRelease'},
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: [],
},
responderGrant: {registrationName: 'onResponderGrant'},
responderReject: {registrationName: 'onResponderReject'},
responderTerminate: {registrationName: 'onResponderTerminate'},
};
/**
@ -322,7 +351,7 @@ function setResponderAndExtractTransfer(
? eventTypes.startShouldSetResponder
: isMoveish(topLevelType)
? eventTypes.moveShouldSetResponder
: topLevelType === 'topSelectionChange'
: topLevelType === TOP_SELECTION_CHANGE
? eventTypes.selectionChangeShouldSetResponder
: eventTypes.scrollShouldSetResponder;
@ -427,8 +456,8 @@ function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
// 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 === 'topScroll' && !nativeEvent.responderIgnoreScroll) ||
(trackedTouchCount > 0 && topLevelType === 'topSelectionChange') ||
((topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll) ||
(trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE) ||
isStartish(topLevelType) ||
isMoveish(topLevelType))
);
@ -534,7 +563,7 @@ const ResponderEventPlugin = {
}
const isResponderTerminate =
responderInst && topLevelType === 'topTouchCancel';
responderInst && topLevelType === TOP_TOUCH_CANCEL;
const isResponderRelease =
responderInst &&
!isResponderTerminate &&
@ -556,23 +585,10 @@ const ResponderEventPlugin = {
changeResponder(null);
}
const numberActiveTouches =
ResponderTouchHistoryStore.touchHistory.numberActiveTouches;
if (
ResponderEventPlugin.GlobalInteractionHandler &&
numberActiveTouches !== previousActiveTouches
) {
ResponderEventPlugin.GlobalInteractionHandler.onChange(
numberActiveTouches,
);
}
previousActiveTouches = numberActiveTouches;
return extracted;
},
GlobalResponderHandler: null,
GlobalInteractionHandler: null,
injection: {
/**
@ -580,17 +596,9 @@ const ResponderEventPlugin = {
* Object that handles any change in responder. Use this to inject
* integration with an existing touch handling system etc.
*/
injectGlobalResponderHandler: function(GlobalResponderHandler) {
injectGlobalResponderHandler(GlobalResponderHandler) {
ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
},
/**
* @param {{onChange: (numberActiveTouches) => void} GlobalInteractionHandler
* Object that handles any change in the number of active touches.
*/
injectGlobalInteractionHandler: function(GlobalInteractionHandler) {
ResponderEventPlugin.GlobalInteractionHandler = GlobalInteractionHandler;
},
},
};

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @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

@ -10,7 +10,7 @@
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import {isEndish, isMoveish, isStartish} from './EventPluginUtils';
import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes';
/**
* Tracks the position and time of each active touch by `touch.identifier`. We

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {DOMTopLevelEventType} from 'react-dom/src/events/DOMTopLevelEventTypes';
type RNTopLevelEventType =
| 'topMouseDown'
| 'topMouseMove'
| 'topMouseUp'
| 'topScroll'
| 'topSelectionChange'
| 'topTouchCancel'
| 'topTouchEnd'
| 'topTouchMove'
| 'topTouchStart';
export type TopLevelType = DOMTopLevelEventType | RNTopLevelEventType;

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
// Note: ideally these would be imported from DOMTopLevelEventTypes,
// but our build system currently doesn't let us do that from a fork.
export const TOP_TOUCH_START = 'touchstart';
export const TOP_TOUCH_MOVE = 'touchmove';
export const TOP_TOUCH_END = 'touchend';
export const TOP_TOUCH_CANCEL = 'touchcancel';
export const TOP_SCROLL = 'scroll';
export const TOP_SELECTION_CHANGE = 'selectionchange';
export const TOP_MOUSE_DOWN = 'mousedown';
export const TOP_MOUSE_MOVE = 'mousemove';
export const TOP_MOUSE_UP = 'mouseup';
export function isStartish(topLevelType: mixed): boolean {
return topLevelType === TOP_TOUCH_START || topLevelType === TOP_MOUSE_DOWN;
}
export function isMoveish(topLevelType: mixed): boolean {
return topLevelType === TOP_TOUCH_MOVE || topLevelType === TOP_MOUSE_MOVE;
}
export function isEndish(topLevelType: mixed): boolean {
return (
topLevelType === TOP_TOUCH_END ||
topLevelType === TOP_TOUCH_CANCEL ||
topLevelType === TOP_MOUSE_UP
);
}
export const startDependencies = [TOP_TOUCH_START, TOP_MOUSE_DOWN];
export const moveDependencies = [TOP_TOUCH_MOVE, TOP_MOUSE_MOVE];
export const endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END, TOP_MOUSE_UP];

View File

@ -309,14 +309,9 @@ describe('ReactDOM', () => {
const actual = [];
function click(node) {
const fakeNativeEvent = function() {};
fakeNativeEvent.target = node;
fakeNativeEvent.path = [node, container];
ReactTestUtils.simulateNativeEventOnNode(
'topClick',
node,
fakeNativeEvent,
);
ReactTestUtils.Simulate.click(node, {
path: [node, container],
});
}
class Wrapper extends React.Component {

View File

@ -838,12 +838,7 @@ describe('ReactDOMFiber', () => {
expect(portal.tagName).toBe('DIV');
const fakeNativeEvent = {};
ReactTestUtils.simulateNativeEventOnNode(
'topClick',
portal,
fakeNativeEvent,
);
ReactTestUtils.Simulate.click(portal);
expect(ops).toEqual(['portal clicked', 'parent clicked']);
});
@ -858,14 +853,12 @@ describe('ReactDOMFiber', () => {
function simulateMouseMove(from, to) {
if (from) {
ReactTestUtils.simulateNativeEventOnNode('topMouseOut', from, {
target: from,
ReactTestUtils.SimulateNative.mouseOut(from, {
relatedTarget: to,
});
}
if (to) {
ReactTestUtils.simulateNativeEventOnNode('topMouseOver', to, {
target: to,
ReactTestUtils.SimulateNative.mouseOver(to, {
relatedTarget: from,
});
}
@ -983,12 +976,7 @@ describe('ReactDOMFiber', () => {
expect(node.tagName).toEqual('DIV');
function click(target) {
const fakeNativeEvent = {};
ReactTestUtils.simulateNativeEventOnNode(
'topClick',
target,
fakeNativeEvent,
);
ReactTestUtils.Simulate.click(target);
}
click(node);

View File

@ -694,10 +694,9 @@ describe('ReactDOMInput', () => {
setUntrackedValue.call(node, 'giraffe');
const fakeNativeEvent = function() {};
fakeNativeEvent.target = node;
fakeNativeEvent.path = [node, container];
ReactTestUtils.simulateNativeEventOnNode('topInput', node, fakeNativeEvent);
ReactTestUtils.SimulateNative.input(node, {
path: [node, container],
});
expect(handled).toBe(true);
});

View File

@ -21,8 +21,16 @@ import * as ReactDOMFiberTextarea from './ReactDOMFiberTextarea';
import * as inputValueTracking from './inputValueTracking';
import setInnerHTML from './setInnerHTML';
import setTextContent from './setTextContent';
import {
TOP_ERROR,
TOP_INVALID,
TOP_LOAD,
TOP_RESET,
TOP_SUBMIT,
TOP_TOGGLE,
} from '../events/DOMTopLevelEventTypes';
import {listenTo, trapBubbledEvent} from '../events/ReactBrowserEventEmitter';
import {mediaEventTypes} from '../events/BrowserEventConstants';
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
import * as CSSPropertyOperations from '../shared/CSSPropertyOperations';
import {Namespaces, getIntrinsicNamespace} from '../shared/DOMNamespaces';
import {
@ -440,43 +448,41 @@ export function setInitialProperties(
switch (tag) {
case 'iframe':
case 'object':
trapBubbledEvent('topLoad', 'load', domElement);
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'video':
case 'audio':
// Create listener for each media event
for (const event in mediaEventTypes) {
if (mediaEventTypes.hasOwnProperty(event)) {
trapBubbledEvent(event, mediaEventTypes[event], domElement);
}
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
}
props = rawProps;
break;
case 'source':
trapBubbledEvent('topError', 'error', domElement);
trapBubbledEvent(TOP_ERROR, domElement);
props = rawProps;
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent('topError', 'error', domElement);
trapBubbledEvent('topLoad', 'load', domElement);
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'form':
trapBubbledEvent('topReset', 'reset', domElement);
trapBubbledEvent('topSubmit', 'submit', domElement);
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(TOP_SUBMIT, domElement);
props = rawProps;
break;
case 'details':
trapBubbledEvent('topToggle', 'toggle', domElement);
trapBubbledEvent(TOP_TOGGLE, domElement);
props = rawProps;
break;
case 'input':
ReactDOMFiberInput.initWrapperState(domElement, rawProps);
props = ReactDOMFiberInput.getHostProps(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@ -488,7 +494,7 @@ export function setInitialProperties(
case 'select':
ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
props = ReactDOMFiberSelect.getHostProps(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@ -496,7 +502,7 @@ export function setInitialProperties(
case 'textarea':
ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
props = ReactDOMFiberTextarea.getHostProps(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@ -829,36 +835,34 @@ export function diffHydratedProperties(
switch (tag) {
case 'iframe':
case 'object':
trapBubbledEvent('topLoad', 'load', domElement);
trapBubbledEvent(TOP_LOAD, domElement);
break;
case 'video':
case 'audio':
// Create listener for each media event
for (const event in mediaEventTypes) {
if (mediaEventTypes.hasOwnProperty(event)) {
trapBubbledEvent(event, mediaEventTypes[event], domElement);
}
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
}
break;
case 'source':
trapBubbledEvent('topError', 'error', domElement);
trapBubbledEvent(TOP_ERROR, domElement);
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent('topError', 'error', domElement);
trapBubbledEvent('topLoad', 'load', domElement);
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
break;
case 'form':
trapBubbledEvent('topReset', 'reset', domElement);
trapBubbledEvent('topSubmit', 'submit', domElement);
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(TOP_SUBMIT, domElement);
break;
case 'details':
trapBubbledEvent('topToggle', 'toggle', domElement);
trapBubbledEvent(TOP_TOGGLE, domElement);
break;
case 'input':
ReactDOMFiberInput.initWrapperState(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
@ -868,14 +872,14 @@ export function diffHydratedProperties(
break;
case 'select':
ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'textarea':
ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
trapBubbledEvent('topInvalid', 'invalid', domElement);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');

View File

@ -5,11 +5,23 @@
* LICENSE file in the root directory of this source tree.
*/
import type {TopLevelTypes} from './BrowserEventConstants';
import type {TopLevelType} from 'events/TopLevelEventTypes';
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import {
TOP_BLUR,
TOP_COMPOSITION_START,
TOP_COMPOSITION_END,
TOP_COMPOSITION_UPDATE,
TOP_KEY_DOWN,
TOP_KEY_PRESS,
TOP_KEY_UP,
TOP_MOUSE_DOWN,
TOP_TEXT_INPUT,
TOP_PASTE,
} from './DOMTopLevelEventTypes';
import * as FallbackCompositionState from './FallbackCompositionState';
import SyntheticCompositionEvent from './SyntheticCompositionEvent';
import SyntheticInputEvent from './SyntheticInputEvent';
@ -50,10 +62,10 @@ const eventTypes = {
captured: 'onBeforeInputCapture',
},
dependencies: [
'topCompositionEnd',
'topKeyPress',
'topTextInput',
'topPaste',
TOP_COMPOSITION_END,
TOP_KEY_PRESS,
TOP_TEXT_INPUT,
TOP_PASTE,
],
},
compositionEnd: {
@ -62,12 +74,12 @@ const eventTypes = {
captured: 'onCompositionEndCapture',
},
dependencies: [
'topBlur',
'topCompositionEnd',
'topKeyDown',
'topKeyPress',
'topKeyUp',
'topMouseDown',
TOP_BLUR,
TOP_COMPOSITION_END,
TOP_KEY_DOWN,
TOP_KEY_PRESS,
TOP_KEY_UP,
TOP_MOUSE_DOWN,
],
},
compositionStart: {
@ -76,12 +88,12 @@ const eventTypes = {
captured: 'onCompositionStartCapture',
},
dependencies: [
'topBlur',
'topCompositionStart',
'topKeyDown',
'topKeyPress',
'topKeyUp',
'topMouseDown',
TOP_BLUR,
TOP_COMPOSITION_START,
TOP_KEY_DOWN,
TOP_KEY_PRESS,
TOP_KEY_UP,
TOP_MOUSE_DOWN,
],
},
compositionUpdate: {
@ -90,12 +102,12 @@ const eventTypes = {
captured: 'onCompositionUpdateCapture',
},
dependencies: [
'topBlur',
'topCompositionUpdate',
'topKeyDown',
'topKeyPress',
'topKeyUp',
'topMouseDown',
TOP_BLUR,
TOP_COMPOSITION_UPDATE,
TOP_KEY_DOWN,
TOP_KEY_PRESS,
TOP_KEY_UP,
TOP_MOUSE_DOWN,
],
},
};
@ -124,11 +136,11 @@ function isKeypressCommand(nativeEvent) {
*/
function getCompositionEventType(topLevelType) {
switch (topLevelType) {
case 'topCompositionStart':
case TOP_COMPOSITION_START:
return eventTypes.compositionStart;
case 'topCompositionEnd':
case TOP_COMPOSITION_END:
return eventTypes.compositionEnd;
case 'topCompositionUpdate':
case TOP_COMPOSITION_UPDATE:
return eventTypes.compositionUpdate;
}
}
@ -142,7 +154,7 @@ function getCompositionEventType(topLevelType) {
* @return {boolean}
*/
function isFallbackCompositionStart(topLevelType, nativeEvent) {
return topLevelType === 'topKeyDown' && nativeEvent.keyCode === START_KEYCODE;
return topLevelType === TOP_KEY_DOWN && nativeEvent.keyCode === START_KEYCODE;
}
/**
@ -154,16 +166,16 @@ function isFallbackCompositionStart(topLevelType, nativeEvent) {
*/
function isFallbackCompositionEnd(topLevelType, nativeEvent) {
switch (topLevelType) {
case 'topKeyUp':
case TOP_KEY_UP:
// Command keys insert or clear IME input.
return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
case 'topKeyDown':
case TOP_KEY_DOWN:
// Expect IME keyCode on each keydown. If we get any other
// code we must have exited earlier.
return nativeEvent.keyCode !== START_KEYCODE;
case 'topKeyPress':
case 'topMouseDown':
case 'topBlur':
case TOP_KEY_PRESS:
case TOP_MOUSE_DOWN:
case TOP_BLUR:
// Events are not possible without cancelling IME.
return true;
default:
@ -252,15 +264,15 @@ function extractCompositionEvent(
}
/**
* @param {TopLevelTypes} topLevelType Record from `BrowserEventConstants`.
* @param {TopLevelType} topLevelType Number from `TopLevelType`.
* @param {object} nativeEvent Native browser event.
* @return {?string} The string corresponding to this `beforeInput` event.
*/
function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
function getNativeBeforeInputChars(topLevelType: TopLevelType, nativeEvent) {
switch (topLevelType) {
case 'topCompositionEnd':
case TOP_COMPOSITION_END:
return getDataFromCustomEvent(nativeEvent);
case 'topKeyPress':
case TOP_KEY_PRESS:
/**
* If native `textInput` events are available, our goal is to make
* use of them. However, there is a special case: the spacebar key.
@ -283,7 +295,7 @@ function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
hasSpaceKeypress = true;
return SPACEBAR_CHAR;
case 'topTextInput':
case TOP_TEXT_INPUT:
// Record the characters to be added to the DOM.
const chars = nativeEvent.data;
@ -306,18 +318,18 @@ function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
* For browsers that do not provide the `textInput` event, extract the
* appropriate string to use for SyntheticInputEvent.
*
* @param {string} topLevelType Record from `BrowserEventConstants`.
* @param {number} topLevelType Number from `TopLevelEventTypes`.
* @param {object} nativeEvent Native browser event.
* @return {?string} The fallback string for this `beforeInput` event.
*/
function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
function getFallbackBeforeInputChars(topLevelType: TopLevelType, nativeEvent) {
// If we are currently composing (IME) and using a fallback to do so,
// try to extract the composed characters from the fallback object.
// If composition event is available, we extract a string only at
// compositionevent, otherwise extract it at fallback events.
if (isComposing) {
if (
topLevelType === 'topCompositionEnd' ||
topLevelType === TOP_COMPOSITION_END ||
(!canUseCompositionEvent &&
isFallbackCompositionEnd(topLevelType, nativeEvent))
) {
@ -330,11 +342,11 @@ function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
}
switch (topLevelType) {
case 'topPaste':
case TOP_PASTE:
// If a paste event occurs after a keypress, throw out the input
// chars. Paste events should not lead to BeforeInput events.
return null;
case 'topKeyPress':
case TOP_KEY_PRESS:
/**
* As of v27, Firefox may fire keypress events even when no character
* will be inserted. A few possibilities:
@ -365,7 +377,7 @@ function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
}
}
return null;
case 'topCompositionEnd':
case TOP_COMPOSITION_END:
return useFallbackCompositionData ? null : nativeEvent.data;
default:
return null;

View File

@ -1,97 +0,0 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import getVendorPrefixedEventName from './getVendorPrefixedEventName';
/**
* Types of raw signals from the browser caught at the top level.
*
* For events like 'submit' or audio/video events which don't consistently
* bubble (which we trap at a lower node than `document`), binding
* at `document` would cause duplicate events so we don't include them here.
*/
export const topLevelTypes = {
topAnimationEnd: getVendorPrefixedEventName('animationend'),
topAnimationIteration: getVendorPrefixedEventName('animationiteration'),
topAnimationStart: getVendorPrefixedEventName('animationstart'),
topBlur: 'blur',
topCancel: 'cancel',
topChange: 'change',
topClick: 'click',
topClose: 'close',
topCompositionEnd: 'compositionend',
topCompositionStart: 'compositionstart',
topCompositionUpdate: 'compositionupdate',
topContextMenu: 'contextmenu',
topCopy: 'copy',
topCut: 'cut',
topDoubleClick: 'dblclick',
topDrag: 'drag',
topDragEnd: 'dragend',
topDragEnter: 'dragenter',
topDragExit: 'dragexit',
topDragLeave: 'dragleave',
topDragOver: 'dragover',
topDragStart: 'dragstart',
topDrop: 'drop',
topFocus: 'focus',
topInput: 'input',
topKeyDown: 'keydown',
topKeyPress: 'keypress',
topKeyUp: 'keyup',
topLoad: 'load',
topLoadStart: 'loadstart',
topMouseDown: 'mousedown',
topMouseMove: 'mousemove',
topMouseOut: 'mouseout',
topMouseOver: 'mouseover',
topMouseUp: 'mouseup',
topPaste: 'paste',
topScroll: 'scroll',
topSelectionChange: 'selectionchange',
topTextInput: 'textInput',
topToggle: 'toggle',
topTouchCancel: 'touchcancel',
topTouchEnd: 'touchend',
topTouchMove: 'touchmove',
topTouchStart: 'touchstart',
topTransitionEnd: getVendorPrefixedEventName('transitionend'),
topWheel: 'wheel',
};
// There are so many media events, it makes sense to just
// maintain a list of them. Note these aren't technically
// "top-level" since they don't bubble. We should come up
// with a better naming convention if we come to refactoring
// the event system.
export const mediaEventTypes = {
topAbort: 'abort',
topCanPlay: 'canplay',
topCanPlayThrough: 'canplaythrough',
topDurationChange: 'durationchange',
topEmptied: 'emptied',
topEncrypted: 'encrypted',
topEnded: 'ended',
topError: 'error',
topLoadedData: 'loadeddata',
topLoadedMetadata: 'loadedmetadata',
topLoadStart: 'loadstart',
topPause: 'pause',
topPlay: 'play',
topPlaying: 'playing',
topProgress: 'progress',
topRateChange: 'ratechange',
topSeeked: 'seeked',
topSeeking: 'seeking',
topStalled: 'stalled',
topSuspend: 'suspend',
topTimeUpdate: 'timeupdate',
topVolumeChange: 'volumechange',
topWaiting: 'waiting',
};
export type TopLevelTypes = $Enum<typeof topLevelTypes>;

View File

@ -13,6 +13,16 @@ import SyntheticEvent from 'events/SyntheticEvent';
import isTextInputElement from 'shared/isTextInputElement';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import {
TOP_BLUR,
TOP_CHANGE,
TOP_CLICK,
TOP_FOCUS,
TOP_INPUT,
TOP_KEY_DOWN,
TOP_KEY_UP,
TOP_SELECTION_CHANGE,
} from './DOMTopLevelEventTypes';
import getEventTarget from './getEventTarget';
import isEventSupported from './isEventSupported';
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
@ -26,14 +36,14 @@ const eventTypes = {
captured: 'onChangeCapture',
},
dependencies: [
'topBlur',
'topChange',
'topClick',
'topFocus',
'topInput',
'topKeyDown',
'topKeyUp',
'topSelectionChange',
TOP_BLUR,
TOP_CHANGE,
TOP_CLICK,
TOP_FOCUS,
TOP_INPUT,
TOP_KEY_DOWN,
TOP_KEY_UP,
TOP_SELECTION_CHANGE,
],
},
};
@ -100,7 +110,7 @@ function getInstIfValueChanged(targetInst) {
}
function getTargetInstForChangeEvent(topLevelType, targetInst) {
if (topLevelType === 'topChange') {
if (topLevelType === TOP_CHANGE) {
return targetInst;
}
}
@ -155,7 +165,7 @@ function handlePropertyChange(nativeEvent) {
}
function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
if (topLevelType === 'topFocus') {
if (topLevelType === TOP_FOCUS) {
// In IE9, propertychange fires for most input events but is buggy and
// doesn't fire when text is deleted, but conveniently, selectionchange
// appears to fire in all of the remaining cases so we catch those and
@ -168,7 +178,7 @@ function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
// missed a blur event somehow.
stopWatchingForValueChange();
startWatchingForValueChange(target, targetInst);
} else if (topLevelType === 'topBlur') {
} else if (topLevelType === TOP_BLUR) {
stopWatchingForValueChange();
}
}
@ -176,9 +186,9 @@ function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
// For IE8 and IE9.
function getTargetInstForInputEventPolyfill(topLevelType, targetInst) {
if (
topLevelType === 'topSelectionChange' ||
topLevelType === 'topKeyUp' ||
topLevelType === 'topKeyDown'
topLevelType === TOP_SELECTION_CHANGE ||
topLevelType === TOP_KEY_UP ||
topLevelType === TOP_KEY_DOWN
) {
// On the selectionchange event, the target is just document which isn't
// helpful for us so just check activeElement instead.
@ -210,13 +220,13 @@ function shouldUseClickEvent(elem) {
}
function getTargetInstForClickEvent(topLevelType, targetInst) {
if (topLevelType === 'topClick') {
if (topLevelType === TOP_CLICK) {
return getInstIfValueChanged(targetInst);
}
}
function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
if (topLevelType === 'topInput' || topLevelType === 'topChange') {
if (topLevelType === TOP_INPUT || topLevelType === TOP_CHANGE) {
return getInstIfValueChanged(targetInst);
}
}
@ -292,7 +302,7 @@ const ChangeEventPlugin = {
}
// When blurring, set the value attribute for number inputs
if (topLevelType === 'topBlur') {
if (topLevelType === TOP_BLUR) {
handleControlledInputBlur(targetInst, targetNode);
}
},

View File

@ -0,0 +1,205 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {TopLevelType} from 'events/TopLevelEventTypes';
import getVendorPrefixedEventName from './getVendorPrefixedEventName';
/**
* To identify top level events in react-dom, we use constants defined by this
* module. Those are completely opaque to every other module but we rely on them
* being the raw DOM event names inside this module. This allows us to build a
* very efficient mapping from top level identifiers to the raw event type.
*
* The use of an `opaque` flow type makes sure that we can only access the value
* of a constant in this module.
*/
// eslint-disable-next-line no-undef
export opaque type DOMTopLevelEventType =
| 'abort'
| 'animationend'
| 'animationiteration'
| 'animationstart'
| 'blur'
| 'canplay'
| 'canplaythrough'
| 'cancel'
| 'change'
| 'click'
| 'close'
| 'compositionend'
| 'compositionstart'
| 'compositionupdate'
| 'contextmenu'
| 'copy'
| 'cut'
| 'dblclick'
| 'drag'
| 'dragend'
| 'dragenter'
| 'dragexit'
| 'dragleave'
| 'dragover'
| 'dragstart'
| 'drop'
| 'durationchange'
| 'emptied'
| 'encrypted'
| 'ended'
| 'error'
| 'focus'
| 'input'
| 'invalid'
| 'keydown'
| 'keypress'
| 'keyup'
| 'load'
| 'loadstart'
| 'loadeddata'
| 'loadedmetadata'
| 'mousedown'
| 'mousemove'
| 'mouseout'
| 'mouseover'
| 'mouseup'
| 'paste'
| 'pause'
| 'play'
| 'playing'
| 'progress'
| 'ratechange'
| 'reset'
| 'scroll'
| 'seeked'
| 'seeking'
| 'selectionchange'
| 'stalled'
| 'submit'
| 'suspend'
| 'textInput'
| 'timeupdate'
| 'toggle'
| 'touchcancel'
| 'touchend'
| 'touchmove'
| 'touchstart'
| 'transitionend'
| 'volumechange'
| 'waiting'
| 'wheel';
export const TOP_ABORT: TopLevelType = 'abort';
export const TOP_ANIMATION_END: TopLevelType = getVendorPrefixedEventName(
'animationend',
);
export const TOP_ANIMATION_ITERATION: TopLevelType = getVendorPrefixedEventName(
'animationiteration',
);
export const TOP_ANIMATION_START: TopLevelType = getVendorPrefixedEventName(
'animationstart',
);
export const TOP_BLUR: TopLevelType = 'blur';
export const TOP_CAN_PLAY: TopLevelType = 'canplay';
export const TOP_CAN_PLAY_THROUGH: TopLevelType = 'canplaythrough';
export const TOP_CANCEL: TopLevelType = 'cancel';
export const TOP_CHANGE: TopLevelType = 'change';
export const TOP_CLICK: TopLevelType = 'click';
export const TOP_CLOSE: TopLevelType = 'close';
export const TOP_COMPOSITION_END: TopLevelType = 'compositionend';
export const TOP_COMPOSITION_START: TopLevelType = 'compositionstart';
export const TOP_COMPOSITION_UPDATE: TopLevelType = 'compositionupdate';
export const TOP_CONTEXT_MENU: TopLevelType = 'contextmenu';
export const TOP_COPY: TopLevelType = 'copy';
export const TOP_CUT: TopLevelType = 'cut';
export const TOP_DOUBLE_CLICK: TopLevelType = 'dblclick';
export const TOP_DRAG: TopLevelType = 'drag';
export const TOP_DRAG_END: TopLevelType = 'dragend';
export const TOP_DRAG_ENTER: TopLevelType = 'dragenter';
export const TOP_DRAG_EXIT: TopLevelType = 'dragexit';
export const TOP_DRAG_LEAVE: TopLevelType = 'dragleave';
export const TOP_DRAG_OVER: TopLevelType = 'dragover';
export const TOP_DRAG_START: TopLevelType = 'dragstart';
export const TOP_DROP: TopLevelType = 'drop';
export const TOP_DURATION_CHANGE: TopLevelType = 'durationchange';
export const TOP_EMPTIED: TopLevelType = 'emptied';
export const TOP_ENCRYPTED: TopLevelType = 'encrypted';
export const TOP_ENDED: TopLevelType = 'ended';
export const TOP_ERROR: TopLevelType = 'error';
export const TOP_FOCUS: TopLevelType = 'focus';
export const TOP_INPUT: TopLevelType = 'input';
export const TOP_INVALID: TopLevelType = 'invalid';
export const TOP_KEY_DOWN: TopLevelType = 'keydown';
export const TOP_KEY_PRESS: TopLevelType = 'keypress';
export const TOP_KEY_UP: TopLevelType = 'keyup';
export const TOP_LOAD: TopLevelType = 'load';
export const TOP_LOAD_START: TopLevelType = 'loadstart';
export const TOP_LOADED_DATA: TopLevelType = 'loadeddata';
export const TOP_LOADED_METADATA: TopLevelType = 'loadedmetadata';
export const TOP_MOUSE_DOWN: TopLevelType = 'mousedown';
export const TOP_MOUSE_MOVE: TopLevelType = 'mousemove';
export const TOP_MOUSE_OUT: TopLevelType = 'mouseout';
export const TOP_MOUSE_OVER: TopLevelType = 'mouseover';
export const TOP_MOUSE_UP: TopLevelType = 'mouseup';
export const TOP_PASTE: TopLevelType = 'paste';
export const TOP_PAUSE: TopLevelType = 'pause';
export const TOP_PLAY: TopLevelType = 'play';
export const TOP_PLAYING: TopLevelType = 'playing';
export const TOP_PROGRESS: TopLevelType = 'progress';
export const TOP_RATE_CHANGE: TopLevelType = 'ratechange';
export const TOP_RESET: TopLevelType = 'reset';
export const TOP_SCROLL: TopLevelType = 'scroll';
export const TOP_SEEKED: TopLevelType = 'seeked';
export const TOP_SEEKING: TopLevelType = 'seeking';
export const TOP_SELECTION_CHANGE: TopLevelType = 'selectionchange';
export const TOP_STALLED: TopLevelType = 'stalled';
export const TOP_SUBMIT: TopLevelType = 'submit';
export const TOP_SUSPEND: TopLevelType = 'suspend';
export const TOP_TEXT_INPUT: TopLevelType = 'textInput';
export const TOP_TIME_UPDATE: TopLevelType = 'timeupdate';
export const TOP_TOGGLE: TopLevelType = 'toggle';
export const TOP_TOUCH_CANCEL: TopLevelType = 'touchcancel';
export const TOP_TOUCH_END: TopLevelType = 'touchend';
export const TOP_TOUCH_MOVE: TopLevelType = 'touchmove';
export const TOP_TOUCH_START: TopLevelType = 'touchstart';
export const TOP_TRANSITION_END: TopLevelType = getVendorPrefixedEventName(
'transitionend',
);
export const TOP_VOLUME_CHANGE: TopLevelType = 'volumechange';
export const TOP_WAITING: TopLevelType = 'waiting';
export const TOP_WHEEL: TopLevelType = 'wheel';
export const mediaEventTypes: Array<TopLevelType> = [
TOP_ABORT,
TOP_CAN_PLAY,
TOP_CAN_PLAY_THROUGH,
TOP_DURATION_CHANGE,
TOP_EMPTIED,
TOP_ENCRYPTED,
TOP_ENDED,
TOP_ERROR,
TOP_LOADED_DATA,
TOP_LOADED_METADATA,
TOP_LOAD_START,
TOP_PAUSE,
TOP_PLAY,
TOP_PLAYING,
TOP_PROGRESS,
TOP_RATE_CHANGE,
TOP_SEEKED,
TOP_SEEKING,
TOP_STALLED,
TOP_SUSPEND,
TOP_TIME_UPDATE,
TOP_VOLUME_CHANGE,
TOP_WAITING,
];
export function getRawEventName(topLevelType: TopLevelType): string {
return topLevelType;
}

View File

@ -7,6 +7,7 @@
import {accumulateEnterLeaveDispatches} from 'events/EventPropagators';
import {TOP_MOUSE_OUT, TOP_MOUSE_OVER} from './DOMTopLevelEventTypes';
import SyntheticMouseEvent from './SyntheticMouseEvent';
import {
getClosestInstanceFromNode,
@ -16,11 +17,11 @@ import {
const eventTypes = {
mouseEnter: {
registrationName: 'onMouseEnter',
dependencies: ['topMouseOut', 'topMouseOver'],
dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER],
},
mouseLeave: {
registrationName: 'onMouseLeave',
dependencies: ['topMouseOut', 'topMouseOver'],
dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER],
},
};
@ -41,12 +42,12 @@ const EnterLeaveEventPlugin = {
nativeEventTarget,
) {
if (
topLevelType === 'topMouseOver' &&
topLevelType === TOP_MOUSE_OVER &&
(nativeEvent.relatedTarget || nativeEvent.fromElement)
) {
return null;
}
if (topLevelType !== 'topMouseOut' && topLevelType !== 'topMouseOver') {
if (topLevelType !== TOP_MOUSE_OUT && topLevelType !== TOP_MOUSE_OVER) {
// Must not be a mouse in or mouse out - ignoring.
return null;
}
@ -67,7 +68,7 @@ const EnterLeaveEventPlugin = {
let from;
let to;
if (topLevelType === 'topMouseOut') {
if (topLevelType === TOP_MOUSE_OUT) {
from = targetInst;
const related = nativeEvent.relatedTarget || nativeEvent.toElement;
to = related ? getClosestInstanceFromNode(related) : null;

View File

@ -8,7 +8,7 @@
*/
export function addEventBubbleListener(
element: Element,
element: Document | Element,
eventType: string,
listener: Function,
): void {
@ -16,7 +16,7 @@ export function addEventBubbleListener(
}
export function addEventCaptureListener(
element: Element,
element: Document | Element,
eventType: string,
listener: Function,
): void {

View File

@ -3,9 +3,18 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {registrationNameDependencies} from 'events/EventPluginRegistry';
import {
TOP_BLUR,
TOP_CANCEL,
TOP_CLOSE,
TOP_FOCUS,
TOP_SCROLL,
} from './DOMTopLevelEventTypes';
import {
setEnabled,
isEnabled,
@ -13,7 +22,6 @@ import {
trapCapturedEvent,
} from './ReactDOMEventListener';
import isEventSupported from './isEventSupported';
import {topLevelTypes} from './BrowserEventConstants';
/**
* Summary of `ReactBrowserEventEmitter` event handling:
@ -79,7 +87,7 @@ let reactTopListenersCounter = 0;
*/
const topListenersIDKey = '_reactListenersID' + ('' + Math.random()).slice(2);
function getListeningForDocument(mountAt) {
function getListeningForDocument(mountAt: any) {
// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`
// directly.
if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {
@ -108,37 +116,39 @@ function getListeningForDocument(mountAt) {
* they bubble to document.
*
* @param {string} registrationName Name of listener (e.g. `onClick`).
* @param {object} contentDocumentHandle Document which owns the container
* @param {object} mountAt Container where to mount the listener
*/
export function listenTo(registrationName, contentDocumentHandle) {
const mountAt = contentDocumentHandle;
export function listenTo(
registrationName: string,
mountAt: Document | Element,
) {
const isListening = getListeningForDocument(mountAt);
const dependencies = registrationNameDependencies[registrationName];
for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
if (dependency === 'topScroll') {
trapCapturedEvent('topScroll', 'scroll', mountAt);
} else if (dependency === 'topFocus' || dependency === 'topBlur') {
trapCapturedEvent('topFocus', 'focus', mountAt);
trapCapturedEvent('topBlur', 'blur', mountAt);
if (dependency === TOP_SCROLL) {
trapCapturedEvent(TOP_SCROLL, mountAt);
} else if (dependency === TOP_FOCUS || dependency === TOP_BLUR) {
trapCapturedEvent(TOP_FOCUS, mountAt);
trapCapturedEvent(TOP_BLUR, mountAt);
// to make sure blur and focus event listeners are only attached once
isListening.topBlur = true;
isListening.topFocus = true;
} else if (dependency === 'topCancel') {
isListening[TOP_BLUR] = true;
isListening[TOP_FOCUS] = true;
} else if (dependency === TOP_CANCEL) {
if (isEventSupported('cancel', true)) {
trapCapturedEvent('topCancel', 'cancel', mountAt);
trapCapturedEvent(TOP_CANCEL, mountAt);
}
isListening.topCancel = true;
} else if (dependency === 'topClose') {
isListening[TOP_CANCEL] = true;
} else if (dependency === TOP_CLOSE) {
if (isEventSupported('close', true)) {
trapCapturedEvent('topClose', 'close', mountAt);
trapCapturedEvent(TOP_CLOSE, mountAt);
}
isListening.topClose = true;
} else if (topLevelTypes.hasOwnProperty(dependency)) {
trapBubbledEvent(dependency, topLevelTypes[dependency], mountAt);
isListening[TOP_CLOSE] = true;
} else {
trapBubbledEvent(dependency, mountAt);
}
isListening[dependency] = true;
@ -146,7 +156,10 @@ export function listenTo(registrationName, contentDocumentHandle) {
}
}
export function isListeningToAllDependencies(registrationName, mountAt) {
export function isListeningToAllDependencies(
registrationName: string,
mountAt: Document | Element,
) {
const isListening = getListeningForDocument(mountAt);
const dependencies = registrationNameDependencies[registrationName];
for (let i = 0; i < dependencies.length; i++) {

View File

@ -3,17 +3,23 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
import {runExtractedEventsInBatch} from 'events/EventPluginHub';
import {isFiberMounted} from 'react-reconciler/reflection';
import {HostRoot} from 'shared/ReactTypeOfWork';
import type {AnyNativeEvent} from 'events/PluginModuleType';
import type {TopLevelType} from 'events/TopLevelEventTypes';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import {addEventBubbleListener, addEventCaptureListener} from './EventListener';
import getEventTarget from './getEventTarget';
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
import SimpleEventPlugin from './SimpleEventPlugin';
import {getRawEventName} from './DOMTopLevelEventTypes';
const {isInteractiveTopLevelEventType} = SimpleEventPlugin;
@ -40,7 +46,16 @@ function findRootContainerNode(inst) {
}
// Used to store ancestor hierarchy in top level callback
function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
function getTopLevelCallbackBookKeeping(
topLevelType,
nativeEvent,
targetInst,
): {
topLevelType: ?TopLevelType,
nativeEvent: ?AnyNativeEvent,
targetInst: Fiber,
ancestors: Array<Fiber>,
} {
if (callbackBookkeepingPool.length) {
const instance = callbackBookkeepingPool.pop();
instance.topLevelType = topLevelType;
@ -101,7 +116,7 @@ function handleTopLevel(bookKeeping) {
// TODO: can we stop exporting these?
export let _enabled = true;
export function setEnabled(enabled) {
export function setEnabled(enabled: ?boolean) {
_enabled = !!enabled;
}
@ -112,14 +127,16 @@ export function isEnabled() {
/**
* Traps top-level events by using event bubbling.
*
* @param {string} topLevelType Record from `BrowserEventConstants`.
* @param {string} handlerBaseName Event name (e.g. "click").
* @param {number} topLevelType Number from `TopLevelEventTypes`.
* @param {object} element Element on which to attach listener.
* @return {?object} An object with a remove function which will forcefully
* remove the listener.
* @internal
*/
export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
export function trapBubbledEvent(
topLevelType: TopLevelType,
element: Document | Element,
) {
if (!element) {
return null;
}
@ -129,7 +146,7 @@ export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
addEventBubbleListener(
element,
handlerBaseName,
getRawEventName(topLevelType),
// Check if interactive and wrap in interactiveUpdates
dispatch.bind(null, topLevelType),
);
@ -138,14 +155,16 @@ export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
/**
* Traps a top-level event by using event capturing.
*
* @param {string} topLevelType Record from `BrowserEventConstants`.
* @param {string} handlerBaseName Event name (e.g. "click").
* @param {number} topLevelType Number from `TopLevelEventTypes`.
* @param {object} element Element on which to attach listener.
* @return {?object} An object with a remove function which will forcefully
* remove the listener.
* @internal
*/
export function trapCapturedEvent(topLevelType, handlerBaseName, element) {
export function trapCapturedEvent(
topLevelType: TopLevelType,
element: Document | Element,
) {
if (!element) {
return null;
}
@ -155,7 +174,7 @@ export function trapCapturedEvent(topLevelType, handlerBaseName, element) {
addEventCaptureListener(
element,
handlerBaseName,
getRawEventName(topLevelType),
// Check if interactive and wrap in interactiveUpdates
dispatch.bind(null, topLevelType),
);
@ -165,7 +184,10 @@ function dispatchInteractiveEvent(topLevelType, nativeEvent) {
interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}
export function dispatchEvent(topLevelType, nativeEvent) {
export function dispatchEvent(
topLevelType: TopLevelType,
nativeEvent: AnyNativeEvent,
) {
if (!_enabled) {
return;
}

View File

@ -12,6 +12,16 @@ import isTextInputElement from 'shared/isTextInputElement';
import getActiveElement from 'fbjs/lib/getActiveElement';
import shallowEqual from 'fbjs/lib/shallowEqual';
import {
TOP_BLUR,
TOP_CONTEXT_MENU,
TOP_FOCUS,
TOP_KEY_DOWN,
TOP_KEY_UP,
TOP_MOUSE_DOWN,
TOP_MOUSE_UP,
TOP_SELECTION_CHANGE,
} from './DOMTopLevelEventTypes';
import {isListeningToAllDependencies} from './ReactBrowserEventEmitter';
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
import * as ReactInputSelection from '../client/ReactInputSelection';
@ -29,14 +39,14 @@ const eventTypes = {
captured: 'onSelectCapture',
},
dependencies: [
'topBlur',
'topContextMenu',
'topFocus',
'topKeyDown',
'topKeyUp',
'topMouseDown',
'topMouseUp',
'topSelectionChange',
TOP_BLUR,
TOP_CONTEXT_MENU,
TOP_FOCUS,
TOP_KEY_DOWN,
TOP_KEY_UP,
TOP_MOUSE_DOWN,
TOP_MOUSE_UP,
TOP_SELECTION_CHANGE,
],
},
};
@ -156,7 +166,7 @@ const SelectEventPlugin = {
switch (topLevelType) {
// Track the input node that has focus.
case 'topFocus':
case TOP_FOCUS:
if (
isTextInputElement(targetNode) ||
targetNode.contentEditable === 'true'
@ -166,18 +176,18 @@ const SelectEventPlugin = {
lastSelection = null;
}
break;
case 'topBlur':
case TOP_BLUR:
activeElement = null;
activeElementInst = null;
lastSelection = null;
break;
// Don't fire the event while the user is dragging. This matches the
// semantics of the native select event.
case 'topMouseDown':
case TOP_MOUSE_DOWN:
mouseDown = true;
break;
case 'topContextMenu':
case 'topMouseUp':
case TOP_CONTEXT_MENU:
case TOP_MOUSE_UP:
mouseDown = false;
return constructSelectEvent(nativeEvent, nativeEventTarget);
// Chrome and IE fire non-standard event when selection is changed (and
@ -189,13 +199,13 @@ const SelectEventPlugin = {
// keyup, but we check on keydown as well in the case of holding down a
// key, when multiple keydown events are fired but only one keyup is.
// This is also our approach for IE handling, for the reason above.
case 'topSelectionChange':
case TOP_SELECTION_CHANGE:
if (skipSelectionChangeEvent) {
break;
}
// falls through
case 'topKeyDown':
case 'topKeyUp':
case TOP_KEY_DOWN:
case TOP_KEY_UP:
return constructSelectEvent(nativeEvent, nativeEventTarget);
}

View File

@ -7,7 +7,7 @@
* @flow
*/
import type {TopLevelTypes} from './BrowserEventConstants';
import type {TopLevelType} from 'events/TopLevelEventTypes';
import type {
DispatchConfig,
ReactSyntheticEvent,
@ -17,6 +17,8 @@ import type {EventTypes, PluginModule} from 'events/PluginModuleType';
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import SyntheticEvent from 'events/SyntheticEvent';
import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes';
import warning from 'fbjs/lib/warning';
import SyntheticAnimationEvent from './SyntheticAnimationEvent';
@ -41,93 +43,96 @@ import getEventCharCode from './getEventCharCode';
* bubbled: 'onAbort',
* captured: 'onAbortCapture',
* },
* dependencies: ['topAbort'],
* dependencies: [TOP_ABORT],
* },
* ...
* };
* topLevelEventsToDispatchConfig = {
* 'topAbort': { sameConfig }
* };
* topLevelEventsToDispatchConfig = new Map([
* [TOP_ABORT, { sameConfig }],
* ]);
*/
const interactiveEventTypeNames: Array<string> = [
'blur',
'cancel',
'click',
'close',
'contextMenu',
'copy',
'cut',
'doubleClick',
'dragEnd',
'dragStart',
'drop',
'focus',
'input',
'invalid',
'keyDown',
'keyPress',
'keyUp',
'mouseDown',
'mouseUp',
'paste',
'pause',
'play',
'rateChange',
'reset',
'seeked',
'submit',
'touchCancel',
'touchEnd',
'touchStart',
'volumeChange',
type EventTuple = [TopLevelType, string];
const interactiveEventTypeNames: Array<EventTuple> = [
[DOMTopLevelEventTypes.TOP_BLUR, 'blur'],
[DOMTopLevelEventTypes.TOP_CANCEL, 'cancel'],
[DOMTopLevelEventTypes.TOP_CLICK, 'click'],
[DOMTopLevelEventTypes.TOP_CLOSE, 'close'],
[DOMTopLevelEventTypes.TOP_CONTEXT_MENU, 'contextMenu'],
[DOMTopLevelEventTypes.TOP_COPY, 'copy'],
[DOMTopLevelEventTypes.TOP_CUT, 'cut'],
[DOMTopLevelEventTypes.TOP_DOUBLE_CLICK, 'doubleClick'],
[DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd'],
[DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart'],
[DOMTopLevelEventTypes.TOP_DROP, 'drop'],
[DOMTopLevelEventTypes.TOP_FOCUS, 'focus'],
[DOMTopLevelEventTypes.TOP_INPUT, 'input'],
[DOMTopLevelEventTypes.TOP_INVALID, 'invalid'],
[DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown'],
[DOMTopLevelEventTypes.TOP_KEY_PRESS, 'keyPress'],
[DOMTopLevelEventTypes.TOP_KEY_UP, 'keyUp'],
[DOMTopLevelEventTypes.TOP_MOUSE_DOWN, 'mouseDown'],
[DOMTopLevelEventTypes.TOP_MOUSE_UP, 'mouseUp'],
[DOMTopLevelEventTypes.TOP_PASTE, 'paste'],
[DOMTopLevelEventTypes.TOP_PAUSE, 'pause'],
[DOMTopLevelEventTypes.TOP_PLAY, 'play'],
[DOMTopLevelEventTypes.TOP_RATE_CHANGE, 'rateChange'],
[DOMTopLevelEventTypes.TOP_RESET, 'reset'],
[DOMTopLevelEventTypes.TOP_SEEKED, 'seeked'],
[DOMTopLevelEventTypes.TOP_SUBMIT, 'submit'],
[DOMTopLevelEventTypes.TOP_TOUCH_CANCEL, 'touchCancel'],
[DOMTopLevelEventTypes.TOP_TOUCH_END, 'touchEnd'],
[DOMTopLevelEventTypes.TOP_TOUCH_START, 'touchStart'],
[DOMTopLevelEventTypes.TOP_VOLUME_CHANGE, 'volumeChange'],
];
const nonInteractiveEventTypeNames: Array<string> = [
'abort',
'animationEnd',
'animationIteration',
'animationStart',
'canPlay',
'canPlayThrough',
'drag',
'dragEnter',
'dragExit',
'dragLeave',
'dragOver',
'durationChange',
'emptied',
'encrypted',
'ended',
'error',
'load',
'loadedData',
'loadedMetadata',
'loadStart',
'mouseMove',
'mouseOut',
'mouseOver',
'playing',
'progress',
'scroll',
'seeking',
'stalled',
'suspend',
'timeUpdate',
'toggle',
'touchMove',
'transitionEnd',
'waiting',
'wheel',
const nonInteractiveEventTypeNames: Array<EventTuple> = [
[DOMTopLevelEventTypes.TOP_ABORT, 'abort'],
[DOMTopLevelEventTypes.TOP_ANIMATION_END, 'animationEnd'],
[DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION, 'animationIteration'],
[DOMTopLevelEventTypes.TOP_ANIMATION_START, 'animationStart'],
[DOMTopLevelEventTypes.TOP_CAN_PLAY, 'canPlay'],
[DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH, 'canPlayThrough'],
[DOMTopLevelEventTypes.TOP_DRAG, 'drag'],
[DOMTopLevelEventTypes.TOP_DRAG_ENTER, 'dragEnter'],
[DOMTopLevelEventTypes.TOP_DRAG_EXIT, 'dragExit'],
[DOMTopLevelEventTypes.TOP_DRAG_LEAVE, 'dragLeave'],
[DOMTopLevelEventTypes.TOP_DRAG_OVER, 'dragOver'],
[DOMTopLevelEventTypes.TOP_DURATION_CHANGE, 'durationChange'],
[DOMTopLevelEventTypes.TOP_EMPTIED, 'emptied'],
[DOMTopLevelEventTypes.TOP_ENCRYPTED, 'encrypted'],
[DOMTopLevelEventTypes.TOP_ENDED, 'ended'],
[DOMTopLevelEventTypes.TOP_ERROR, 'error'],
[DOMTopLevelEventTypes.TOP_LOAD, 'load'],
[DOMTopLevelEventTypes.TOP_LOADED_DATA, 'loadedData'],
[DOMTopLevelEventTypes.TOP_LOADED_METADATA, 'loadedMetadata'],
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'],
[DOMTopLevelEventTypes.TOP_MOUSE_MOVE, 'mouseMove'],
[DOMTopLevelEventTypes.TOP_MOUSE_OUT, 'mouseOut'],
[DOMTopLevelEventTypes.TOP_MOUSE_OVER, 'mouseOver'],
[DOMTopLevelEventTypes.TOP_PLAYING, 'playing'],
[DOMTopLevelEventTypes.TOP_PROGRESS, 'progress'],
[DOMTopLevelEventTypes.TOP_SCROLL, 'scroll'],
[DOMTopLevelEventTypes.TOP_SEEKING, 'seeking'],
[DOMTopLevelEventTypes.TOP_STALLED, 'stalled'],
[DOMTopLevelEventTypes.TOP_SUSPEND, 'suspend'],
[DOMTopLevelEventTypes.TOP_TIME_UPDATE, 'timeUpdate'],
[DOMTopLevelEventTypes.TOP_TOGGLE, 'toggle'],
[DOMTopLevelEventTypes.TOP_TOUCH_MOVE, 'touchMove'],
[DOMTopLevelEventTypes.TOP_TRANSITION_END, 'transitionEnd'],
[DOMTopLevelEventTypes.TOP_WAITING, 'waiting'],
[DOMTopLevelEventTypes.TOP_WHEEL, 'wheel'],
];
const eventTypes: EventTypes = {};
const topLevelEventsToDispatchConfig: {
[key: TopLevelTypes]: DispatchConfig,
[key: TopLevelType]: DispatchConfig,
} = {};
function addEventTypeNameToConfig(event: string, isInteractive: boolean) {
function addEventTypeNameToConfig(
[topEvent, event]: EventTuple,
isInteractive: boolean,
) {
const capitalizedEvent = event[0].toUpperCase() + event.slice(1);
const onEvent = 'on' + capitalizedEvent;
const topEvent = 'top' + capitalizedEvent;
const type = {
phasedRegistrationNames: {
@ -141,58 +146,60 @@ function addEventTypeNameToConfig(event: string, isInteractive: boolean) {
topLevelEventsToDispatchConfig[topEvent] = type;
}
interactiveEventTypeNames.forEach(eventTypeName => {
addEventTypeNameToConfig(eventTypeName, true);
interactiveEventTypeNames.forEach(eventTuple => {
addEventTypeNameToConfig(eventTuple, true);
});
nonInteractiveEventTypeNames.forEach(eventTypeName => {
addEventTypeNameToConfig(eventTypeName, false);
nonInteractiveEventTypeNames.forEach(eventTuple => {
addEventTypeNameToConfig(eventTuple, false);
});
// Only used in DEV for exhaustiveness validation.
const knownHTMLTopLevelTypes = [
'topAbort',
'topCancel',
'topCanPlay',
'topCanPlayThrough',
'topClose',
'topDurationChange',
'topEmptied',
'topEncrypted',
'topEnded',
'topError',
'topInput',
'topInvalid',
'topLoad',
'topLoadedData',
'topLoadedMetadata',
'topLoadStart',
'topPause',
'topPlay',
'topPlaying',
'topProgress',
'topRateChange',
'topReset',
'topSeeked',
'topSeeking',
'topStalled',
'topSubmit',
'topSuspend',
'topTimeUpdate',
'topToggle',
'topVolumeChange',
'topWaiting',
const knownHTMLTopLevelTypes: Array<TopLevelType> = [
DOMTopLevelEventTypes.TOP_ABORT,
DOMTopLevelEventTypes.TOP_CANCEL,
DOMTopLevelEventTypes.TOP_CAN_PLAY,
DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH,
DOMTopLevelEventTypes.TOP_CLOSE,
DOMTopLevelEventTypes.TOP_DURATION_CHANGE,
DOMTopLevelEventTypes.TOP_EMPTIED,
DOMTopLevelEventTypes.TOP_ENCRYPTED,
DOMTopLevelEventTypes.TOP_ENDED,
DOMTopLevelEventTypes.TOP_ERROR,
DOMTopLevelEventTypes.TOP_INPUT,
DOMTopLevelEventTypes.TOP_INVALID,
DOMTopLevelEventTypes.TOP_LOAD,
DOMTopLevelEventTypes.TOP_LOADED_DATA,
DOMTopLevelEventTypes.TOP_LOADED_METADATA,
DOMTopLevelEventTypes.TOP_LOAD_START,
DOMTopLevelEventTypes.TOP_PAUSE,
DOMTopLevelEventTypes.TOP_PLAY,
DOMTopLevelEventTypes.TOP_PLAYING,
DOMTopLevelEventTypes.TOP_PROGRESS,
DOMTopLevelEventTypes.TOP_RATE_CHANGE,
DOMTopLevelEventTypes.TOP_RESET,
DOMTopLevelEventTypes.TOP_SEEKED,
DOMTopLevelEventTypes.TOP_SEEKING,
DOMTopLevelEventTypes.TOP_STALLED,
DOMTopLevelEventTypes.TOP_SUBMIT,
DOMTopLevelEventTypes.TOP_SUSPEND,
DOMTopLevelEventTypes.TOP_TIME_UPDATE,
DOMTopLevelEventTypes.TOP_TOGGLE,
DOMTopLevelEventTypes.TOP_VOLUME_CHANGE,
DOMTopLevelEventTypes.TOP_WAITING,
];
const SimpleEventPlugin: PluginModule<MouseEvent> = {
const SimpleEventPlugin: PluginModule<MouseEvent> & {
isInteractiveTopLevelEventType: (topLevelType: TopLevelType) => boolean,
} = {
eventTypes: eventTypes,
isInteractiveTopLevelEventType(topLevelType: TopLevelTypes): boolean {
isInteractiveTopLevelEventType(topLevelType: TopLevelType): boolean {
const config = topLevelEventsToDispatchConfig[topLevelType];
return config !== undefined && config.isInteractive === true;
},
extractEvents: function(
topLevelType: TopLevelTypes,
topLevelType: TopLevelType,
targetInst: Fiber,
nativeEvent: MouseEvent,
nativeEventTarget: EventTarget,
@ -203,7 +210,7 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
}
let EventConstructor;
switch (topLevelType) {
case 'topKeyPress':
case DOMTopLevelEventTypes.TOP_KEY_PRESS:
// Firefox creates a keypress event for function keys too. This removes
// the unwanted keypress events. Enter is however both printable and
// non-printable. One would expect Tab to be as well (but it isn't).
@ -211,65 +218,65 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
return null;
}
/* falls through */
case 'topKeyDown':
case 'topKeyUp':
case DOMTopLevelEventTypes.TOP_KEY_DOWN:
case DOMTopLevelEventTypes.TOP_KEY_UP:
EventConstructor = SyntheticKeyboardEvent;
break;
case 'topBlur':
case 'topFocus':
case DOMTopLevelEventTypes.TOP_BLUR:
case DOMTopLevelEventTypes.TOP_FOCUS:
EventConstructor = SyntheticFocusEvent;
break;
case 'topClick':
case DOMTopLevelEventTypes.TOP_CLICK:
// Firefox creates a click event on right mouse clicks. This removes the
// unwanted click events.
if (nativeEvent.button === 2) {
return null;
}
/* falls through */
case 'topDoubleClick':
case 'topMouseDown':
case 'topMouseMove':
case 'topMouseUp':
case DOMTopLevelEventTypes.TOP_DOUBLE_CLICK:
case DOMTopLevelEventTypes.TOP_MOUSE_DOWN:
case DOMTopLevelEventTypes.TOP_MOUSE_MOVE:
case DOMTopLevelEventTypes.TOP_MOUSE_UP:
// TODO: Disabled elements should not respond to mouse events
/* falls through */
case 'topMouseOut':
case 'topMouseOver':
case 'topContextMenu':
case DOMTopLevelEventTypes.TOP_MOUSE_OUT:
case DOMTopLevelEventTypes.TOP_MOUSE_OVER:
case DOMTopLevelEventTypes.TOP_CONTEXT_MENU:
EventConstructor = SyntheticMouseEvent;
break;
case 'topDrag':
case 'topDragEnd':
case 'topDragEnter':
case 'topDragExit':
case 'topDragLeave':
case 'topDragOver':
case 'topDragStart':
case 'topDrop':
case DOMTopLevelEventTypes.TOP_DRAG:
case DOMTopLevelEventTypes.TOP_DRAG_END:
case DOMTopLevelEventTypes.TOP_DRAG_ENTER:
case DOMTopLevelEventTypes.TOP_DRAG_EXIT:
case DOMTopLevelEventTypes.TOP_DRAG_LEAVE:
case DOMTopLevelEventTypes.TOP_DRAG_OVER:
case DOMTopLevelEventTypes.TOP_DRAG_START:
case DOMTopLevelEventTypes.TOP_DROP:
EventConstructor = SyntheticDragEvent;
break;
case 'topTouchCancel':
case 'topTouchEnd':
case 'topTouchMove':
case 'topTouchStart':
case DOMTopLevelEventTypes.TOP_TOUCH_CANCEL:
case DOMTopLevelEventTypes.TOP_TOUCH_END:
case DOMTopLevelEventTypes.TOP_TOUCH_MOVE:
case DOMTopLevelEventTypes.TOP_TOUCH_START:
EventConstructor = SyntheticTouchEvent;
break;
case 'topAnimationEnd':
case 'topAnimationIteration':
case 'topAnimationStart':
case DOMTopLevelEventTypes.TOP_ANIMATION_END:
case DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION:
case DOMTopLevelEventTypes.TOP_ANIMATION_START:
EventConstructor = SyntheticAnimationEvent;
break;
case 'topTransitionEnd':
case DOMTopLevelEventTypes.TOP_TRANSITION_END:
EventConstructor = SyntheticTransitionEvent;
break;
case 'topScroll':
case DOMTopLevelEventTypes.TOP_SCROLL:
EventConstructor = SyntheticUIEvent;
break;
case 'topWheel':
case DOMTopLevelEventTypes.TOP_WHEEL:
EventConstructor = SyntheticWheelEvent;
break;
case 'topCopy':
case 'topCut':
case 'topPaste':
case DOMTopLevelEventTypes.TOP_COPY:
case DOMTopLevelEventTypes.TOP_CUT:
case DOMTopLevelEventTypes.TOP_PASTE:
EventConstructor = SyntheticClipboardEvent;
break;
default:

View File

@ -7,12 +7,33 @@
* @flow
*/
import {isStartish, isEndish} from 'events/EventPluginUtils';
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import TouchEventUtils from 'fbjs/lib/TouchEventUtils';
import type {TopLevelType} from 'events/TopLevelEventTypes';
import {
TOP_MOUSE_DOWN,
TOP_MOUSE_MOVE,
TOP_MOUSE_UP,
TOP_TOUCH_CANCEL,
TOP_TOUCH_END,
TOP_TOUCH_MOVE,
TOP_TOUCH_START,
} from './DOMTopLevelEventTypes';
import SyntheticUIEvent from './SyntheticUIEvent';
function isStartish(topLevelType) {
return topLevelType === TOP_MOUSE_DOWN || topLevelType === TOP_TOUCH_START;
}
function isEndish(topLevelType) {
return (
topLevelType === TOP_MOUSE_UP ||
topLevelType === TOP_TOUCH_END ||
topLevelType === TOP_TOUCH_CANCEL
);
}
/**
* We are extending the Flow 'Touch' declaration to enable using bracket
* notation to access properties.
@ -75,13 +96,13 @@ function getDistance(coords: CoordinatesType, nativeEvent: _Touch): number {
}
const touchEvents = [
'topTouchStart',
'topTouchCancel',
'topTouchEnd',
'topTouchMove',
TOP_TOUCH_START,
TOP_TOUCH_CANCEL,
TOP_TOUCH_END,
TOP_TOUCH_MOVE,
];
const dependencies = ['topMouseDown', 'topMouseMove', 'topMouseUp'].concat(
const dependencies = [TOP_MOUSE_DOWN, TOP_MOUSE_MOVE, TOP_MOUSE_UP].concat(
touchEvents,
);
@ -105,7 +126,7 @@ const TapEventPlugin = {
eventTypes: eventTypes,
extractEvents: function(
topLevelType: mixed,
topLevelType: TopLevelType,
targetInst: mixed,
nativeEvent: _Touch,
nativeEventTarget: EventTarget,

View File

@ -18,7 +18,7 @@ import {
import SyntheticEvent from 'events/SyntheticEvent';
import invariant from 'fbjs/lib/invariant';
import {topLevelTypes, mediaEventTypes} from '../events/BrowserEventConstants';
import * as DOMTopLevelEventTypes from '../events/DOMTopLevelEventTypes';
const {findDOMNode} = ReactDOM;
const {
@ -36,6 +36,33 @@ function Event(suffix) {}
* @class ReactTestUtils
*/
/**
* Simulates a top level event being dispatched from a raw event that occurred
* on an `Element` node.
* @param {number} topLevelType A number from `TopLevelEventTypes`
* @param {!Element} node The dom to simulate an event occurring on.
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
*/
function simulateNativeEventOnNode(topLevelType, node, fakeNativeEvent) {
fakeNativeEvent.target = node;
ReactDOMEventListener.dispatchEvent(topLevelType, fakeNativeEvent);
}
/**
* Simulates a top level event being dispatched from a raw event that occurred
* on the `ReactDOMComponent` `comp`.
* @param {Object} topLevelType A type from `BrowserEventConstants.topLevelTypes`.
* @param {!ReactDOMComponent} comp
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
*/
function simulateNativeEventOnDOMComponent(
topLevelType,
comp,
fakeNativeEvent,
) {
simulateNativeEventOnNode(topLevelType, findDOMNode(comp), fakeNativeEvent);
}
function findAllInRenderedFiberTreeInternal(fiber, test) {
if (!fiber) {
return [];
@ -291,37 +318,6 @@ const ReactTestUtils = {
return this;
},
/**
* Simulates a top level event being dispatched from a raw event that occurred
* on an `Element` node.
* @param {Object} topLevelType A type from `BrowserEventConstants.topLevelTypes`
* @param {!Element} node The dom to simulate an event occurring on.
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
*/
simulateNativeEventOnNode: function(topLevelType, node, fakeNativeEvent) {
fakeNativeEvent.target = node;
ReactDOMEventListener.dispatchEvent(topLevelType, fakeNativeEvent);
},
/**
* Simulates a top level event being dispatched from a raw event that occurred
* on the `ReactDOMComponent` `comp`.
* @param {Object} topLevelType A type from `BrowserEventConstants.topLevelTypes`.
* @param {!ReactDOMComponent} comp
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
*/
simulateNativeEventOnDOMComponent: function(
topLevelType,
comp,
fakeNativeEvent,
) {
ReactTestUtils.simulateNativeEventOnNode(
topLevelType,
findDOMNode(comp),
fakeNativeEvent,
);
},
nativeTouchData: function(x, y) {
return {
touches: [{pageX: x, pageY: y}],
@ -436,20 +432,20 @@ buildSimulators();
* to dispatch synthetic events.
*/
function makeNativeSimulator(eventType) {
function makeNativeSimulator(eventType, topLevelType) {
return function(domComponentOrNode, nativeEventData) {
const fakeNativeEvent = new Event(eventType);
Object.assign(fakeNativeEvent, nativeEventData);
if (ReactTestUtils.isDOMComponent(domComponentOrNode)) {
ReactTestUtils.simulateNativeEventOnDOMComponent(
eventType,
simulateNativeEventOnDOMComponent(
topLevelType,
domComponentOrNode,
fakeNativeEvent,
);
} else if (domComponentOrNode.tagName) {
// Will allow on actual dom nodes.
ReactTestUtils.simulateNativeEventOnNode(
eventType,
simulateNativeEventOnNode(
topLevelType,
domComponentOrNode,
fakeNativeEvent,
);
@ -457,23 +453,84 @@ function makeNativeSimulator(eventType) {
};
}
const eventKeys = [].concat(
Object.keys(topLevelTypes),
Object.keys(mediaEventTypes),
);
eventKeys.forEach(function(eventType) {
// Event type is stored as 'topClick' - we transform that to 'click'
const convenienceName =
eventType.indexOf('top') === 0
? eventType.charAt(3).toLowerCase() + eventType.substr(4)
: eventType;
[
[DOMTopLevelEventTypes.TOP_ABORT, 'abort'],
[DOMTopLevelEventTypes.TOP_ANIMATION_END, 'animationEnd'],
[DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION, 'animationIteration'],
[DOMTopLevelEventTypes.TOP_ANIMATION_START, 'animationStart'],
[DOMTopLevelEventTypes.TOP_BLUR, 'blur'],
[DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH, 'canPlayThrough'],
[DOMTopLevelEventTypes.TOP_CAN_PLAY, 'canPlay'],
[DOMTopLevelEventTypes.TOP_CANCEL, 'cancel'],
[DOMTopLevelEventTypes.TOP_CHANGE, 'change'],
[DOMTopLevelEventTypes.TOP_CLICK, 'click'],
[DOMTopLevelEventTypes.TOP_CLOSE, 'close'],
[DOMTopLevelEventTypes.TOP_COMPOSITION_END, 'compositionEnd'],
[DOMTopLevelEventTypes.TOP_COMPOSITION_START, 'compositionStart'],
[DOMTopLevelEventTypes.TOP_COMPOSITION_UPDATE, 'compositionUpdate'],
[DOMTopLevelEventTypes.TOP_CONTEXT_MENU, 'contextMenu'],
[DOMTopLevelEventTypes.TOP_COPY, 'copy'],
[DOMTopLevelEventTypes.TOP_CUT, 'cut'],
[DOMTopLevelEventTypes.TOP_DOUBLE_CLICK, 'doubleClick'],
[DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd'],
[DOMTopLevelEventTypes.TOP_DRAG_ENTER, 'dragEnter'],
[DOMTopLevelEventTypes.TOP_DRAG_EXIT, 'dragExit'],
[DOMTopLevelEventTypes.TOP_DRAG_LEAVE, 'dragLeave'],
[DOMTopLevelEventTypes.TOP_DRAG_OVER, 'dragOver'],
[DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart'],
[DOMTopLevelEventTypes.TOP_DRAG, 'drag'],
[DOMTopLevelEventTypes.TOP_DROP, 'drop'],
[DOMTopLevelEventTypes.TOP_DURATION_CHANGE, 'durationChange'],
[DOMTopLevelEventTypes.TOP_EMPTIED, 'emptied'],
[DOMTopLevelEventTypes.TOP_ENCRYPTED, 'encrypted'],
[DOMTopLevelEventTypes.TOP_ENDED, 'ended'],
[DOMTopLevelEventTypes.TOP_ERROR, 'error'],
[DOMTopLevelEventTypes.TOP_FOCUS, 'focus'],
[DOMTopLevelEventTypes.TOP_INPUT, 'input'],
[DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown'],
[DOMTopLevelEventTypes.TOP_KEY_PRESS, 'keyPress'],
[DOMTopLevelEventTypes.TOP_KEY_UP, 'keyUp'],
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'],
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'],
[DOMTopLevelEventTypes.TOP_LOAD, 'load'],
[DOMTopLevelEventTypes.TOP_LOADED_DATA, 'loadedData'],
[DOMTopLevelEventTypes.TOP_LOADED_METADATA, 'loadedMetadata'],
[DOMTopLevelEventTypes.TOP_MOUSE_DOWN, 'mouseDown'],
[DOMTopLevelEventTypes.TOP_MOUSE_MOVE, 'mouseMove'],
[DOMTopLevelEventTypes.TOP_MOUSE_OUT, 'mouseOut'],
[DOMTopLevelEventTypes.TOP_MOUSE_OVER, 'mouseOver'],
[DOMTopLevelEventTypes.TOP_MOUSE_UP, 'mouseUp'],
[DOMTopLevelEventTypes.TOP_PASTE, 'paste'],
[DOMTopLevelEventTypes.TOP_PAUSE, 'pause'],
[DOMTopLevelEventTypes.TOP_PLAY, 'play'],
[DOMTopLevelEventTypes.TOP_PLAYING, 'playing'],
[DOMTopLevelEventTypes.TOP_PROGRESS, 'progress'],
[DOMTopLevelEventTypes.TOP_RATE_CHANGE, 'rateChange'],
[DOMTopLevelEventTypes.TOP_SCROLL, 'scroll'],
[DOMTopLevelEventTypes.TOP_SEEKED, 'seeked'],
[DOMTopLevelEventTypes.TOP_SEEKING, 'seeking'],
[DOMTopLevelEventTypes.TOP_SELECTION_CHANGE, 'selectionChange'],
[DOMTopLevelEventTypes.TOP_STALLED, 'stalled'],
[DOMTopLevelEventTypes.TOP_SUSPEND, 'suspend'],
[DOMTopLevelEventTypes.TOP_TEXT_INPUT, 'textInput'],
[DOMTopLevelEventTypes.TOP_TIME_UPDATE, 'timeUpdate'],
[DOMTopLevelEventTypes.TOP_TOGGLE, 'toggle'],
[DOMTopLevelEventTypes.TOP_TOUCH_CANCEL, 'touchCancel'],
[DOMTopLevelEventTypes.TOP_TOUCH_END, 'touchEnd'],
[DOMTopLevelEventTypes.TOP_TOUCH_MOVE, 'touchMove'],
[DOMTopLevelEventTypes.TOP_TOUCH_START, 'touchStart'],
[DOMTopLevelEventTypes.TOP_TRANSITION_END, 'transitionEnd'],
[DOMTopLevelEventTypes.TOP_VOLUME_CHANGE, 'volumeChange'],
[DOMTopLevelEventTypes.TOP_WAITING, 'waiting'],
[DOMTopLevelEventTypes.TOP_WHEEL, 'wheel'],
].forEach(([topLevelType, eventType]) => {
/**
* @param {!Element|ReactDOMComponent} domComponentOrNode
* @param {?Event} nativeEventData Fake native event to use in SyntheticEvent.
*/
ReactTestUtils.SimulateNative[convenienceName] = makeNativeSimulator(
ReactTestUtils.SimulateNative[eventType] = makeNativeSimulator(
eventType,
topLevelType,
);
});

View File

@ -12,6 +12,7 @@ import {
accumulateTwoPhaseDispatches,
accumulateDirectDispatches,
} from 'events/EventPropagators';
import type {TopLevelType} from 'events/TopLevelEventTypes';
import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
import SyntheticEvent from 'events/SyntheticEvent';
import invariant from 'fbjs/lib/invariant';
@ -29,7 +30,7 @@ const ReactNativeBridgeEventPlugin = {
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType: string,
topLevelType: TopLevelType,
targetInst: Object,
nativeEvent: AnyNativeEvent,
nativeEventTarget: Object,

View File

@ -15,6 +15,7 @@ import warning from 'fbjs/lib/warning';
import {getInstanceFromNode} from './ReactNativeComponentTree';
import type {AnyNativeEvent} from 'events/PluginModuleType';
import type {TopLevelType} from 'events/TopLevelEventTypes';
export {getListener, registrationNameModules as registrationNames};
@ -88,7 +89,7 @@ const removeTouchesAtIndices = function(
*/
export function _receiveRootNodeIDEvent(
rootNodeID: number,
topLevelType: string,
topLevelType: TopLevelType,
nativeEventParam: ?AnyNativeEvent,
) {
const nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
@ -114,7 +115,7 @@ export function _receiveRootNodeIDEvent(
*/
export function receiveEvent(
rootNodeID: number,
topLevelType: string,
topLevelType: TopLevelType,
nativeEventParam: AnyNativeEvent,
) {
_receiveRootNodeIDEvent(rootNodeID, topLevelType, nativeEventParam);
@ -145,7 +146,7 @@ export function receiveEvent(
* identifier 0, also abandoning traditional click handlers.
*/
export function receiveTouches(
eventTopLevelType: string,
eventTopLevelType: TopLevelType,
touches: Array<Object>,
changedIndices: Array<number>,
) {

View File

@ -143,6 +143,14 @@ const forks = Object.freeze({
return null;
}
},
// React DOM uses different top level event names and supports mouse events.
'events/ResponderTopLevelEventTypes': (bundleType, entry) => {
if (entry === 'react-dom' || entry.startsWith('react-dom/')) {
return 'events/forks/ResponderTopLevelEventTypes.dom.js';
}
return null;
},
});
module.exports = forks;

1541
yarn.lock

File diff suppressed because it is too large Load Diff