diff --git a/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js b/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js index 304172ebfa..e17d72889d 100644 --- a/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js +++ b/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule SimpleEventPlugin + * @flow */ 'use strict'; @@ -30,6 +31,14 @@ var emptyFunction = require('emptyFunction'); var getEventCharCode = require('getEventCharCode'); var invariant = require('invariant'); +import type {TopLevelTypes} from 'EventConstants'; +import type { + DispatchConfig, + ReactSyntheticEvent, +} from 'ReactSyntheticEventType'; +import type {ReactInstance} from 'ReactInstanceType'; +import type {PluginModule} from 'PluginModuleType'; + /** * Turns * ['abort', ...] @@ -48,8 +57,8 @@ var invariant = require('invariant'); * 'topAbort': { sameConfig } * }; */ -var eventTypes = {}; -var topLevelEventsToDispatchConfig = {}; +var eventTypes: {[key: string]: DispatchConfig} = {}; +var topLevelEventsToDispatchConfig: {[key: TopLevelTypes]: DispatchConfig} = {}; [ 'abort', 'animationEnd', @@ -131,22 +140,22 @@ var topLevelEventsToDispatchConfig = {}; var onClickListeners = {}; -function getDictionaryKey(inst) { +function getDictionaryKey(inst: ReactInstance): string { // Prevents V8 performance issue: // https://github.com/facebook/react/pull/7232 return '.' + inst._rootNodeID; } -var SimpleEventPlugin = { +var SimpleEventPlugin: PluginModule = { eventTypes: eventTypes, extractEvents: function( - topLevelType, - targetInst, - nativeEvent, - nativeEventTarget - ) { + topLevelType: TopLevelTypes, + targetInst: ReactInstance, + nativeEvent: MouseEvent, + nativeEventTarget: EventTarget, + ): null | ReactSyntheticEvent { var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType]; if (!dispatchConfig) { return null; @@ -268,7 +277,11 @@ var SimpleEventPlugin = { return event; }, - didPutListener: function(inst, registrationName, listener) { + didPutListener: function( + inst: ReactInstance, + registrationName: string, + listener: () => void, + ): void { // Mobile Safari does not fire properly bubble click events on // non-interactive elements, which means delegated click listeners do not // fire. The workaround for this bug involves attaching an empty click @@ -286,7 +299,10 @@ var SimpleEventPlugin = { } }, - willDeleteListener: function(inst, registrationName) { + willDeleteListener: function( + inst: ReactInstance, + registrationName: string, + ): void { if (registrationName === 'onClick') { var key = getDictionaryKey(inst); onClickListeners[key].remove(); diff --git a/src/renderers/dom/client/eventPlugins/TapEventPlugin.js b/src/renderers/dom/client/eventPlugins/TapEventPlugin.js index fb6ccc5797..363ecc095b 100644 --- a/src/renderers/dom/client/eventPlugins/TapEventPlugin.js +++ b/src/renderers/dom/client/eventPlugins/TapEventPlugin.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule TapEventPlugin + * @flow */ 'use strict'; @@ -20,19 +21,60 @@ var ViewportMetrics = require('ViewportMetrics'); var isStartish = EventPluginUtils.isStartish; var isEndish = EventPluginUtils.isEndish; +import type {DispatchConfig} from 'ReactSyntheticEventType'; +import type {PluginModule} from 'PluginModuleType'; +import type {ReactInstance} from 'ReactInstanceType'; +import type {TopLevelTypes} from 'EventConstants'; + +/** + * We are extending the Flow 'Touch' declaration to enable using bracket + * notation to access properties. + * Without this adjustment Flow throws + * "Indexable signature not found in Touch". + * See https://github.com/facebook/flow/issues/1323 + */ +type TouchPropertyKey = + 'clientX' | + 'clientY' | + 'pageX' | + 'pageY'; + +declare class _Touch extends Touch { + [key: TouchPropertyKey]: number; +} + +type AxisCoordinateData = { + page: TouchPropertyKey, + client: TouchPropertyKey, + envScroll: 'currentPageScrollLeft' | 'currentPageScrollTop', +}; + +type AxisType = { + x: AxisCoordinateData, + y: AxisCoordinateData, +}; + +type CoordinatesType = { + x: number, + y: number, +}; + /** * Number of pixels that are tolerated in between a `touchStart` and `touchEnd` * in order to still be considered a 'tap' event. */ var tapMoveThreshold = 10; -var startCoords = {x: null, y: null}; +var startCoords: CoordinatesType = {x: 0, y: 0}; -var Axis = { +var Axis: AxisType = { x: {page: 'pageX', client: 'clientX', envScroll: 'currentPageScrollLeft'}, y: {page: 'pageY', client: 'clientY', envScroll: 'currentPageScrollTop'}, }; -function getAxisCoordOfEvent(axis, nativeEvent) { +function getAxisCoordOfEvent( + axis: AxisCoordinateData, + nativeEvent: _Touch, +): number { var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent); if (singleTouch) { return singleTouch[axis.page]; @@ -42,7 +84,10 @@ function getAxisCoordOfEvent(axis, nativeEvent) { nativeEvent[axis.client] + ViewportMetrics[axis.envScroll]; } -function getDistance(coords, nativeEvent) { +function getDistance( + coords: CoordinatesType, + nativeEvent: _Touch, +): number { var pageX = getAxisCoordOfEvent(Axis.x, nativeEvent); var pageY = getAxisCoordOfEvent(Axis.y, nativeEvent); return Math.pow( @@ -64,7 +109,7 @@ var dependencies = [ 'topMouseUp', ].concat(touchEvents); -var eventTypes = { +var eventTypes: DispatchConfig = { touchTap: { phasedRegistrationNames: { bubbled: 'onTouchTap', @@ -78,17 +123,17 @@ var usedTouch = false; var usedTouchTime = 0; var TOUCH_DELAY = 1000; -var TapEventPlugin = { +var TapEventPlugin: PluginModule<_Touch> = { tapMoveThreshold: tapMoveThreshold, eventTypes: eventTypes, extractEvents: function( - topLevelType, - targetInst, - nativeEvent, - nativeEventTarget + topLevelType: TopLevelTypes, + targetInst: ReactInstance, + nativeEvent: _Touch, + nativeEventTarget: EventTarget, ) { if (!isStartish(topLevelType) && !isEndish(topLevelType)) { return null; diff --git a/src/renderers/shared/stack/event/EventPluginRegistry.js b/src/renderers/shared/stack/event/EventPluginRegistry.js index 6a33b71fd1..f44593eab8 100644 --- a/src/renderers/shared/stack/event/EventPluginRegistry.js +++ b/src/renderers/shared/stack/event/EventPluginRegistry.js @@ -15,15 +15,15 @@ import type { DispatchConfig, ReactSyntheticEvent, -} from 'ReactSyntheticEvent'; +} from 'ReactSyntheticEventType'; -type PluginName = string; +import type { + AnyNativeEvent, + PluginName, + PluginModule, +} from 'PluginModuleType'; -type PluginModule = { - eventTypes: any, -}; - -type NamesToPlugins = {[key: PluginName]: PluginModule}; +type NamesToPlugins = {[key: PluginName]: PluginModule}; type EventPluginOrder = null | Array; @@ -94,7 +94,7 @@ function recomputePluginOrdering(): void { */ function publishEventForPlugin( dispatchConfig: DispatchConfig, - pluginModule: PluginModule, + pluginModule: PluginModule, eventName: string, ): boolean { invariant( @@ -139,7 +139,7 @@ function publishEventForPlugin( */ function publishRegistrationName( registrationName: string, - pluginModule: PluginModule, + pluginModule: PluginModule, eventName: string, ): void { invariant( @@ -267,22 +267,27 @@ var EventPluginRegistry = { */ getPluginModuleForEvent: function( event: ReactSyntheticEvent, - ): null | PluginModule { + ): null | PluginModule { var dispatchConfig = event.dispatchConfig; if (dispatchConfig.registrationName) { return EventPluginRegistry.registrationNameModules[ dispatchConfig.registrationName ] || null; } - for (var phase in dispatchConfig.phasedRegistrationNames) { - if (!dispatchConfig.phasedRegistrationNames.hasOwnProperty(phase)) { - continue; - } - var pluginModule = EventPluginRegistry.registrationNameModules[ - dispatchConfig.phasedRegistrationNames[phase] - ]; - if (pluginModule) { - return pluginModule; + if (dispatchConfig.phasedRegistrationNames !== undefined) { + // pulling phasedRegistrationNames out of dispatchConfig helps Flow see + // that it is not undefined. + var {phasedRegistrationNames} = dispatchConfig; + for (var phase in phasedRegistrationNames) { + if (!phasedRegistrationNames.hasOwnProperty(phase)) { + continue; + } + var pluginModule = EventPluginRegistry.registrationNameModules[ + phasedRegistrationNames[phase] + ]; + if (pluginModule) { + return pluginModule; + } } } return null; diff --git a/src/renderers/shared/stack/event/PluginModuleType.js b/src/renderers/shared/stack/event/PluginModuleType.js new file mode 100644 index 0000000000..7f22bacebc --- /dev/null +++ b/src/renderers/shared/stack/event/PluginModuleType.js @@ -0,0 +1,49 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule PluginModuleType + * @flow + */ + +'use strict'; + +import type {ReactInstance} from 'ReactInstanceType'; +import type { + DispatchConfig, + ReactSyntheticEvent, +} from 'ReactSyntheticEventType'; + +type EventTypes = {[key: string]: DispatchConfig}; + +export type AnyNativeEvent = + Event | + KeyboardEvent | + MouseEvent | + Touch; + +export type PluginName = string; + +export type PluginModule = { + eventTypes: EventTypes, + extractEvents: ( + topLevelType: string, + targetInst: ReactInstance, + nativeTarget: NativeEvent, + nativeEventTarget: EventTarget, + ) => null | ReactSyntheticEvent, + didPutListener?: ( + inst: ReactInstance, + registrationName: string, + listener: () => void, + ) => void, + willDeleteListener?: ( + inst: ReactInstance, + registrationName: string, + ) => void, + tapMoveThreshold?: number, +}; diff --git a/src/renderers/shared/stack/event/ReactSyntheticEvent.js b/src/renderers/shared/stack/event/ReactSyntheticEvent.js deleted file mode 100644 index 672e137da1..0000000000 --- a/src/renderers/shared/stack/event/ReactSyntheticEvent.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * Flow type for SyntheticEvent class that includes private properties - * - * @providesModule ReactSyntheticEvent - * @flow - */ - -'use strict'; - -export type DispatchConfig = any; - -export class ReactSyntheticEvent extends SyntheticEvent { - dispatchConfig: DispatchConfig; -} diff --git a/src/renderers/shared/stack/event/ReactSyntheticEventType.js b/src/renderers/shared/stack/event/ReactSyntheticEventType.js new file mode 100644 index 0000000000..b40c03fd94 --- /dev/null +++ b/src/renderers/shared/stack/event/ReactSyntheticEventType.js @@ -0,0 +1,36 @@ +/* + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * Flow type for SyntheticEvent class that includes private properties + * + * @providesModule ReactSyntheticEventType + * @flow + */ + +'use strict'; + +import type {ReactInstance} from 'ReactInstanceType'; + +export type DispatchConfig = { + dependencies: Array, + phasedRegistrationNames?: { + bubbled: string, + captured: string, + }, + registrationName?: string, +}; + +export type ReactSyntheticEvent = { + dispatchConfig: DispatchConfig; + getPooled: ( + dispatchConfig: DispatchConfig, + targetInst: ReactInstance, + nativeTarget: Event, + nativeEventTarget: EventTarget, + ) => ReactSyntheticEvent; +} & SyntheticEvent; diff --git a/src/renderers/shared/stack/reconciler/ReactInstanceType.js b/src/renderers/shared/stack/reconciler/ReactInstanceType.js index 15f321b6cb..0336467cb9 100644 --- a/src/renderers/shared/stack/reconciler/ReactInstanceType.js +++ b/src/renderers/shared/stack/reconciler/ReactInstanceType.js @@ -28,6 +28,7 @@ export type ReactInstance = { detachRef: (ref: string) => void, getName: () => string, getPublicInstance: any, + _rootNodeID: number, // instantiateReactComponent _mountIndex: number,