@hanzogui/react-native-use-responder-events 2.0.0

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