Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81155 views
1
/**
2
* Copyright 2013-2014, 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 ResponderEventPlugin
10
*/
11
12
"use strict";
13
14
var EventConstants = require('EventConstants');
15
var EventPluginUtils = require('EventPluginUtils');
16
var EventPropagators = require('EventPropagators');
17
var SyntheticEvent = require('SyntheticEvent');
18
19
var accumulateInto = require('accumulateInto');
20
var keyOf = require('keyOf');
21
22
var isStartish = EventPluginUtils.isStartish;
23
var isMoveish = EventPluginUtils.isMoveish;
24
var isEndish = EventPluginUtils.isEndish;
25
var executeDirectDispatch = EventPluginUtils.executeDirectDispatch;
26
var hasDispatches = EventPluginUtils.hasDispatches;
27
var executeDispatchesInOrderStopAtTrue =
28
EventPluginUtils.executeDispatchesInOrderStopAtTrue;
29
30
/**
31
* ID of element that should respond to touch/move types of interactions, as
32
* indicated explicitly by relevant callbacks.
33
*/
34
var responderID = null;
35
var isPressing = false;
36
37
var eventTypes = {
38
/**
39
* On a `touchStart`/`mouseDown`, is it desired that this element become the
40
* responder?
41
*/
42
startShouldSetResponder: {
43
phasedRegistrationNames: {
44
bubbled: keyOf({onStartShouldSetResponder: null}),
45
captured: keyOf({onStartShouldSetResponderCapture: null})
46
}
47
},
48
49
/**
50
* On a `scroll`, is it desired that this element become the responder? This
51
* is usually not needed, but should be used to retroactively infer that a
52
* `touchStart` had occured during momentum scroll. During a momentum scroll,
53
* a touch start will be immediately followed by a scroll event if the view is
54
* currently scrolling.
55
*/
56
scrollShouldSetResponder: {
57
phasedRegistrationNames: {
58
bubbled: keyOf({onScrollShouldSetResponder: null}),
59
captured: keyOf({onScrollShouldSetResponderCapture: null})
60
}
61
},
62
63
/**
64
* On a `touchMove`/`mouseMove`, is it desired that this element become the
65
* responder?
66
*/
67
moveShouldSetResponder: {
68
phasedRegistrationNames: {
69
bubbled: keyOf({onMoveShouldSetResponder: null}),
70
captured: keyOf({onMoveShouldSetResponderCapture: null})
71
}
72
},
73
74
/**
75
* Direct responder events dispatched directly to responder. Do not bubble.
76
*/
77
responderMove: {registrationName: keyOf({onResponderMove: null})},
78
responderRelease: {registrationName: keyOf({onResponderRelease: null})},
79
responderTerminationRequest: {
80
registrationName: keyOf({onResponderTerminationRequest: null})
81
},
82
responderGrant: {registrationName: keyOf({onResponderGrant: null})},
83
responderReject: {registrationName: keyOf({onResponderReject: null})},
84
responderTerminate: {registrationName: keyOf({onResponderTerminate: null})}
85
};
86
87
/**
88
* Performs negotiation between any existing/current responder, checks to see if
89
* any new entity is interested in becoming responder, performs that handshake
90
* and returns any events that must be emitted to notify the relevant parties.
91
*
92
* A note about event ordering in the `EventPluginHub`.
93
*
94
* Suppose plugins are injected in the following order:
95
*
96
* `[R, S, C]`
97
*
98
* To help illustrate the example, assume `S` is `SimpleEventPlugin` (for
99
* `onClick` etc) and `R` is `ResponderEventPlugin`.
100
*
101
* "Deferred-Dispatched Events":
102
*
103
* - The current event plugin system will traverse the list of injected plugins,
104
* in order, and extract events by collecting the plugin's return value of
105
* `extractEvents()`.
106
* - These events that are returned from `extractEvents` are "deferred
107
* dispatched events".
108
* - When returned from `extractEvents`, deferred-dispatched events contain an
109
* "accumulation" of deferred dispatches.
110
* - These deferred dispatches are accumulated/collected before they are
111
* returned, but processed at a later time by the `EventPluginHub` (hence the
112
* name deferred).
113
*
114
* In the process of returning their deferred-dispatched events, event plugins
115
* themselves can dispatch events on-demand without returning them from
116
* `extractEvents`. Plugins might want to do this, so that they can use event
117
* dispatching as a tool that helps them decide which events should be extracted
118
* in the first place.
119
*
120
* "On-Demand-Dispatched Events":
121
*
122
* - On-demand-dispatched events are not returned from `extractEvents`.
123
* - On-demand-dispatched events are dispatched during the process of returning
124
* the deferred-dispatched events.
125
* - They should not have side effects.
126
* - They should be avoided, and/or eventually be replaced with another
127
* abstraction that allows event plugins to perform multiple "rounds" of event
128
* extraction.
129
*
130
* Therefore, the sequence of event dispatches becomes:
131
*
132
* - `R`s on-demand events (if any) (dispatched by `R` on-demand)
133
* - `S`s on-demand events (if any) (dispatched by `S` on-demand)
134
* - `C`s on-demand events (if any) (dispatched by `C` on-demand)
135
* - `R`s extracted events (if any) (dispatched by `EventPluginHub`)
136
* - `S`s extracted events (if any) (dispatched by `EventPluginHub`)
137
* - `C`s extracted events (if any) (dispatched by `EventPluginHub`)
138
*
139
* In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
140
* on-demand dispatch returns `true` (and some other details are satisfied) the
141
* `onResponderGrant` deferred dispatched event is returned from
142
* `extractEvents`. The sequence of dispatch executions in this case
143
* will appear as follows:
144
*
145
* - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
146
* - `touchStartCapture` (`EventPluginHub` dispatches as usual)
147
* - `touchStart` (`EventPluginHub` dispatches as usual)
148
* - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
149
*
150
* @param {string} topLevelType Record from `EventConstants`.
151
* @param {string} topLevelTargetID ID of deepest React rendered element.
152
* @param {object} nativeEvent Native browser event.
153
* @return {*} An accumulation of synthetic events.
154
*/
155
function setResponderAndExtractTransfer(
156
topLevelType,
157
topLevelTargetID,
158
nativeEvent) {
159
var shouldSetEventType =
160
isStartish(topLevelType) ? eventTypes.startShouldSetResponder :
161
isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder :
162
eventTypes.scrollShouldSetResponder;
163
164
var bubbleShouldSetFrom = responderID || topLevelTargetID;
165
var shouldSetEvent = SyntheticEvent.getPooled(
166
shouldSetEventType,
167
bubbleShouldSetFrom,
168
nativeEvent
169
);
170
EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
171
var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
172
if (!shouldSetEvent.isPersistent()) {
173
shouldSetEvent.constructor.release(shouldSetEvent);
174
}
175
176
if (!wantsResponderID || wantsResponderID === responderID) {
177
return null;
178
}
179
var extracted;
180
var grantEvent = SyntheticEvent.getPooled(
181
eventTypes.responderGrant,
182
wantsResponderID,
183
nativeEvent
184
);
185
186
EventPropagators.accumulateDirectDispatches(grantEvent);
187
if (responderID) {
188
var terminationRequestEvent = SyntheticEvent.getPooled(
189
eventTypes.responderTerminationRequest,
190
responderID,
191
nativeEvent
192
);
193
EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
194
var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
195
executeDirectDispatch(terminationRequestEvent);
196
if (!terminationRequestEvent.isPersistent()) {
197
terminationRequestEvent.constructor.release(terminationRequestEvent);
198
}
199
200
if (shouldSwitch) {
201
var terminateType = eventTypes.responderTerminate;
202
var terminateEvent = SyntheticEvent.getPooled(
203
terminateType,
204
responderID,
205
nativeEvent
206
);
207
EventPropagators.accumulateDirectDispatches(terminateEvent);
208
extracted = accumulateInto(extracted, [grantEvent, terminateEvent]);
209
responderID = wantsResponderID;
210
} else {
211
var rejectEvent = SyntheticEvent.getPooled(
212
eventTypes.responderReject,
213
wantsResponderID,
214
nativeEvent
215
);
216
EventPropagators.accumulateDirectDispatches(rejectEvent);
217
extracted = accumulateInto(extracted, rejectEvent);
218
}
219
} else {
220
extracted = accumulateInto(extracted, grantEvent);
221
responderID = wantsResponderID;
222
}
223
return extracted;
224
}
225
226
/**
227
* A transfer is a negotiation between a currently set responder and the next
228
* element to claim responder status. Any start event could trigger a transfer
229
* of responderID. Any move event could trigger a transfer, so long as there is
230
* currently a responder set (in other words as long as the user is pressing
231
* down).
232
*
233
* @param {string} topLevelType Record from `EventConstants`.
234
* @return {boolean} True if a transfer of responder could possibly occur.
235
*/
236
function canTriggerTransfer(topLevelType) {
237
return topLevelType === EventConstants.topLevelTypes.topScroll ||
238
isStartish(topLevelType) ||
239
(isPressing && isMoveish(topLevelType));
240
}
241
242
/**
243
* Event plugin for formalizing the negotiation between claiming locks on
244
* receiving touches.
245
*/
246
var ResponderEventPlugin = {
247
248
getResponderID: function() {
249
return responderID;
250
},
251
252
eventTypes: eventTypes,
253
254
/**
255
* @param {string} topLevelType Record from `EventConstants`.
256
* @param {DOMEventTarget} topLevelTarget The listening component root node.
257
* @param {string} topLevelTargetID ID of `topLevelTarget`.
258
* @param {object} nativeEvent Native browser event.
259
* @return {*} An accumulation of synthetic events.
260
* @see {EventPluginHub.extractEvents}
261
*/
262
extractEvents: function(
263
topLevelType,
264
topLevelTarget,
265
topLevelTargetID,
266
nativeEvent) {
267
var extracted;
268
// Must have missed an end event - reset the state here.
269
if (responderID && isStartish(topLevelType)) {
270
responderID = null;
271
}
272
if (isStartish(topLevelType)) {
273
isPressing = true;
274
} else if (isEndish(topLevelType)) {
275
isPressing = false;
276
}
277
if (canTriggerTransfer(topLevelType)) {
278
var transfer = setResponderAndExtractTransfer(
279
topLevelType,
280
topLevelTargetID,
281
nativeEvent
282
);
283
if (transfer) {
284
extracted = accumulateInto(extracted, transfer);
285
}
286
}
287
// Now that we know the responder is set correctly, we can dispatch
288
// responder type events (directly to the responder).
289
var type = isMoveish(topLevelType) ? eventTypes.responderMove :
290
isEndish(topLevelType) ? eventTypes.responderRelease :
291
isStartish(topLevelType) ? eventTypes.responderStart : null;
292
if (type) {
293
var gesture = SyntheticEvent.getPooled(
294
type,
295
responderID || '',
296
nativeEvent
297
);
298
EventPropagators.accumulateDirectDispatches(gesture);
299
extracted = accumulateInto(extracted, gesture);
300
}
301
if (type === eventTypes.responderRelease) {
302
responderID = null;
303
}
304
return extracted;
305
}
306
307
};
308
309
module.exports = ResponderEventPlugin;
310
311