react-native-gesture-handler 3.0.0-beta.4 → 3.0.0-beta.5

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 (162) hide show
  1. package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +12 -4
  2. package/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +6 -2
  3. package/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt +21 -0
  4. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +113 -49
  5. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +75 -98
  6. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +7 -10
  7. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.kt +64 -2
  8. package/apple/RNGestureHandler.mm +50 -27
  9. package/apple/RNGestureHandlerButton.h +4 -2
  10. package/apple/RNGestureHandlerButton.mm +106 -27
  11. package/apple/RNGestureHandlerButtonComponentView.mm +17 -2
  12. package/apple/RNGestureHandlerDetector.mm +99 -75
  13. package/apple/RNGestureHandlerModule.mm +11 -14
  14. package/apple/RNGestureHandlerRegistry.h +14 -0
  15. package/apple/RNGestureHandlerRegistry.m +56 -0
  16. package/lib/module/RNGestureHandlerModule.web.js +5 -1
  17. package/lib/module/RNGestureHandlerModule.web.js.map +1 -1
  18. package/lib/module/components/GestureButtons.js +16 -5
  19. package/lib/module/components/GestureButtons.js.map +1 -1
  20. package/lib/module/components/GestureHandlerButton.js.map +1 -1
  21. package/lib/module/components/GestureHandlerButton.web.js +63 -23
  22. package/lib/module/components/GestureHandlerButton.web.js.map +1 -1
  23. package/lib/module/components/Pressable/Pressable.js +1 -0
  24. package/lib/module/components/Pressable/Pressable.js.map +1 -1
  25. package/lib/module/components/ReanimatedDrawerLayout.js.map +1 -1
  26. package/lib/module/components/ReanimatedSwipeable/ReanimatedSwipeable.js +38 -5
  27. package/lib/module/components/ReanimatedSwipeable/ReanimatedSwipeable.js.map +1 -1
  28. package/lib/module/handlers/gestures/GestureDetector/useDetectorUpdater.js +1 -2
  29. package/lib/module/handlers/gestures/GestureDetector/useDetectorUpdater.js.map +1 -1
  30. package/lib/module/handlers/gestures/GestureDetector/utils.js +0 -47
  31. package/lib/module/handlers/gestures/GestureDetector/utils.js.map +1 -1
  32. package/lib/module/handlers/gestures/reanimatedWrapper.js +14 -2
  33. package/lib/module/handlers/gestures/reanimatedWrapper.js.map +1 -1
  34. package/lib/module/mocks/module.js +3 -2
  35. package/lib/module/mocks/module.js.map +1 -1
  36. package/lib/module/specs/NativeRNGestureHandlerModule.js.map +1 -1
  37. package/lib/module/specs/RNGestureHandlerButtonNativeComponent.ts +28 -13
  38. package/lib/module/v3/NativeProxy.js +5 -3
  39. package/lib/module/v3/NativeProxy.js.map +1 -1
  40. package/lib/module/v3/NativeProxy.web.js +2 -2
  41. package/lib/module/v3/NativeProxy.web.js.map +1 -1
  42. package/lib/module/v3/components/GestureButtons.js +8 -3
  43. package/lib/module/v3/components/GestureButtons.js.map +1 -1
  44. package/lib/module/v3/components/Touchable/Touchable.js +53 -4
  45. package/lib/module/v3/components/Touchable/Touchable.js.map +1 -1
  46. package/lib/module/v3/detectors/HostGestureDetector.web.js +178 -59
  47. package/lib/module/v3/detectors/HostGestureDetector.web.js.map +1 -1
  48. package/lib/module/v3/detectors/NativeDetector.js +3 -2
  49. package/lib/module/v3/detectors/NativeDetector.js.map +1 -1
  50. package/lib/module/v3/detectors/VirtualDetector/InterceptingGestureDetector.js +3 -4
  51. package/lib/module/v3/detectors/VirtualDetector/InterceptingGestureDetector.js.map +1 -1
  52. package/lib/module/v3/detectors/VirtualDetector/VirtualDetector.js +2 -2
  53. package/lib/module/v3/detectors/VirtualDetector/VirtualDetector.js.map +1 -1
  54. package/lib/module/v3/detectors/useGestureRelationsUpdater.js +23 -0
  55. package/lib/module/v3/detectors/useGestureRelationsUpdater.js.map +1 -0
  56. package/lib/module/v3/detectors/utils.js +10 -8
  57. package/lib/module/v3/detectors/utils.js.map +1 -1
  58. package/lib/module/v3/hooks/useGesture.js +3 -18
  59. package/lib/module/v3/hooks/useGesture.js.map +1 -1
  60. package/lib/module/v3/hooks/utils/configUtils.js +1 -3
  61. package/lib/module/v3/hooks/utils/configUtils.js.map +1 -1
  62. package/lib/module/v3/hooks/utils/eventHandlersUtils.js +31 -29
  63. package/lib/module/v3/hooks/utils/eventHandlersUtils.js.map +1 -1
  64. package/lib/module/v3/hooks/utils/reanimatedUtils.js +8 -2
  65. package/lib/module/v3/hooks/utils/reanimatedUtils.js.map +1 -1
  66. package/lib/module/web/tools/NodeManager.js +44 -0
  67. package/lib/module/web/tools/NodeManager.js.map +1 -1
  68. package/lib/typescript/RNGestureHandlerModule.web.d.ts +1 -1
  69. package/lib/typescript/RNGestureHandlerModule.web.d.ts.map +1 -1
  70. package/lib/typescript/components/GestureButtons.d.ts +14 -6
  71. package/lib/typescript/components/GestureButtons.d.ts.map +1 -1
  72. package/lib/typescript/components/GestureHandlerButton.d.ts +62 -8
  73. package/lib/typescript/components/GestureHandlerButton.d.ts.map +1 -1
  74. package/lib/typescript/components/GestureHandlerButton.web.d.ts +10 -3
  75. package/lib/typescript/components/GestureHandlerButton.web.d.ts.map +1 -1
  76. package/lib/typescript/components/Pressable/Pressable.d.ts.map +1 -1
  77. package/lib/typescript/components/Pressable/PressableProps.d.ts +1 -1
  78. package/lib/typescript/components/Pressable/PressableProps.d.ts.map +1 -1
  79. package/lib/typescript/components/ReanimatedDrawerLayout.d.ts +16 -14
  80. package/lib/typescript/components/ReanimatedDrawerLayout.d.ts.map +1 -1
  81. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeable.d.ts +2 -1
  82. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeable.d.ts.map +1 -1
  83. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeableProps.d.ts +30 -34
  84. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeableProps.d.ts.map +1 -1
  85. package/lib/typescript/handlers/gestures/GestureDetector/useDetectorUpdater.d.ts.map +1 -1
  86. package/lib/typescript/handlers/gestures/GestureDetector/utils.d.ts +0 -1
  87. package/lib/typescript/handlers/gestures/GestureDetector/utils.d.ts.map +1 -1
  88. package/lib/typescript/handlers/gestures/reanimatedWrapper.d.ts.map +1 -1
  89. package/lib/typescript/mocks/module.d.ts +1 -1
  90. package/lib/typescript/mocks/module.d.ts.map +1 -1
  91. package/lib/typescript/specs/NativeRNGestureHandlerModule.d.ts +2 -2
  92. package/lib/typescript/specs/NativeRNGestureHandlerModule.d.ts.map +1 -1
  93. package/lib/typescript/specs/RNGestureHandlerButtonNativeComponent.d.ts +19 -11
  94. package/lib/typescript/specs/RNGestureHandlerButtonNativeComponent.d.ts.map +1 -1
  95. package/lib/typescript/v3/NativeProxy.d.ts +1 -1
  96. package/lib/typescript/v3/NativeProxy.d.ts.map +1 -1
  97. package/lib/typescript/v3/NativeProxy.web.d.ts +1 -1
  98. package/lib/typescript/v3/NativeProxy.web.d.ts.map +1 -1
  99. package/lib/typescript/v3/components/GestureButtons.d.ts +1 -38
  100. package/lib/typescript/v3/components/GestureButtons.d.ts.map +1 -1
  101. package/lib/typescript/v3/components/GestureButtonsProps.d.ts +1 -1
  102. package/lib/typescript/v3/components/GestureButtonsProps.d.ts.map +1 -1
  103. package/lib/typescript/v3/components/Touchable/Touchable.d.ts.map +1 -1
  104. package/lib/typescript/v3/components/Touchable/TouchableProps.d.ts +39 -1
  105. package/lib/typescript/v3/components/Touchable/TouchableProps.d.ts.map +1 -1
  106. package/lib/typescript/v3/detectors/HostGestureDetector.web.d.ts.map +1 -1
  107. package/lib/typescript/v3/detectors/NativeDetector.d.ts.map +1 -1
  108. package/lib/typescript/v3/detectors/VirtualDetector/InterceptingGestureDetector.d.ts.map +1 -1
  109. package/lib/typescript/v3/detectors/useGestureRelationsUpdater.d.ts +3 -0
  110. package/lib/typescript/v3/detectors/useGestureRelationsUpdater.d.ts.map +1 -0
  111. package/lib/typescript/v3/detectors/utils.d.ts +3 -3
  112. package/lib/typescript/v3/detectors/utils.d.ts.map +1 -1
  113. package/lib/typescript/v3/hooks/useGesture.d.ts.map +1 -1
  114. package/lib/typescript/v3/hooks/utils/configUtils.d.ts.map +1 -1
  115. package/lib/typescript/v3/hooks/utils/eventHandlersUtils.d.ts.map +1 -1
  116. package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts +1 -0
  117. package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts.map +1 -1
  118. package/lib/typescript/web/tools/NodeManager.d.ts +7 -0
  119. package/lib/typescript/web/tools/NodeManager.d.ts.map +1 -1
  120. package/package.json +3 -3
  121. package/src/RNGestureHandlerModule.web.ts +5 -1
  122. package/src/components/GestureButtons.tsx +23 -7
  123. package/src/components/GestureHandlerButton.tsx +70 -8
  124. package/src/components/GestureHandlerButton.web.tsx +97 -29
  125. package/src/components/Pressable/Pressable.tsx +1 -0
  126. package/src/components/Pressable/PressableProps.tsx +2 -1
  127. package/src/components/ReanimatedDrawerLayout.tsx +27 -23
  128. package/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +51 -5
  129. package/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts +31 -39
  130. package/src/handlers/gestures/GestureDetector/useDetectorUpdater.ts +1 -2
  131. package/src/handlers/gestures/GestureDetector/utils.ts +0 -52
  132. package/src/handlers/gestures/reanimatedWrapper.ts +20 -2
  133. package/src/mocks/module.tsx +4 -2
  134. package/src/specs/NativeRNGestureHandlerModule.ts +2 -4
  135. package/src/specs/RNGestureHandlerButtonNativeComponent.ts +28 -13
  136. package/src/v3/NativeProxy.ts +9 -7
  137. package/src/v3/NativeProxy.web.ts +2 -2
  138. package/src/v3/components/GestureButtons.tsx +13 -5
  139. package/src/v3/components/GestureButtonsProps.ts +1 -0
  140. package/src/v3/components/Touchable/Touchable.tsx +65 -4
  141. package/src/v3/components/Touchable/TouchableProps.ts +49 -1
  142. package/src/v3/detectors/HostGestureDetector.web.tsx +265 -108
  143. package/src/v3/detectors/NativeDetector.tsx +3 -2
  144. package/src/v3/detectors/VirtualDetector/InterceptingGestureDetector.tsx +3 -4
  145. package/src/v3/detectors/VirtualDetector/VirtualDetector.tsx +2 -2
  146. package/src/v3/detectors/useGestureRelationsUpdater.ts +30 -0
  147. package/src/v3/detectors/utils.ts +28 -12
  148. package/src/v3/hooks/useGesture.ts +4 -14
  149. package/src/v3/hooks/utils/configUtils.ts +2 -3
  150. package/src/v3/hooks/utils/eventHandlersUtils.ts +43 -32
  151. package/src/v3/hooks/utils/reanimatedUtils.ts +10 -10
  152. package/src/web/tools/NodeManager.ts +57 -0
  153. package/lib/module/RNRenderer.js +0 -6
  154. package/lib/module/RNRenderer.js.map +0 -1
  155. package/lib/module/RNRenderer.web.js +0 -6
  156. package/lib/module/RNRenderer.web.js.map +0 -1
  157. package/lib/typescript/RNRenderer.d.ts +0 -2
  158. package/lib/typescript/RNRenderer.d.ts.map +0 -1
  159. package/lib/typescript/RNRenderer.web.d.ts +0 -4
  160. package/lib/typescript/RNRenderer.web.d.ts.map +0 -1
  161. package/src/RNRenderer.ts +0 -3
  162. package/src/RNRenderer.web.ts +0 -3
@@ -362,11 +362,19 @@ class GestureHandlerOrchestrator(
362
362
  if (view === wrapperView) {
363
363
  return true
364
364
  }
365
- var parent = view.parent
366
- while (parent != null && parent !== wrapperView) {
367
- parent = parent.parent
365
+ var current: View = view
366
+ while (true) {
367
+ val parent = current.parent as? ViewGroup ?: return false
368
+
369
+ when {
370
+ // A disappearing child (kept drawable for an exit animation, e.g. RNScreens during a
371
+ // navigation transition) still has `parent` set but is gone from `mChildren`. Treat as
372
+ // detached - `cancelTouchTarget` already synthesized ACTION_CANCEL into this subtree.
373
+ parent.indexOfChild(current) < 0 -> return false
374
+ parent === wrapperView -> return true
375
+ else -> current = parent
376
+ }
368
377
  }
369
- return parent === wrapperView
370
378
  }
371
379
 
372
380
  fun isAnyHandlerActive() = gestureHandlers.any { it.state == GestureHandler.STATE_ACTIVE }
@@ -345,8 +345,12 @@ class NativeViewGestureHandler : GestureHandler() {
345
345
 
346
346
  // recognize alongside every handler besides RootViewGestureHandler;
347
347
  // also if other handler is NativeViewGestureHandler then don't override the default implementation
348
- override fun shouldRecognizeSimultaneously(handler: GestureHandler) =
349
- handler !is RNGestureHandlerRootHelper.RootViewGestureHandler && handler !is NativeViewGestureHandler
348
+ override fun shouldRecognizeSimultaneously(handler: GestureHandler): Boolean? =
349
+ if (handler is NativeViewGestureHandler) {
350
+ null
351
+ } else {
352
+ handler !is RNGestureHandlerRootHelper.RootViewGestureHandler
353
+ }
350
354
 
351
355
  override fun wantsToHandleEventBeforeActivation() = true
352
356
 
@@ -1,6 +1,7 @@
1
1
  package com.swmansion.gesturehandler.react
2
2
 
3
3
  import android.content.Context
4
+ import android.view.Display
4
5
  import android.view.MotionEvent
5
6
  import android.view.accessibility.AccessibilityManager
6
7
  import com.facebook.react.bridge.ReactContext
@@ -15,3 +16,23 @@ fun Context.isScreenReaderOn() =
15
16
  fun MotionEvent.isHoverAction(): Boolean = action == MotionEvent.ACTION_HOVER_MOVE ||
16
17
  action == MotionEvent.ACTION_HOVER_ENTER ||
17
18
  action == MotionEvent.ACTION_HOVER_EXIT
19
+
20
+ val Display.minimumFrameTime: Float
21
+ get() {
22
+ val supportedModes = this.supportedModes
23
+ var maxRefreshRate = 0f
24
+
25
+ supportedModes.forEach { mode ->
26
+ if (mode.refreshRate > maxRefreshRate) {
27
+ maxRefreshRate = mode.refreshRate
28
+ }
29
+ }
30
+
31
+ val effectiveRefreshRate = when {
32
+ maxRefreshRate > 0f -> maxRefreshRate
33
+ refreshRate > 0f -> refreshRate
34
+ else -> 60f
35
+ }
36
+
37
+ return 1000.0f / effectiveRefreshRate
38
+ }
@@ -23,12 +23,12 @@ import android.view.View
23
23
  import android.view.ViewGroup
24
24
  import android.view.accessibility.AccessibilityNodeInfo
25
25
  import androidx.core.view.children
26
- import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
26
+ import androidx.interpolator.view.animation.FastOutSlowInInterpolator
27
27
  import com.facebook.react.R
28
+ import com.facebook.react.bridge.Dynamic
28
29
  import com.facebook.react.module.annotations.ReactModule
29
30
  import com.facebook.react.uimanager.BackgroundStyleApplicator
30
31
  import com.facebook.react.uimanager.LengthPercentage
31
- import com.facebook.react.uimanager.LengthPercentageType
32
32
  import com.facebook.react.uimanager.PixelUtil
33
33
  import com.facebook.react.uimanager.PointerEvents
34
34
  import com.facebook.react.uimanager.ReactPointerEventsView
@@ -178,74 +178,76 @@ class RNGestureHandlerButtonViewManager :
178
178
  view.setOverflow(overflow)
179
179
  }
180
180
 
181
- private fun setBorderRadiusInternal(view: ButtonViewGroup, prop: BorderRadiusProp, value: Float) {
182
- val isUnset = value.isNaN() || value < 0f
183
- val lp = if (isUnset) null else LengthPercentage(value, LengthPercentageType.POINT)
181
+ private fun setBorderRadiusInternal(view: ButtonViewGroup, prop: BorderRadiusProp, value: Dynamic) {
182
+ // setFromDynamic returns null for null Dynamics, negative numbers, and
183
+ // unparseable strings which is what we want for "unset" so that
184
+ // general / physical radii continue to cascade.
185
+ val lp = LengthPercentage.setFromDynamic(value)
184
186
  BackgroundStyleApplicator.setBorderRadius(view, prop, lp)
185
187
  }
186
188
 
187
189
  @ReactProp(name = ViewProps.BORDER_RADIUS)
188
- override fun setBorderRadius(view: ButtonViewGroup, borderRadius: Float) {
189
- setBorderRadiusInternal(view, BorderRadiusProp.BORDER_RADIUS, borderRadius)
190
+ override fun setBorderRadius(view: ButtonViewGroup, value: Dynamic) {
191
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_RADIUS, value)
190
192
  }
191
193
 
192
194
  @ReactProp(name = "borderTopLeftRadius")
193
- override fun setBorderTopLeftRadius(view: ButtonViewGroup, value: Float) {
195
+ override fun setBorderTopLeftRadius(view: ButtonViewGroup, value: Dynamic) {
194
196
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_TOP_LEFT_RADIUS, value)
195
197
  }
196
198
 
197
199
  @ReactProp(name = "borderTopRightRadius")
198
- override fun setBorderTopRightRadius(view: ButtonViewGroup, value: Float) {
200
+ override fun setBorderTopRightRadius(view: ButtonViewGroup, value: Dynamic) {
199
201
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_TOP_RIGHT_RADIUS, value)
200
202
  }
201
203
 
202
204
  @ReactProp(name = "borderBottomRightRadius")
203
- override fun setBorderBottomRightRadius(view: ButtonViewGroup, value: Float) {
205
+ override fun setBorderBottomRightRadius(view: ButtonViewGroup, value: Dynamic) {
204
206
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_BOTTOM_RIGHT_RADIUS, value)
205
207
  }
206
208
 
207
209
  @ReactProp(name = "borderBottomLeftRadius")
208
- override fun setBorderBottomLeftRadius(view: ButtonViewGroup, value: Float) {
210
+ override fun setBorderBottomLeftRadius(view: ButtonViewGroup, value: Dynamic) {
209
211
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_BOTTOM_LEFT_RADIUS, value)
210
212
  }
211
213
 
212
214
  @ReactProp(name = "borderTopStartRadius")
213
- override fun setBorderTopStartRadius(view: ButtonViewGroup, value: Float) {
215
+ override fun setBorderTopStartRadius(view: ButtonViewGroup, value: Dynamic) {
214
216
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_TOP_START_RADIUS, value)
215
217
  }
216
218
 
217
219
  @ReactProp(name = "borderTopEndRadius")
218
- override fun setBorderTopEndRadius(view: ButtonViewGroup, value: Float) {
220
+ override fun setBorderTopEndRadius(view: ButtonViewGroup, value: Dynamic) {
219
221
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_TOP_END_RADIUS, value)
220
222
  }
221
223
 
222
224
  @ReactProp(name = "borderBottomStartRadius")
223
- override fun setBorderBottomStartRadius(view: ButtonViewGroup, value: Float) {
225
+ override fun setBorderBottomStartRadius(view: ButtonViewGroup, value: Dynamic) {
224
226
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_BOTTOM_START_RADIUS, value)
225
227
  }
226
228
 
227
229
  @ReactProp(name = "borderBottomEndRadius")
228
- override fun setBorderBottomEndRadius(view: ButtonViewGroup, value: Float) {
230
+ override fun setBorderBottomEndRadius(view: ButtonViewGroup, value: Dynamic) {
229
231
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_BOTTOM_END_RADIUS, value)
230
232
  }
231
233
 
232
234
  @ReactProp(name = "borderEndEndRadius")
233
- override fun setBorderEndEndRadius(view: ButtonViewGroup, value: Float) {
235
+ override fun setBorderEndEndRadius(view: ButtonViewGroup, value: Dynamic) {
234
236
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_END_END_RADIUS, value)
235
237
  }
236
238
 
237
239
  @ReactProp(name = "borderEndStartRadius")
238
- override fun setBorderEndStartRadius(view: ButtonViewGroup, value: Float) {
240
+ override fun setBorderEndStartRadius(view: ButtonViewGroup, value: Dynamic) {
239
241
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_END_START_RADIUS, value)
240
242
  }
241
243
 
242
244
  @ReactProp(name = "borderStartEndRadius")
243
- override fun setBorderStartEndRadius(view: ButtonViewGroup, value: Float) {
245
+ override fun setBorderStartEndRadius(view: ButtonViewGroup, value: Dynamic) {
244
246
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_START_END_RADIUS, value)
245
247
  }
246
248
 
247
249
  @ReactProp(name = "borderStartStartRadius")
248
- override fun setBorderStartStartRadius(view: ButtonViewGroup, value: Float) {
250
+ override fun setBorderStartStartRadius(view: ButtonViewGroup, value: Dynamic) {
249
251
  setBorderRadiusInternal(view, BorderRadiusProp.BORDER_START_START_RADIUS, value)
250
252
  }
251
253
 
@@ -269,14 +271,29 @@ class RNGestureHandlerButtonViewManager :
269
271
  view.isSoundEffectsEnabled = !touchSoundDisabled
270
272
  }
271
273
 
272
- @ReactProp(name = "pressAndHoldAnimationDuration")
273
- override fun setPressAndHoldAnimationDuration(view: ButtonViewGroup, pressAndHoldAnimationDuration: Int) {
274
- view.pressAndHoldAnimationDuration = pressAndHoldAnimationDuration
274
+ @ReactProp(name = "tapAnimationInDuration")
275
+ override fun setTapAnimationInDuration(view: ButtonViewGroup, value: Int) {
276
+ view.tapAnimationInDuration = if (value > 0) value else 0
275
277
  }
276
278
 
277
- @ReactProp(name = "tapAnimationDuration")
278
- override fun setTapAnimationDuration(view: ButtonViewGroup, tapAnimationDuration: Int) {
279
- view.tapAnimationDuration = if (tapAnimationDuration > 0) tapAnimationDuration else 0
279
+ @ReactProp(name = "tapAnimationOutDuration")
280
+ override fun setTapAnimationOutDuration(view: ButtonViewGroup, value: Int) {
281
+ view.tapAnimationOutDuration = if (value > 0) value else 0
282
+ }
283
+
284
+ @ReactProp(name = "longPressDuration")
285
+ override fun setLongPressDuration(view: ButtonViewGroup, value: Int) {
286
+ view.longPressDuration = value
287
+ }
288
+
289
+ @ReactProp(name = "longPressAnimationOutDuration")
290
+ override fun setLongPressAnimationOutDuration(view: ButtonViewGroup, value: Int) {
291
+ view.longPressAnimationOutDuration = value
292
+ }
293
+
294
+ @ReactProp(name = "needsOffscreenAlphaCompositing")
295
+ override fun setNeedsOffscreenAlphaCompositing(view: ButtonViewGroup, value: Boolean) {
296
+ view.needsOffscreenAlphaCompositing = value
280
297
  }
281
298
 
282
299
  @ReactProp(name = "defaultOpacity")
@@ -354,9 +371,11 @@ class RNGestureHandlerButtonViewManager :
354
371
  var useBorderlessDrawable = false
355
372
 
356
373
  var exclusive = true
357
- var tapAnimationDuration: Int = 100
358
- var pressAndHoldAnimationDuration: Int = -1
359
- get() = if (field < 0) tapAnimationDuration else field
374
+ var tapAnimationInDuration: Int = 50
375
+ var tapAnimationOutDuration: Int = 100
376
+ var longPressDuration: Int = -1
377
+ var longPressAnimationOutDuration: Int = -1
378
+ get() = if (field < 0) tapAnimationOutDuration else field
360
379
  var activeOpacity: Float = 1.0f
361
380
  var defaultOpacity: Float = 1.0f
362
381
  var activeScale: Float = 1.0f
@@ -370,6 +389,7 @@ class RNGestureHandlerButtonViewManager :
370
389
  set(value) = withBackgroundUpdate {
371
390
  field = value
372
391
  }
392
+ var needsOffscreenAlphaCompositing = false
373
393
 
374
394
  override var pointerEvents: PointerEvents = PointerEvents.AUTO
375
395
 
@@ -387,6 +407,11 @@ class RNGestureHandlerButtonViewManager :
387
407
  // When null the ripple lives on the foreground drawable instead.
388
408
  private var selectableDrawable: Drawable? = null
389
409
 
410
+ // When true, dispatchDraw clips children to the resolved border-radius shape
411
+ // (overflow: hidden). ViewGroup's clipChildren is only a rectangular clip and
412
+ // wouldn't respect rounded corners.
413
+ private var clipChildrenToShape = false
414
+
390
415
  var isTouched = false
391
416
 
392
417
  init {
@@ -404,16 +429,7 @@ class RNGestureHandlerButtonViewManager :
404
429
  }
405
430
 
406
431
  fun setOverflow(overflow: String?) {
407
- when (overflow) {
408
- "hidden" -> {
409
- clipChildren = true
410
- clipToPadding = true
411
- }
412
- else -> {
413
- clipChildren = false
414
- clipToPadding = false
415
- }
416
- }
432
+ clipChildrenToShape = overflow == "hidden"
417
433
  invalidate()
418
434
  }
419
435
 
@@ -534,6 +550,28 @@ class RNGestureHandlerButtonViewManager :
534
550
  }
535
551
 
536
552
  currentAnimator?.cancel()
553
+ currentAnimator = null
554
+
555
+ // Sub-frame durations: snap directly. ObjectAnimator with duration 0
556
+ // still defers its property write to the next frame callback, so if a
557
+ // follow-up animateTo() cancels it in the same frame the property never
558
+ // lands on its target and the next animator captures a stale starting
559
+ // value (e.g. an instant press-in followed by press-out in the same
560
+ // frame, leaving the press-out to animate default → default).
561
+ if (durationMs < (display?.minimumFrameTime ?: 16f)) {
562
+ if (hasOpacity) {
563
+ alpha = opacity
564
+ }
565
+ if (hasScale) {
566
+ scaleX = scale
567
+ scaleY = scale
568
+ }
569
+ if (hasUnderlay) {
570
+ underlayDrawable!!.alpha = (underlayOpacity * 255).toInt()
571
+ }
572
+ return
573
+ }
574
+
537
575
  val animators = ArrayList<Animator>()
538
576
  if (hasOpacity) {
539
577
  animators.add(ObjectAnimator.ofFloat(this, "alpha", opacity))
@@ -548,7 +586,7 @@ class RNGestureHandlerButtonViewManager :
548
586
  currentAnimator = AnimatorSet().apply {
549
587
  playTogether(animators)
550
588
  duration = durationMs
551
- interpolator = LinearOutSlowInInterpolator()
589
+ interpolator = FastOutSlowInInterpolator()
552
590
  start()
553
591
  }
554
592
  }
@@ -559,27 +597,33 @@ class RNGestureHandlerButtonViewManager :
559
597
  pendingPressOut = null
560
598
  }
561
599
  pressInTimestamp = SystemClock.uptimeMillis()
562
- animateTo(activeOpacity, activeScale, activeUnderlayOpacity, pressAndHoldAnimationDuration.toLong())
600
+ animateTo(activeOpacity, activeScale, activeUnderlayOpacity, tapAnimationInDuration.toLong())
563
601
  }
564
602
 
565
603
  private fun animatePressOut() {
566
604
  pendingPressOut?.let { handler.removeCallbacks(it) }
567
- val pressAndHoldMs = pressAndHoldAnimationDuration.toLong()
568
- val tapMs = tapAnimationDuration.toLong()
605
+ val tapInMs = tapAnimationInDuration.toLong()
606
+ val tapOutMs = tapAnimationOutDuration.toLong()
607
+ val longPressMs = longPressDuration.toLong()
608
+ val longPressOutMs = longPressAnimationOutDuration.toLong()
569
609
  val elapsed = SystemClock.uptimeMillis() - pressInTimestamp
570
610
 
571
- if (elapsed >= pressAndHoldMs) {
572
- animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, pressAndHoldMs)
573
- // elapsed * 2 to ensure there is at least half of the tapAnimationDuration left for the animation to play
574
- } else if (elapsed * 2 >= tapMs) {
611
+ if (longPressMs >= 0 && elapsed >= longPressMs) {
612
+ // Long-press release - use the configured long-press out duration.
613
+ animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, longPressOutMs)
614
+ } else if (elapsed >= tapInMs) {
615
+ // Press-in animation fully finished — release with the configured out duration.
616
+ animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, tapOutMs)
617
+ // elapsed * 2 to ensure there is at least half of the tapAnimationOutDuration left for the animation to play
618
+ } else if (elapsed * 2 >= tapOutMs) {
575
619
  animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, elapsed)
576
620
  } else {
577
- val remaining = tapMs - elapsed
621
+ val remaining = tapInMs - elapsed
578
622
  animateTo(activeOpacity, activeScale, activeUnderlayOpacity, remaining)
579
623
 
580
624
  val runnable = Runnable {
581
625
  pendingPressOut = null
582
- animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, tapMs)
626
+ animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, tapOutMs)
583
627
  }
584
628
  pendingPressOut = runnable
585
629
  handler.postDelayed(runnable, remaining)
@@ -646,7 +690,14 @@ class RNGestureHandlerButtonViewManager :
646
690
  canvas.restore()
647
691
  }
648
692
  }
649
- super.dispatchDraw(canvas)
693
+ if (clipChildrenToShape) {
694
+ canvas.save()
695
+ BackgroundStyleApplicator.clipToPaddingBox(this, canvas)
696
+ super.dispatchDraw(canvas)
697
+ canvas.restore()
698
+ } else {
699
+ super.dispatchDraw(canvas)
700
+ }
650
701
  }
651
702
 
652
703
  override fun verifyDrawable(who: Drawable): Boolean =
@@ -699,6 +750,14 @@ class RNGestureHandlerButtonViewManager :
699
750
  pendingPressOut = null
700
751
  currentAnimator?.cancel()
701
752
  currentAnimator = null
753
+ applyStartAnimationState()
754
+
755
+ if (touchResponder === this) {
756
+ touchResponder = null
757
+ }
758
+ if (soundResponder === this) {
759
+ soundResponder = null
760
+ }
702
761
  }
703
762
 
704
763
  override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
@@ -847,6 +906,11 @@ class RNGestureHandlerButtonViewManager :
847
906
  // by default Viewgroup would pass hotspot change events
848
907
  }
849
908
 
909
+ // Default to skipping the offscreen buffer so children's border anti-aliasing
910
+ // at the view edge isn't clipped by the layer bounds when alpha != 1.
911
+ // `needsOffscreenAlphaCompositing` opts back into the standard View behavior.
912
+ override fun hasOverlappingRendering(): Boolean = needsOffscreenAlphaCompositing
913
+
850
914
  companion object {
851
915
  var resolveOutValue = TypedValue()
852
916
  var touchResponder: ButtonViewGroup? = null
@@ -16,61 +16,33 @@ import com.swmansion.gesturehandler.react.RNGestureHandlerRootView
16
16
  class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
17
17
  private val reactContext: ThemedReactContext
18
18
  get() = context as ThemedReactContext
19
- private var handlersToAttach: List<Int>? = null
20
- private var virtualChildrenToAttach: List<VirtualChildren>? = null
19
+ private var handlersToAttach: List<Int> = emptyList()
20
+ private var virtualChildrenToAttach: List<VirtualChildren> = emptyList()
21
21
  private var nativeHandlers: MutableSet<Int> = mutableSetOf()
22
+ private var subscribedHandlers: MutableSet<Int> = mutableSetOf()
22
23
  private var attachedHandlers: MutableSet<Int> = mutableSetOf()
23
- private var attachedVirtualHandlers: MutableMap<Int, MutableSet<Int>> = mutableMapOf()
24
+ private var subscribedVirtualHandlers: MutableMap<Int, MutableSet<Int>> = mutableMapOf()
24
25
  private var moduleId: Int = -1
25
26
 
26
27
  data class VirtualChildren(val handlerTags: List<Int>, val viewTag: Int)
27
28
 
28
29
  fun setHandlerTags(handlerTags: ReadableArray?) {
29
- val newHandlers = handlerTags?.toArrayList()?.map { (it as Double).toInt() } ?: emptyList()
30
- if (moduleId == -1) {
31
- // It's possible that handlerTags will be set before module id. In that case, store
32
- // the handler ids and attach them after setting module id.
33
- handlersToAttach = newHandlers
34
- return
30
+ handlersToAttach = handlerTags?.toArrayList()?.map { (it as Double).toInt() } ?: emptyList()
31
+ if (moduleId != -1) {
32
+ attachHandlers(handlersToAttach)
35
33
  }
36
-
37
- attachHandlers(newHandlers)
38
34
  }
39
35
 
40
36
  override fun onAttachedToWindow() {
41
37
  super.onAttachedToWindow()
42
-
43
38
  if (moduleId != -1) {
44
- handlersToAttach?.let {
45
- attachHandlers(it)
46
- }
47
-
48
- virtualChildrenToAttach?.let {
49
- attachVirtualChildren(it)
50
- }
51
-
52
- handlersToAttach = null
53
- virtualChildrenToAttach = null
39
+ attachHandlers(handlersToAttach)
40
+ attachVirtualChildren(virtualChildrenToAttach)
54
41
  }
55
42
  }
56
43
 
57
44
  override fun onDetachedFromWindow() {
58
- if (attachedHandlers.isNotEmpty()) {
59
- handlersToAttach = attachedHandlers.toMutableList().also {
60
- it.addAll(handlersToAttach ?: emptyList())
61
- }
62
- }
63
-
64
- if (attachedVirtualHandlers.isNotEmpty()) {
65
- virtualChildrenToAttach = attachedVirtualHandlers.map {
66
- VirtualChildren(it.value.toList(), it.key)
67
- }.toMutableList().also {
68
- it.addAll(virtualChildrenToAttach ?: emptyList())
69
- }
70
- }
71
-
72
45
  detachAllHandlers()
73
-
74
46
  super.onDetachedFromWindow()
75
47
  }
76
48
 
@@ -78,30 +50,15 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
78
50
  assert(this.moduleId == -1) { "Tried to change moduleId of a native detector" }
79
51
 
80
52
  this.moduleId = id
81
- this.attachHandlers(handlersToAttach ?: return)
82
- handlersToAttach = null
83
- this.attachVirtualChildren(virtualChildrenToAttach ?: return)
84
- virtualChildrenToAttach = null
53
+ attachHandlers(handlersToAttach)
54
+ attachVirtualChildren(virtualChildrenToAttach)
85
55
  }
86
56
 
87
57
  fun setVirtualChildren(newVirtualChildren: ReadableArray?) {
88
- val mappedChildren = newVirtualChildren?.mapVirtualChildren().orEmpty()
89
-
90
- if (moduleId == -1) {
91
- // It's possible that handlerTags will be set before module id. In that case, store
92
- // the handler ids and attach them after setting module id.
93
- virtualChildrenToAttach = mappedChildren
94
- return
58
+ virtualChildrenToAttach = newVirtualChildren?.mapVirtualChildren().orEmpty()
59
+ if (moduleId != -1) {
60
+ attachVirtualChildren(virtualChildrenToAttach)
95
61
  }
96
-
97
- attachVirtualChildren(mappedChildren)
98
- }
99
-
100
- private fun shouldAttachGestureToChildView(tag: Int): Boolean {
101
- val registry = RNGestureHandlerModule.registries[moduleId]
102
- ?: throw Exception("Tried to access a non-existent registry")
103
-
104
- return registry.getHandler(tag)?.wantsToAttachDirectlyToView() ?: false
105
62
  }
106
63
 
107
64
  // We override this `addView` because it is called inside `addView(child: View?, index: Int)`
@@ -127,49 +84,61 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
127
84
  newHandlers: List<Int>,
128
85
  viewTag: Int = this.id,
129
86
  actionType: Int = GestureHandler.ACTION_TYPE_NATIVE_DETECTOR,
130
- attachedHandlers: MutableSet<Int> = this.attachedHandlers,
87
+ subscribedHandlers: MutableSet<Int> = this.subscribedHandlers,
131
88
  ) {
132
89
  val registry = RNGestureHandlerModule.registries[moduleId]
133
90
  ?: throw Exception("Tried to access a non-existent registry")
134
91
 
135
- val handlersToDetach = attachedHandlers.toMutableSet()
92
+ val handlersToDetach = subscribedHandlers.toMutableSet()
136
93
 
137
94
  for (tag in newHandlers) {
138
95
  handlersToDetach.remove(tag)
139
- if (!attachedHandlers.contains(tag)) {
140
- if (shouldAttachGestureToChildView(tag) && actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
141
- // It might happen that `attachHandlers` will be called before children are added into view hierarchy. In that case we cannot
142
- // attach `NativeViewGestureHandlers` here and we have to do it in `addView` method.
143
- assert(childCount <= 1) {
144
- "Cannot attach native gesture handlers when the detector has multiple children"
145
- }
146
- nativeHandlers.add(tag)
147
- } else {
148
- registry.attachHandlerToView(tag, viewTag, actionType, this)
149
- if (actionType == GestureHandler.ACTION_TYPE_VIRTUAL_DETECTOR) {
150
- registry.getHandler(tag)?.hostDetectorView = this
151
- }
152
- attachedHandlers.add(tag)
96
+ if (!subscribedHandlers.contains(tag)) {
97
+ registry.observeHandler(tag, this) { handler ->
98
+ attachReadyHandler(handler, actionType, viewTag)
153
99
  }
100
+ subscribedHandlers.add(tag)
154
101
  }
155
102
  }
156
103
 
157
104
  for (tag in handlersToDetach) {
158
- registry.detachHandlerFromHostDetector(tag, this)
105
+ registry.cancelObservation(tag, this)
106
+ if (attachedHandlers.contains(tag)) {
107
+ registry.detachHandlerFromHostDetector(tag, this)
108
+ attachedHandlers.remove(tag)
109
+ }
110
+ subscribedHandlers.remove(tag)
159
111
  nativeHandlers.remove(tag)
160
- attachedHandlers.remove(tag)
161
112
  }
113
+ }
162
114
 
163
- val child = getChildAt(0)
115
+ // Invoked from the registry's `observeHandler` callback once the handler is known to exist.
116
+ // Branches on handler kind + actionType to pick the right binding flow. May be called multiple
117
+ // times for the same tag (handler re-registration), so each branch must be idempotent.
118
+ private fun attachReadyHandler(handler: GestureHandler, actionType: Int, viewTag: Int) {
119
+ val registry = RNGestureHandlerModule.registries[moduleId]
120
+ ?: throw Exception("Tried to access a non-existent registry")
164
121
 
165
- // This covers the case where `NativeViewGestureHandlers` are attached after child views were created.
166
- if (child != null) {
167
- tryAttachNativeHandlersToChildView(child)
122
+ if (handler.wantsToAttachDirectlyToView() && actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
123
+ assert(childCount <= 1) {
124
+ "Cannot attach native gesture handlers when the detector has multiple children"
125
+ }
126
+ nativeHandlers.add(handler.tag)
127
+ if (childCount != 0) {
128
+ tryAttachNativeHandlersToChildView(getChildAt(0))
129
+ }
130
+ return
168
131
  }
132
+
133
+ registry.attachHandlerToView(handler.tag, viewTag, actionType, this)
134
+ if (actionType == GestureHandler.ACTION_TYPE_VIRTUAL_DETECTOR) {
135
+ handler.hostDetectorView = this
136
+ }
137
+ attachedHandlers.add(handler.tag)
169
138
  }
170
139
 
171
140
  private fun attachVirtualChildren(virtualChildrenToAttach: List<VirtualChildren>) {
172
- val virtualChildrenToDetach = attachedVirtualHandlers.keys.toMutableSet()
141
+ val virtualChildrenToDetach = subscribedVirtualHandlers.keys.toMutableSet()
173
142
 
174
143
  for (child in virtualChildrenToAttach) {
175
144
  virtualChildrenToDetach.remove(child.viewTag)
@@ -179,22 +148,26 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
179
148
  ?: throw Exception("Tried to access a non-existent registry")
180
149
 
181
150
  for (child in virtualChildrenToDetach) {
182
- for (tag in attachedVirtualHandlers[child]!!) {
183
- registry.detachHandlerFromHostDetector(tag, this)
151
+ for (tag in subscribedVirtualHandlers[child]!!) {
152
+ registry.cancelObservation(tag, this)
153
+ if (attachedHandlers.contains(tag)) {
154
+ registry.detachHandlerFromHostDetector(tag, this)
155
+ attachedHandlers.remove(tag)
156
+ }
184
157
  }
185
- attachedVirtualHandlers.remove(tag)
158
+ subscribedVirtualHandlers.remove(child)
186
159
  }
187
160
 
188
161
  for (child in virtualChildrenToAttach) {
189
- if (!attachedVirtualHandlers.containsKey(child.viewTag)) {
190
- attachedVirtualHandlers[child.viewTag] = mutableSetOf()
162
+ if (!subscribedVirtualHandlers.containsKey(child.viewTag)) {
163
+ subscribedVirtualHandlers[child.viewTag] = mutableSetOf()
191
164
  }
192
165
 
193
166
  attachHandlers(
194
167
  child.handlerTags,
195
168
  child.viewTag,
196
169
  GestureHandler.ACTION_TYPE_VIRTUAL_DETECTOR,
197
- attachedVirtualHandlers[child.viewTag]!!,
170
+ subscribedVirtualHandlers[child.viewTag]!!,
198
171
  )
199
172
  }
200
173
  }
@@ -225,8 +198,13 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
225
198
  }
226
199
 
227
200
  for (tag in nativeHandlers) {
201
+ // Defensive: a tag may be in `nativeHandlers` from an earlier ready callback but the
202
+ // underlying handler may have been dropped since. Skip; a re-registration will fire the
203
+ // observation again.
204
+ if (registry.getHandler(tag) == null) {
205
+ continue
206
+ }
228
207
  registry.attachHandlerToView(tag, id, GestureHandler.ACTION_TYPE_NATIVE_DETECTOR, this)
229
-
230
208
  attachedHandlers.add(tag)
231
209
  }
232
210
  }
@@ -236,6 +214,9 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
236
214
  ?: throw Exception("Tried to access a non-existent registry")
237
215
 
238
216
  for (tag in nativeHandlers) {
217
+ if (!attachedHandlers.contains(tag)) {
218
+ continue
219
+ }
239
220
  registry.detachHandlerFromHostDetector(tag, this)
240
221
  attachedHandlers.remove(tag)
241
222
  }
@@ -247,20 +228,16 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
247
228
  }
248
229
 
249
230
  fun detachAllHandlers() {
250
- val registry = RNGestureHandlerModule.registries[moduleId]
251
- ?: throw Exception("Tried to access a non-existent registry")
252
-
253
- for (tag in attachedHandlers.toMutableSet()) {
254
- registry.detachHandlerFromHostDetector(tag, this)
255
- attachedHandlers.remove(tag)
256
- }
257
-
258
- for (child in attachedVirtualHandlers) {
259
- for (tag in child.value) {
231
+ RNGestureHandlerModule.registries[moduleId]?.let { registry ->
232
+ registry.cancelAllObservationsForOwner(this)
233
+ for (tag in attachedHandlers) {
260
234
  registry.detachHandlerFromHostDetector(tag, this)
261
235
  }
262
- child.value.clear()
263
236
  }
237
+ attachedHandlers.clear()
238
+ subscribedVirtualHandlers.clear()
239
+ subscribedHandlers.clear()
240
+ nativeHandlers.clear()
264
241
  }
265
242
 
266
243
  fun recordHandlerIfNotPresent(handler: GestureHandler) {