Skip to content

Commit c1f0b4e

Browse files
flarniezpao
authored andcommitted
Flow type event plugins (#7667)
* Type SimpleEventPlugin and TapEventPlugin - Renamed file from 'ReactSynteticEvent' to 'ReactSyntheticEventType' - Fills in the 'any' holes that were left in DispatchConfig type and the type annotations in EventPluginRegistry. - Adds polymorphic PluginModule type and related types - Uses hack to support indexable properties on 'Touch' type in TapEventPlugin The issue in TapEventPlugin is that the code is accessing one of four possible properties on the 'Touch' type native event using the bracket accessor. Classes in Flow don't support using the bracket accessor, unless you use a declaration and the syntax `[key: Type]: Type`.[1] The downside of using that here is that we create a global type, which we may not need in other files. [1]: facebook/flow#1323 Other options: - Use looser typing or a '@fixme' comment and open an issue with Flow to support indexing on regular classes. - Rewrite TapEventPlugin to not use the bracket accessor on 'Touch'. I thought the current implementation was elegant and didn't want to change it. But we could do something like this: ``` if (nativeEvent.pageX || nativeEvent.pageY) { return axis.page === 'pageX' ? nativeEvent.pageX : nativeEvent.pageY; } else { var clientAxis = axis.client === 'clientX' ? nativeEvent.clientX : nativeEvent.clientY; return nativeEvent[axis.client] + ViewportMetrics[axis.envScroll]; } ``` (cherry picked from commit 7b2d965)
1 parent 26d0607 commit c1f0b4e

File tree

7 files changed

+192
-61
lines changed

7 files changed

+192
-61
lines changed

src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js

+27-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*
99
* @providesModule SimpleEventPlugin
10+
* @flow
1011
*/
1112

1213
'use strict';
@@ -30,6 +31,14 @@ var emptyFunction = require('emptyFunction');
3031
var getEventCharCode = require('getEventCharCode');
3132
var invariant = require('invariant');
3233

34+
import type {TopLevelTypes} from 'EventConstants';
35+
import type {
36+
DispatchConfig,
37+
ReactSyntheticEvent,
38+
} from 'ReactSyntheticEventType';
39+
import type {ReactInstance} from 'ReactInstanceType';
40+
import type {PluginModule} from 'PluginModuleType';
41+
3342
/**
3443
* Turns
3544
* ['abort', ...]
@@ -48,8 +57,8 @@ var invariant = require('invariant');
4857
* 'topAbort': { sameConfig }
4958
* };
5059
*/
51-
var eventTypes = {};
52-
var topLevelEventsToDispatchConfig = {};
60+
var eventTypes: {[key: string]: DispatchConfig} = {};
61+
var topLevelEventsToDispatchConfig: {[key: TopLevelTypes]: DispatchConfig} = {};
5362
[
5463
'abort',
5564
'animationEnd',
@@ -131,22 +140,22 @@ var topLevelEventsToDispatchConfig = {};
131140

132141
var onClickListeners = {};
133142

134-
function getDictionaryKey(inst) {
143+
function getDictionaryKey(inst: ReactInstance): string {
135144
// Prevents V8 performance issue:
136145
// https://github.com/facebook/react/pull/7232
137146
return '.' + inst._rootNodeID;
138147
}
139148

140-
var SimpleEventPlugin = {
149+
var SimpleEventPlugin: PluginModule<MouseEvent> = {
141150

142151
eventTypes: eventTypes,
143152

144153
extractEvents: function(
145-
topLevelType,
146-
targetInst,
147-
nativeEvent,
148-
nativeEventTarget
149-
) {
154+
topLevelType: TopLevelTypes,
155+
targetInst: ReactInstance,
156+
nativeEvent: MouseEvent,
157+
nativeEventTarget: EventTarget,
158+
): null | ReactSyntheticEvent {
150159
var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
151160
if (!dispatchConfig) {
152161
return null;
@@ -268,7 +277,11 @@ var SimpleEventPlugin = {
268277
return event;
269278
},
270279

271-
didPutListener: function(inst, registrationName, listener) {
280+
didPutListener: function(
281+
inst: ReactInstance,
282+
registrationName: string,
283+
listener: () => void,
284+
): void {
272285
// Mobile Safari does not fire properly bubble click events on
273286
// non-interactive elements, which means delegated click listeners do not
274287
// fire. The workaround for this bug involves attaching an empty click
@@ -286,7 +299,10 @@ var SimpleEventPlugin = {
286299
}
287300
},
288301

289-
willDeleteListener: function(inst, registrationName) {
302+
willDeleteListener: function(
303+
inst: ReactInstance,
304+
registrationName: string,
305+
): void {
290306
if (registrationName === 'onClick') {
291307
var key = getDictionaryKey(inst);
292308
onClickListeners[key].remove();

src/renderers/dom/client/eventPlugins/TapEventPlugin.js

+55-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*
99
* @providesModule TapEventPlugin
10+
* @flow
1011
*/
1112

1213
'use strict';
@@ -20,19 +21,60 @@ var ViewportMetrics = require('ViewportMetrics');
2021
var isStartish = EventPluginUtils.isStartish;
2122
var isEndish = EventPluginUtils.isEndish;
2223

24+
import type {DispatchConfig} from 'ReactSyntheticEventType';
25+
import type {PluginModule} from 'PluginModuleType';
26+
import type {ReactInstance} from 'ReactInstanceType';
27+
import type {TopLevelTypes} from 'EventConstants';
28+
29+
/**
30+
* We are extending the Flow 'Touch' declaration to enable using bracket
31+
* notation to access properties.
32+
* Without this adjustment Flow throws
33+
* "Indexable signature not found in Touch".
34+
* See https://github.com/facebook/flow/issues/1323
35+
*/
36+
type TouchPropertyKey =
37+
'clientX' |
38+
'clientY' |
39+
'pageX' |
40+
'pageY';
41+
42+
declare class _Touch extends Touch {
43+
[key: TouchPropertyKey]: number;
44+
}
45+
46+
type AxisCoordinateData = {
47+
page: TouchPropertyKey,
48+
client: TouchPropertyKey,
49+
envScroll: 'currentPageScrollLeft' | 'currentPageScrollTop',
50+
};
51+
52+
type AxisType = {
53+
x: AxisCoordinateData,
54+
y: AxisCoordinateData,
55+
};
56+
57+
type CoordinatesType = {
58+
x: number,
59+
y: number,
60+
};
61+
2362
/**
2463
* Number of pixels that are tolerated in between a `touchStart` and `touchEnd`
2564
* in order to still be considered a 'tap' event.
2665
*/
2766
var tapMoveThreshold = 10;
28-
var startCoords = {x: null, y: null};
67+
var startCoords: CoordinatesType = {x: 0, y: 0};
2968

30-
var Axis = {
69+
var Axis: AxisType = {
3170
x: {page: 'pageX', client: 'clientX', envScroll: 'currentPageScrollLeft'},
3271
y: {page: 'pageY', client: 'clientY', envScroll: 'currentPageScrollTop'},
3372
};
3473

35-
function getAxisCoordOfEvent(axis, nativeEvent) {
74+
function getAxisCoordOfEvent(
75+
axis: AxisCoordinateData,
76+
nativeEvent: _Touch,
77+
): number {
3678
var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
3779
if (singleTouch) {
3880
return singleTouch[axis.page];
@@ -42,7 +84,10 @@ function getAxisCoordOfEvent(axis, nativeEvent) {
4284
nativeEvent[axis.client] + ViewportMetrics[axis.envScroll];
4385
}
4486

45-
function getDistance(coords, nativeEvent) {
87+
function getDistance(
88+
coords: CoordinatesType,
89+
nativeEvent: _Touch,
90+
): number {
4691
var pageX = getAxisCoordOfEvent(Axis.x, nativeEvent);
4792
var pageY = getAxisCoordOfEvent(Axis.y, nativeEvent);
4893
return Math.pow(
@@ -64,7 +109,7 @@ var dependencies = [
64109
'topMouseUp',
65110
].concat(touchEvents);
66111

67-
var eventTypes = {
112+
var eventTypes: DispatchConfig = {
68113
touchTap: {
69114
phasedRegistrationNames: {
70115
bubbled: 'onTouchTap',
@@ -78,17 +123,17 @@ var usedTouch = false;
78123
var usedTouchTime = 0;
79124
var TOUCH_DELAY = 1000;
80125

81-
var TapEventPlugin = {
126+
var TapEventPlugin: PluginModule<_Touch> = {
82127

83128
tapMoveThreshold: tapMoveThreshold,
84129

85130
eventTypes: eventTypes,
86131

87132
extractEvents: function(
88-
topLevelType,
89-
targetInst,
90-
nativeEvent,
91-
nativeEventTarget
133+
topLevelType: TopLevelTypes,
134+
targetInst: ReactInstance,
135+
nativeEvent: _Touch,
136+
nativeEventTarget: EventTarget,
92137
) {
93138
if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
94139
return null;

src/renderers/shared/stack/event/EventPluginRegistry.js

+24-19
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
import type {
1616
DispatchConfig,
1717
ReactSyntheticEvent,
18-
} from 'ReactSyntheticEvent';
18+
} from 'ReactSyntheticEventType';
1919

20-
type PluginName = string;
21-
22-
type PluginModule = {
23-
eventTypes: any,
24-
};
20+
import type {
21+
AnyNativeEvent,
22+
PluginName,
23+
PluginModule,
24+
} from 'PluginModuleType';
2525

26-
type NamesToPlugins = {[key: PluginName]: PluginModule};
26+
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>};
2727

2828
type EventPluginOrder = null | Array<PluginName>;
2929

@@ -94,7 +94,7 @@ function recomputePluginOrdering(): void {
9494
*/
9595
function publishEventForPlugin(
9696
dispatchConfig: DispatchConfig,
97-
pluginModule: PluginModule,
97+
pluginModule: PluginModule<AnyNativeEvent>,
9898
eventName: string,
9999
): boolean {
100100
invariant(
@@ -139,7 +139,7 @@ function publishEventForPlugin(
139139
*/
140140
function publishRegistrationName(
141141
registrationName: string,
142-
pluginModule: PluginModule,
142+
pluginModule: PluginModule<AnyNativeEvent>,
143143
eventName: string,
144144
): void {
145145
invariant(
@@ -267,22 +267,27 @@ var EventPluginRegistry = {
267267
*/
268268
getPluginModuleForEvent: function(
269269
event: ReactSyntheticEvent,
270-
): null | PluginModule {
270+
): null | PluginModule<AnyNativeEvent> {
271271
var dispatchConfig = event.dispatchConfig;
272272
if (dispatchConfig.registrationName) {
273273
return EventPluginRegistry.registrationNameModules[
274274
dispatchConfig.registrationName
275275
] || null;
276276
}
277-
for (var phase in dispatchConfig.phasedRegistrationNames) {
278-
if (!dispatchConfig.phasedRegistrationNames.hasOwnProperty(phase)) {
279-
continue;
280-
}
281-
var pluginModule = EventPluginRegistry.registrationNameModules[
282-
dispatchConfig.phasedRegistrationNames[phase]
283-
];
284-
if (pluginModule) {
285-
return pluginModule;
277+
if (dispatchConfig.phasedRegistrationNames !== undefined) {
278+
// pulling phasedRegistrationNames out of dispatchConfig helps Flow see
279+
// that it is not undefined.
280+
var {phasedRegistrationNames} = dispatchConfig;
281+
for (var phase in phasedRegistrationNames) {
282+
if (!phasedRegistrationNames.hasOwnProperty(phase)) {
283+
continue;
284+
}
285+
var pluginModule = EventPluginRegistry.registrationNameModules[
286+
phasedRegistrationNames[phase]
287+
];
288+
if (pluginModule) {
289+
return pluginModule;
290+
}
286291
}
287292
}
288293
return null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Copyright 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule PluginModuleType
10+
* @flow
11+
*/
12+
13+
'use strict';
14+
15+
import type {ReactInstance} from 'ReactInstanceType';
16+
import type {
17+
DispatchConfig,
18+
ReactSyntheticEvent,
19+
} from 'ReactSyntheticEventType';
20+
21+
type EventTypes = {[key: string]: DispatchConfig};
22+
23+
export type AnyNativeEvent =
24+
Event |
25+
KeyboardEvent |
26+
MouseEvent |
27+
Touch;
28+
29+
export type PluginName = string;
30+
31+
export type PluginModule<NativeEvent> = {
32+
eventTypes: EventTypes,
33+
extractEvents: (
34+
topLevelType: string,
35+
targetInst: ReactInstance,
36+
nativeTarget: NativeEvent,
37+
nativeEventTarget: EventTarget,
38+
) => null | ReactSyntheticEvent,
39+
didPutListener?: (
40+
inst: ReactInstance,
41+
registrationName: string,
42+
listener: () => void,
43+
) => void,
44+
willDeleteListener?: (
45+
inst: ReactInstance,
46+
registrationName: string,
47+
) => void,
48+
tapMoveThreshold?: number,
49+
};

src/renderers/shared/stack/event/ReactSyntheticEvent.js

-21
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* Flow type for SyntheticEvent class that includes private properties
10+
*
11+
* @providesModule ReactSyntheticEventType
12+
* @flow
13+
*/
14+
15+
'use strict';
16+
17+
import type {ReactInstance} from 'ReactInstanceType';
18+
19+
export type DispatchConfig = {
20+
dependencies: Array<string>,
21+
phasedRegistrationNames?: {
22+
bubbled: string,
23+
captured: string,
24+
},
25+
registrationName?: string,
26+
};
27+
28+
export type ReactSyntheticEvent = {
29+
dispatchConfig: DispatchConfig;
30+
getPooled: (
31+
dispatchConfig: DispatchConfig,
32+
targetInst: ReactInstance,
33+
nativeTarget: Event,
34+
nativeEventTarget: EventTarget,
35+
) => ReactSyntheticEvent;
36+
} & SyntheticEvent;

0 commit comments

Comments
 (0)