react-native-gesture-handler 3.0.0 → 3.0.2

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 (100) hide show
  1. package/README.md +4 -0
  2. package/RNGestureHandler.podspec +1 -1
  3. package/android/build.gradle +6 -5
  4. package/android/gradle.properties +1 -1
  5. package/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +1 -1
  6. package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +28 -32
  7. package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +44 -6
  8. package/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt +4 -3
  9. package/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +0 -13
  10. package/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt +1 -1
  11. package/android/src/main/java/com/swmansion/gesturehandler/core/TapGestureHandler.kt +1 -1
  12. package/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt +3 -0
  13. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +30 -3
  14. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +4 -0
  15. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt +8 -6
  16. package/apple/Handlers/RNNativeViewHandler.mm +4 -1
  17. package/apple/RNGestureHandlerButton.h +5 -0
  18. package/apple/RNGestureHandlerButtonComponentView.mm +38 -0
  19. package/apple/RNGestureHandlerDetector.h +0 -2
  20. package/apple/RNGestureHandlerDetector.mm +6 -6
  21. package/apple/RNGestureHandlerManager.h +5 -0
  22. package/apple/RNGestureHandlerManager.mm +14 -12
  23. package/lib/module/components/Pressable/Pressable.js +6 -2
  24. package/lib/module/components/Pressable/Pressable.js.map +1 -1
  25. package/lib/module/components/Pressable/StateMachine.js +10 -1
  26. package/lib/module/components/Pressable/StateMachine.js.map +1 -1
  27. package/lib/module/components/Pressable/stateDefinitions.js +3 -2
  28. package/lib/module/components/Pressable/stateDefinitions.js.map +1 -1
  29. package/lib/module/components/Pressable/utils.js +32 -1
  30. package/lib/module/components/Pressable/utils.js.map +1 -1
  31. package/lib/module/components/ReanimatedDrawerLayout.js +5 -4
  32. package/lib/module/components/ReanimatedDrawerLayout.js.map +1 -1
  33. package/lib/module/components/ReanimatedSwipeable/ReanimatedSwipeable.js +8 -2
  34. package/lib/module/components/ReanimatedSwipeable/ReanimatedSwipeable.js.map +1 -1
  35. package/lib/module/components/utils.js +14 -0
  36. package/lib/module/components/utils.js.map +1 -1
  37. package/lib/module/handlers/gestures/GestureDetector/utils.js +6 -6
  38. package/lib/module/handlers/gestures/GestureDetector/utils.js.map +1 -1
  39. package/lib/module/handlers/gestures/gesture.js +8 -0
  40. package/lib/module/handlers/gestures/gesture.js.map +1 -1
  41. package/lib/module/handlers/gestures/gestureComposition.js +14 -2
  42. package/lib/module/handlers/gestures/gestureComposition.js.map +1 -1
  43. package/lib/module/v3/components/GestureButtons.js +3 -0
  44. package/lib/module/v3/components/GestureButtons.js.map +1 -1
  45. package/lib/module/v3/components/Pressable.js +24 -5
  46. package/lib/module/v3/components/Pressable.js.map +1 -1
  47. package/lib/module/v3/components/Touchable/Touchable.js +3 -0
  48. package/lib/module/v3/components/Touchable/Touchable.js.map +1 -1
  49. package/lib/module/v3/hooks/utils/configUtils.js +3 -3
  50. package/lib/module/v3/hooks/utils/configUtils.js.map +1 -1
  51. package/lib/module/v3/hooks/utils/propsWhiteList.js +7 -6
  52. package/lib/module/v3/hooks/utils/propsWhiteList.js.map +1 -1
  53. package/lib/module/v3/hooks/utils/reanimatedUtils.js +8 -10
  54. package/lib/module/v3/hooks/utils/reanimatedUtils.js.map +1 -1
  55. package/lib/module/web/tools/GestureHandlerWebDelegate.js +5 -4
  56. package/lib/module/web/tools/GestureHandlerWebDelegate.js.map +1 -1
  57. package/lib/typescript/components/Pressable/Pressable.d.ts.map +1 -1
  58. package/lib/typescript/components/Pressable/StateMachine.d.ts +1 -0
  59. package/lib/typescript/components/Pressable/StateMachine.d.ts.map +1 -1
  60. package/lib/typescript/components/Pressable/stateDefinitions.d.ts.map +1 -1
  61. package/lib/typescript/components/Pressable/utils.d.ts +2 -1
  62. package/lib/typescript/components/Pressable/utils.d.ts.map +1 -1
  63. package/lib/typescript/components/ReanimatedDrawerLayout.d.ts +14 -2
  64. package/lib/typescript/components/ReanimatedDrawerLayout.d.ts.map +1 -1
  65. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeable.d.ts.map +1 -1
  66. package/lib/typescript/components/utils.d.ts +11 -0
  67. package/lib/typescript/components/utils.d.ts.map +1 -1
  68. package/lib/typescript/handlers/gestures/GestureDetector/utils.d.ts +4 -4
  69. package/lib/typescript/handlers/gestures/GestureDetector/utils.d.ts.map +1 -1
  70. package/lib/typescript/handlers/gestures/gesture.d.ts +4 -0
  71. package/lib/typescript/handlers/gestures/gesture.d.ts.map +1 -1
  72. package/lib/typescript/handlers/gestures/gestureComposition.d.ts.map +1 -1
  73. package/lib/typescript/v3/components/GestureButtons.d.ts.map +1 -1
  74. package/lib/typescript/v3/components/Pressable.d.ts.map +1 -1
  75. package/lib/typescript/v3/components/Touchable/Touchable.d.ts.map +1 -1
  76. package/lib/typescript/v3/hooks/utils/configUtils.d.ts.map +1 -1
  77. package/lib/typescript/v3/hooks/utils/propsWhiteList.d.ts.map +1 -1
  78. package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts.map +1 -1
  79. package/lib/typescript/web/tools/GestureHandlerWebDelegate.d.ts.map +1 -1
  80. package/package.json +6 -6
  81. package/scripts/gesture_handler_utils.rb +23 -17
  82. package/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorShadowNode.cpp +19 -0
  83. package/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorShadowNode.h +1 -0
  84. package/src/components/Pressable/Pressable.tsx +9 -1
  85. package/src/components/Pressable/StateMachine.tsx +16 -1
  86. package/src/components/Pressable/stateDefinitions.ts +3 -2
  87. package/src/components/Pressable/utils.ts +37 -0
  88. package/src/components/ReanimatedDrawerLayout.tsx +27 -8
  89. package/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +8 -2
  90. package/src/components/utils.ts +22 -0
  91. package/src/handlers/gestures/GestureDetector/utils.ts +6 -10
  92. package/src/handlers/gestures/gesture.ts +11 -0
  93. package/src/handlers/gestures/gestureComposition.ts +15 -2
  94. package/src/v3/components/GestureButtons.tsx +4 -0
  95. package/src/v3/components/Pressable.tsx +33 -3
  96. package/src/v3/components/Touchable/Touchable.tsx +4 -0
  97. package/src/v3/hooks/utils/configUtils.ts +4 -5
  98. package/src/v3/hooks/utils/propsWhiteList.ts +6 -6
  99. package/src/v3/hooks/utils/reanimatedUtils.ts +9 -10
  100. package/src/web/tools/GestureHandlerWebDelegate.ts +10 -4
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  <img src="https://user-images.githubusercontent.com/16062886/117444014-2d1ffd80-af39-11eb-9bbb-33c320599d93.png" width="100%" alt="React Native Gesture Handler by Software Mansion">
2
2
 
3
+ [![Ad](https://swm-delivery.com/www/images/zone-gh-react-native-gesture-handler-1?n=1)](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-1&n=1)
4
+ [![Ad](https://swm-delivery.com/www/images/zone-gh-react-native-gesture-handler-2?n=1)](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-2&n=1)
5
+ [![Ad](https://swm-delivery.com/www/images/zone-gh-react-native-gesture-handler-3?n=1)](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-3&n=1)
6
+
3
7
  ### Declarative API exposing platform native touch and gesture system to React Native.
4
8
 
5
9
  React Native Gesture Handler provides native-driven gesture management APIs for building best possible touch-based experiences in React Native.
@@ -5,7 +5,7 @@ is_gh_example_app = ENV["GH_EXAMPLE_APP_NAME"] != nil
5
5
 
6
6
  compilation_metadata_dir = "CompilationDatabase"
7
7
  compilation_metadata_generation_flag = is_gh_example_app ? '-gen-cdb-fragment-path ' + compilation_metadata_dir : ''
8
- version_flag = "-DREACT_NATIVE_MINOR_VERSION=#{get_react_native_minor_version()}"
8
+ version_flag = "-DREACT_NATIVE_MINOR_VERSION=#{GestureHandlerUtils.get_react_native_minor_version()}"
9
9
 
10
10
  Pod::Spec.new do |s|
11
11
  # NPM package specification
@@ -2,17 +2,18 @@ import groovy.json.JsonSlurper
2
2
  import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask
3
3
 
4
4
  buildscript {
5
- def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['RNGH_kotlinVersion']
6
-
7
5
  repositories {
8
6
  mavenCentral()
9
7
  google()
10
8
  }
11
9
 
12
10
  dependencies {
13
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
14
- classpath("com.android.tools.build:gradle:8.10.1")
15
- classpath("com.diffplug.spotless:spotless-plugin-gradle:7.0.4")
11
+ if (project == rootProject) {
12
+ def kotlin_version = project.properties['RNGH_kotlinVersion']
13
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
14
+ classpath("com.android.tools.build:gradle:8.10.1")
15
+ classpath("com.diffplug.spotless:spotless-plugin-gradle:7.0.4")
16
+ }
16
17
  }
17
18
  }
18
19
 
@@ -16,4 +16,4 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemor
16
16
  # This option should only be used with decoupled projects. More details, visit
17
17
  # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18
18
  # org.gradle.parallel=true
19
- RNGH_kotlinVersion=2.0.21
19
+ RNGH_kotlinVersion=2.2.0
@@ -88,7 +88,7 @@ class FlingGestureHandler : GestureHandler() {
88
88
  }
89
89
 
90
90
  override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
91
- if (!shouldActivateWithMouse(sourceEvent)) {
91
+ if (shouldSkipEvent(sourceEvent)) {
92
92
  return
93
93
  }
94
94
 
@@ -4,7 +4,6 @@ import android.app.Activity
4
4
  import android.content.Context
5
5
  import android.content.ContextWrapper
6
6
  import android.graphics.PointF
7
- import android.os.Build
8
7
  import android.view.MotionEvent
9
8
  import android.view.MotionEvent.PointerCoords
10
9
  import android.view.MotionEvent.PointerProperties
@@ -411,7 +410,10 @@ open class GestureHandler {
411
410
  }
412
411
  }
413
412
 
414
- numberOfPointers = adaptedTransformedEvent.pointerCount
413
+ numberOfPointers = when (adaptedTransformedEvent.actionMasked) {
414
+ MotionEvent.ACTION_POINTER_UP -> adaptedTransformedEvent.pointerCount - 1
415
+ else -> adaptedTransformedEvent.pointerCount
416
+ }
415
417
 
416
418
  x = adaptedTransformedEvent.x
417
419
  y = adaptedTransformedEvent.y
@@ -820,44 +822,38 @@ open class GestureHandler {
820
822
  return clickedButton and mouseButton != 0
821
823
  }
822
824
 
823
- protected fun shouldActivateWithMouse(sourceEvent: MotionEvent): Boolean {
824
- // While using mouse, we get both sets of events, for example ACTION_DOWN and ACTION_BUTTON_PRESS. That's why we want to take actions to only one of them.
825
- // On API >= 23, we will use events with infix BUTTON, otherwise we use standard action events (like ACTION_DOWN).
825
+ // Decides whether the gesture should ignore this event. While using a mouse we receive both the
826
+ // touch-compatible stream (ACTION_DOWN/UP/...) and the BUTTON_* events, so we act on only the
827
+ // latter, and we drop events coming from a button other than the configured `mouseButton`.
828
+ // Non-mouse input is never skipped here.
829
+ protected fun shouldSkipEvent(sourceEvent: MotionEvent): Boolean {
830
+ if (sourceEvent.getToolType(0) != MotionEvent.TOOL_TYPE_MOUSE) {
831
+ return false
832
+ }
826
833
 
827
834
  with(sourceEvent) {
828
- // To use actionButton, we need API >= 23.
829
- if (getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE &&
830
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
835
+ // While using mouse, we want to ignore default events for touch.
836
+ if (actionMasked == MotionEvent.ACTION_DOWN ||
837
+ actionMasked == MotionEvent.ACTION_UP ||
838
+ actionMasked == MotionEvent.ACTION_POINTER_UP ||
839
+ actionMasked == MotionEvent.ACTION_POINTER_DOWN
831
840
  ) {
832
- // While using mouse, we want to ignore default events for touch.
833
- if (action == MotionEvent.ACTION_DOWN ||
834
- action == MotionEvent.ACTION_UP ||
835
- action == MotionEvent.ACTION_POINTER_UP ||
836
- action == MotionEvent.ACTION_POINTER_DOWN
837
- ) {
838
- return@shouldActivateWithMouse false
839
- }
841
+ return@shouldSkipEvent true
842
+ }
840
843
 
841
- // We don't want to do anything if wrong button was clicked. If we received event for BUTTON, we have to use actionButton to get which one was clicked.
842
- if (action != MotionEvent.ACTION_MOVE && !isButtonInConfig(actionButton)) {
843
- return@shouldActivateWithMouse false
844
- }
844
+ // Skip events from a button other than the configured one. For BUTTON_* events the clicked
845
+ // button is read from `actionButton`.
846
+ if (actionMasked != MotionEvent.ACTION_MOVE && !isButtonInConfig(actionButton)) {
847
+ return@shouldSkipEvent true
848
+ }
845
849
 
846
- // When we receive ACTION_MOVE, we have to check buttonState field.
847
- if (action == MotionEvent.ACTION_MOVE && !isButtonInConfig(buttonState)) {
848
- return@shouldActivateWithMouse false
849
- }
850
- } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
851
- // We do not fully support mouse below API 23, so we will ignore BUTTON events.
852
- if (action == MotionEvent.ACTION_BUTTON_PRESS ||
853
- action == MotionEvent.ACTION_BUTTON_RELEASE
854
- ) {
855
- return@shouldActivateWithMouse false
856
- }
850
+ // For ACTION_MOVE the pressed button is read from `buttonState`.
851
+ if (actionMasked == MotionEvent.ACTION_MOVE && !isButtonInConfig(buttonState)) {
852
+ return@shouldSkipEvent true
857
853
  }
858
854
  }
859
855
 
860
- return true
856
+ return false
861
857
  }
862
858
 
863
859
  /**
@@ -492,7 +492,7 @@ class GestureHandlerOrchestrator(
492
492
  top + view.height > parent.height
493
493
  }
494
494
 
495
- private fun extractAncestorHandlers(view: View, coords: FloatArray, pointerId: Int): Boolean {
495
+ private fun extractAncestorHandlers(view: View, coords: FloatArray, pointerId: Int, event: MotionEvent): Boolean {
496
496
  var found = false
497
497
  var parent = view.parent
498
498
 
@@ -509,6 +509,10 @@ class GestureHandlerOrchestrator(
509
509
  handlerRegistry.getHandlersForView(parent)?.let {
510
510
  synchronized(it) {
511
511
  for (handler in it) {
512
+ if (shouldHandlerSkipHoverEvents(handler, event)) {
513
+ continue
514
+ }
515
+
512
516
  if (handler.isEnabled && handler.isWithinBounds(view, coords[0], coords[1])) {
513
517
  found = true
514
518
  recordHandlerIfNotPresent(handler, parentViewGroup)
@@ -542,13 +546,33 @@ class GestureHandlerOrchestrator(
542
546
  coords: FloatArray,
543
547
  pointerId: Int,
544
548
  event: MotionEvent,
549
+ useChildBounds: Boolean = false,
545
550
  ): Boolean {
551
+ var boundsView = view
552
+ var boundsX = coords[0]
553
+ var boundsY = coords[1]
554
+
555
+ // When `useChildBounds` is set, handler bounds are checked in the coordinate space of the view's
556
+ // single child instead of against the view itself. The caller only sets this for a native detector
557
+ // with exactly one child, so its interactive area follows the child's transforms (e.g. `translateX`)
558
+ // rather than the detector's transform-agnostic frame. For an identity transform the child fills the
559
+ // detector, so this matches the detector's own frame and `hitSlop` expansion (#4049) keeps working;
560
+ // for a translated child the area follows the content.
561
+ if (useChildBounds) {
562
+ val child = (view as RNGestureHandlerDetectorView).getChildAt(0)
563
+ val childPoint = tempPoint
564
+ transformPointToChildViewCoords(coords[0], coords[1], view, child, childPoint)
565
+ boundsView = child
566
+ boundsX = childPoint.x
567
+ boundsY = childPoint.y
568
+ }
569
+
546
570
  var found = false
547
571
  handlerRegistry.getHandlersForView(view)?.let {
548
572
  synchronized(it) {
549
573
  for (handler in it) {
550
574
  // skip disabled and out-of-bounds handlers
551
- if (!handler.isEnabled || !handler.isWithinBounds(view, coords[0], coords[1])) {
575
+ if (!handler.isEnabled || !handler.isWithinBounds(boundsView, boundsX, boundsY)) {
552
576
  continue
553
577
  }
554
578
 
@@ -568,15 +592,21 @@ class GestureHandlerOrchestrator(
568
592
  if (coords[0] in 0f..view.width.toFloat() &&
569
593
  coords[1] in 0f..view.height.toFloat() &&
570
594
  isViewOverflowingParent(view) &&
571
- extractAncestorHandlers(view, coords, pointerId)
595
+ extractAncestorHandlers(view, coords, pointerId, event)
572
596
  ) {
573
597
  found = true
574
598
  }
575
599
 
576
600
  if (view is ReactCompoundView) {
577
- val tagForCoords = view.reactTagForTouch(coords[0], coords[1])
601
+ // Some implementations (e.g. RNScreens' DimmingView) intentionally throw from `reactTagForTouch`
602
+ val tagForCoords =
603
+ try {
604
+ view.reactTagForTouch(coords[0], coords[1])
605
+ } catch (e: IllegalStateException) {
606
+ null
607
+ }
578
608
 
579
- if (tagForCoords != view.id) {
609
+ if (tagForCoords != null && tagForCoords != view.id) {
580
610
  handlerRegistry.getHandlersForViewWithTag(tagForCoords)?.let {
581
611
  synchronized(it) {
582
612
  for (handler in it) {
@@ -680,8 +710,16 @@ class GestureHandlerOrchestrator(
680
710
  is ViewGroup -> {
681
711
  extractGestureHandlers(view, coords, pointerId, event).also { found ->
682
712
  // A child view is handling touch, also extract handlers attached to this view
683
- if (found || view is RNGestureHandlerDetectorView) {
713
+ if (found) {
684
714
  recordViewHandlersForPointer(view, coords, pointerId, event)
715
+ } else if (view is RNGestureHandlerDetectorView) {
716
+ // No child consumed the touch, but we still record the detector's own handlers so
717
+ // that `hitSlop` expansion keeps working. The detector's frame ignores
718
+ // child transforms, so for a single-child detector we check bounds in the child's
719
+ // transform-aware coordinate space - otherwise the detector would steal presses over
720
+ // areas its content has been moved away from.
721
+ val useChildBounds = view.childCount == 1
722
+ recordViewHandlersForPointer(view, coords, pointerId, event, useChildBounds)
685
723
  }
686
724
  }
687
725
  }
@@ -71,7 +71,7 @@ class LongPressGestureHandler(context: Context) : GestureHandler() {
71
71
  }
72
72
 
73
73
  override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
74
- if (!shouldActivateWithMouse(sourceEvent)) {
74
+ if (shouldSkipEvent(sourceEvent)) {
75
75
  return
76
76
  }
77
77
 
@@ -108,7 +108,8 @@ class LongPressGestureHandler(context: Context) : GestureHandler() {
108
108
  currentPointers == numberOfPointersRequired &&
109
109
  (
110
110
  sourceEvent.actionMasked == MotionEvent.ACTION_DOWN ||
111
- sourceEvent.actionMasked == MotionEvent.ACTION_POINTER_DOWN
111
+ sourceEvent.actionMasked == MotionEvent.ACTION_POINTER_DOWN ||
112
+ sourceEvent.actionMasked == MotionEvent.ACTION_BUTTON_PRESS
112
113
  )
113
114
  ) {
114
115
  handler = Handler(Looper.getMainLooper())
@@ -199,7 +200,7 @@ class LongPressGestureHandler(context: Context) : GestureHandler() {
199
200
  handler.maxDist = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MAX_DIST))
200
201
  }
201
202
  if (config.hasKey(KEY_NUMBER_OF_POINTERS)) {
202
- handler.numberOfPointers = config.getInt(KEY_NUMBER_OF_POINTERS)
203
+ handler.numberOfPointersRequired = config.getInt(KEY_NUMBER_OF_POINTERS)
203
204
  }
204
205
  }
205
206
 
@@ -14,10 +14,8 @@ import com.facebook.react.views.swiperefresh.ReactSwipeRefreshLayout
14
14
  import com.facebook.react.views.text.ReactTextView
15
15
  import com.facebook.react.views.textinput.ReactEditText
16
16
  import com.facebook.react.views.view.ReactViewGroup
17
- import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
18
17
  import com.swmansion.gesturehandler.react.RNGestureHandlerRootHelper
19
18
  import com.swmansion.gesturehandler.react.events.eventbuilders.NativeGestureHandlerEventDataBuilder
20
- import com.swmansion.gesturehandler.react.isScreenReaderOn
21
19
 
22
20
  class NativeViewGestureHandler : GestureHandler() {
23
21
  override val isContinuous = true
@@ -121,17 +119,6 @@ class NativeViewGestureHandler : GestureHandler() {
121
119
 
122
120
  override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
123
121
  val view = view!!
124
-
125
- val isTouchExplorationEnabled = view.context.isScreenReaderOn()
126
-
127
- if (view is RNGestureHandlerButtonViewManager.ButtonViewGroup && isTouchExplorationEnabled) {
128
- // Fix for: https://github.com/software-mansion/react-native-gesture-handler/issues/2808
129
- // When TalkBack is enabled, events are often not being sent to the orchestrator for processing.
130
- // Instead, states will be changed directly by an alternative mechanism added in this PR:
131
- // https://github.com/software-mansion/react-native-gesture-handler/pull/2234
132
- return
133
- }
134
-
135
122
  if (event.actionMasked == MotionEvent.ACTION_UP) {
136
123
  if (state == STATE_UNDETERMINED && !hook.canBegin(event)) {
137
124
  cancel()
@@ -160,7 +160,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
160
160
  }
161
161
 
162
162
  override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
163
- if (!shouldActivateWithMouse(sourceEvent)) {
163
+ if (shouldSkipEvent(sourceEvent)) {
164
164
  return
165
165
  }
166
166
 
@@ -89,7 +89,7 @@ class TapGestureHandler : GestureHandler() {
89
89
  }
90
90
 
91
91
  override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
92
- if (!shouldActivateWithMouse(sourceEvent)) {
92
+ if (shouldSkipEvent(sourceEvent)) {
93
93
  return
94
94
  }
95
95
 
@@ -17,6 +17,9 @@ fun MotionEvent.isHoverAction(): Boolean = action == MotionEvent.ACTION_HOVER_MO
17
17
  action == MotionEvent.ACTION_HOVER_ENTER ||
18
18
  action == MotionEvent.ACTION_HOVER_EXIT
19
19
 
20
+ fun MotionEvent.isButtonAction(): Boolean = actionMasked == MotionEvent.ACTION_BUTTON_PRESS ||
21
+ actionMasked == MotionEvent.ACTION_BUTTON_RELEASE
22
+
20
23
  val Display.minimumFrameTime: Float
21
24
  get() {
22
25
  val supportedModes = this.supportedModes
@@ -27,6 +27,7 @@ import androidx.core.view.children
27
27
  import androidx.interpolator.view.animation.FastOutSlowInInterpolator
28
28
  import com.facebook.react.R
29
29
  import com.facebook.react.bridge.Dynamic
30
+ import com.facebook.react.bridge.ReadableArray
30
31
  import com.facebook.react.module.annotations.ReactModule
31
32
  import com.facebook.react.uimanager.BackgroundStyleApplicator
32
33
  import com.facebook.react.uimanager.LengthPercentage
@@ -347,6 +348,7 @@ class RNGestureHandlerButtonViewManager :
347
348
  super.onAfterUpdateTransaction(view)
348
349
 
349
350
  view.updateBackground()
351
+ view.updateLongPressAccessibility()
350
352
  }
351
353
 
352
354
  override fun getDelegate(): ViewManagerDelegate<ButtonViewGroup>? = mDelegate
@@ -434,6 +436,23 @@ class RNGestureHandlerButtonViewManager :
434
436
  invalidate()
435
437
  }
436
438
 
439
+ fun updateLongPressAccessibility() {
440
+ val hasLongPress = hasLongPressAccessibilityAction()
441
+ setOnLongClickListener(if (hasLongPress) dummyLongClickListener else null)
442
+ isLongClickable = hasLongPress
443
+ }
444
+
445
+ private fun hasLongPressAccessibilityAction(): Boolean {
446
+ val actions = getTag(R.id.accessibility_actions) as? ReadableArray ?: return false
447
+ for (i in 0 until actions.size()) {
448
+ if (actions.getMap(i)?.getString("name") == "longpress") {
449
+ return true
450
+ }
451
+ }
452
+
453
+ return false
454
+ }
455
+
437
456
  override fun setBackgroundColor(color: Int) {
438
457
  BackgroundStyleApplicator.setBackgroundColor(this, color)
439
458
  }
@@ -876,10 +895,11 @@ class RNGestureHandlerButtonViewManager :
876
895
  }
877
896
 
878
897
  override fun performClick(): Boolean {
879
- // don't preform click when a child button is pressed (mainly to prevent sound effect of
898
+ // don't perform click when a child button is pressed (mainly to prevent sound effect of
880
899
  // a parent button from playing)
881
900
  return if (!isChildTouched()) {
882
- if (context.isScreenReaderOn()) {
901
+ // Don't activate native handlers when isPressed is true (motion events are passing through)
902
+ if (context.isScreenReaderOn() && !isPressed) {
883
903
  RNGestureHandlerRootView.findGestureHandlerRootView(this)?.activateNativeHandlers(this)
884
904
  } else if (receivedKeyEvent) {
885
905
  RNGestureHandlerRootView.findGestureHandlerRootView(this)?.activateNativeHandlers(this)
@@ -936,7 +956,14 @@ class RNGestureHandlerButtonViewManager :
936
956
  var resolveOutValue = TypedValue()
937
957
  var touchResponder: ButtonViewGroup? = null
938
958
  var soundResponder: ButtonViewGroup? = null
939
- var dummyClickListener = OnClickListener { }
959
+ val dummyClickListener = OnClickListener { }
960
+ val dummyLongClickListener = OnLongClickListener { view ->
961
+ if (view.context.isScreenReaderOn()) {
962
+ view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK, null)
963
+ } else {
964
+ false
965
+ }
966
+ }
940
967
  }
941
968
  }
942
969
 
@@ -47,6 +47,10 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
47
47
  }
48
48
 
49
49
  fun setModuleId(id: Int) {
50
+ if (this.moduleId == id) {
51
+ return
52
+ }
53
+
50
54
  assert(this.moduleId == -1) { "Tried to change moduleId of a native detector" }
51
55
 
52
56
  this.moduleId = id
@@ -62,12 +62,14 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) {
62
62
  }
63
63
  }
64
64
 
65
- override fun dispatchGenericMotionEvent(ev: MotionEvent) =
66
- if (rootViewEnabled && ev.isHoverAction() && rootHelper!!.dispatchTouchEvent(ev)) {
67
- true
68
- } else {
69
- super.dispatchGenericMotionEvent(ev)
70
- }
65
+ override fun dispatchGenericMotionEvent(ev: MotionEvent) = if (rootViewEnabled &&
66
+ (ev.isHoverAction() || ev.isButtonAction()) &&
67
+ rootHelper!!.dispatchTouchEvent(ev)
68
+ ) {
69
+ true
70
+ } else {
71
+ super.dispatchGenericMotionEvent(ev)
72
+ }
71
73
 
72
74
  override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
73
75
  if (rootViewEnabled) {
@@ -145,10 +145,13 @@
145
145
  // Pressing UISwitch triggers only touchUp and valueChanged callbacks. In order to align its behavior
146
146
  // with other UIControls, we have to dispatch full Gesture Handler events flow in one callback, as
147
147
  // touchesDown is not executed.
148
+ #if !TARGET_OS_TV
148
149
  if ([view isKindOfClass:[UISwitch class]]) {
149
150
  _pointerType = RNGestureHandlerTouch;
150
151
  [control addTarget:self action:@selector(handleSwitch:) forControlEvents:UIControlEventValueChanged];
151
- } else {
152
+ } else
153
+ #endif // !TARGET_OS_TV
154
+ {
152
155
  [control addTarget:self action:@selector(handleTouchDown:forEvent:) forControlEvents:UIControlEventTouchDown];
153
156
  [control addTarget:self
154
157
  action:@selector(handleTouchUpOutside:forEvent:)
@@ -51,6 +51,11 @@
51
51
  */
52
52
  - (void)applyStartAnimationState;
53
53
 
54
+ #if TARGET_OS_TV
55
+ - (void)handleAnimatePressIn;
56
+ - (void)handleAnimatePressOut;
57
+ #endif // TARGET_OS_TV
58
+
54
59
  /**
55
60
  * Resets all transient press/animation state so the button can be safely reused
56
61
  * by Fabric view recycling. Cancels pending press-out blocks, removes in-flight
@@ -68,6 +68,44 @@ static RNGestureHandlerPointerEvents RCTPointerEventsToEnum(facebook::react::Poi
68
68
  return self;
69
69
  }
70
70
 
71
+ #if TARGET_OS_TV
72
+ - (void)emitPressInEvent
73
+ {
74
+ if (!_buttonView.userEnabled) {
75
+ return;
76
+ }
77
+
78
+ [_buttonView sendActionsForControlEvents:UIControlEventTouchDown];
79
+ }
80
+
81
+ - (void)emitPressOutEvent
82
+ {
83
+ if (!_buttonView.userEnabled) {
84
+ return;
85
+ }
86
+
87
+ [_buttonView sendActionsForControlEvents:UIControlEventTouchUpInside];
88
+ }
89
+
90
+ - (void)animatePressIn
91
+ {
92
+ if (!_buttonView.userEnabled) {
93
+ return;
94
+ }
95
+
96
+ [_buttonView handleAnimatePressIn];
97
+ }
98
+
99
+ - (void)animatePressOut
100
+ {
101
+ if (!_buttonView.userEnabled) {
102
+ return;
103
+ }
104
+
105
+ [_buttonView handleAnimatePressOut];
106
+ }
107
+ #endif // TARGET_OS_TV
108
+
71
109
  - (void)prepareForRecycle
72
110
  {
73
111
  [self.layer removeAnimationForKey:@"transform"];
@@ -33,8 +33,6 @@ NS_ASSUME_NONNULL_BEGIN
33
33
 
34
34
  - (void)detachNativeGestureHandlers;
35
35
 
36
- - (BOOL)shouldAttachGestureToSubview:(nonnull NSNumber *)handlerTag;
37
-
38
36
  @end
39
37
 
40
38
  NS_ASSUME_NONNULL_END
@@ -254,14 +254,14 @@
254
254
  withHostDetector:self];
255
255
  } else {
256
256
  // Hierarchy was folded into a single UIView.
257
- [manager.registry attachHandlerWithTag:handler.tag toView:self withActionType:actionType withHostDetector:self];
257
+ [manager attachHandlerForDetectorWithTag:handler.tag toView:self withActionType:actionType withHostDetector:self];
258
258
  handler.virtualViewTag = @(viewTag);
259
259
  }
260
260
  [_attachedHandlers addObject:handler.tag];
261
261
  return;
262
262
  }
263
263
 
264
- [manager.registry attachHandlerWithTag:handler.tag toView:self withActionType:actionType withHostDetector:self];
264
+ [manager attachHandlerForDetectorWithTag:handler.tag toView:self withActionType:actionType withHostDetector:self];
265
265
  [_attachedHandlers addObject:handler.tag];
266
266
  }
267
267
 
@@ -351,10 +351,10 @@
351
351
  if ([handlerManager.registry handlerWithTag:handlerTag] == nil) {
352
352
  continue;
353
353
  }
354
- [handlerManager.registry attachHandlerWithTag:handlerTag
355
- toView:view
356
- withActionType:RNGestureHandlerActionTypeNativeDetector
357
- withHostDetector:self];
354
+ [handlerManager attachHandlerForDetectorWithTag:handlerTag
355
+ toView:view
356
+ withActionType:RNGestureHandlerActionTypeNativeDetector
357
+ withHostDetector:self];
358
358
  [_attachedHandlers addObject:handlerTag];
359
359
  }
360
360
  }
@@ -29,6 +29,11 @@
29
29
  withActionType:(RNGestureHandlerActionType)actionType
30
30
  withHostDetector:(nullable RNGHUIView *)hostDetector;
31
31
 
32
+ - (void)attachHandlerForDetectorWithTag:(nonnull NSNumber *)handlerTag
33
+ toView:(nonnull RNGHUIView *)view
34
+ withActionType:(RNGestureHandlerActionType)actionType
35
+ withHostDetector:(nullable RNGHUIView *)hostDetector;
36
+
32
37
  - (void)setGestureHandlerConfig:(nonnull NSNumber *)handlerTag config:(nonnull NSDictionary *)config;
33
38
 
34
39
  - (void)updateGestureHandlerConfig:(nonnull NSNumber *)handlerTag config:(nonnull NSDictionary *)config;
@@ -4,7 +4,6 @@
4
4
  #import <React/RCTEventDispatcherProtocol.h>
5
5
  #import <React/RCTLog.h>
6
6
  #import <React/RCTModalHostViewController.h>
7
- #import <React/RCTRootContentView.h>
8
7
  #import <React/RCTRootView.h>
9
8
  #import <React/RCTUIManager.h>
10
9
  #import <React/RCTViewManager.h>
@@ -210,6 +209,15 @@ constexpr int NEW_ARCH_NUMBER_OF_ATTACH_RETRIES = 25;
210
209
  [self registerViewWithGestureRecognizerAttachedIfNeeded:view];
211
210
  }
212
211
 
212
+ - (void)attachHandlerForDetectorWithTag:(nonnull NSNumber *)handlerTag
213
+ toView:(nonnull RNGHUIView *)view
214
+ withActionType:(RNGestureHandlerActionType)actionType
215
+ withHostDetector:(nullable RNGHUIView *)hostDetector
216
+ {
217
+ [_registry attachHandlerWithTag:handlerTag toView:view withActionType:actionType withHostDetector:hostDetector];
218
+ [self registerViewWithGestureRecognizerAttachedIfNeeded:view];
219
+ }
220
+
213
221
  - (void)setGestureHandlerConfig:(NSNumber *)handlerTag config:(NSDictionary *)config
214
222
  {
215
223
  RNGestureHandler *handler = [_registry handlerWithTag:handlerTag];
@@ -472,11 +480,11 @@ constexpr int NEW_ARCH_NUMBER_OF_ATTACH_RETRIES = 25;
472
480
 
473
481
  - (void)sendEventForNativeAnimatedEvent:(RNGestureHandlerStateChange *)event
474
482
  {
475
- // Delivers the event to NativeAnimatedModule.
476
- // Currently, NativeAnimated[Turbo]Module is RCTEventDispatcherObserver so we can
477
- // simply send a direct event which is handled by the observer but ignored on JS side.
478
- // TODO: send event directly to NativeAnimated[Turbo]Module
479
- [self sendEventForDirectEvent:event];
483
+ // Delivers the event to NativeAnimated[Turbo]Module via the dispatcher's
484
+ // observer mechanism. We don't go through sendEvent: because that also
485
+ // dispatches the event to JS via RCTEventEmitter.receiveEvent, which is no
486
+ // longer a registered callable module in RN 0.86+ (Fabric-only).
487
+ [_eventDispatcher notifyObserversOfEvent:event];
480
488
  }
481
489
 
482
490
  - (void)sendEventForJSFunctionOldAPI:(RNGestureHandlerStateChange *)event
@@ -491,12 +499,6 @@ constexpr int NEW_ARCH_NUMBER_OF_ATTACH_RETRIES = 25;
491
499
  [self sendEventForDeviceEvent:event];
492
500
  }
493
501
 
494
- - (void)sendEventForDirectEvent:(RNGestureHandlerStateChange *)event
495
- {
496
- // Delivers the event to JS as a direct event.
497
- [_eventDispatcher sendEvent:event];
498
- }
499
-
500
502
  - (void)sendEventForDeviceEvent:(RNGestureHandlerStateChange *)event
501
503
  {
502
504
  // Delivers the event to JS as a device event.