@tamagui/react-native-use-responder-events 1.0.1-beta.194
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/PressResponder.js +373 -0
- package/dist/cjs/PressResponder.js.map +7 -0
- package/dist/cjs/ResponderSystem.js +365 -0
- package/dist/cjs/ResponderSystem.js.map +7 -0
- package/dist/cjs/ResponderTouchHistoryStore.js +184 -0
- package/dist/cjs/ResponderTouchHistoryStore.js.map +7 -0
- package/dist/cjs/createResponderEvent.js +152 -0
- package/dist/cjs/createResponderEvent.js.map +7 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/types.js +98 -0
- package/dist/cjs/types.js.map +7 -0
- package/dist/cjs/useResponderEvents.js +76 -0
- package/dist/cjs/useResponderEvents.js.map +7 -0
- package/dist/cjs/utils.js +169 -0
- package/dist/cjs/utils.js.map +7 -0
- package/dist/esm/PressResponder.js +349 -0
- package/dist/esm/PressResponder.js.map +7 -0
- package/dist/esm/ResponderSystem.js +338 -0
- package/dist/esm/ResponderSystem.js.map +7 -0
- package/dist/esm/ResponderTouchHistoryStore.js +160 -0
- package/dist/esm/ResponderTouchHistoryStore.js.map +7 -0
- package/dist/esm/createResponderEvent.js +130 -0
- package/dist/esm/createResponderEvent.js.map +7 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/types.js +55 -0
- package/dist/esm/types.js.map +7 -0
- package/dist/esm/useResponderEvents.js +45 -0
- package/dist/esm/useResponderEvents.js.map +7 -0
- package/dist/esm/utils.js +137 -0
- package/dist/esm/utils.js.map +7 -0
- package/package.json +30 -0
- package/src/ResponderSystem.ts +531 -0
- package/src/ResponderTouchHistoryStore.ts +213 -0
- package/src/createResponderEvent.ts +187 -0
- package/src/index.ts +1 -0
- package/src/types.ts +78 -0
- package/src/useResponderEvents.ts +72 -0
- package/src/utils.ts +192 -0
- package/types/PressResponder.d.ts +92 -0
- package/types/ResponderSystem.d.ts +47 -0
- package/types/ResponderTouchHistoryStore.d.ts +37 -0
- package/types/createResponderEvent.d.ts +36 -0
- package/types/index.d.ts +2 -0
- package/types/types.d.ts +52 -0
- package/types/useResponderEvents.d.ts +9 -0
- package/types/utils.d.ts +38 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Nicolas Gallagher
|
|
3
|
+
* This source code is licensed under the MIT license found in the
|
|
4
|
+
* LICENSE file in the root directory of this source tree.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ResponderEvent } from './createResponderEvent'
|
|
8
|
+
import createResponderEvent from './createResponderEvent'
|
|
9
|
+
import { ResponderTouchHistoryStore } from './ResponderTouchHistoryStore'
|
|
10
|
+
import { isCancelish, isEndish, isMoveish, isScroll, isSelectionChange, isStartish } from './types'
|
|
11
|
+
import { canUseDOM } from './utils'
|
|
12
|
+
import {
|
|
13
|
+
getLowestCommonAncestor,
|
|
14
|
+
getResponderPaths,
|
|
15
|
+
hasTargetTouches,
|
|
16
|
+
hasValidSelection,
|
|
17
|
+
isPrimaryPointerDown,
|
|
18
|
+
setResponderId,
|
|
19
|
+
} from './utils'
|
|
20
|
+
|
|
21
|
+
/* ------------ TYPES ------------ */
|
|
22
|
+
|
|
23
|
+
type ResponderId = string
|
|
24
|
+
|
|
25
|
+
type ActiveResponderInstance = {
|
|
26
|
+
id: ResponderId
|
|
27
|
+
idPath: Array<number>
|
|
28
|
+
node: any
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type EmptyResponderInstance = {
|
|
32
|
+
id: null
|
|
33
|
+
idPath: null
|
|
34
|
+
node: null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type ResponderInstance = ActiveResponderInstance | EmptyResponderInstance
|
|
38
|
+
|
|
39
|
+
export type ResponderConfig = {
|
|
40
|
+
// Direct responder events dispatched directly to responder. Do not bubble.
|
|
41
|
+
onResponderEnd?: ((e: ResponderEvent) => void) | null
|
|
42
|
+
onResponderGrant?: ((e: ResponderEvent) => void | boolean) | null
|
|
43
|
+
onResponderMove?: ((e: ResponderEvent) => void) | null
|
|
44
|
+
onResponderRelease?: ((e: ResponderEvent) => void) | null
|
|
45
|
+
onResponderReject?: ((e: ResponderEvent) => void) | null
|
|
46
|
+
onResponderStart?: ((e: ResponderEvent) => void) | null
|
|
47
|
+
onResponderTerminate?: ((e: ResponderEvent) => void) | null
|
|
48
|
+
onResponderTerminationRequest?: ((e: ResponderEvent) => boolean) | null
|
|
49
|
+
// On pointer down, should this element become the responder?
|
|
50
|
+
onStartShouldSetResponder?: ((e: ResponderEvent) => boolean) | null
|
|
51
|
+
onStartShouldSetResponderCapture?: ((e: ResponderEvent) => boolean) | null
|
|
52
|
+
// On pointer move, should this element become the responder?
|
|
53
|
+
onMoveShouldSetResponder?: ((e: ResponderEvent) => boolean) | null
|
|
54
|
+
onMoveShouldSetResponderCapture?: ((e: ResponderEvent) => boolean) | null
|
|
55
|
+
// On scroll, should this element become the responder? Do no bubble
|
|
56
|
+
onScrollShouldSetResponder?: ((e: ResponderEvent) => boolean) | null
|
|
57
|
+
onScrollShouldSetResponderCapture?: ((e: ResponderEvent) => boolean) | null
|
|
58
|
+
// On text selection change, should this element become the responder?
|
|
59
|
+
onSelectionChangeShouldSetResponder?: ((e: ResponderEvent) => boolean) | null
|
|
60
|
+
onSelectionChangeShouldSetResponderCapture?: ((e: ResponderEvent) => boolean) | null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const emptyObject = {}
|
|
64
|
+
|
|
65
|
+
/* ------------ IMPLEMENTATION ------------ */
|
|
66
|
+
|
|
67
|
+
const startRegistration = [
|
|
68
|
+
'onStartShouldSetResponderCapture',
|
|
69
|
+
'onStartShouldSetResponder',
|
|
70
|
+
{ bubbles: true },
|
|
71
|
+
]
|
|
72
|
+
const moveRegistration = [
|
|
73
|
+
'onMoveShouldSetResponderCapture',
|
|
74
|
+
'onMoveShouldSetResponder',
|
|
75
|
+
{ bubbles: true },
|
|
76
|
+
]
|
|
77
|
+
const scrollRegistration = [
|
|
78
|
+
'onScrollShouldSetResponderCapture',
|
|
79
|
+
'onScrollShouldSetResponder',
|
|
80
|
+
{ bubbles: false },
|
|
81
|
+
]
|
|
82
|
+
const shouldSetResponderEvents = {
|
|
83
|
+
touchstart: startRegistration,
|
|
84
|
+
mousedown: startRegistration,
|
|
85
|
+
touchmove: moveRegistration,
|
|
86
|
+
mousemove: moveRegistration,
|
|
87
|
+
scroll: scrollRegistration,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const emptyResponder = { id: null, idPath: null, node: null }
|
|
91
|
+
const responderListenersMap = new Map()
|
|
92
|
+
|
|
93
|
+
let isEmulatingMouseEvents = false
|
|
94
|
+
let trackedTouchCount = 0
|
|
95
|
+
let currentResponder: ResponderInstance = {
|
|
96
|
+
id: null,
|
|
97
|
+
node: null,
|
|
98
|
+
idPath: null,
|
|
99
|
+
}
|
|
100
|
+
const responderTouchHistoryStore = new ResponderTouchHistoryStore()
|
|
101
|
+
|
|
102
|
+
function changeCurrentResponder(responder: ResponderInstance) {
|
|
103
|
+
currentResponder = responder
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getResponderConfig(id: ResponderId): ResponderConfig | any {
|
|
107
|
+
const config = responderListenersMap.get(id)
|
|
108
|
+
return config != null ? config : emptyObject
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Process native events
|
|
113
|
+
*
|
|
114
|
+
* A single event listener is used to manage the responder system.
|
|
115
|
+
* All pointers are tracked in the ResponderTouchHistoryStore. Native events
|
|
116
|
+
* are interpreted in terms of the Responder System and checked to see if
|
|
117
|
+
* the responder should be transferred. Each host node that is attached to
|
|
118
|
+
* the Responder System has an ID, which is used to look up its associated
|
|
119
|
+
* callbacks.
|
|
120
|
+
*/
|
|
121
|
+
function eventListener(domEvent: any) {
|
|
122
|
+
const eventType = domEvent.type
|
|
123
|
+
const eventTarget = domEvent.target
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Manage emulated events and early bailout.
|
|
127
|
+
* Since PointerEvent is not used yet (lack of support in older Safari), it's
|
|
128
|
+
* necessary to manually manage the mess of browser touch/mouse events.
|
|
129
|
+
* And bailout early for termination events when there is no active responder.
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
// Flag when browser may produce emulated events
|
|
133
|
+
if (eventType === 'touchstart') {
|
|
134
|
+
isEmulatingMouseEvents = true
|
|
135
|
+
}
|
|
136
|
+
// Remove flag when browser will not produce emulated events
|
|
137
|
+
if (eventType === 'touchmove' || trackedTouchCount > 1) {
|
|
138
|
+
isEmulatingMouseEvents = false
|
|
139
|
+
}
|
|
140
|
+
// Ignore various events in particular circumstances
|
|
141
|
+
if (
|
|
142
|
+
// Ignore browser emulated mouse events
|
|
143
|
+
(eventType === 'mousedown' && isEmulatingMouseEvents) ||
|
|
144
|
+
(eventType === 'mousemove' && isEmulatingMouseEvents) ||
|
|
145
|
+
// Ignore mousemove if a mousedown didn't occur first
|
|
146
|
+
(eventType === 'mousemove' && trackedTouchCount < 1)
|
|
147
|
+
) {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
// Remove flag after emulated events are finished
|
|
151
|
+
if (isEmulatingMouseEvents && eventType === 'mouseup') {
|
|
152
|
+
if (trackedTouchCount === 0) {
|
|
153
|
+
isEmulatingMouseEvents = false
|
|
154
|
+
}
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const isStartEvent = isStartish(eventType) && isPrimaryPointerDown(domEvent)
|
|
159
|
+
const isMoveEvent = isMoveish(eventType)
|
|
160
|
+
const isEndEvent = isEndish(eventType)
|
|
161
|
+
const isScrollEvent = isScroll(eventType)
|
|
162
|
+
const isSelectionChangeEvent = isSelectionChange(eventType)
|
|
163
|
+
const responderEvent = createResponderEvent(domEvent, responderTouchHistoryStore)
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Record the state of active pointers
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
if (isStartEvent || isMoveEvent || isEndEvent) {
|
|
170
|
+
if (domEvent.touches) {
|
|
171
|
+
trackedTouchCount = domEvent.touches.length
|
|
172
|
+
} else {
|
|
173
|
+
if (isStartEvent) {
|
|
174
|
+
trackedTouchCount = 1
|
|
175
|
+
} else if (isEndEvent) {
|
|
176
|
+
trackedTouchCount = 0
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
responderTouchHistoryStore.recordTouchTrack(eventType, responderEvent.nativeEvent as any)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Responder System logic
|
|
184
|
+
*/
|
|
185
|
+
|
|
186
|
+
let eventPaths: any = getResponderPaths(domEvent)
|
|
187
|
+
let wasNegotiated = false
|
|
188
|
+
let wantsResponder
|
|
189
|
+
|
|
190
|
+
// If an event occured that might change the current responder...
|
|
191
|
+
if (isStartEvent || isMoveEvent || (isScrollEvent && trackedTouchCount > 0)) {
|
|
192
|
+
// If there is already a responder, prune the event paths to the lowest common ancestor
|
|
193
|
+
// of the existing responder and deepest target of the event.
|
|
194
|
+
const currentResponderIdPath = currentResponder.idPath
|
|
195
|
+
const eventIdPath = eventPaths.idPath
|
|
196
|
+
|
|
197
|
+
if (currentResponderIdPath != null && eventIdPath != null) {
|
|
198
|
+
const lowestCommonAncestor = getLowestCommonAncestor(currentResponderIdPath, eventIdPath)
|
|
199
|
+
if (lowestCommonAncestor != null) {
|
|
200
|
+
const indexOfLowestCommonAncestor = eventIdPath.indexOf(lowestCommonAncestor)
|
|
201
|
+
// Skip the current responder so it doesn't receive unexpected "shouldSet" events.
|
|
202
|
+
const index =
|
|
203
|
+
indexOfLowestCommonAncestor + (lowestCommonAncestor === currentResponder.id ? 1 : 0)
|
|
204
|
+
eventPaths = {
|
|
205
|
+
idPath: eventIdPath.slice(index),
|
|
206
|
+
nodePath: eventPaths.nodePath.slice(index),
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
eventPaths = null
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (eventPaths != null) {
|
|
214
|
+
// If a node wants to become the responder, attempt to transfer.
|
|
215
|
+
wantsResponder = findWantsResponder(eventPaths, domEvent, responderEvent)
|
|
216
|
+
if (wantsResponder != null) {
|
|
217
|
+
// Sets responder if none exists, or negotates with existing responder.
|
|
218
|
+
attemptTransfer(responderEvent, wantsResponder)
|
|
219
|
+
wasNegotiated = true
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// If there is now a responder, invoke its callbacks for the lifecycle of the gesture.
|
|
225
|
+
if (currentResponder.id != null && currentResponder.node != null) {
|
|
226
|
+
const { id, node } = currentResponder
|
|
227
|
+
const {
|
|
228
|
+
onResponderStart,
|
|
229
|
+
onResponderMove,
|
|
230
|
+
onResponderEnd,
|
|
231
|
+
onResponderRelease,
|
|
232
|
+
onResponderTerminate,
|
|
233
|
+
onResponderTerminationRequest,
|
|
234
|
+
} = getResponderConfig(id)
|
|
235
|
+
|
|
236
|
+
responderEvent.bubbles = false
|
|
237
|
+
responderEvent.cancelable = false
|
|
238
|
+
responderEvent.currentTarget = node
|
|
239
|
+
|
|
240
|
+
// Start
|
|
241
|
+
if (isStartEvent) {
|
|
242
|
+
if (onResponderStart != null) {
|
|
243
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderStart'
|
|
244
|
+
onResponderStart(responderEvent)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Move
|
|
248
|
+
else if (isMoveEvent) {
|
|
249
|
+
if (onResponderMove != null) {
|
|
250
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderMove'
|
|
251
|
+
onResponderMove(responderEvent)
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
const isTerminateEvent =
|
|
255
|
+
isCancelish(eventType) ||
|
|
256
|
+
// native context menu
|
|
257
|
+
eventType === 'contextmenu' ||
|
|
258
|
+
// window blur
|
|
259
|
+
(eventType === 'blur' && eventTarget === window) ||
|
|
260
|
+
// responder (or ancestors) blur
|
|
261
|
+
(eventType === 'blur' && eventTarget.contains(node) && domEvent.relatedTarget !== node) ||
|
|
262
|
+
// native scroll without using a pointer
|
|
263
|
+
(isScrollEvent && trackedTouchCount === 0) ||
|
|
264
|
+
// native scroll on node that is parent of the responder (allow siblings to scroll)
|
|
265
|
+
(isScrollEvent && eventTarget.contains(node) && eventTarget !== node) ||
|
|
266
|
+
// native select/selectionchange on node
|
|
267
|
+
(isSelectionChangeEvent && hasValidSelection(domEvent))
|
|
268
|
+
|
|
269
|
+
const isReleaseEvent =
|
|
270
|
+
isEndEvent && !isTerminateEvent && !hasTargetTouches(node, domEvent.touches)
|
|
271
|
+
|
|
272
|
+
// End
|
|
273
|
+
if (isEndEvent) {
|
|
274
|
+
if (onResponderEnd != null) {
|
|
275
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderEnd'
|
|
276
|
+
onResponderEnd(responderEvent)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Release
|
|
280
|
+
if (isReleaseEvent) {
|
|
281
|
+
if (onResponderRelease != null) {
|
|
282
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderRelease'
|
|
283
|
+
onResponderRelease(responderEvent)
|
|
284
|
+
}
|
|
285
|
+
changeCurrentResponder(emptyResponder)
|
|
286
|
+
}
|
|
287
|
+
// Terminate
|
|
288
|
+
if (isTerminateEvent) {
|
|
289
|
+
let shouldTerminate = true
|
|
290
|
+
|
|
291
|
+
// Responders can still avoid termination but only for these events.
|
|
292
|
+
if (
|
|
293
|
+
eventType === 'contextmenu' ||
|
|
294
|
+
eventType === 'scroll' ||
|
|
295
|
+
eventType === 'selectionchange'
|
|
296
|
+
) {
|
|
297
|
+
// Only call this function is it wasn't already called during negotiation.
|
|
298
|
+
if (wasNegotiated) {
|
|
299
|
+
shouldTerminate = false
|
|
300
|
+
} else if (onResponderTerminationRequest != null) {
|
|
301
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderTerminationRequest'
|
|
302
|
+
if (onResponderTerminationRequest(responderEvent) === false) {
|
|
303
|
+
shouldTerminate = false
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (shouldTerminate) {
|
|
309
|
+
if (onResponderTerminate != null) {
|
|
310
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderTerminate'
|
|
311
|
+
onResponderTerminate(responderEvent)
|
|
312
|
+
}
|
|
313
|
+
changeCurrentResponder(emptyResponder)
|
|
314
|
+
isEmulatingMouseEvents = false
|
|
315
|
+
trackedTouchCount = 0
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Walk the event path to/from the target node. At each node, stop and call the
|
|
324
|
+
* relevant "shouldSet" functions for the given event type. If any of those functions
|
|
325
|
+
* call "stopPropagation" on the event, stop searching for a responder.
|
|
326
|
+
*/
|
|
327
|
+
function findWantsResponder(eventPaths, domEvent, responderEvent) {
|
|
328
|
+
const shouldSetCallbacks = shouldSetResponderEvents[domEvent.type as any] // for Flow
|
|
329
|
+
|
|
330
|
+
if (shouldSetCallbacks != null) {
|
|
331
|
+
const { idPath, nodePath } = eventPaths
|
|
332
|
+
|
|
333
|
+
const shouldSetCallbackCaptureName = shouldSetCallbacks[0]
|
|
334
|
+
const shouldSetCallbackBubbleName = shouldSetCallbacks[1]
|
|
335
|
+
const { bubbles } = shouldSetCallbacks[2]
|
|
336
|
+
|
|
337
|
+
const check = function (id, node, callbackName) {
|
|
338
|
+
const config = getResponderConfig(id)
|
|
339
|
+
const shouldSetCallback = config[callbackName]
|
|
340
|
+
if (shouldSetCallback != null) {
|
|
341
|
+
responderEvent.currentTarget = node
|
|
342
|
+
if (shouldSetCallback(responderEvent) === true) {
|
|
343
|
+
// Start the path from the potential responder
|
|
344
|
+
const prunedIdPath = idPath.slice(idPath.indexOf(id))
|
|
345
|
+
return { id, node, idPath: prunedIdPath }
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// capture
|
|
351
|
+
for (let i = idPath.length - 1; i >= 0; i--) {
|
|
352
|
+
const id = idPath[i]
|
|
353
|
+
const node = nodePath[i]
|
|
354
|
+
const result = check(id, node, shouldSetCallbackCaptureName)
|
|
355
|
+
if (result != null) {
|
|
356
|
+
return result
|
|
357
|
+
}
|
|
358
|
+
if (responderEvent.isPropagationStopped() === true) {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// bubble
|
|
364
|
+
if (bubbles) {
|
|
365
|
+
for (let i = 0; i < idPath.length; i++) {
|
|
366
|
+
const id = idPath[i]
|
|
367
|
+
const node = nodePath[i]
|
|
368
|
+
const result = check(id, node, shouldSetCallbackBubbleName)
|
|
369
|
+
if (result != null) {
|
|
370
|
+
return result
|
|
371
|
+
}
|
|
372
|
+
if (responderEvent.isPropagationStopped() === true) {
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
const id = idPath[0]
|
|
378
|
+
const node = nodePath[0]
|
|
379
|
+
const target = domEvent.target
|
|
380
|
+
if (target === node) {
|
|
381
|
+
return check(id, node, shouldSetCallbackBubbleName)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Attempt to transfer the responder.
|
|
389
|
+
*/
|
|
390
|
+
function attemptTransfer(responderEvent: ResponderEvent, wantsResponder: ActiveResponderInstance) {
|
|
391
|
+
const { id: currentId, node: currentNode } = currentResponder
|
|
392
|
+
const { id, node } = wantsResponder
|
|
393
|
+
|
|
394
|
+
const { onResponderGrant, onResponderReject } = getResponderConfig(id)
|
|
395
|
+
|
|
396
|
+
responderEvent.bubbles = false
|
|
397
|
+
responderEvent.cancelable = false
|
|
398
|
+
responderEvent.currentTarget = node
|
|
399
|
+
|
|
400
|
+
// Set responder
|
|
401
|
+
if (currentId == null) {
|
|
402
|
+
if (onResponderGrant != null) {
|
|
403
|
+
responderEvent.currentTarget = node
|
|
404
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderGrant'
|
|
405
|
+
onResponderGrant(responderEvent)
|
|
406
|
+
}
|
|
407
|
+
changeCurrentResponder(wantsResponder)
|
|
408
|
+
}
|
|
409
|
+
// Negotiate with current responder
|
|
410
|
+
else {
|
|
411
|
+
const { onResponderTerminate, onResponderTerminationRequest } = getResponderConfig(currentId)
|
|
412
|
+
|
|
413
|
+
let allowTransfer = true
|
|
414
|
+
if (onResponderTerminationRequest != null) {
|
|
415
|
+
responderEvent.currentTarget = currentNode
|
|
416
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderTerminationRequest'
|
|
417
|
+
if (onResponderTerminationRequest(responderEvent) === false) {
|
|
418
|
+
allowTransfer = false
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (allowTransfer) {
|
|
423
|
+
// Terminate existing responder
|
|
424
|
+
if (onResponderTerminate != null) {
|
|
425
|
+
responderEvent.currentTarget = currentNode
|
|
426
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderTerminate'
|
|
427
|
+
onResponderTerminate(responderEvent)
|
|
428
|
+
}
|
|
429
|
+
// Grant next responder
|
|
430
|
+
if (onResponderGrant != null) {
|
|
431
|
+
responderEvent.currentTarget = node
|
|
432
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderGrant'
|
|
433
|
+
onResponderGrant(responderEvent)
|
|
434
|
+
}
|
|
435
|
+
changeCurrentResponder(wantsResponder)
|
|
436
|
+
} else {
|
|
437
|
+
// Reject responder request
|
|
438
|
+
if (onResponderReject != null) {
|
|
439
|
+
responderEvent.currentTarget = node
|
|
440
|
+
responderEvent.dispatchConfig.registrationName = 'onResponderReject'
|
|
441
|
+
onResponderReject(responderEvent)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* ------------ PUBLIC API ------------ */
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Attach Listeners
|
|
451
|
+
*
|
|
452
|
+
* Use native events as ReactDOM doesn't have a non-plugin API to implement
|
|
453
|
+
* this system.
|
|
454
|
+
*/
|
|
455
|
+
const documentEventsCapturePhase = ['blur', 'scroll']
|
|
456
|
+
const documentEventsBubblePhase = [
|
|
457
|
+
// mouse
|
|
458
|
+
'mousedown',
|
|
459
|
+
'mousemove',
|
|
460
|
+
'mouseup',
|
|
461
|
+
'dragstart',
|
|
462
|
+
// touch
|
|
463
|
+
'touchstart',
|
|
464
|
+
'touchmove',
|
|
465
|
+
'touchend',
|
|
466
|
+
'touchcancel',
|
|
467
|
+
// other
|
|
468
|
+
'contextmenu',
|
|
469
|
+
'select',
|
|
470
|
+
'selectionchange',
|
|
471
|
+
]
|
|
472
|
+
export function attachListeners() {
|
|
473
|
+
if (canUseDOM && window['__reactResponderSystemActive'] == null) {
|
|
474
|
+
window.addEventListener('blur', eventListener)
|
|
475
|
+
documentEventsBubblePhase.forEach((eventType) => {
|
|
476
|
+
document.addEventListener(eventType, eventListener)
|
|
477
|
+
})
|
|
478
|
+
documentEventsCapturePhase.forEach((eventType) => {
|
|
479
|
+
document.addEventListener(eventType, eventListener, true)
|
|
480
|
+
})
|
|
481
|
+
window['__reactResponderSystemActive'] = true
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Register a node with the ResponderSystem.
|
|
487
|
+
*/
|
|
488
|
+
export function addNode(id: ResponderId, node: any, config: ResponderConfig) {
|
|
489
|
+
setResponderId(node, id)
|
|
490
|
+
responderListenersMap.set(id, config)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Unregister a node with the ResponderSystem.
|
|
495
|
+
*/
|
|
496
|
+
export function removeNode(id: ResponderId) {
|
|
497
|
+
if (currentResponder.id === id) {
|
|
498
|
+
terminateResponder()
|
|
499
|
+
}
|
|
500
|
+
if (responderListenersMap.has(id)) {
|
|
501
|
+
responderListenersMap.delete(id)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Allow the current responder to be terminated from within components to support
|
|
507
|
+
* more complex requirements, such as use with other React libraries for working
|
|
508
|
+
* with scroll views, input views, etc.
|
|
509
|
+
*/
|
|
510
|
+
export function terminateResponder() {
|
|
511
|
+
const { id, node } = currentResponder
|
|
512
|
+
if (id != null && node != null) {
|
|
513
|
+
const { onResponderTerminate } = getResponderConfig(id)
|
|
514
|
+
if (onResponderTerminate != null) {
|
|
515
|
+
const event = createResponderEvent({}, responderTouchHistoryStore)
|
|
516
|
+
event.currentTarget = node
|
|
517
|
+
onResponderTerminate(event)
|
|
518
|
+
}
|
|
519
|
+
changeCurrentResponder(emptyResponder)
|
|
520
|
+
}
|
|
521
|
+
isEmulatingMouseEvents = false
|
|
522
|
+
trackedTouchCount = 0
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Allow unit tests to inspect the current responder in the system.
|
|
527
|
+
* FOR TESTING ONLY.
|
|
528
|
+
*/
|
|
529
|
+
export function getResponderNode(): any {
|
|
530
|
+
return currentResponder.node
|
|
531
|
+
}
|