From daa017c8d36fa4bee4579048e9589be29a738b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 Aug 2025 11:25:11 +0200 Subject: [PATCH 01/67] Separate Reanimated from JS in NativeDetector --- .../RNGestureHandlerDetectorNativeComponent.ts | 5 ++++- .../src/v3/NativeDetector.tsx | 15 ++++++++++++--- .../src/v3/hooks/events/useGestureHandlerEvent.ts | 2 +- .../v3/hooks/events/useGestureStateChangeEvent.ts | 2 +- .../src/v3/hooks/events/useTouchEvent.ts | 2 +- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerDetectorNativeComponent.ts b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerDetectorNativeComponent.ts index e48659d984..ae794a8488 100644 --- a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerDetectorNativeComponent.ts +++ b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerDetectorNativeComponent.ts @@ -44,9 +44,12 @@ type GestureHandlerTouchEvent = Readonly<{ export interface NativeProps extends ViewProps { onGestureHandlerEvent?: DirectEventHandler; - onGestureHandlerAnimatedEvent?: DirectEventHandler; onGestureHandlerStateChange?: DirectEventHandler; onGestureHandlerTouchEvent?: DirectEventHandler; + onGestureHandlerReanimatedEvent?: DirectEventHandler; + onGestureHandlerReanimatedStateChange?: DirectEventHandler; + onGestureHandlerReanimatedTouchEvent?: DirectEventHandler; + onGestureHandlerAnimatedEvent?: DirectEventHandler; handlerTags: Int32[]; moduleId: Int32; diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx index 22d347d1f5..be9fd53c34 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx @@ -40,12 +40,21 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { gesture.gestureEvents.onGestureHandlerStateChange } onGestureHandlerEvent={gesture.gestureEvents.onGestureHandlerEvent} - onGestureHandlerAnimatedEvent={ - gesture.gestureEvents.onGestureHandlerAnimatedEvent - } onGestureHandlerTouchEvent={ gesture.gestureEvents.onGestureHandlerTouchEvent } + onGestureHandlerReanimatedStateChange={ + gesture.gestureEvents.onGestureHandlerStateChange + } + onGestureHandlerReanimatedEvent={ + gesture.gestureEvents.onGestureHandlerEvent + } + onGestureHandlerReanimatedTouchEvent={ + gesture.gestureEvents.onGestureHandlerTouchEvent + } + onGestureHandlerAnimatedEvent={ + gesture.gestureEvents.onGestureHandlerAnimatedEvent + } moduleId={globalThis._RNGH_MODULE_ID} handlerTags={[gesture.tag]} style={styles.detector}> diff --git a/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureHandlerEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureHandlerEvent.ts index e75e056ee4..a5840697c2 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureHandlerEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureHandlerEvent.ts @@ -63,7 +63,7 @@ export function useGestureHandlerEvent(handlerTag: number, config: any) { 'worklet'; onGestureHandlerEvent(event, reanimatedHandler?.context); }, - ['onGestureHandlerEvent'], + ['onGestureHandlerReanimatedEvent'], !!reanimatedHandler?.doDependenciesDiffer ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureStateChangeEvent.ts index ce1aa9c584..4ca5aca1ef 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureStateChangeEvent.ts @@ -70,7 +70,7 @@ export function useGestureStateChangeEvent(handlerTag: number, config: any) { // eslint-disable-next-line react-hooks/rules-of-hooks const reanimatedEvent = Reanimated?.useEvent( onGestureHandlerStateChange, - ['onGestureHandlerStateChange'], + ['onGestureHandlerReanimatedStateChange'], !!reanimatedHandler?.doDependenciesDiffer ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/events/useTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/events/useTouchEvent.ts index c98e8e1a73..bb86d04f17 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/events/useTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/events/useTouchEvent.ts @@ -60,7 +60,7 @@ export function useTouchEvent(handlerTag: number, config: any) { // eslint-disable-next-line react-hooks/rules-of-hooks const reanimatedEvent = Reanimated?.useEvent( onGestureHandlerTouchEvent, - ['onGestureHandlerTouchEvent'], + ['onGestureHandlerReanimatedTouchEvent'], !!reanimatedHandler?.doDependenciesDiffer ); From 1e17cf8934391a95c46818992f41a8451c51d234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 Aug 2025 11:30:37 +0200 Subject: [PATCH 02/67] Separate Reanimated on Android --- .../core/FlingGestureHandler.kt | 2 +- .../gesturehandler/core/GestureHandler.kt | 9 +++- .../core/HoverGestureHandler.kt | 2 +- .../core/LongPressGestureHandler.kt | 2 +- .../core/ManualGestureHandler.kt | 2 +- .../core/NativeViewGestureHandler.kt | 2 +- .../gesturehandler/core/PanGestureHandler.kt | 2 +- .../core/PinchGestureHandler.kt | 2 +- .../core/RotationGestureHandler.kt | 2 +- .../gesturehandler/core/TapGestureHandler.kt | 2 +- .../react/RNGestureHandlerModule.kt | 1 + .../react/RNGestureHandlerRootViewManager.kt | 2 + .../react/events/EventTarget.kt | 7 ++++ .../{ => events}/RNGestureHandlerEvent.kt | 41 +++++++++++-------- .../RNGestureHandlerEventDispatcher.kt | 41 ++++++++++++------- .../RNGestureHandlerStateChangeEvent.kt | 17 ++++++-- .../RNGestureHandlerTouchEvent.kt | 13 +++--- .../FlingGestureHandlerEventDataBuilder.kt | 2 +- .../GestureHandlerEventDataBuilder.kt | 2 +- .../HoverGestureHandlerEventDataBuilder.kt | 2 +- ...LongPressGestureHandlerEventDataBuilder.kt | 2 +- .../ManualGestureHandlerEventDataBuilder.kt | 2 +- .../NativeGestureHandlerEventDataBuilder.kt | 2 +- .../PanGestureHandlerEventDataBuilder.kt | 2 +- .../PinchGestureHandlerEventDataBuilder.kt | 2 +- .../RotationGestureHandlerEventDataBuilder.kt | 2 +- .../TapGestureHandlerEventDataBuilder.kt | 2 +- 27 files changed, 107 insertions(+), 62 deletions(-) create mode 100644 packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventTarget.kt rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/RNGestureHandlerEvent.kt (72%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/RNGestureHandlerEventDispatcher.kt (86%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/RNGestureHandlerStateChangeEvent.kt (84%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/RNGestureHandlerTouchEvent.kt (83%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/FlingGestureHandlerEventDataBuilder.kt (93%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/GestureHandlerEventDataBuilder.kt (89%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/HoverGestureHandlerEventDataBuilder.kt (94%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/LongPressGestureHandlerEventDataBuilder.kt (94%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/ManualGestureHandlerEventDataBuilder.kt (76%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/NativeGestureHandlerEventDataBuilder.kt (88%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/PanGestureHandlerEventDataBuilder.kt (96%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/PinchGestureHandlerEventDataBuilder.kt (92%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/RotationGestureHandlerEventDataBuilder.kt (92%) rename packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/{ => events}/eventbuilders/TapGestureHandlerEventDataBuilder.kt (93%) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt index 3a61180ec8..7385f5de3c 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt @@ -6,7 +6,7 @@ import android.os.Looper import android.view.MotionEvent import android.view.VelocityTracker import com.facebook.react.bridge.ReadableMap -import com.swmansion.gesturehandler.react.eventbuilders.FlingGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.FlingGestureHandlerEventDataBuilder class FlingGestureHandler : GestureHandler() { var numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt index 5da8ccb7b4..3d5c4b098f 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt @@ -19,8 +19,8 @@ import com.facebook.react.uimanager.PixelUtil import com.swmansion.gesturehandler.BuildConfig import com.swmansion.gesturehandler.RNSVGHitTester import com.swmansion.gesturehandler.react.RNGestureHandlerDetectorView -import com.swmansion.gesturehandler.react.RNGestureHandlerTouchEvent -import com.swmansion.gesturehandler.react.eventbuilders.GestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.RNGestureHandlerTouchEvent +import com.swmansion.gesturehandler.react.events.eventbuilders.GestureHandlerEventDataBuilder import java.lang.IllegalStateException import java.util.* @@ -80,6 +80,7 @@ open class GestureHandler { private val trackedPointers: Array = Array(MAX_POINTERS_COUNT) { null } var needsPointerData = false var dispatchesAnimatedEvents = false + var dispatchesReanimatedEvents = false private var hitSlop: FloatArray? = null var eventCoalescingKey: Short = 0 @@ -882,6 +883,9 @@ open class GestureHandler { if (config.hasKey(KEY_DISPATCHES_ANIMATED_EVENTS)) { handler.dispatchesAnimatedEvents = config.getBoolean(KEY_DISPATCHES_ANIMATED_EVENTS) } + if (config.hasKey(KEY_SHOULD_USE_REANIMATED)) { + handler.dispatchesReanimatedEvents = config.getBoolean(KEY_SHOULD_USE_REANIMATED) + } if (config.hasKey(KEY_MANUAL_ACTIVATION)) { handler.manualActivation = config.getBoolean(KEY_MANUAL_ACTIVATION) } @@ -897,6 +901,7 @@ open class GestureHandler { private const val KEY_ENABLED = "enabled" private const val KEY_NEEDS_POINTER_DATA = "needsPointerData" private const val KEY_DISPATCHES_ANIMATED_EVENTS = "dispatchesAnimatedEvents" + private const val KEY_SHOULD_USE_REANIMATED = "shouldUseReanimated" private const val KEY_MANUAL_ACTIVATION = "manualActivation" private const val KEY_MOUSE_BUTTON = "mouseButton" private const val KEY_HIT_SLOP = "hitSlop" diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/HoverGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/HoverGestureHandler.kt index 5b8e8dfc88..bc0a7c3468 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/HoverGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/HoverGestureHandler.kt @@ -8,7 +8,7 @@ import android.view.View import android.view.ViewGroup import com.swmansion.gesturehandler.react.RNGestureHandlerRootHelper import com.swmansion.gesturehandler.react.RNViewConfigurationHelper -import com.swmansion.gesturehandler.react.eventbuilders.HoverGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.HoverGestureHandlerEventDataBuilder class HoverGestureHandler : GestureHandler() { private var handler: Handler? = null diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt index c249354477..a1004e242c 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt @@ -7,7 +7,7 @@ import android.os.SystemClock import android.view.MotionEvent import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.PixelUtil -import com.swmansion.gesturehandler.react.eventbuilders.LongPressGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.LongPressGestureHandlerEventDataBuilder class LongPressGestureHandler(context: Context) : GestureHandler() { var minDurationMs = DEFAULT_MIN_DURATION_MS diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/ManualGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/ManualGestureHandler.kt index d3322b691c..6fffde54a4 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/ManualGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/ManualGestureHandler.kt @@ -2,7 +2,7 @@ package com.swmansion.gesturehandler.core import android.content.Context import android.view.MotionEvent -import com.swmansion.gesturehandler.react.eventbuilders.ManualGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.ManualGestureHandlerEventDataBuilder class ManualGestureHandler : GestureHandler() { override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt index 6124b722b6..4d4ff34cfe 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt @@ -15,7 +15,7 @@ import com.facebook.react.views.text.ReactTextView import com.facebook.react.views.textinput.ReactEditText import com.facebook.react.views.view.ReactViewGroup import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager -import com.swmansion.gesturehandler.react.eventbuilders.NativeGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.NativeGestureHandlerEventDataBuilder import com.swmansion.gesturehandler.react.isScreenReaderOn class NativeViewGestureHandler : GestureHandler() { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt index 33bd963595..b639da6303 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt @@ -10,7 +10,7 @@ import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.PixelUtil import com.swmansion.gesturehandler.core.GestureUtils.getLastPointerX import com.swmansion.gesturehandler.core.GestureUtils.getLastPointerY -import com.swmansion.gesturehandler.react.eventbuilders.PanGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.PanGestureHandlerEventDataBuilder class PanGestureHandler(context: Context?) : GestureHandler() { var velocityX = 0f diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PinchGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PinchGestureHandler.kt index 1581cfdfaa..6482714240 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PinchGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PinchGestureHandler.kt @@ -4,7 +4,7 @@ import android.content.Context import android.graphics.PointF import android.view.MotionEvent import android.view.ViewConfiguration -import com.swmansion.gesturehandler.react.eventbuilders.PinchGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.PinchGestureHandlerEventDataBuilder import kotlin.math.abs class PinchGestureHandler : GestureHandler() { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/RotationGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/RotationGestureHandler.kt index 2d660f6d4d..a9e6237e63 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/RotationGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/RotationGestureHandler.kt @@ -4,7 +4,7 @@ import android.content.Context import android.graphics.PointF import android.view.MotionEvent import com.swmansion.gesturehandler.core.RotationGestureDetector.OnRotationGestureListener -import com.swmansion.gesturehandler.react.eventbuilders.RotationGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.RotationGestureHandlerEventDataBuilder import kotlin.math.abs class RotationGestureHandler : GestureHandler() { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/TapGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/TapGestureHandler.kt index 3a4d828b5b..302940b3d2 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/TapGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/TapGestureHandler.kt @@ -8,7 +8,7 @@ import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.PixelUtil import com.swmansion.gesturehandler.core.GestureUtils.getLastPointerX import com.swmansion.gesturehandler.core.GestureUtils.getLastPointerY -import com.swmansion.gesturehandler.react.eventbuilders.TapGestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.TapGestureHandlerEventDataBuilder import kotlin.math.abs class TapGestureHandler : GestureHandler() { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index 7a5f06c611..9bf342658d 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -15,6 +15,7 @@ import com.facebook.soloader.SoLoader import com.swmansion.gesturehandler.NativeRNGestureHandlerModuleSpec import com.swmansion.gesturehandler.ReanimatedProxy import com.swmansion.gesturehandler.core.GestureHandler +import com.swmansion.gesturehandler.react.events.RNGestureHandlerEventDispatcher // UIManagerModule.resolveRootTagFromReactTag() was deprecated and will be removed in the next RN release // ref: https://github.com/facebook/react-native/commit/acbf9e18ea666b07c1224a324602a41d0a66985e diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootViewManager.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootViewManager.kt index c8eedb803a..7f5a0b60c0 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootViewManager.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootViewManager.kt @@ -6,6 +6,8 @@ import com.facebook.react.uimanager.ViewGroupManager import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNGestureHandlerRootViewManagerDelegate import com.facebook.react.viewmanagers.RNGestureHandlerRootViewManagerInterface +import com.swmansion.gesturehandler.react.events.RNGestureHandlerEvent +import com.swmansion.gesturehandler.react.events.RNGestureHandlerStateChangeEvent /** * React native's view manager used for creating instances of []RNGestureHandlerRootView}. It diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventTarget.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventTarget.kt new file mode 100644 index 0000000000..ccff2ea77d --- /dev/null +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventTarget.kt @@ -0,0 +1,7 @@ +package com.swmansion.gesturehandler.react.events + +enum class EventTarget { + JS, + Reanimated, + Animated, +} diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt similarity index 72% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt index 153105fbff..4adf2bead8 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt @@ -4,7 +4,7 @@ // ref: https://github.com/facebook/react-native/commit/2fbbdbb2ce897e8da3f471b08b93f167d566db1d @file:Suppress("DEPRECATION") -package com.swmansion.gesturehandler.react +package com.swmansion.gesturehandler.react.events import androidx.core.util.Pools import com.facebook.react.bridge.Arguments @@ -12,26 +12,19 @@ import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.Event import com.swmansion.gesturehandler.core.GestureHandler -import com.swmansion.gesturehandler.react.eventbuilders.GestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.GestureHandlerEventDataBuilder class RNGestureHandlerEvent private constructor() : Event() { private var dataBuilder: GestureHandlerEventDataBuilder<*>? = null private var coalescingKey: Short = 0 private var actionType: Int = GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT - private var useAnimatedEvent = false - - // On the new architecture, native animated expects event names prefixed with `top` instead of `on`, - // since we know when the native animated node is the target of the event we can use the different - // event name where appropriate. - // TODO: This is a workaround not as solution, but doing this properly would require a total overhaul of - // how GH sends events (which needs to be done, but maybe wait until the RN's apis stop changing) - private var useTopPrefixedName: Boolean = false + private var eventTarget: EventTarget = EventTarget.JS private fun init( handler: T, actionType: Int, dataBuilder: GestureHandlerEventDataBuilder, - useNativeAnimatedName: Boolean, + eventTarget: EventTarget, ) { val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) { handler.viewForEvents!! @@ -43,8 +36,7 @@ class RNGestureHandlerEvent private constructor() : Event this.actionType = actionType this.dataBuilder = dataBuilder - this.useTopPrefixedName = useNativeAnimatedName - this.useAnimatedEvent = useAnimatedEvent + this.eventTarget = eventTarget coalescingKey = handler.eventCoalescingKey } @@ -53,9 +45,15 @@ class RNGestureHandlerEvent private constructor() : Event EVENTS_POOL.release(this) } - override fun getEventName() = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR && useTopPrefixedName) { - NATIVE_DETECTOR_ANIMATED_EVENT_NAME - } else if (useTopPrefixedName) { + override fun getEventName() = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) { + if (eventTarget == EventTarget.Animated) { + NATIVE_DETECTOR_ANIMATED_EVENT_NAME + } else if (eventTarget == EventTarget.Reanimated) { + REANIMATED_EVENT_NAME + } else { + EVENT_NAME + } + } else if (eventTarget == EventTarget.Animated) { NATIVE_ANIMATED_EVENT_NAME } else { EVENT_NAME @@ -73,6 +71,13 @@ class RNGestureHandlerEvent private constructor() : Event companion object { const val EVENT_NAME = "onGestureHandlerEvent" + const val REANIMATED_EVENT_NAME = "onGestureHandlerReanimatedEvent" + + // On the new architecture, native animated expects event names prefixed with `top` instead of `on`, + // since we know when the native animated node is the target of the event we can use the different + // event name where appropriate. + // TODO: This is a workaround not as solution, but doing this properly would require a total overhaul of + // how GH sends events (which needs to be done, but maybe wait until the RN's apis stop changing) const val NATIVE_ANIMATED_EVENT_NAME = "topGestureHandlerEvent" const val NATIVE_DETECTOR_ANIMATED_EVENT_NAME = "topGestureHandlerAnimatedEvent" private const val TOUCH_EVENTS_POOL_SIZE = 7 // magic @@ -82,9 +87,9 @@ class RNGestureHandlerEvent private constructor() : Event handler: T, actionType: Int, dataBuilder: GestureHandlerEventDataBuilder, - useTopPrefixedName: Boolean = false, + eventTarget: EventTarget, ): RNGestureHandlerEvent = (EVENTS_POOL.acquire() ?: RNGestureHandlerEvent()).apply { - init(handler, actionType, dataBuilder, useTopPrefixedName) + init(handler, actionType, dataBuilder, eventTarget) } fun createEventData(dataBuilder: GestureHandlerEventDataBuilder<*>): WritableMap = Arguments.createMap().apply { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEventDispatcher.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEventDispatcher.kt similarity index 86% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEventDispatcher.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEventDispatcher.kt index 4725dcb5ad..6389b159ef 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEventDispatcher.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEventDispatcher.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react +package com.swmansion.gesturehandler.react.events import android.view.MotionEvent import com.facebook.react.bridge.ReactApplicationContext @@ -8,6 +8,8 @@ import com.swmansion.gesturehandler.ReanimatedProxy import com.swmansion.gesturehandler.core.GestureHandler import com.swmansion.gesturehandler.core.OnTouchEventListener import com.swmansion.gesturehandler.dispatchEvent +import com.swmansion.gesturehandler.react.RNGestureHandlerFactoryUtil +import com.swmansion.gesturehandler.react.deviceEventEmitter class RNGestureHandlerEventDispatcher(private val reactApplicationContext: ReactApplicationContext) : OnTouchEventListener { @@ -33,7 +35,8 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React return } - val handlerFactory = RNGestureHandlerFactoryUtil.findFactoryForHandler(handler) ?: return + val handlerFactory = RNGestureHandlerFactoryUtil.findFactoryForHandler(handler) + ?: return when (handler.actionType) { GestureHandler.ACTION_TYPE_REANIMATED_WORKLET -> { // Reanimated worklet @@ -41,6 +44,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React handler, handler.actionType, handlerFactory.createEventBuilder(handler), + EventTarget.JS, // For API v2 compatibility ) sendEventForReanimated(event) } @@ -50,7 +54,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React handler, handler.actionType, handlerFactory.createEventBuilder(handler), - true, + EventTarget.Animated, ) sendEventForNativeAnimatedEvent(event) } @@ -68,20 +72,19 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data) } GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> { - if (handler.dispatchesAnimatedEvents) { - val animatedEvent = RNGestureHandlerEvent.obtain( - handler, - handler.actionType, - handlerFactory.createEventBuilder(handler), - true, - ) - handler.viewForEvents!!.dispatchEvent(animatedEvent) + val eventTarget = if (handler.dispatchesAnimatedEvents) { + EventTarget.Animated + } else if (handler.dispatchesReanimatedEvents) { + EventTarget.Reanimated + } else { + EventTarget.JS } val event = RNGestureHandlerEvent.obtain( handler, handler.actionType, handlerFactory.createEventBuilder(handler), + eventTarget, ) handler.viewForEvents!!.dispatchEvent(event) @@ -96,7 +99,8 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React // root containers use negative tags, we don't need to dispatch events for them to the JS return } - val handlerFactory = RNGestureHandlerFactoryUtil.findFactoryForHandler(handler) ?: return + val handlerFactory = RNGestureHandlerFactoryUtil.findFactoryForHandler(handler) + ?: return when (handler.actionType) { GestureHandler.ACTION_TYPE_REANIMATED_WORKLET -> { @@ -107,6 +111,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React oldState, handler.actionType, handlerFactory.createEventBuilder(handler), + EventTarget.JS, // For API v2 compatibility ) sendEventForReanimated(event) } @@ -132,12 +137,15 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React } GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> { + val eventTarget = if (handler.dispatchesReanimatedEvents) EventTarget.Reanimated else EventTarget.JS + val event = RNGestureHandlerStateChangeEvent.obtain( handler, newState, oldState, handler.actionType, handlerFactory.createEventBuilder(handler), + eventTarget, ) handler.viewForEvents!!.dispatchEvent(event) @@ -164,7 +172,11 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React when (handler.actionType) { GestureHandler.ACTION_TYPE_REANIMATED_WORKLET -> { // Reanimated worklet - val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType) + val event = RNGestureHandlerTouchEvent.obtain( + handler, + handler.actionType, + EventTarget.JS, // For API v2 compatibility + ) sendEventForReanimated(event) } GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API -> { @@ -173,7 +185,8 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data) } GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> { - val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType) + val eventTarget = if (handler.dispatchesReanimatedEvents) EventTarget.Reanimated else EventTarget.JS + val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType, eventTarget) handler.viewForEvents!!.dispatchEvent(event) } diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerStateChangeEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerStateChangeEvent.kt similarity index 84% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerStateChangeEvent.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerStateChangeEvent.kt index f63aa296a6..9b355723dc 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerStateChangeEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerStateChangeEvent.kt @@ -4,7 +4,7 @@ // ref: https://github.com/facebook/react-native/commit/2fbbdbb2ce897e8da3f471b08b93f167d566db1d @file:Suppress("DEPRECATION") -package com.swmansion.gesturehandler.react +package com.swmansion.gesturehandler.react.events import androidx.core.util.Pools import com.facebook.react.bridge.Arguments @@ -12,13 +12,14 @@ import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.Event import com.swmansion.gesturehandler.core.GestureHandler -import com.swmansion.gesturehandler.react.eventbuilders.GestureHandlerEventDataBuilder +import com.swmansion.gesturehandler.react.events.eventbuilders.GestureHandlerEventDataBuilder class RNGestureHandlerStateChangeEvent private constructor() : Event() { private var dataBuilder: GestureHandlerEventDataBuilder<*>? = null private var newState: Int = GestureHandler.STATE_UNDETERMINED private var oldState: Int = GestureHandler.STATE_UNDETERMINED private var actionType: Int = GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT + private lateinit var eventTarget: EventTarget private fun init( handler: T, @@ -26,6 +27,7 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event, + eventTarget: EventTarget, ) { val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) { handler.viewForEvents!! @@ -39,6 +41,7 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event( TOUCH_EVENTS_POOL_SIZE, @@ -75,11 +83,12 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event, + eventTarget: EventTarget, ): RNGestureHandlerStateChangeEvent = ( EVENTS_POOL.acquire() ?: RNGestureHandlerStateChangeEvent() ).apply { - init(handler, newState, oldState, actionType, dataBuilder) + init(handler, newState, oldState, actionType, dataBuilder, eventTarget) } fun createEventData(dataBuilder: GestureHandlerEventDataBuilder<*>, newState: Int, oldState: Int): WritableMap = diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerTouchEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerTouchEvent.kt similarity index 83% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerTouchEvent.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerTouchEvent.kt index bc0dec8705..579c05b8df 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerTouchEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerTouchEvent.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react +package com.swmansion.gesturehandler.react.events import androidx.core.util.Pools import com.facebook.react.bridge.Arguments @@ -11,8 +11,9 @@ class RNGestureHandlerTouchEvent private constructor() : Event init(handler: T, actionType: Int) { + private fun init(handler: T, actionType: Int, eventTarget: EventTarget) { val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) { handler.viewForEvents!! } else { @@ -24,6 +25,7 @@ class RNGestureHandlerTouchEvent private constructor() : Event( TOUCH_EVENTS_POOL_SIZE, ) - fun obtain(handler: T, actionType: Int): RNGestureHandlerTouchEvent = + fun obtain(handler: T, actionType: Int, eventTarget: EventTarget): RNGestureHandlerTouchEvent = (EVENTS_POOL.acquire() ?: RNGestureHandlerTouchEvent()).apply { - init(handler, actionType) + init(handler, actionType, eventTarget) } fun createEventData(handler: T): WritableMap = Arguments.createMap().apply { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/FlingGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/FlingGestureHandlerEventDataBuilder.kt similarity index 93% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/FlingGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/FlingGestureHandlerEventDataBuilder.kt index dfd6ec1fa5..951a50520d 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/FlingGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/FlingGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.PixelUtil diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/GestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/GestureHandlerEventDataBuilder.kt similarity index 89% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/GestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/GestureHandlerEventDataBuilder.kt index 51c1e1355a..e12038e6a9 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/GestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/GestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.swmansion.gesturehandler.core.GestureHandler diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/HoverGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/HoverGestureHandlerEventDataBuilder.kt similarity index 94% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/HoverGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/HoverGestureHandlerEventDataBuilder.kt index d746c5c64d..9dd48319c6 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/HoverGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/HoverGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.PixelUtil diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/LongPressGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/LongPressGestureHandlerEventDataBuilder.kt similarity index 94% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/LongPressGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/LongPressGestureHandlerEventDataBuilder.kt index c5bb0deb29..e0269922d4 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/LongPressGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/LongPressGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.PixelUtil diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/ManualGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/ManualGestureHandlerEventDataBuilder.kt similarity index 76% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/ManualGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/ManualGestureHandlerEventDataBuilder.kt index 1df3057de4..e5480f4ef6 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/ManualGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/ManualGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.swmansion.gesturehandler.core.ManualGestureHandler diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/NativeGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/NativeGestureHandlerEventDataBuilder.kt similarity index 88% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/NativeGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/NativeGestureHandlerEventDataBuilder.kt index 6ce41b007d..8fee369abe 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/NativeGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/NativeGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.swmansion.gesturehandler.core.NativeViewGestureHandler diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/PanGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/PanGestureHandlerEventDataBuilder.kt similarity index 96% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/PanGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/PanGestureHandlerEventDataBuilder.kt index 9121f7c9c8..c24d859733 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/PanGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/PanGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.PixelUtil diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/PinchGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/PinchGestureHandlerEventDataBuilder.kt similarity index 92% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/PinchGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/PinchGestureHandlerEventDataBuilder.kt index b9985145a1..fac0a4fd8f 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/PinchGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/PinchGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.PixelUtil diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/RotationGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/RotationGestureHandlerEventDataBuilder.kt similarity index 92% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/RotationGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/RotationGestureHandlerEventDataBuilder.kt index 503d40ce54..4659de8ad7 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/RotationGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/RotationGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.PixelUtil diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/TapGestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/TapGestureHandlerEventDataBuilder.kt similarity index 93% rename from packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/TapGestureHandlerEventDataBuilder.kt rename to packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/TapGestureHandlerEventDataBuilder.kt index eff9095715..4caee1e062 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/TapGestureHandlerEventDataBuilder.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/eventbuilders/TapGestureHandlerEventDataBuilder.kt @@ -1,4 +1,4 @@ -package com.swmansion.gesturehandler.react.eventbuilders +package com.swmansion.gesturehandler.react.events.eventbuilders import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.PixelUtil From 08d1e2bcb15084c733614f44320dbf4348773c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 Aug 2025 13:47:42 +0200 Subject: [PATCH 03/67] Separate Reanimated on iOS --- apps/basic-example/ios/Podfile.lock | 2 +- .../apple/RNGHEventTarget.h | 14 ++ .../apple/RNGestureHandler.h | 7 +- .../apple/RNGestureHandler.mm | 49 +++++-- .../apple/RNGestureHandlerDetector.h | 7 + .../apple/RNGestureHandlerDetector.mm | 25 ++++ .../apple/RNGestureHandlerEvents.h | 5 +- .../apple/RNGestureHandlerEvents.mm | 22 ++- .../apple/RNGestureHandlerManager.mm | 133 ++++++++++++------ .../apple/RNGestureHandlerNativeEventUtils.h | 5 + .../apple/RNGestureHandlerNativeEventUtils.mm | 27 ++++ 11 files changed, 231 insertions(+), 65 deletions(-) create mode 100644 packages/react-native-gesture-handler/apple/RNGHEventTarget.h diff --git a/apps/basic-example/ios/Podfile.lock b/apps/basic-example/ios/Podfile.lock index 994cfe9ae9..3a58ab8417 100644 --- a/apps/basic-example/ios/Podfile.lock +++ b/apps/basic-example/ios/Podfile.lock @@ -2678,7 +2678,7 @@ SPEC CHECKSUMS: RNReanimated: 25060745a200605462ff56cf488411db066631ce RNWorklets: 9bb08cb0ef718ce063f61ca18f95f57aec9b9673 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e + Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb PODFILE CHECKSUM: d05778d3a61b8d49242579ea0aa864580fbb1f64 diff --git a/packages/react-native-gesture-handler/apple/RNGHEventTarget.h b/packages/react-native-gesture-handler/apple/RNGHEventTarget.h new file mode 100644 index 0000000000..2885adc6e6 --- /dev/null +++ b/packages/react-native-gesture-handler/apple/RNGHEventTarget.h @@ -0,0 +1,14 @@ +// +// RNGHEventTarget.h +// Pods +// +// Created by MichaƂ Bert on 21/08/2025. +// + +#import + +typedef NS_ENUM(NSInteger, RNGestureHandlerEventTarget) { + RNGestureHandlerEventTargetJS = 0, + RNGestureHandlerEventTargetReanimated, + RNGestureHandlerEventTargetAnimated +}; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.h b/packages/react-native-gesture-handler/apple/RNGestureHandler.h index eaa2ec941a..1825e7c011 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.h @@ -1,3 +1,4 @@ +#import "RNGHEventTarget.h" #import "RNGHUIKit.h" #import "RNGestureHandlerActionType.h" #import "RNGestureHandlerDirection.h" @@ -40,11 +41,12 @@ - (void)sendEvent:(nonnull RNGestureHandlerStateChange *)event withActionType:(RNGestureHandlerActionType)actionType - forAnimated:(BOOL)forAnimated + forTarget:(RNGestureHandlerEventTarget)eventTarget forView:(nonnull RNGHUIView *)detectorView; - (void)sendNativeTouchEventForGestureHandler:(nonnull RNGestureHandler *)handler - withPointerType:(NSInteger)pointerType; + withPointerType:(NSInteger)pointerType + forTarget:(RNGestureHandlerEventTarget)eventTarget; @end @@ -78,6 +80,7 @@ @property (nonatomic) BOOL needsPointerData; @property (nonatomic) BOOL manualActivation; @property (nonatomic) BOOL dispatchesAnimatedEvents; +@property (nonatomic) BOOL dispatchesReanimatedEvents; - (BOOL)isViewParagraphComponent:(nullable RNGHUIView *)view; - (nonnull RNGHUIView *)chooseViewForInteraction:(nonnull UIGestureRecognizer *)recognizer; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index 418d3eb7f6..0798947250 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -147,6 +147,11 @@ - (void)configure:(NSDictionary *)config _dispatchesAnimatedEvents = [RCTConvert BOOL:prop]; } + prop = config[@"shouldUseReanimated"]; + if (prop != nil) { + _dispatchesReanimatedEvents = [RCTConvert BOOL:prop]; + } + prop = config[@"manualActivation"]; if (prop != nil) { self.manualActivation = [RCTConvert BOOL:prop]; @@ -348,12 +353,15 @@ - (void)sendEventsInState:(RNGestureHandlerState)state } if (state == RNGestureHandlerStateActive) { - id touchEvent = [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag - handlerTag:_tag - state:state - extraData:extraData - forAnimated:_dispatchesAnimatedEvents - coalescingKey:self->_eventCoalescingKey]; + id touchEvent = + [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag + handlerTag:_tag + state:state + extraData:extraData + forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated + : RNGestureHandlerEventTargetJS + coalescingKey:self->_eventCoalescingKey]; [self sendEvent:touchEvent]; } } @@ -370,30 +378,41 @@ - (void)sendEvent:(RNGestureHandlerStateChange *)event { [self.emitter sendEvent:event withActionType:self.actionType - forAnimated:_dispatchesAnimatedEvents + forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated + : RNGestureHandlerEventTargetJS forView:[self findViewForEvents]]; } - (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(NSNumber *)reactTag { if (_actionType == RNGestureHandlerActionTypeNativeDetector) { - [self.emitter sendNativeTouchEventForGestureHandler:self withPointerType:_pointerType]; + [self.emitter sendNativeTouchEventForGestureHandler:self + withPointerType:_pointerType + forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated + : RNGestureHandlerEventTargetJS]; } else { id extraData = [RNGestureHandlerEventExtraData forEventType:_pointerTracker.eventType withChangedPointers:_pointerTracker.changedPointersData withAllPointers:_pointerTracker.allPointersData withNumberOfTouches:_pointerTracker.trackedPointersCount withPointerType:_pointerType]; - id event = [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag - handlerTag:_tag - state:state - extraData:extraData - forAnimated:_dispatchesAnimatedEvents - coalescingKey:[_tag intValue]]; + id event = + [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag + handlerTag:_tag + state:state + extraData:extraData + forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated + : RNGestureHandlerEventTargetJS + coalescingKey:[_tag intValue]]; [self.emitter sendEvent:event withActionType:self.actionType - forAnimated:_dispatchesAnimatedEvents + forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated + : RNGestureHandlerEventTargetJS forView:self.recognizer.view]; } } diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.h index 9ee88d0eb2..4fde3bc649 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.h @@ -20,6 +20,13 @@ NS_ASSUME_NONNULL_BEGIN - (void)dispatchTouchEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerTouchEvent)event; +- (void)dispatchReanimatedStateChangeEvent: + (RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedStateChange)event; + +- (void)dispatchReanimatedGestureEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedEvent)event; + +- (void)dispatchReanimatedTouchEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedTouchEvent)event; + - (void)tryAttachNativeHandlersToChildView; - (void)detachNativeGestureHandlers; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm index 071c4a0436..a05bbb6a42 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm @@ -87,6 +87,31 @@ - (void)dispatchTouchEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandl } } +- (void)dispatchReanimatedStateChangeEvent: + (RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedStateChange)event +{ + if (_eventEmitter != nullptr) { + std::dynamic_pointer_cast(_eventEmitter) + ->onGestureHandlerReanimatedStateChange(event); + } +} + +- (void)dispatchReanimatedGestureEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedEvent)event +{ + if (_eventEmitter != nullptr) { + std::dynamic_pointer_cast(_eventEmitter) + ->onGestureHandlerReanimatedEvent(event); + } +} + +- (void)dispatchReanimatedTouchEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedTouchEvent)event +{ + if (_eventEmitter != nullptr) { + std::dynamic_pointer_cast(_eventEmitter) + ->onGestureHandlerReanimatedTouchEvent(event); + } +} + - (BOOL)shouldAttachGestureToSubview:(NSNumber *)handlerTag { RNGestureHandlerManager *handlerManager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId]; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h index ab047bf6cb..6e1c9e714e 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h @@ -2,6 +2,7 @@ #import +#import "RNGHEventTarget.h" #import "RNGHStylusData.h" #import "RNGHTouchEventType.h" #import "RNGHUIKit.h" @@ -62,13 +63,13 @@ @property (nonatomic, strong, readonly) NSNumber *handlerTag; @property (nonatomic, strong, readonly) RNGestureHandlerEventExtraData *extraData; @property (nonatomic, readonly) RNGestureHandlerState state; -@property (nonatomic, readonly) BOOL forAnimated; +@property (nonatomic, readonly) RNGestureHandlerEventTarget eventTarget; - (instancetype)initWithReactTag:(NSNumber *)reactTag handlerTag:(NSNumber *)handlerTag state:(RNGestureHandlerState)state extraData:(RNGestureHandlerEventExtraData *)extraData - forAnimated:(BOOL)forAnimated + forTarget:(RNGestureHandlerEventTarget)eventTarget coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER; @end diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm index da99b29b22..b194151e85 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm @@ -173,7 +173,7 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag handlerTag:(NSNumber *)handlerTag state:(RNGestureHandlerState)state extraData:(RNGestureHandlerEventExtraData *)extraData - forAnimated:(BOOL)forAnimated + forTarget:(RNGestureHandlerEventTarget)eventTarget coalescingKey:(uint16_t)coalescingKey { if ((self = [super init])) { @@ -182,7 +182,7 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag _state = state; _extraData = extraData; _coalescingKey = coalescingKey; - _forAnimated = forAnimated; + _eventTarget = eventTarget; } return self; } @@ -191,7 +191,14 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag - (NSString *)eventName { - return _forAnimated ? @"onGestureHandlerAnimatedEvent" : @"onGestureHandlerEvent"; + switch (_eventTarget) { + case RNGestureHandlerEventTargetJS: + return @"onGestureHandlerEvent"; + case RNGestureHandlerEventTargetReanimated: + return @"onGestureHandlerReanimatedEvent"; + case RNGestureHandlerEventTargetAnimated: + return @"onGestureHandlerAnimatedEvent"; + } } - (BOOL)canCoalesce @@ -211,7 +218,7 @@ + (NSString *)moduleDotMethod - (NSArray *)arguments { - if (_forAnimated) { + if (_eventTarget == RNGestureHandlerEventTargetAnimated) { NSMutableDictionary *body = [[NSMutableDictionary alloc] init]; [body setObject:_viewTag forKey:@"target"]; [body setObject:_handlerTag forKey:@"handlerTag"]; @@ -223,7 +230,12 @@ - (NSArray *)arguments [body setObject:_viewTag forKey:@"target"]; [body setObject:_handlerTag forKey:@"handlerTag"]; [body setObject:@(_state) forKey:@"state"]; - return @[ self.viewTag, @"onGestureHandlerEvent", body ]; + return @[ + self.viewTag, + _eventTarget == RNGestureHandlerEventTargetReanimated ? @"onGestureHandlerReanimatedEvent" + : @"onGestureHandlerEvent", + body + ]; } } diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm index 7d86f7d072..c936b52c4a 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm @@ -9,6 +9,7 @@ #import #import +#import "RNGHEventTarget.h" #import "RNGestureHandler.h" #import "RNGestureHandlerActionType.h" #import "RNGestureHandlerNativeEventUtils.h" @@ -291,23 +292,38 @@ - (void)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer - (void)sendEvent:(RNGestureHandlerStateChange *)event withActionType:(RNGestureHandlerActionType)actionType - forAnimated:(BOOL)forAnimated + forTarget:(RNGestureHandlerEventTarget)eventTarget forView:(RNGHUIView *)detectorView // Typing as RNGestureHandlerDetector is preferable // but results in a compilation error. { switch (actionType) { case RNGestureHandlerActionTypeNativeDetector: { if ([event isKindOfClass:[RNGestureHandlerEvent class]]) { - if (forAnimated) { - [self sendEventForNativeAnimatedEvent:event]; + switch (eventTarget) { + case RNGestureHandlerEventTargetAnimated: + [self sendEventForNativeAnimatedEvent:event]; + break; + case RNGestureHandlerEventTargetReanimated: { + RNGestureHandlerEvent *gestureEvent = (RNGestureHandlerEvent *)event; + auto nativeEvent = [gestureEvent getReanimatedNativeEvent]; + [(RNGestureHandlerDetector *)detectorView dispatchReanimatedGestureEvent:nativeEvent]; + break; + } + case RNGestureHandlerEventTargetJS: { + RNGestureHandlerEvent *gestureEvent = (RNGestureHandlerEvent *)event; + auto nativeEvent = [gestureEvent getNativeEvent]; + [(RNGestureHandlerDetector *)detectorView dispatchGestureEvent:nativeEvent]; + break; + } } - - RNGestureHandlerEvent *gestureEvent = (RNGestureHandlerEvent *)event; - auto nativeEvent = [gestureEvent getNativeEvent]; - [(RNGestureHandlerDetector *)detectorView dispatchGestureEvent:nativeEvent]; } else { - auto nativeEvent = [event getNativeEvent]; - [(RNGestureHandlerDetector *)detectorView dispatchStateChangeEvent:nativeEvent]; + if (eventTarget == RNGestureHandlerEventTargetReanimated) { + auto nativeEvent = [event getReanimatedNativeEvent]; + [(RNGestureHandlerDetector *)detectorView dispatchReanimatedStateChangeEvent:nativeEvent]; + } else { + auto nativeEvent = [event getNativeEvent]; + [(RNGestureHandlerDetector *)detectorView dispatchStateChangeEvent:nativeEvent]; + } } break; } @@ -337,41 +353,78 @@ - (void)sendEvent:(RNGestureHandlerStateChange *)event } } -- (void)sendNativeTouchEventForGestureHandler:(RNGestureHandler *)handler withPointerType:(NSInteger)pointerType +- (void)sendNativeTouchEventForGestureHandler:(RNGestureHandler *)handler + withPointerType:(NSInteger)pointerType + forTarget:(RNGestureHandlerEventTarget)eventTarget { - facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerTouchEvent nativeEvent = { - .handlerTag = [handler.tag intValue], - .state = static_cast(handler.state), - .pointerType = static_cast(pointerType), - .numberOfTouches = handler.pointerTracker.trackedPointersCount, - .eventType = static_cast(handler.pointerTracker.eventType), - .changedTouches = {}, - .allTouches = {}, - }; - - for (NSDictionary *touch in handler.pointerTracker.allPointersData) { - nativeEvent.allTouches.push_back({ - .id = [[touch valueForKey:@"id"] intValue], - .x = [[touch valueForKey:@"x"] doubleValue], - .y = [[touch valueForKey:@"y"] doubleValue], - .absoluteX = [[touch valueForKey:@"absoluteX"] doubleValue], - .absoluteY = [[touch valueForKey:@"absoluteY"] doubleValue], - }); - } + RNGestureHandlerDetector *detector = (RNGestureHandlerDetector *)[handler findViewForEvents]; - for (NSDictionary *touch in handler.pointerTracker.changedPointersData) { - nativeEvent.changedTouches.push_back({ - .id = [[touch valueForKey:@"id"] intValue], - .x = [[touch valueForKey:@"x"] doubleValue], - .y = [[touch valueForKey:@"y"] doubleValue], - .absoluteX = [[touch valueForKey:@"absoluteX"] doubleValue], - .absoluteY = [[touch valueForKey:@"absoluteY"] doubleValue], - }); - } + // We have to double the logic since event types come from codegen. + if (eventTarget == RNGestureHandlerEventTargetReanimated) { + facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedTouchEvent nativeEvent = { + .handlerTag = [handler.tag intValue], + .state = static_cast(handler.state), + .pointerType = static_cast(pointerType), + .numberOfTouches = handler.pointerTracker.trackedPointersCount, + .eventType = static_cast(handler.pointerTracker.eventType), + .changedTouches = {}, + .allTouches = {}, + }; - RNGestureHandlerDetector *detector = (RNGestureHandlerDetector *)[handler findViewForEvents]; + for (NSDictionary *touch in handler.pointerTracker.allPointersData) { + nativeEvent.allTouches.push_back({ + .id = [[touch valueForKey:@"id"] intValue], + .x = [[touch valueForKey:@"x"] doubleValue], + .y = [[touch valueForKey:@"y"] doubleValue], + .absoluteX = [[touch valueForKey:@"absoluteX"] doubleValue], + .absoluteY = [[touch valueForKey:@"absoluteY"] doubleValue], + }); + } + + for (NSDictionary *touch in handler.pointerTracker.changedPointersData) { + nativeEvent.changedTouches.push_back({ + .id = [[touch valueForKey:@"id"] intValue], + .x = [[touch valueForKey:@"x"] doubleValue], + .y = [[touch valueForKey:@"y"] doubleValue], + .absoluteX = [[touch valueForKey:@"absoluteX"] doubleValue], + .absoluteY = [[touch valueForKey:@"absoluteY"] doubleValue], + }); + } + + [detector dispatchReanimatedTouchEvent:nativeEvent]; + } else { + facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerTouchEvent nativeEvent = { + .handlerTag = [handler.tag intValue], + .state = static_cast(handler.state), + .pointerType = static_cast(pointerType), + .numberOfTouches = handler.pointerTracker.trackedPointersCount, + .eventType = static_cast(handler.pointerTracker.eventType), + .changedTouches = {}, + .allTouches = {}, + }; - [detector dispatchTouchEvent:nativeEvent]; + for (NSDictionary *touch in handler.pointerTracker.allPointersData) { + nativeEvent.allTouches.push_back({ + .id = [[touch valueForKey:@"id"] intValue], + .x = [[touch valueForKey:@"x"] doubleValue], + .y = [[touch valueForKey:@"y"] doubleValue], + .absoluteX = [[touch valueForKey:@"absoluteX"] doubleValue], + .absoluteY = [[touch valueForKey:@"absoluteY"] doubleValue], + }); + } + + for (NSDictionary *touch in handler.pointerTracker.changedPointersData) { + nativeEvent.changedTouches.push_back({ + .id = [[touch valueForKey:@"id"] intValue], + .x = [[touch valueForKey:@"x"] doubleValue], + .y = [[touch valueForKey:@"y"] doubleValue], + .absoluteX = [[touch valueForKey:@"absoluteX"] doubleValue], + .absoluteY = [[touch valueForKey:@"absoluteY"] doubleValue], + }); + } + + [detector dispatchTouchEvent:nativeEvent]; + } } - (void)sendEventForReanimated:(RNGestureHandlerStateChange *)event diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.h index 21543720a3..87c97b56c0 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.h @@ -6,10 +6,15 @@ - (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerEvent)getNativeEvent; +- (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedEvent)getReanimatedNativeEvent; + @end @interface RNGestureHandlerStateChange (NativeEvent) - (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerStateChange)getNativeEvent; +- (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedStateChange) + getReanimatedNativeEvent; + @end diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.mm index feea2e7785..8d005e8146 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.mm @@ -67,6 +67,19 @@ @implementation RNGestureHandlerEvent (NativeEvent) return nativeEvent; } +- (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedEvent)getReanimatedNativeEvent +{ + folly::dynamic handlerData = rngh_dynamicFromId(self.extraData.data); + + facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedEvent nativeEvent = { + .handlerTag = [self.handlerTag intValue], + .state = static_cast(self.state), + .handlerData = handlerData, + }; + + return nativeEvent; +} + @end @implementation RNGestureHandlerStateChange (NativeEvent) @@ -85,4 +98,18 @@ @implementation RNGestureHandlerStateChange (NativeEvent) return nativeEvent; } +- (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedStateChange)getReanimatedNativeEvent +{ + folly::dynamic handlerData = rngh_dynamicFromId(self.extraData.data); + + facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedStateChange nativeEvent = { + .handlerTag = [self.handlerTag intValue], + .state = static_cast(self.state), + .oldState = static_cast(self.previousState), + .handlerData = handlerData, + }; + + return nativeEvent; +} + @end From 256f7073c6de001dcf40bec7989d759339d064cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 Aug 2025 14:40:55 +0200 Subject: [PATCH 04/67] Reorganize structure --- .../js}/useGestureStateChangeEvent.ts | 10 +++++----- .../js/useGestureTouchEvent.ts} | 12 ++++++------ .../js/useGestureUpdateEvent.ts} | 12 ++++++------ .../src/v3/hooks/useGesture.ts | 4 ++-- .../{useGestureEvent.ts => useGestureCallbacks.ts} | 14 ++++++-------- 5 files changed, 25 insertions(+), 27 deletions(-) rename packages/react-native-gesture-handler/src/v3/hooks/{events => callbacks/js}/useGestureStateChangeEvent.ts (88%) rename packages/react-native-gesture-handler/src/v3/hooks/{events/useTouchEvent.ts => callbacks/js/useGestureTouchEvent.ts} (82%) rename packages/react-native-gesture-handler/src/v3/hooks/{events/useGestureHandlerEvent.ts => callbacks/js/useGestureUpdateEvent.ts} (85%) rename packages/react-native-gesture-handler/src/v3/hooks/{useGestureEvent.ts => useGestureCallbacks.ts} (60%) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts similarity index 88% rename from packages/react-native-gesture-handler/src/v3/hooks/events/useGestureStateChangeEvent.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index 4ca5aca1ef..be6e0e6f78 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,12 +1,12 @@ -import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; +import { CALLBACK_TYPE } from '../../../../handlers/gestures/gesture'; import { isEventForHandlerWithTag, isNativeEvent, runWorkletCallback, -} from '../utils'; -import { State } from '../../../State'; -import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers, StateChangeEvent } from '../../types'; +} from '../../utils'; +import { State } from '../../../../State'; +import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; +import { CallbackHandlers, StateChangeEvent } from '../../../types'; export function useGestureStateChangeEvent(handlerTag: number, config: any) { const { onBegin, onStart, onEnd, onFinalize } = config; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/events/useTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts similarity index 82% rename from packages/react-native-gesture-handler/src/v3/hooks/events/useTouchEvent.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index bb86d04f17..f0a9480240 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/events/useTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,16 +1,16 @@ -import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; +import { GestureTouchEvent } from '../../../../handlers/gestureHandlerCommon'; import { isEventForHandlerWithTag, isNativeEvent, runWorkletCallback, touchEventTypeToCallbackType, -} from '../utils'; -import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; -import { TouchEventType } from '../../../TouchEventType'; -import { CallbackHandlers, TouchEvent } from '../../types'; +} from '../../utils'; +import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; +import { TouchEventType } from '../../../../TouchEventType'; +import { CallbackHandlers, TouchEvent } from '../../../types'; import { NativeSyntheticEvent } from 'react-native'; -export function useTouchEvent(handlerTag: number, config: any) { +export function useGestureTouchEvent(handlerTag: number, config: any) { const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = config; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureHandlerEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts similarity index 85% rename from packages/react-native-gesture-handler/src/v3/hooks/events/useGestureHandlerEvent.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index a5840697c2..5a390db00d 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/events/useGestureHandlerEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -1,17 +1,17 @@ -import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; +import { CALLBACK_TYPE } from '../../../../handlers/gestures/gesture'; import { isAnimatedEvent, isEventForHandlerWithTag, runWorkletCallback, -} from '../utils'; +} from '../../utils'; import { Reanimated, ReanimatedContext, -} from '../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers, UpdateEvent } from '../../types'; -import { tagMessage } from '../../../utils'; +} from '../../../../handlers/gestures/reanimatedWrapper'; +import { CallbackHandlers, UpdateEvent } from '../../../types'; +import { tagMessage } from '../../../../utils'; -export function useGestureHandlerEvent(handlerTag: number, config: any) { +export function useGestureUpdateEvent(handlerTag: number, config: any) { const { onUpdate } = config; const handlers: CallbackHandlers = { ...(onUpdate && { onUpdate }) }; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 7f7b85ad05..6d17290efa 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo } from 'react'; import { getNextHandlerTag } from '../../handlers/getNextHandlerTag'; import RNGestureHandlerModule from '../../RNGestureHandlerModule'; -import { useGestureEvent } from './useGestureEvent'; +import { useGestureCallbacks } from './useGestureCallbacks'; import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; import { tagMessage } from '../../utils'; import { AnimatedEvent } from '../types'; @@ -71,7 +71,7 @@ export function useGesture( onGestureHandlerEvent, onGestureHandlerAnimatedEvent, onGestureHandlerTouchEvent, - } = useGestureEvent(tag, config); + } = useGestureCallbacks(tag, config); // This should never happen, but since we don't want to call hooks conditionally, // we have to mark these as possibly undefined to make TypeScript happy. diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGestureEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts similarity index 60% rename from packages/react-native-gesture-handler/src/v3/hooks/useGestureEvent.ts rename to packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts index 015fa89aa6..70af5c4bfa 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGestureEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts @@ -1,20 +1,18 @@ -import { useGestureStateChangeEvent } from './events/useGestureStateChangeEvent'; -import { useGestureHandlerEvent } from './events/useGestureHandlerEvent'; -import { useTouchEvent } from './events/useTouchEvent'; +import { useGestureStateChangeEvent } from './callbacks/js/useGestureStateChangeEvent'; +import { useGestureUpdateEvent } from './callbacks/js/useGestureUpdateEvent'; +import { useGestureTouchEvent } from './callbacks/js/useGestureTouchEvent'; import { AnimatedEvent } from '../types'; import { checkMappingForChangeProperties, isAnimatedEvent } from './utils'; -export function useGestureEvent(handlerTag: number, config: any) { +export function useGestureCallbacks(handlerTag: number, config: any) { const onGestureHandlerStateChange = useGestureStateChangeEvent( handlerTag, config ); - const onGestureHandlerEvent = useGestureHandlerEvent(handlerTag, config); - - const onGestureHandlerTouchEvent = useTouchEvent(handlerTag, config); + const onGestureHandlerEvent = useGestureUpdateEvent(handlerTag, config); + const onGestureHandlerTouchEvent = useGestureTouchEvent(handlerTag, config); let onGestureHandlerAnimatedEvent: AnimatedEvent | undefined; - if (isAnimatedEvent(config.onUpdate)) { for (const mapping of config.onUpdate._argMapping) { checkMappingForChangeProperties(mapping); From 19fd9856f4a405d0c9b45e918396e66f3c8e983a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 Aug 2025 16:01:11 +0200 Subject: [PATCH 05/67] Create ceparate hooks for Reanimated --- .../src/v3/NativeDetector.tsx | 4 +- .../js/useGestureStateChangeEvent.ts | 81 ++----------------- .../callbacks/js/useGestureTouchEvent.ts | 70 ++-------------- .../callbacks/js/useGestureUpdateEvent.ts | 79 +++--------------- .../hooks/callbacks/onGestureHandlerEvent.ts | 40 +++++++++ .../callbacks/onGestureHandlerStateChange.ts | 50 ++++++++++++ .../callbacks/onGestureHandlerTouchEvent.ts | 45 +++++++++++ .../useReanimatedStateChangeEvent.ts | 24 ++++++ .../reanimated/useReanimatedTouchEvent.ts | 25 ++++++ .../reanimated/useReanimatedUpdateEvent.ts | 26 ++++++ .../src/v3/hooks/useGesture.ts | 11 ++- .../src/v3/hooks/useGestureCallbacks.ts | 25 ++++-- .../src/v3/hooks/utils.ts | 2 +- 13 files changed, 271 insertions(+), 211 deletions(-) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerEvent.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerStateChange.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerTouchEvent.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx index be9fd53c34..e7807e1f3f 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx @@ -44,10 +44,10 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { gesture.gestureEvents.onGestureHandlerTouchEvent } onGestureHandlerReanimatedStateChange={ - gesture.gestureEvents.onGestureHandlerStateChange + gesture.gestureEvents.onReanimatedStateChange } onGestureHandlerReanimatedEvent={ - gesture.gestureEvents.onGestureHandlerEvent + gesture.gestureEvents.onReanimatedUpdateEvent } onGestureHandlerReanimatedTouchEvent={ gesture.gestureEvents.onGestureHandlerTouchEvent diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index be6e0e6f78..22021b57dd 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,80 +1,15 @@ -import { CALLBACK_TYPE } from '../../../../handlers/gestures/gesture'; -import { - isEventForHandlerWithTag, - isNativeEvent, - runWorkletCallback, -} from '../../utils'; -import { State } from '../../../../State'; -import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers, StateChangeEvent } from '../../../types'; +import { CallbackHandlers } from '../../../types'; +import { onGestureHandlerStateChange } from '../onGestureHandlerStateChange'; -export function useGestureStateChangeEvent(handlerTag: number, config: any) { +export function gestureStateChangeEvent(handlerTag: number, config: any) { const { onBegin, onStart, onEnd, onFinalize } = config; const handlers: CallbackHandlers = { - ...(onBegin && { onBegin }), - ...(onStart && { onStart }), - ...(onEnd && { onEnd }), - ...(onFinalize && { onFinalize }), + ...(onBegin ? { onBegin } : {}), + ...(onStart ? { onStart } : {}), + ...(onEnd ? { onEnd } : {}), + ...(onFinalize ? { onFinalize } : {}), }; - const onGestureHandlerStateChange = ( - event: StateChangeEvent> - ) => { - 'worklet'; - - if (!isEventForHandlerWithTag(handlerTag, event)) { - return; - } - - let oldState: State | undefined; - let state: State | undefined; - - if (isNativeEvent(event)) { - oldState = event.nativeEvent.oldState; - state = event.nativeEvent.state; - } else { - oldState = event.oldState; - state = event.state; - } - - if (oldState === State.UNDETERMINED && state === State.BEGAN) { - runWorkletCallback(CALLBACK_TYPE.BEGAN, handlers, event); - } else if ( - (oldState === State.BEGAN || oldState === State.UNDETERMINED) && - state === State.ACTIVE - ) { - runWorkletCallback(CALLBACK_TYPE.START, handlers, event); - } else if (oldState !== state && state === State.END) { - if (oldState === State.ACTIVE) { - runWorkletCallback(CALLBACK_TYPE.END, handlers, event, true); - } - runWorkletCallback(CALLBACK_TYPE.FINALIZE, handlers, event, true); - } else if ( - (state === State.FAILED || state === State.CANCELLED) && - state !== oldState - ) { - if (oldState === State.ACTIVE) { - runWorkletCallback(CALLBACK_TYPE.END, handlers, event, false); - } - runWorkletCallback(CALLBACK_TYPE.FINALIZE, handlers, event, false); - } - }; - - if (config.disableReanimated) { - return onGestureHandlerStateChange; - } - - // eslint-disable-next-line react-hooks/rules-of-hooks - const reanimatedHandler = Reanimated?.useHandler(handlers); - // eslint-disable-next-line react-hooks/rules-of-hooks - const reanimatedEvent = Reanimated?.useEvent( - onGestureHandlerStateChange, - ['onGestureHandlerReanimatedStateChange'], - !!reanimatedHandler?.doDependenciesDiffer - ); - - return config.shouldUseReanimated - ? reanimatedEvent - : onGestureHandlerStateChange; + return onGestureHandlerStateChange(handlerTag, handlers); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index f0a9480240..346af43c94 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,70 +1,16 @@ -import { GestureTouchEvent } from '../../../../handlers/gestureHandlerCommon'; -import { - isEventForHandlerWithTag, - isNativeEvent, - runWorkletCallback, - touchEventTypeToCallbackType, -} from '../../utils'; -import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { TouchEventType } from '../../../../TouchEventType'; -import { CallbackHandlers, TouchEvent } from '../../../types'; -import { NativeSyntheticEvent } from 'react-native'; +import { CallbackHandlers } from '../../../types'; +import { onGestureHandlerTouchEvent } from '../onGestureHandlerTouchEvent'; -export function useGestureTouchEvent(handlerTag: number, config: any) { +export function gestureTouchEvent(handlerTag: number, config: any) { const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = config; const handlers: CallbackHandlers = { - ...(onTouchesDown && { onTouchesDown }), - ...(onTouchesMove && { onTouchesMove }), - ...(onTouchesUp && { onTouchesUp }), - ...(onTouchesCancelled && { onTouchesCancelled }), + ...(onTouchesDown ? { onTouchesDown } : {}), + ...(onTouchesMove ? { onTouchesMove } : {}), + ...(onTouchesUp ? { onTouchesUp } : {}), + ...(onTouchesCancelled ? { onTouchesCancelled } : {}), }; - const onGestureHandlerTouchEvent = (event: TouchEvent) => { - 'worklet'; - - if (!isEventForHandlerWithTag(handlerTag, event)) { - return; - } - - if ( - isNativeEvent(event) && - event.nativeEvent.eventType !== TouchEventType.UNDETERMINED - ) { - runWorkletCallback( - touchEventTypeToCallbackType( - (event as NativeSyntheticEvent).nativeEvent - .eventType - ), - handlers, - event - ); - } else if ( - (event as GestureTouchEvent).eventType !== TouchEventType.UNDETERMINED - ) { - runWorkletCallback( - touchEventTypeToCallbackType((event as GestureTouchEvent).eventType), - handlers, - event - ); - } - }; - - if (config.disableReanimated) { - return onGestureHandlerTouchEvent; - } - - // eslint-disable-next-line react-hooks/rules-of-hooks - const reanimatedHandler = Reanimated?.useHandler(handlers); - // eslint-disable-next-line react-hooks/rules-of-hooks - const reanimatedEvent = Reanimated?.useEvent( - onGestureHandlerTouchEvent, - ['onGestureHandlerReanimatedTouchEvent'], - !!reanimatedHandler?.doDependenciesDiffer - ); - - return config.shouldUseReanimated - ? reanimatedEvent - : onGestureHandlerTouchEvent; + return onGestureHandlerTouchEvent(handlerTag, handlers); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index 5a390db00d..d3cfa8621f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -1,76 +1,23 @@ -import { CALLBACK_TYPE } from '../../../../handlers/gestures/gesture'; -import { - isAnimatedEvent, - isEventForHandlerWithTag, - runWorkletCallback, -} from '../../utils'; -import { - Reanimated, - ReanimatedContext, -} from '../../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers, UpdateEvent } from '../../../types'; -import { tagMessage } from '../../../../utils'; +import { isAnimatedEvent } from '../../utils'; +import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; +import { CallbackHandlers } from '../../../types'; +import { onGestureHandlerEvent } from '../onGestureHandlerEvent'; -export function useGestureUpdateEvent(handlerTag: number, config: any) { - const { onUpdate } = config; +export function gestureUpdateEvent(handlerTag: number, config: any) { + const { onUpdate, changeEventCalculator } = config; - const handlers: CallbackHandlers = { ...(onUpdate && { onUpdate }) }; - - const onGestureHandlerEvent = ( - event: UpdateEvent>, - context: ReanimatedContext | undefined - ) => { - 'worklet'; - - if (!isEventForHandlerWithTag(handlerTag, event)) { - return; - } - - // This should never happen, but since we don't want to call hooks conditionally, we have to mark - // context as possibly undefined to make TypeScript happy. - if (!context) { - throw new Error(tagMessage('Event handler context is not defined')); - } - - runWorkletCallback( - CALLBACK_TYPE.UPDATE, - handlers, - config.changeEventCalculator - ? config.changeEventCalculator(event, context.lastUpdateEvent) - : event - ); - - // TODO: Investigate why this is always undefined - context.lastUpdateEvent = event; - }; + const handlers: CallbackHandlers = { ...(onUpdate ? { onUpdate } : {}) }; const jsContext: ReanimatedContext = { lastUpdateEvent: undefined, }; - if (config.disableReanimated) { - return isAnimatedEvent(config.onUpdate) - ? undefined - : (event: UpdateEvent>) => - onGestureHandlerEvent(event, jsContext); - } - - // eslint-disable-next-line react-hooks/rules-of-hooks - const reanimatedHandler = Reanimated?.useHandler(handlers); - // eslint-disable-next-line react-hooks/rules-of-hooks - const reanimatedEvent = Reanimated?.useEvent( - (event: UpdateEvent>) => { - 'worklet'; - onGestureHandlerEvent(event, reanimatedHandler?.context); - }, - ['onGestureHandlerReanimatedEvent'], - !!reanimatedHandler?.doDependenciesDiffer - ); - return isAnimatedEvent(config.onUpdate) ? undefined - : config.shouldUseReanimated - ? reanimatedEvent - : (event: UpdateEvent>) => - onGestureHandlerEvent(event, jsContext); + : onGestureHandlerEvent( + handlerTag, + handlers, + jsContext, + changeEventCalculator + ); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerEvent.ts new file mode 100644 index 0000000000..d7af9a810a --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerEvent.ts @@ -0,0 +1,40 @@ +import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; +import { tagMessage } from '../../../utils'; +import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper'; +import { CallbackHandlers, UpdateEvent } from '../../types'; +import { isEventForHandlerWithTag, runCallback } from '../utils'; + +export function onGestureHandlerEvent( + handlerTag: number, + callbacks: CallbackHandlers, + context: ReanimatedContext | undefined, + changeEventCalculator?: ( + current: UpdateEvent>, + previous?: UpdateEvent> + ) => UpdateEvent> +) { + return (event: UpdateEvent>) => { + 'worklet'; + + if (!isEventForHandlerWithTag(handlerTag, event)) { + return; + } + + // This should never happen, but since we don't want to call hooks conditionally, we have to mark + // context as possibly undefined to make TypeScript happy. + if (!context) { + throw new Error(tagMessage('Event handler context is not defined')); + } + + runCallback( + CALLBACK_TYPE.UPDATE, + callbacks, + changeEventCalculator + ? changeEventCalculator(event, context.lastUpdateEvent) + : event + ); + + // TODO: Investigate why this is always undefined + context.lastUpdateEvent = event; + }; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerStateChange.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerStateChange.ts new file mode 100644 index 0000000000..8a1314bd8b --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerStateChange.ts @@ -0,0 +1,50 @@ +import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; +import { State } from '../../../State'; +import { CallbackHandlers, StateChangeEvent } from '../../types'; +import { isEventForHandlerWithTag, isNativeEvent, runCallback } from '../utils'; + +export function onGestureHandlerStateChange( + handlerTag: number, + callbacks: CallbackHandlers +) { + return (event: StateChangeEvent>) => { + 'worklet'; + + if (!isEventForHandlerWithTag(handlerTag, event)) { + return; + } + + let oldState: State | undefined; + let state: State | undefined; + + if (isNativeEvent(event)) { + oldState = event.nativeEvent.oldState; + state = event.nativeEvent.state; + } else { + oldState = event.oldState; + state = event.state; + } + + if (oldState === State.UNDETERMINED && state === State.BEGAN) { + runCallback(CALLBACK_TYPE.BEGAN, callbacks, event); + } else if ( + (oldState === State.BEGAN || oldState === State.UNDETERMINED) && + state === State.ACTIVE + ) { + runCallback(CALLBACK_TYPE.START, callbacks, event); + } else if (oldState !== state && state === State.END) { + if (oldState === State.ACTIVE) { + runCallback(CALLBACK_TYPE.END, callbacks, event, true); + } + runCallback(CALLBACK_TYPE.FINALIZE, callbacks, event, true); + } else if ( + (state === State.FAILED || state === State.CANCELLED) && + state !== oldState + ) { + if (oldState === State.ACTIVE) { + runCallback(CALLBACK_TYPE.END, callbacks, event, false); + } + runCallback(CALLBACK_TYPE.FINALIZE, callbacks, event, false); + } + }; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerTouchEvent.ts new file mode 100644 index 0000000000..36ab6e7a1e --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerTouchEvent.ts @@ -0,0 +1,45 @@ +import { NativeSyntheticEvent } from 'react-native'; +import { CallbackHandlers, TouchEvent } from '../../types'; +import { + isEventForHandlerWithTag, + isNativeEvent, + runCallback, + touchEventTypeToCallbackType, +} from '../utils'; +import { TouchEventType } from '../../../TouchEventType'; +import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; + +export function onGestureHandlerTouchEvent( + handlerTag: number, + callbacks: CallbackHandlers +) { + return (event: TouchEvent) => { + 'worklet'; + + if (!isEventForHandlerWithTag(handlerTag, event)) { + return; + } + + if ( + isNativeEvent(event) && + event.nativeEvent.eventType !== TouchEventType.UNDETERMINED + ) { + runCallback( + touchEventTypeToCallbackType( + (event as NativeSyntheticEvent).nativeEvent + .eventType + ), + callbacks, + event + ); + } else if ( + (event as GestureTouchEvent).eventType !== TouchEventType.UNDETERMINED + ) { + runCallback( + touchEventTypeToCallbackType((event as GestureTouchEvent).eventType), + callbacks, + event + ); + } + }; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts new file mode 100644 index 0000000000..b6b24b76b8 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts @@ -0,0 +1,24 @@ +import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; +import { CallbackHandlers } from '../../../types'; +import { onGestureHandlerStateChange } from '../onGestureHandlerStateChange'; + +export function useReanimatedStateChangeEvent(handlerTag: number, config: any) { + const { onBegin, onStart, onEnd, onFinalize } = config; + + const handlers: CallbackHandlers = { + ...(onBegin ? { onBegin } : {}), + ...(onStart ? { onStart } : {}), + ...(onEnd ? { onEnd } : {}), + ...(onFinalize ? { onFinalize } : {}), + }; + + const callback = onGestureHandlerStateChange(handlerTag, handlers); + + const reanimatedHandler = Reanimated?.useHandler(handlers); + + return Reanimated?.useEvent( + callback, + ['onGestureHandlerReanimatedStateChange'], + !!reanimatedHandler?.doDependenciesDiffer + ); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts new file mode 100644 index 0000000000..31e6a2499c --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts @@ -0,0 +1,25 @@ +import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; +import { CallbackHandlers } from '../../../types'; +import { onGestureHandlerTouchEvent } from '../onGestureHandlerTouchEvent'; + +export function useReanimatedTouchEvent(handlerTag: number, config: any) { + const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = + config; + + const handlers: CallbackHandlers = { + ...(onTouchesDown ? { onTouchesDown } : {}), + ...(onTouchesMove ? { onTouchesMove } : {}), + ...(onTouchesUp ? { onTouchesUp } : {}), + ...(onTouchesCancelled ? { onTouchesCancelled } : {}), + }; + + const callback = onGestureHandlerTouchEvent(handlerTag, handlers); + + const reanimatedHandler = Reanimated?.useHandler(handlers); + + return Reanimated?.useEvent( + callback, + ['onGestureHandlerReanimatedTouchEvent'], + !!reanimatedHandler?.doDependenciesDiffer + ); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts new file mode 100644 index 0000000000..7fa6b565bf --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts @@ -0,0 +1,26 @@ +import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; +import { CallbackHandlers } from '../../../types'; +import { onGestureHandlerEvent } from '../onGestureHandlerEvent'; + +export function useReanimatedUpdateEvent(handlerTag: number, config: any) { + const { onUpdate, changeEventCalculator } = config; + + const handlers: CallbackHandlers = { ...(onUpdate ? { onUpdate } : {}) }; + + const reanimatedHandler = Reanimated?.useHandler(handlers); + + const callback = onGestureHandlerEvent( + handlerTag, + handlers, + reanimatedHandler?.context, + changeEventCalculator + ); + + const reanimatedEvent = Reanimated?.useEvent( + callback, + ['onGestureHandlerReanimatedEvent'], + !!reanimatedHandler?.doDependenciesDiffer + ); + + return reanimatedEvent; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 6d17290efa..01b47da94c 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -21,6 +21,9 @@ type GestureEvents = { onGestureHandlerStateChange: (event: any) => void; onGestureHandlerEvent: undefined | ((event: any) => void); onGestureHandlerTouchEvent: (event: any) => void; + onReanimatedStateChange: undefined | ((event: any) => void); + onReanimatedUpdateEvent: undefined | ((event: any) => void); + onReanimatedTouchEvent: undefined | ((event: any) => void); onGestureHandlerAnimatedEvent: undefined | AnimatedEvent; }; @@ -69,8 +72,11 @@ export function useGesture( const { onGestureHandlerStateChange, onGestureHandlerEvent, - onGestureHandlerAnimatedEvent, onGestureHandlerTouchEvent, + onReanimatedStateChange, + onReanimatedUpdateEvent, + onReanimatedTouchEvent, + onGestureHandlerAnimatedEvent, } = useGestureCallbacks(tag, config); // This should never happen, but since we don't want to call hooks conditionally, @@ -119,6 +125,9 @@ export function useGesture( onGestureHandlerStateChange, onGestureHandlerEvent, onGestureHandlerTouchEvent, + onReanimatedStateChange, + onReanimatedUpdateEvent, + onReanimatedTouchEvent, onGestureHandlerAnimatedEvent, }, }; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts index 70af5c4bfa..79150b397a 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts @@ -1,17 +1,27 @@ -import { useGestureStateChangeEvent } from './callbacks/js/useGestureStateChangeEvent'; -import { useGestureUpdateEvent } from './callbacks/js/useGestureUpdateEvent'; -import { useGestureTouchEvent } from './callbacks/js/useGestureTouchEvent'; +import { gestureStateChangeEvent } from './callbacks/js/useGestureStateChangeEvent'; +import { gestureUpdateEvent } from './callbacks/js/useGestureUpdateEvent'; +import { gestureTouchEvent } from './callbacks/js/useGestureTouchEvent'; import { AnimatedEvent } from '../types'; import { checkMappingForChangeProperties, isAnimatedEvent } from './utils'; +import { useReanimatedStateChangeEvent } from './callbacks/reanimated/useReanimatedStateChangeEvent'; +import { useReanimatedUpdateEvent } from './callbacks/reanimated/useReanimatedUpdateEvent'; +import { useReanimatedTouchEvent } from './callbacks/reanimated/useReanimatedTouchEvent'; export function useGestureCallbacks(handlerTag: number, config: any) { - const onGestureHandlerStateChange = useGestureStateChangeEvent( + const onGestureHandlerStateChange = gestureStateChangeEvent( handlerTag, config ); + const onGestureHandlerEvent = gestureUpdateEvent(handlerTag, config); + const onGestureHandlerTouchEvent = gestureTouchEvent(handlerTag, config); + + const onReanimatedStateChange = useReanimatedStateChangeEvent( + handlerTag, + config + ); + const onReanimatedUpdateEvent = useReanimatedUpdateEvent(handlerTag, config); + const onReanimatedTouchEvent = useReanimatedTouchEvent(handlerTag, config); - const onGestureHandlerEvent = useGestureUpdateEvent(handlerTag, config); - const onGestureHandlerTouchEvent = useGestureTouchEvent(handlerTag, config); let onGestureHandlerAnimatedEvent: AnimatedEvent | undefined; if (isAnimatedEvent(config.onUpdate)) { for (const mapping of config.onUpdate._argMapping) { @@ -26,6 +36,9 @@ export function useGestureCallbacks(handlerTag: number, config: any) { onGestureHandlerStateChange, onGestureHandlerEvent, onGestureHandlerTouchEvent, + onReanimatedStateChange, + onReanimatedUpdateEvent, + onReanimatedTouchEvent, onGestureHandlerAnimatedEvent, }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index 7365f5882b..06a6eb7ee9 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -52,7 +52,7 @@ export function touchEventTypeToCallbackType( return CALLBACK_TYPE.UNDEFINED; } -export function runWorkletCallback( +export function runCallback( type: CALLBACK_TYPE, config: CallbackHandlers, event: GestureHandlerEvent>, From 133319befe50b9b0fef54c75965734dc7de01045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 Aug 2025 09:41:58 +0200 Subject: [PATCH 06/67] Add checks in useGesture --- .../src/v3/hooks/useGesture.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 01b47da94c..888a5a32c7 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -69,6 +69,7 @@ export function useGesture( Reanimated !== undefined && hasWorkletEventHandlers(config); config.needsPointerData = shouldHandleTouchEvents(config); + // TODO: Call only necessary hooks depending on which callbacks are defined (?) const { onGestureHandlerStateChange, onGestureHandlerEvent, @@ -90,10 +91,25 @@ export function useGesture( throw new Error(tagMessage('Failed to create event handlers.')); } + if ( + config.shouldUseReanimated && + (!onReanimatedStateChange || + !onReanimatedUpdateEvent || + !onReanimatedTouchEvent) + ) { + throw new Error(tagMessage('Failed to create reanimated event handlers.')); + } + config.dispatchesAnimatedEvents = !!onGestureHandlerAnimatedEvent && '__isNative' in onGestureHandlerAnimatedEvent; + if (config.dispatchesAnimatedEvents && config.shouldUseReanimated) { + throw new Error( + tagMessage('Cannot use Reanimated and Animated events at the same time.') + ); + } + useMemo(() => { RNGestureHandlerModule.createGestureHandler(type, tag, {}); RNGestureHandlerModule.flushOperations(); From e50f6cc52244882685a694b3a1682b2e74405681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 Aug 2025 09:51:27 +0200 Subject: [PATCH 07/67] use isAnimatedEvent --- .../src/v3/hooks/useGesture.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 888a5a32c7..8be53f0e27 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -5,6 +5,7 @@ import { useGestureCallbacks } from './useGestureCallbacks'; import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; import { tagMessage } from '../../utils'; import { AnimatedEvent } from '../types'; +import { isAnimatedEvent } from './utils'; type GestureType = | 'TapGestureHandler' @@ -68,6 +69,14 @@ export function useGesture( config.shouldUseReanimated = Reanimated !== undefined && hasWorkletEventHandlers(config); config.needsPointerData = shouldHandleTouchEvents(config); + // TODO: Remove this when we properly type config + config.dispatchesAnimatedEvents = isAnimatedEvent(config.onUpdate as any); + + if (config.dispatchesAnimatedEvents && config.shouldUseReanimated) { + throw new Error( + tagMessage('Cannot use Reanimated and Animated events at the same time.') + ); + } // TODO: Call only necessary hooks depending on which callbacks are defined (?) const { @@ -100,16 +109,6 @@ export function useGesture( throw new Error(tagMessage('Failed to create reanimated event handlers.')); } - config.dispatchesAnimatedEvents = - !!onGestureHandlerAnimatedEvent && - '__isNative' in onGestureHandlerAnimatedEvent; - - if (config.dispatchesAnimatedEvents && config.shouldUseReanimated) { - throw new Error( - tagMessage('Cannot use Reanimated and Animated events at the same time.') - ); - } - useMemo(() => { RNGestureHandlerModule.createGestureHandler(type, tag, {}); RNGestureHandlerModule.flushOperations(); From b3ef022d7c4edbd1960818794f5b683cfad28e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 Aug 2025 11:37:56 +0200 Subject: [PATCH 08/67] Extract handlers in separate function --- .../js/useGestureStateChangeEvent.ts | 11 +---- .../callbacks/js/useGestureTouchEvent.ts | 12 +---- .../callbacks/js/useGestureUpdateEvent.ts | 7 +-- .../useReanimatedStateChangeEvent.ts | 11 +---- .../reanimated/useReanimatedTouchEvent.ts | 12 +---- .../reanimated/useReanimatedUpdateEvent.ts | 6 +-- .../src/v3/hooks/utils.ts | 44 +++++++++++++++++++ 7 files changed, 56 insertions(+), 47 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index 22021b57dd..3a47d6ad31 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,15 +1,8 @@ -import { CallbackHandlers } from '../../../types'; +import { extractStateChangeHandlers } from '../../utils'; import { onGestureHandlerStateChange } from '../onGestureHandlerStateChange'; export function gestureStateChangeEvent(handlerTag: number, config: any) { - const { onBegin, onStart, onEnd, onFinalize } = config; - - const handlers: CallbackHandlers = { - ...(onBegin ? { onBegin } : {}), - ...(onStart ? { onStart } : {}), - ...(onEnd ? { onEnd } : {}), - ...(onFinalize ? { onFinalize } : {}), - }; + const handlers = extractStateChangeHandlers(config); return onGestureHandlerStateChange(handlerTag, handlers); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index 346af43c94..139843c1f5 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,16 +1,8 @@ -import { CallbackHandlers } from '../../../types'; +import { extractTouchHandlers } from '../../utils'; import { onGestureHandlerTouchEvent } from '../onGestureHandlerTouchEvent'; export function gestureTouchEvent(handlerTag: number, config: any) { - const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = - config; - - const handlers: CallbackHandlers = { - ...(onTouchesDown ? { onTouchesDown } : {}), - ...(onTouchesMove ? { onTouchesMove } : {}), - ...(onTouchesUp ? { onTouchesUp } : {}), - ...(onTouchesCancelled ? { onTouchesCancelled } : {}), - }; + const handlers = extractTouchHandlers(config); return onGestureHandlerTouchEvent(handlerTag, handlers); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index d3cfa8621f..fcb9dff8f1 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -1,12 +1,9 @@ -import { isAnimatedEvent } from '../../utils'; +import { extractUpdateHandlers, isAnimatedEvent } from '../../utils'; import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers } from '../../../types'; import { onGestureHandlerEvent } from '../onGestureHandlerEvent'; export function gestureUpdateEvent(handlerTag: number, config: any) { - const { onUpdate, changeEventCalculator } = config; - - const handlers: CallbackHandlers = { ...(onUpdate ? { onUpdate } : {}) }; + const { handlers, changeEventCalculator } = extractUpdateHandlers(config); const jsContext: ReanimatedContext = { lastUpdateEvent: undefined, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts index b6b24b76b8..ce2ee6bacb 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts @@ -1,16 +1,9 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers } from '../../../types'; +import { extractStateChangeHandlers } from '../../utils'; import { onGestureHandlerStateChange } from '../onGestureHandlerStateChange'; export function useReanimatedStateChangeEvent(handlerTag: number, config: any) { - const { onBegin, onStart, onEnd, onFinalize } = config; - - const handlers: CallbackHandlers = { - ...(onBegin ? { onBegin } : {}), - ...(onStart ? { onStart } : {}), - ...(onEnd ? { onEnd } : {}), - ...(onFinalize ? { onFinalize } : {}), - }; + const handlers = extractStateChangeHandlers(config); const callback = onGestureHandlerStateChange(handlerTag, handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts index 31e6a2499c..808e09d6f2 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts @@ -1,17 +1,9 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers } from '../../../types'; +import { extractTouchHandlers } from '../../utils'; import { onGestureHandlerTouchEvent } from '../onGestureHandlerTouchEvent'; export function useReanimatedTouchEvent(handlerTag: number, config: any) { - const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = - config; - - const handlers: CallbackHandlers = { - ...(onTouchesDown ? { onTouchesDown } : {}), - ...(onTouchesMove ? { onTouchesMove } : {}), - ...(onTouchesUp ? { onTouchesUp } : {}), - ...(onTouchesCancelled ? { onTouchesCancelled } : {}), - }; + const handlers = extractTouchHandlers(config); const callback = onGestureHandlerTouchEvent(handlerTag, handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts index 7fa6b565bf..c585026bdd 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts @@ -1,11 +1,9 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers } from '../../../types'; +import { extractUpdateHandlers } from '../../utils'; import { onGestureHandlerEvent } from '../onGestureHandlerEvent'; export function useReanimatedUpdateEvent(handlerTag: number, config: any) { - const { onUpdate, changeEventCalculator } = config; - - const handlers: CallbackHandlers = { ...(onUpdate ? { onUpdate } : {}) }; + const { handlers, changeEventCalculator } = extractUpdateHandlers(config); const reanimatedHandler = Reanimated?.useHandler(handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index 06a6eb7ee9..6d6a8809dd 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -7,6 +7,7 @@ import { GestureHandlerEvent, GestureStateChangeEventWithData, GestureUpdateEventWithData, + UpdateEvent, } from '../types'; import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon'; import { tagMessage } from '../../utils'; @@ -109,3 +110,46 @@ export function checkMappingForChangeProperties(obj: Animated.Mapping) { } } } + +export function extractStateChangeHandlers(config: any): CallbackHandlers { + 'worklet'; + const { onBegin, onStart, onEnd, onFinalize } = config; + + const handlers: CallbackHandlers = { + ...(onBegin ? { onBegin } : {}), + ...(onStart ? { onStart } : {}), + ...(onEnd ? { onEnd } : {}), + ...(onFinalize ? { onFinalize } : {}), + }; + + return handlers; +} + +export function extractUpdateHandlers(config: any): { + handlers: CallbackHandlers; + changeEventCalculator?: ( + current: UpdateEvent>, + previous?: UpdateEvent> + ) => UpdateEvent>; +} { + 'worklet'; + const { onUpdate, changeEventCalculator } = config; + + const handlers: CallbackHandlers = { ...(onUpdate ? { onUpdate } : {}) }; + + return { handlers, changeEventCalculator }; +} + +export function extractTouchHandlers(config: any): CallbackHandlers { + const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = + config; + + const handlers: CallbackHandlers = { + ...(onTouchesDown ? { onTouchesDown } : {}), + ...(onTouchesMove ? { onTouchesMove } : {}), + ...(onTouchesUp ? { onTouchesUp } : {}), + ...(onTouchesCancelled ? { onTouchesCancelled } : {}), + }; + + return handlers; +} From 952fbe1836a8bb0a19d8623c3888be3ba02f03d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 Aug 2025 12:08:07 +0200 Subject: [PATCH 09/67] Pass correct touch event callback --- packages/react-native-gesture-handler/src/v3/NativeDetector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx index e7807e1f3f..2a84d5696b 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx @@ -50,7 +50,7 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { gesture.gestureEvents.onReanimatedUpdateEvent } onGestureHandlerReanimatedTouchEvent={ - gesture.gestureEvents.onGestureHandlerTouchEvent + gesture.gestureEvents.onReanimatedTouchEvent } onGestureHandlerAnimatedEvent={ gesture.gestureEvents.onGestureHandlerAnimatedEvent From fda23cc2a209ea3a717b79c7176b2b107147a3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 25 Aug 2025 15:01:28 +0200 Subject: [PATCH 10/67] Fix crash when onUpdate is Animated.Event --- .../handlers/gestures/reanimatedWrapper.ts | 20 +++++++++++++++++++ .../reanimated/useReanimatedUpdateEvent.ts | 11 ++++++++++ 2 files changed, 31 insertions(+) diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts index dfce442813..483e401ecd 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -18,6 +18,20 @@ export type ReanimatedContext = { lastUpdateEvent: UpdateEvent> | undefined; }; +interface WorkletProps { + __closure: unknown; + __workletHash: number; + __initData?: unknown; + __init?: () => unknown; + __stackDetails?: unknown; + __pluginVersion?: string; +} + +type WorkletFunction< + TArgs extends unknown[] = unknown[], + TReturn = unknown, +> = ((...args: TArgs) => TReturn) & WorkletProps; + let Reanimated: | { default: { @@ -39,6 +53,12 @@ let Reanimated: useSharedValue: (value: T) => SharedValue; setGestureState: (handlerTag: number, newState: number) => void; isSharedValue: (value: unknown) => value is SharedValue; + isWorkletFunction< + Args extends unknown[] = unknown[], + ReturnValue = unknown, + >( + value: unknown + ): value is WorkletFunction; runOnUI( fn: (...args: A) => R ): (...args: Parameters) => void; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts index c585026bdd..9c35f6185f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts @@ -5,6 +5,17 @@ import { onGestureHandlerEvent } from '../onGestureHandlerEvent'; export function useReanimatedUpdateEvent(handlerTag: number, config: any) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); + // We don't want to call hooks conditionally, therefore `useHandler` and `useEvent` will be always called. + // The only difference is whether we will send events to Reanimated or not. + // The problem here is that if someone passes `Animated.event` as `onUpdate` prop, + // it won't be workletized and therefore `useHandler` will throw. In that case we override it to empty `worklet`. + if (!Reanimated?.isWorkletFunction(handlers.onUpdate)) { + handlers.onUpdate = () => { + 'worklet'; + // no-op + }; + } + const reanimatedHandler = Reanimated?.useHandler(handlers); const callback = onGestureHandlerEvent( From e46554be92c11994552e732717b5c5a0857c033c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 25 Aug 2025 15:22:26 +0200 Subject: [PATCH 11/67] Unify lateinit --- .../gesturehandler/react/events/RNGestureHandlerEvent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt index 4adf2bead8..f409c46d4b 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt @@ -18,7 +18,7 @@ class RNGestureHandlerEvent private constructor() : Event private var dataBuilder: GestureHandlerEventDataBuilder<*>? = null private var coalescingKey: Short = 0 private var actionType: Int = GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT - private var eventTarget: EventTarget = EventTarget.JS + private lateinit var eventTarget: EventTarget private fun init( handler: T, From 13f3af4d21e82e24a25ba42761722e61c72cb582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 25 Aug 2025 15:41:30 +0200 Subject: [PATCH 12/67] Rename EventTarget on Android --- .../react/events/EventHandlerType.kt | 7 ++++ .../react/events/EventTarget.kt | 7 ---- .../react/events/RNGestureHandlerEvent.kt | 16 ++++----- .../events/RNGestureHandlerEventDispatcher.kt | 34 ++++++++++++------- .../RNGestureHandlerStateChangeEvent.kt | 12 +++---- .../events/RNGestureHandlerTouchEvent.kt | 19 ++++++----- 6 files changed, 53 insertions(+), 42 deletions(-) create mode 100644 packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventHandlerType.kt delete mode 100644 packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventTarget.kt diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventHandlerType.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventHandlerType.kt new file mode 100644 index 0000000000..53313d19f4 --- /dev/null +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventHandlerType.kt @@ -0,0 +1,7 @@ +package com.swmansion.gesturehandler.react.events + +enum class EventHandlerType { + ForJS, + ForReanimated, + ForAnimated, +} diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventTarget.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventTarget.kt deleted file mode 100644 index ccff2ea77d..0000000000 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/EventTarget.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.swmansion.gesturehandler.react.events - -enum class EventTarget { - JS, - Reanimated, - Animated, -} diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt index f409c46d4b..8f909ea2e2 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt @@ -18,13 +18,13 @@ class RNGestureHandlerEvent private constructor() : Event private var dataBuilder: GestureHandlerEventDataBuilder<*>? = null private var coalescingKey: Short = 0 private var actionType: Int = GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT - private lateinit var eventTarget: EventTarget + private lateinit var eventHandlerType: EventHandlerType private fun init( handler: T, actionType: Int, dataBuilder: GestureHandlerEventDataBuilder, - eventTarget: EventTarget, + eventHandlerType: EventHandlerType, ) { val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) { handler.viewForEvents!! @@ -36,7 +36,7 @@ class RNGestureHandlerEvent private constructor() : Event this.actionType = actionType this.dataBuilder = dataBuilder - this.eventTarget = eventTarget + this.eventHandlerType = eventHandlerType coalescingKey = handler.eventCoalescingKey } @@ -46,14 +46,14 @@ class RNGestureHandlerEvent private constructor() : Event } override fun getEventName() = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) { - if (eventTarget == EventTarget.Animated) { + if (eventHandlerType == EventHandlerType.ForAnimated) { NATIVE_DETECTOR_ANIMATED_EVENT_NAME - } else if (eventTarget == EventTarget.Reanimated) { + } else if (eventHandlerType == EventHandlerType.ForReanimated) { REANIMATED_EVENT_NAME } else { EVENT_NAME } - } else if (eventTarget == EventTarget.Animated) { + } else if (eventHandlerType == EventHandlerType.ForAnimated) { NATIVE_ANIMATED_EVENT_NAME } else { EVENT_NAME @@ -87,9 +87,9 @@ class RNGestureHandlerEvent private constructor() : Event handler: T, actionType: Int, dataBuilder: GestureHandlerEventDataBuilder, - eventTarget: EventTarget, + eventHandlerType: EventHandlerType, ): RNGestureHandlerEvent = (EVENTS_POOL.acquire() ?: RNGestureHandlerEvent()).apply { - init(handler, actionType, dataBuilder, eventTarget) + init(handler, actionType, dataBuilder, eventHandlerType) } fun createEventData(dataBuilder: GestureHandlerEventDataBuilder<*>): WritableMap = Arguments.createMap().apply { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEventDispatcher.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEventDispatcher.kt index 6389b159ef..a3373873a9 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEventDispatcher.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEventDispatcher.kt @@ -44,7 +44,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React handler, handler.actionType, handlerFactory.createEventBuilder(handler), - EventTarget.JS, // For API v2 compatibility + EventHandlerType.ForJS, // For API v2 compatibility ) sendEventForReanimated(event) } @@ -54,7 +54,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React handler, handler.actionType, handlerFactory.createEventBuilder(handler), - EventTarget.Animated, + EventHandlerType.ForAnimated, ) sendEventForNativeAnimatedEvent(event) } @@ -72,19 +72,19 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data) } GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> { - val eventTarget = if (handler.dispatchesAnimatedEvents) { - EventTarget.Animated + val eventHandlerType = if (handler.dispatchesAnimatedEvents) { + EventHandlerType.ForAnimated } else if (handler.dispatchesReanimatedEvents) { - EventTarget.Reanimated + EventHandlerType.ForReanimated } else { - EventTarget.JS + EventHandlerType.ForJS } val event = RNGestureHandlerEvent.obtain( handler, handler.actionType, handlerFactory.createEventBuilder(handler), - eventTarget, + eventHandlerType, ) handler.viewForEvents!!.dispatchEvent(event) @@ -111,7 +111,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React oldState, handler.actionType, handlerFactory.createEventBuilder(handler), - EventTarget.JS, // For API v2 compatibility + EventHandlerType.ForJS, // For API v2 compatibility ) sendEventForReanimated(event) } @@ -137,7 +137,11 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React } GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> { - val eventTarget = if (handler.dispatchesReanimatedEvents) EventTarget.Reanimated else EventTarget.JS + val eventHandlerType = if (handler.dispatchesReanimatedEvents) { + EventHandlerType.ForReanimated + } else { + EventHandlerType.ForJS + } val event = RNGestureHandlerStateChangeEvent.obtain( handler, @@ -145,7 +149,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React oldState, handler.actionType, handlerFactory.createEventBuilder(handler), - eventTarget, + eventHandlerType, ) handler.viewForEvents!!.dispatchEvent(event) @@ -175,7 +179,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React val event = RNGestureHandlerTouchEvent.obtain( handler, handler.actionType, - EventTarget.JS, // For API v2 compatibility + EventHandlerType.ForJS, // For API v2 compatibility ) sendEventForReanimated(event) } @@ -185,8 +189,12 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data) } GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> { - val eventTarget = if (handler.dispatchesReanimatedEvents) EventTarget.Reanimated else EventTarget.JS - val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType, eventTarget) + val eventHandlerType = if (handler.dispatchesReanimatedEvents) { + EventHandlerType.ForReanimated + } else { + EventHandlerType.ForJS + } + val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType, eventHandlerType) handler.viewForEvents!!.dispatchEvent(event) } diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerStateChangeEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerStateChangeEvent.kt index 9b355723dc..aac38aadf4 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerStateChangeEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerStateChangeEvent.kt @@ -19,7 +19,7 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event init( handler: T, @@ -27,7 +27,7 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event, - eventTarget: EventTarget, + eventHandlerType: EventHandlerType, ) { val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) { handler.viewForEvents!! @@ -41,7 +41,7 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event, - eventTarget: EventTarget, + eventHandlerType: EventHandlerType, ): RNGestureHandlerStateChangeEvent = ( EVENTS_POOL.acquire() ?: RNGestureHandlerStateChangeEvent() ).apply { - init(handler, newState, oldState, actionType, dataBuilder, eventTarget) + init(handler, newState, oldState, actionType, dataBuilder, eventHandlerType) } fun createEventData(dataBuilder: GestureHandlerEventDataBuilder<*>, newState: Int, oldState: Int): WritableMap = diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerTouchEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerTouchEvent.kt index 579c05b8df..25b7ee2b11 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerTouchEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerTouchEvent.kt @@ -11,9 +11,9 @@ class RNGestureHandlerTouchEvent private constructor() : Event init(handler: T, actionType: Int, eventTarget: EventTarget) { + private fun init(handler: T, actionType: Int, eventHandlerType: EventHandlerType) { val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) { handler.viewForEvents!! } else { @@ -25,7 +25,7 @@ class RNGestureHandlerTouchEvent private constructor() : Event obtain(handler: T, actionType: Int, eventTarget: EventTarget): RNGestureHandlerTouchEvent = - (EVENTS_POOL.acquire() ?: RNGestureHandlerTouchEvent()).apply { - init(handler, actionType, eventTarget) - } + fun obtain( + handler: T, + actionType: Int, + eventHandlerType: EventHandlerType, + ): RNGestureHandlerTouchEvent = (EVENTS_POOL.acquire() ?: RNGestureHandlerTouchEvent()).apply { + init(handler, actionType, eventHandlerType) + } fun createEventData(handler: T): WritableMap = Arguments.createMap().apply { putInt("handlerTag", handler.tag) From d7a2b1a6bc81c3c1d9ad795db3c03b304c7bc1e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 25 Aug 2025 15:54:40 +0200 Subject: [PATCH 13/67] Rename EventTarget on iOS --- .../apple/RNGHEventTarget.h | 14 ----- .../apple/RNGestureHandler.h | 6 +- .../apple/RNGestureHandler.mm | 59 ++++++++++--------- .../apple/RNGestureHandlerEventHandlerType.h | 14 +++++ .../apple/RNGestureHandlerEvents.h | 6 +- .../apple/RNGestureHandlerEvents.mm | 18 +++--- .../apple/RNGestureHandlerManager.mm | 18 +++--- 7 files changed, 68 insertions(+), 67 deletions(-) delete mode 100644 packages/react-native-gesture-handler/apple/RNGHEventTarget.h create mode 100644 packages/react-native-gesture-handler/apple/RNGestureHandlerEventHandlerType.h diff --git a/packages/react-native-gesture-handler/apple/RNGHEventTarget.h b/packages/react-native-gesture-handler/apple/RNGHEventTarget.h deleted file mode 100644 index 2885adc6e6..0000000000 --- a/packages/react-native-gesture-handler/apple/RNGHEventTarget.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// RNGHEventTarget.h -// Pods -// -// Created by MichaƂ Bert on 21/08/2025. -// - -#import - -typedef NS_ENUM(NSInteger, RNGestureHandlerEventTarget) { - RNGestureHandlerEventTargetJS = 0, - RNGestureHandlerEventTargetReanimated, - RNGestureHandlerEventTargetAnimated -}; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.h b/packages/react-native-gesture-handler/apple/RNGestureHandler.h index 7e540e96ac..226130b6a0 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.h @@ -1,7 +1,7 @@ -#import "RNGHEventTarget.h" #import "RNGHUIKit.h" #import "RNGestureHandlerActionType.h" #import "RNGestureHandlerDirection.h" +#import "RNGestureHandlerEventHandlerType.h" #import "RNGestureHandlerEvents.h" #import "RNGestureHandlerPointerTracker.h" #import "RNGestureHandlerPointerType.h" @@ -41,12 +41,12 @@ - (void)sendEvent:(nonnull RNGestureHandlerStateChange *)event withActionType:(RNGestureHandlerActionType)actionType - forTarget:(RNGestureHandlerEventTarget)eventTarget + forHandlerType:(RNGestureHandlerEventHandlerType)eventHandlerType forView:(nonnull RNGHUIView *)detectorView; - (void)sendNativeTouchEventForGestureHandler:(nonnull RNGestureHandler *)handler withPointerType:(NSInteger)pointerType - forTarget:(RNGestureHandlerEventTarget)eventTarget; + forHandlerType:(RNGestureHandlerEventHandlerType)eventHandlerType; @end diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index 8491977f6c..58d393f55c 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -358,15 +358,15 @@ - (void)sendEventsInState:(RNGestureHandlerState)state } if (state == RNGestureHandlerStateActive) { - id touchEvent = - [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag - handlerTag:_tag - state:state - extraData:extraData - forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated - : RNGestureHandlerEventTargetJS - coalescingKey:self->_eventCoalescingKey]; + id touchEvent = [[RNGestureHandlerEvent alloc] + initWithReactTag:reactTag + handlerTag:_tag + state:state + extraData:extraData + forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated + : RNGestureHandlerEventHandlerTypeJS + coalescingKey:self->_eventCoalescingKey]; [self sendEvent:touchEvent]; } } @@ -383,41 +383,42 @@ - (void)sendEvent:(RNGestureHandlerStateChange *)event { [self.emitter sendEvent:event withActionType:self.actionType - forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated - : RNGestureHandlerEventTargetJS + forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated + : RNGestureHandlerEventHandlerTypeJS forView:[self findViewForEvents]]; } - (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(NSNumber *)reactTag { if (_actionType == RNGestureHandlerActionTypeNativeDetector) { - [self.emitter sendNativeTouchEventForGestureHandler:self - withPointerType:_pointerType - forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated - : RNGestureHandlerEventTargetJS]; + [self.emitter + sendNativeTouchEventForGestureHandler:self + withPointerType:_pointerType + forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated + : RNGestureHandlerEventHandlerTypeJS]; } else { id extraData = [RNGestureHandlerEventExtraData forEventType:_pointerTracker.eventType withChangedPointers:_pointerTracker.changedPointersData withAllPointers:_pointerTracker.allPointersData withNumberOfTouches:_pointerTracker.trackedPointersCount withPointerType:_pointerType]; - id event = - [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag - handlerTag:_tag - state:state - extraData:extraData - forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated - : RNGestureHandlerEventTargetJS - coalescingKey:[_tag intValue]]; + id event = [[RNGestureHandlerEvent alloc] + initWithReactTag:reactTag + handlerTag:_tag + state:state + extraData:extraData + forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated + : RNGestureHandlerEventHandlerTypeJS + coalescingKey:[_tag intValue]]; [self.emitter sendEvent:event withActionType:self.actionType - forTarget:_dispatchesAnimatedEvents ? RNGestureHandlerEventTargetAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventTargetReanimated - : RNGestureHandlerEventTargetJS + forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated + : RNGestureHandlerEventHandlerTypeJS forView:self.recognizer.view]; } } diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerEventHandlerType.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerEventHandlerType.h new file mode 100644 index 0000000000..10f37f2062 --- /dev/null +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerEventHandlerType.h @@ -0,0 +1,14 @@ +// +// RNGHEventTarget.h +// Pods +// +// Created by MichaƂ Bert on 21/08/2025. +// + +#import + +typedef NS_ENUM(NSInteger, RNGestureHandlerEventHandlerType) { + RNGestureHandlerEventHandlerTypeJS = 0, + RNGestureHandlerEventHandlerTypeReanimated, + RNGestureHandlerEventHandlerTypeAnimated +}; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h index 6e1c9e714e..84c2f6c0c0 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h @@ -2,11 +2,11 @@ #import -#import "RNGHEventTarget.h" #import "RNGHStylusData.h" #import "RNGHTouchEventType.h" #import "RNGHUIKit.h" #import "RNGestureHandlerActionType.h" +#import "RNGestureHandlerEventHandlerType.h" #import "RNGestureHandlerState.h" @interface RNGestureHandlerEventExtraData : NSObject @@ -63,13 +63,13 @@ @property (nonatomic, strong, readonly) NSNumber *handlerTag; @property (nonatomic, strong, readonly) RNGestureHandlerEventExtraData *extraData; @property (nonatomic, readonly) RNGestureHandlerState state; -@property (nonatomic, readonly) RNGestureHandlerEventTarget eventTarget; +@property (nonatomic, readonly) RNGestureHandlerEventHandlerType eventHandlerType; - (instancetype)initWithReactTag:(NSNumber *)reactTag handlerTag:(NSNumber *)handlerTag state:(RNGestureHandlerState)state extraData:(RNGestureHandlerEventExtraData *)extraData - forTarget:(RNGestureHandlerEventTarget)eventTarget + forHandlerType:(RNGestureHandlerEventHandlerType)eventHandlerType coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER; @end diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm index b194151e85..8a9be909a4 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm @@ -173,7 +173,7 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag handlerTag:(NSNumber *)handlerTag state:(RNGestureHandlerState)state extraData:(RNGestureHandlerEventExtraData *)extraData - forTarget:(RNGestureHandlerEventTarget)eventTarget + forHandlerType:(RNGestureHandlerEventHandlerType)eventHandlerType coalescingKey:(uint16_t)coalescingKey { if ((self = [super init])) { @@ -182,7 +182,7 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag _state = state; _extraData = extraData; _coalescingKey = coalescingKey; - _eventTarget = eventTarget; + _eventHandlerType = eventHandlerType; } return self; } @@ -191,12 +191,12 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag - (NSString *)eventName { - switch (_eventTarget) { - case RNGestureHandlerEventTargetJS: + switch (_eventHandlerType) { + case RNGestureHandlerEventHandlerTypeJS: return @"onGestureHandlerEvent"; - case RNGestureHandlerEventTargetReanimated: + case RNGestureHandlerEventHandlerTypeReanimated: return @"onGestureHandlerReanimatedEvent"; - case RNGestureHandlerEventTargetAnimated: + case RNGestureHandlerEventHandlerTypeAnimated: return @"onGestureHandlerAnimatedEvent"; } } @@ -218,7 +218,7 @@ + (NSString *)moduleDotMethod - (NSArray *)arguments { - if (_eventTarget == RNGestureHandlerEventTargetAnimated) { + if (_eventHandlerType == RNGestureHandlerEventHandlerTypeAnimated) { NSMutableDictionary *body = [[NSMutableDictionary alloc] init]; [body setObject:_viewTag forKey:@"target"]; [body setObject:_handlerTag forKey:@"handlerTag"]; @@ -232,8 +232,8 @@ - (NSArray *)arguments [body setObject:@(_state) forKey:@"state"]; return @[ self.viewTag, - _eventTarget == RNGestureHandlerEventTargetReanimated ? @"onGestureHandlerReanimatedEvent" - : @"onGestureHandlerEvent", + _eventHandlerType == RNGestureHandlerEventHandlerTypeReanimated ? @"onGestureHandlerReanimatedEvent" + : @"onGestureHandlerEvent", body ]; } diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm index 38ef0db35d..363d0bb3cc 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm @@ -9,9 +9,9 @@ #import #import -#import "RNGHEventTarget.h" #import "RNGestureHandler.h" #import "RNGestureHandlerActionType.h" +#import "RNGestureHandlerEventHandlerType.h" #import "RNGestureHandlerNativeEventUtils.h" #import "RNGestureHandlerState.h" #import "RNRootViewGestureRecognizer.h" @@ -298,24 +298,24 @@ - (void)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer - (void)sendEvent:(RNGestureHandlerStateChange *)event withActionType:(RNGestureHandlerActionType)actionType - forTarget:(RNGestureHandlerEventTarget)eventTarget + forHandlerType:(RNGestureHandlerEventHandlerType)eventHandlerType forView:(RNGHUIView *)detectorView // Typing as RNGestureHandlerDetector is preferable // but results in a compilation error. { switch (actionType) { case RNGestureHandlerActionTypeNativeDetector: { if ([event isKindOfClass:[RNGestureHandlerEvent class]]) { - switch (eventTarget) { - case RNGestureHandlerEventTargetAnimated: + switch (eventHandlerType) { + case RNGestureHandlerEventHandlerTypeAnimated: [self sendEventForNativeAnimatedEvent:event]; break; - case RNGestureHandlerEventTargetReanimated: { + case RNGestureHandlerEventHandlerTypeReanimated: { RNGestureHandlerEvent *gestureEvent = (RNGestureHandlerEvent *)event; auto nativeEvent = [gestureEvent getReanimatedNativeEvent]; [(RNGestureHandlerDetector *)detectorView dispatchReanimatedGestureEvent:nativeEvent]; break; } - case RNGestureHandlerEventTargetJS: { + case RNGestureHandlerEventHandlerTypeJS: { RNGestureHandlerEvent *gestureEvent = (RNGestureHandlerEvent *)event; auto nativeEvent = [gestureEvent getNativeEvent]; [(RNGestureHandlerDetector *)detectorView dispatchGestureEvent:nativeEvent]; @@ -323,7 +323,7 @@ - (void)sendEvent:(RNGestureHandlerStateChange *)event } } } else { - if (eventTarget == RNGestureHandlerEventTargetReanimated) { + if (eventHandlerType == RNGestureHandlerEventHandlerTypeReanimated) { auto nativeEvent = [event getReanimatedNativeEvent]; [(RNGestureHandlerDetector *)detectorView dispatchReanimatedStateChangeEvent:nativeEvent]; } else { @@ -361,12 +361,12 @@ - (void)sendEvent:(RNGestureHandlerStateChange *)event - (void)sendNativeTouchEventForGestureHandler:(RNGestureHandler *)handler withPointerType:(NSInteger)pointerType - forTarget:(RNGestureHandlerEventTarget)eventTarget + forHandlerType:(RNGestureHandlerEventHandlerType)eventHandlerType { RNGestureHandlerDetector *detector = (RNGestureHandlerDetector *)[handler findViewForEvents]; // We have to double the logic since event types come from codegen. - if (eventTarget == RNGestureHandlerEventTargetReanimated) { + if (eventHandlerType == RNGestureHandlerEventHandlerTypeReanimated) { facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerReanimatedTouchEvent nativeEvent = { .handlerTag = [handler.tag intValue], .state = static_cast(handler.state), From 1b04d3c40b67b6d02964d115d17785a3dd1944d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 25 Aug 2025 16:02:57 +0200 Subject: [PATCH 14/67] Add reset for animated and reanimated events flags --- .../java/com/swmansion/gesturehandler/core/GestureHandler.kt | 4 ++++ .../react-native-gesture-handler/apple/RNGestureHandler.mm | 2 ++ 2 files changed, 6 insertions(+) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt index 6128853ef9..1c4fb2a590 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt @@ -132,6 +132,8 @@ open class GestureHandler { isEnabled = DEFAULT_IS_ENABLED hitSlop = DEFAULT_HIT_SLOP mouseButton = DEFAULT_MOUSE_BUTTON + dispatchesAnimatedEvents = DEFAULT_DISPATCHES_ANIMATED_EVENTS + dispatchesReanimatedEvents = DEFAULT_DISPATCHES_REANIMATED_EVENTS } fun hasCommonPointers(other: GestureHandler): Boolean { @@ -978,6 +980,8 @@ open class GestureHandler { private const val DEFAULT_IS_ENABLED = true private val DEFAULT_HIT_SLOP = null private const val DEFAULT_MOUSE_BUTTON = 0 + private const val DEFAULT_DISPATCHES_ANIMATED_EVENTS = false + private const val DEFAULT_DISPATCHES_REANIMATED_EVENTS = false const val STATE_UNDETERMINED = 0 const val STATE_FAILED = 1 diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index 58d393f55c..ace5c41b3a 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -108,6 +108,8 @@ - (void)resetConfig _handlersThatShouldWait = nil; _hitSlop = RNGHHitSlopEmpty; _needsPointerData = NO; + _dispatchesAnimatedEvents = NO; + _dispatchesReanimatedEvents = NO; #if !TARGET_OS_OSX _recognizer.cancelsTouchesInView = YES; #endif From b8df0159fb78c54550d64ea0ede9f8f276f560e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 25 Aug 2025 16:32:10 +0200 Subject: [PATCH 15/67] Rename getter for event handlers --- ...andlerEvent.ts => GestureHandlerEventWorkletHandler.ts} | 2 +- ...hange.ts => GestureHandlerStateChangeWorkletHandler.ts} | 2 +- ...hEvent.ts => GestureHandlerTouchEventWorkletHandler.ts} | 2 +- .../v3/hooks/callbacks/js/useGestureStateChangeEvent.ts | 4 ++-- .../src/v3/hooks/callbacks/js/useGestureTouchEvent.ts | 4 ++-- .../src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts | 4 ++-- .../callbacks/reanimated/useReanimatedStateChangeEvent.ts | 7 +++++-- .../hooks/callbacks/reanimated/useReanimatedTouchEvent.ts | 7 +++++-- .../hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts | 4 ++-- 9 files changed, 21 insertions(+), 15 deletions(-) rename packages/react-native-gesture-handler/src/v3/hooks/callbacks/{onGestureHandlerEvent.ts => GestureHandlerEventWorkletHandler.ts} (95%) rename packages/react-native-gesture-handler/src/v3/hooks/callbacks/{onGestureHandlerStateChange.ts => GestureHandlerStateChangeWorkletHandler.ts} (96%) rename packages/react-native-gesture-handler/src/v3/hooks/callbacks/{onGestureHandlerTouchEvent.ts => GestureHandlerTouchEventWorkletHandler.ts} (95%) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts similarity index 95% rename from packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerEvent.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts index d7af9a810a..50e0538b61 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts @@ -4,7 +4,7 @@ import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper' import { CallbackHandlers, UpdateEvent } from '../../types'; import { isEventForHandlerWithTag, runCallback } from '../utils'; -export function onGestureHandlerEvent( +export function getGestureHandlerEventWorkletHandler( handlerTag: number, callbacks: CallbackHandlers, context: ReanimatedContext | undefined, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerStateChange.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts similarity index 96% rename from packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerStateChange.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts index 8a1314bd8b..0238317b3c 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerStateChange.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts @@ -3,7 +3,7 @@ import { State } from '../../../State'; import { CallbackHandlers, StateChangeEvent } from '../../types'; import { isEventForHandlerWithTag, isNativeEvent, runCallback } from '../utils'; -export function onGestureHandlerStateChange( +export function getGestureHandlerStateChangeWorkletHandler( handlerTag: number, callbacks: CallbackHandlers ) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts similarity index 95% rename from packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerTouchEvent.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts index 36ab6e7a1e..4d29125912 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/onGestureHandlerTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts @@ -9,7 +9,7 @@ import { import { TouchEventType } from '../../../TouchEventType'; import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; -export function onGestureHandlerTouchEvent( +export function getGestureHandlerTouchEventWorkletHandler( handlerTag: number, callbacks: CallbackHandlers ) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index 3a47d6ad31..499daae715 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,8 +1,8 @@ import { extractStateChangeHandlers } from '../../utils'; -import { onGestureHandlerStateChange } from '../onGestureHandlerStateChange'; +import { getGestureHandlerStateChangeWorkletHandler } from '../GestureHandlerStateChangeWorkletHandler'; export function gestureStateChangeEvent(handlerTag: number, config: any) { const handlers = extractStateChangeHandlers(config); - return onGestureHandlerStateChange(handlerTag, handlers); + return getGestureHandlerStateChangeWorkletHandler(handlerTag, handlers); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index 139843c1f5..d07ada4ff4 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,8 +1,8 @@ import { extractTouchHandlers } from '../../utils'; -import { onGestureHandlerTouchEvent } from '../onGestureHandlerTouchEvent'; +import { getGestureHandlerTouchEventWorkletHandler } from '../GestureHandlerTouchEventWorkletHandler'; export function gestureTouchEvent(handlerTag: number, config: any) { const handlers = extractTouchHandlers(config); - return onGestureHandlerTouchEvent(handlerTag, handlers); + return getGestureHandlerTouchEventWorkletHandler(handlerTag, handlers); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index fcb9dff8f1..ae7ea795a4 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -1,6 +1,6 @@ import { extractUpdateHandlers, isAnimatedEvent } from '../../utils'; import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; -import { onGestureHandlerEvent } from '../onGestureHandlerEvent'; +import { getGestureHandlerEventWorkletHandler } from '../GestureHandlerEventWorkletHandler'; export function gestureUpdateEvent(handlerTag: number, config: any) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); @@ -11,7 +11,7 @@ export function gestureUpdateEvent(handlerTag: number, config: any) { return isAnimatedEvent(config.onUpdate) ? undefined - : onGestureHandlerEvent( + : getGestureHandlerEventWorkletHandler( handlerTag, handlers, jsContext, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts index ce2ee6bacb..62eb38f0d2 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts @@ -1,11 +1,14 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; import { extractStateChangeHandlers } from '../../utils'; -import { onGestureHandlerStateChange } from '../onGestureHandlerStateChange'; +import { getGestureHandlerStateChangeWorkletHandler } from '../GestureHandlerStateChangeWorkletHandler'; export function useReanimatedStateChangeEvent(handlerTag: number, config: any) { const handlers = extractStateChangeHandlers(config); - const callback = onGestureHandlerStateChange(handlerTag, handlers); + const callback = getGestureHandlerStateChangeWorkletHandler( + handlerTag, + handlers + ); const reanimatedHandler = Reanimated?.useHandler(handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts index 808e09d6f2..906f897df8 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts @@ -1,11 +1,14 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; import { extractTouchHandlers } from '../../utils'; -import { onGestureHandlerTouchEvent } from '../onGestureHandlerTouchEvent'; +import { getGestureHandlerTouchEventWorkletHandler } from '../GestureHandlerTouchEventWorkletHandler'; export function useReanimatedTouchEvent(handlerTag: number, config: any) { const handlers = extractTouchHandlers(config); - const callback = onGestureHandlerTouchEvent(handlerTag, handlers); + const callback = getGestureHandlerTouchEventWorkletHandler( + handlerTag, + handlers + ); const reanimatedHandler = Reanimated?.useHandler(handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts index 9c35f6185f..8546764ebd 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts @@ -1,6 +1,6 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; import { extractUpdateHandlers } from '../../utils'; -import { onGestureHandlerEvent } from '../onGestureHandlerEvent'; +import { getGestureHandlerEventWorkletHandler } from '../GestureHandlerEventWorkletHandler'; export function useReanimatedUpdateEvent(handlerTag: number, config: any) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); @@ -18,7 +18,7 @@ export function useReanimatedUpdateEvent(handlerTag: number, config: any) { const reanimatedHandler = Reanimated?.useHandler(handlers); - const callback = onGestureHandlerEvent( + const callback = getGestureHandlerEventWorkletHandler( handlerTag, handlers, reanimatedHandler?.context, From 93343dacead28a6fa6b8cb9af28ab4a003ed65dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 Aug 2025 08:19:20 +0200 Subject: [PATCH 16/67] Correctly handle disableReanimated --- .../src/v3/hooks/useGesture.ts | 4 +++- .../src/v3/hooks/useGestureCallbacks.ts | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 38d97860f0..897472600a 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -123,7 +123,9 @@ export function useGesture( // This has to be done ASAP as other hooks depend `shouldUseReanimated`. config.shouldUseReanimated = - Reanimated !== undefined && hasWorkletEventHandlers(config); + !config.disableReanimated && + Reanimated !== undefined && + hasWorkletEventHandlers(config); config.needsPointerData = shouldHandleTouchEvents(config); // TODO: Remove this when we properly type config config.dispatchesAnimatedEvents = isAnimatedEvent(config.onUpdate as any); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts index 79150b397a..5d63aa81a3 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts @@ -15,12 +15,18 @@ export function useGestureCallbacks(handlerTag: number, config: any) { const onGestureHandlerEvent = gestureUpdateEvent(handlerTag, config); const onGestureHandlerTouchEvent = gestureTouchEvent(handlerTag, config); - const onReanimatedStateChange = useReanimatedStateChangeEvent( - handlerTag, - config - ); - const onReanimatedUpdateEvent = useReanimatedUpdateEvent(handlerTag, config); - const onReanimatedTouchEvent = useReanimatedTouchEvent(handlerTag, config); + let onReanimatedStateChange; + let onReanimatedUpdateEvent; + let onReanimatedTouchEvent; + + if (!config.disableReanimated) { + // eslint-disable-next-line react-hooks/rules-of-hooks + onReanimatedStateChange = useReanimatedStateChangeEvent(handlerTag, config); + // eslint-disable-next-line react-hooks/rules-of-hooks + onReanimatedUpdateEvent = useReanimatedUpdateEvent(handlerTag, config); + // eslint-disable-next-line react-hooks/rules-of-hooks + onReanimatedTouchEvent = useReanimatedTouchEvent(handlerTag, config); + } let onGestureHandlerAnimatedEvent: AnimatedEvent | undefined; if (isAnimatedEvent(config.onUpdate)) { From d1d3efb1df49b29750b09d5cb034a4d6698e0826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 Aug 2025 09:39:10 +0200 Subject: [PATCH 17/67] Unpack nativeEvent --- .../src/v3/hooks/utils.ts | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index bbf8a0972c..cbfce7a903 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -53,6 +53,16 @@ export function touchEventTypeToCallbackType( } return CALLBACK_TYPE.UNDEFINED; } +export function isNativeEvent( + event: GestureHandlerEvent +): event is + | NativeSyntheticEvent> + | NativeSyntheticEvent> + | NativeSyntheticEvent { + 'worklet'; + + return 'nativeEvent' in event; +} export function runCallback( type: CALLBACK_TYPE, @@ -65,18 +75,7 @@ export function runCallback( // TODO: add proper types (likely boolean) // @ts-ignore It works, duh - handler?.(event, ...args); -} - -export function isNativeEvent( - event: GestureHandlerEvent -): event is - | NativeSyntheticEvent> - | NativeSyntheticEvent> - | NativeSyntheticEvent { - 'worklet'; - - return 'nativeEvent' in event; + handler?.(isNativeEvent(event) ? event.nativeEvent : event, ...args); } export function isEventForHandlerWithTag( From 03a1e64230bf2027023127252cfd089099dcefe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 Aug 2025 09:48:55 +0200 Subject: [PATCH 18/67] Bring back hooks --- .../hooks/callbacks/js/useGestureStateChangeEvent.ts | 3 ++- .../v3/hooks/callbacks/js/useGestureTouchEvent.ts | 3 ++- .../v3/hooks/callbacks/js/useGestureUpdateEvent.ts | 3 ++- .../src/v3/hooks/useGestureCallbacks.ts | 12 ++++++------ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index 499daae715..b27f04e244 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,7 +1,8 @@ import { extractStateChangeHandlers } from '../../utils'; import { getGestureHandlerStateChangeWorkletHandler } from '../GestureHandlerStateChangeWorkletHandler'; -export function gestureStateChangeEvent(handlerTag: number, config: any) { +// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks +export function useGestureStateChangeEvent(handlerTag: number, config: any) { const handlers = extractStateChangeHandlers(config); return getGestureHandlerStateChangeWorkletHandler(handlerTag, handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index d07ada4ff4..9a2b921243 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,7 +1,8 @@ import { extractTouchHandlers } from '../../utils'; import { getGestureHandlerTouchEventWorkletHandler } from '../GestureHandlerTouchEventWorkletHandler'; -export function gestureTouchEvent(handlerTag: number, config: any) { +// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks +export function useGestureTouchEvent(handlerTag: number, config: any) { const handlers = extractTouchHandlers(config); return getGestureHandlerTouchEventWorkletHandler(handlerTag, handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index ae7ea795a4..ab1c3b65cb 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -2,7 +2,8 @@ import { extractUpdateHandlers, isAnimatedEvent } from '../../utils'; import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; import { getGestureHandlerEventWorkletHandler } from '../GestureHandlerEventWorkletHandler'; -export function gestureUpdateEvent(handlerTag: number, config: any) { +// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks +export function useGestureUpdateEvent(handlerTag: number, config: any) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); const jsContext: ReanimatedContext = { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts index 5d63aa81a3..5f477a196b 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts @@ -1,6 +1,6 @@ -import { gestureStateChangeEvent } from './callbacks/js/useGestureStateChangeEvent'; -import { gestureUpdateEvent } from './callbacks/js/useGestureUpdateEvent'; -import { gestureTouchEvent } from './callbacks/js/useGestureTouchEvent'; +import { useGestureStateChangeEvent } from './callbacks/js/useGestureStateChangeEvent'; +import { useGestureUpdateEvent } from './callbacks/js/useGestureUpdateEvent'; +import { useGestureTouchEvent } from './callbacks/js/useGestureTouchEvent'; import { AnimatedEvent } from '../types'; import { checkMappingForChangeProperties, isAnimatedEvent } from './utils'; import { useReanimatedStateChangeEvent } from './callbacks/reanimated/useReanimatedStateChangeEvent'; @@ -8,12 +8,12 @@ import { useReanimatedUpdateEvent } from './callbacks/reanimated/useReanimatedUp import { useReanimatedTouchEvent } from './callbacks/reanimated/useReanimatedTouchEvent'; export function useGestureCallbacks(handlerTag: number, config: any) { - const onGestureHandlerStateChange = gestureStateChangeEvent( + const onGestureHandlerStateChange = useGestureStateChangeEvent( handlerTag, config ); - const onGestureHandlerEvent = gestureUpdateEvent(handlerTag, config); - const onGestureHandlerTouchEvent = gestureTouchEvent(handlerTag, config); + const onGestureHandlerEvent = useGestureUpdateEvent(handlerTag, config); + const onGestureHandlerTouchEvent = useGestureTouchEvent(handlerTag, config); let onReanimatedStateChange; let onReanimatedUpdateEvent; From cc1313a5e1bab33e74a27632442c77326157bd53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 Aug 2025 10:03:54 +0200 Subject: [PATCH 19/67] Use old name --- .../apple/RNGestureHandlerEvents.mm | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm index 8a9be909a4..f5a3d6a14f 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm @@ -230,12 +230,7 @@ - (NSArray *)arguments [body setObject:_viewTag forKey:@"target"]; [body setObject:_handlerTag forKey:@"handlerTag"]; [body setObject:@(_state) forKey:@"state"]; - return @[ - self.viewTag, - _eventHandlerType == RNGestureHandlerEventHandlerTypeReanimated ? @"onGestureHandlerReanimatedEvent" - : @"onGestureHandlerEvent", - body - ]; + return @[ self.viewTag, @"onGestureHandlerEvent", body ]; } } From dac39cf5ed0511a447a417a90681032116cd3de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 7 Aug 2025 16:15:45 +0200 Subject: [PATCH 20/67] Disable coalescing on Android --- .../gesturehandler/react/events/RNGestureHandlerEvent.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt index 8f909ea2e2..2e16a0a1b8 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerEvent.kt @@ -59,7 +59,8 @@ class RNGestureHandlerEvent private constructor() : Event EVENT_NAME } - override fun canCoalesce() = true + // Unfortunately getCoalescingKey is not considered when sending event to C++, therefore we have to disable coalescing in v3 + override fun canCoalesce() = actionType != GestureHandler.ACTION_TYPE_NATIVE_DETECTOR override fun getCoalescingKey() = coalescingKey From 51b5a862a8a6c1b0c476bec76c4fa480a02e8511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 13 Aug 2025 19:00:41 +0200 Subject: [PATCH 21/67] Implement DFS for gesture relations --- .../src/v3/NativeDetector.tsx | 120 +++++++++++++++++- .../v3/hooks/relations/useComposedGesture.ts | 91 +++++++++++++ .../src/v3/hooks/relations/useExclusive.ts | 10 ++ .../src/v3/hooks/relations/useRace.ts | 10 ++ .../src/v3/hooks/relations/useSimultaneous.ts | 12 ++ .../src/v3/hooks/useGesture.ts | 34 +---- .../src/v3/hooks/utils.ts | 8 ++ .../src/v3/types.ts | 60 +++++++++ 8 files changed, 312 insertions(+), 33 deletions(-) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx index 2a84d5696b..0eb4d222aa 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx @@ -1,14 +1,15 @@ import React from 'react'; -import { NativeGesture } from './hooks/useGesture'; +import { NativeGesture, ComposedGesture } from './types'; import { Reanimated } from '../handlers/gestures/reanimatedWrapper'; import { Animated, StyleSheet } from 'react-native'; import HostGestureDetector from './HostGestureDetector'; import { tagMessage } from '../utils'; +import { isComposedGesture } from './hooks/utils'; export interface NativeDetectorProps { children?: React.ReactNode; - gesture: NativeGesture; + gesture: NativeGesture | ComposedGesture; } const AnimatedNativeDetector = @@ -34,21 +35,134 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { ); } + // This piece of magic traverses the gesture tree and populates `waitFor` and `simultaneousHandlers` + // arrays for each gesture. It traverses the tree recursively using DFS. + // `waitFor` and `simultaneousHandlers` are global data structures that will be populated into each gesture. + // For `waitFor` we need array as order of the gestures matters. + // For `simultaneousHandlers` we use Set as the order doesn't matter. + // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. + const dfs = ( + node: NativeGesture | ComposedGesture, + waitFor: number[] = [], + simultaneousHandlers: Set = new Set() + ) => { + // If we are in the leaf node, we want to fill gesture relations arrays with current + // waitFor and simultaneousHandlers. + // TODO: handle `simultaneousWithExternalGesture`, `requreExternalGestureToFail`, `blocksExternalGesture` + if (!isComposedGesture(node)) { + node.simultaneousHandlers.push(...simultaneousHandlers); + node.waitFor.push(...waitFor); + + return; + } + + // If we are in the composed gesture, we want to traverse its children. + node.gestures.forEach((child) => { + // If child is composed gesture, we have to correctly fill `waitFor` and `simultaneousHandlers`. + if (isComposedGesture(child)) { + // We have to update `simultaneousHandlers` before traversing the child. + + // If we go from a non-simultaneous gesture to a simultaneous gesture, + // we add the tags of the simultaneous gesture to the `simultaneousHandlers`. + // This way when we traverse the child, we already have the tags of the simultaneous gestures + if ( + node.name !== 'SimultaneousGesture' && + child.name === 'SimultaneousGesture' + ) { + child.tags.forEach((tag) => simultaneousHandlers.add(tag)); + } + + // If we go from a simultaneous gesture to a non-simultaneous gesture, + // we remove the tags of the child gestures from the `simultaneousHandlers`, + // as those are not simultaneous with each other. + if ( + node.name === 'SimultaneousGesture' && + child.name !== 'SimultaneousGesture' + ) { + child.tags.forEach((tag) => simultaneousHandlers.delete(tag)); + } + + // We will keep the current length of `waitFor` to reset it to previous state + // after traversing the child. + const length = waitFor.length; + + // We traverse the child, passing the current `waitFor` and `simultaneousHandlers`. + dfs(child, waitFor, simultaneousHandlers); + + // After traversing the child, we need to update `waitFor` and `simultaneousHandlers` + + // If we go back from a simultaneous gesture to a non-simultaneous gesture, + // we want to delete the tags of the simultaneous gesture from the `simultaneousHandlers` - + // those gestures are not simultaneous with each other anymore. + if ( + child.name === 'SimultaneousGesture' && + node.name !== 'SimultaneousGesture' + ) { + node.tags.forEach((tag) => simultaneousHandlers.delete(tag)); + } + + // If we go back from a non-simultaneous gesture to a simultaneous gesture, + // we want to add the tags of the simultaneous gesture to the `simultaneousHandlers`, + // as those gestures are simultaneous with other children of the current node. + if ( + child.name !== 'SimultaneousGesture' && + node.name === 'SimultaneousGesture' + ) { + node.tags.forEach((tag) => simultaneousHandlers.add(tag)); + } + + // If we go back to an exclusive gesture, we want to add the tags of the child gesture to the `waitFor` array. + // This will allow us to pass exclusive gesture tags to the right subtree of the current node. + if (node.name === 'ExclusiveGesture') { + child.tags.forEach((tag) => waitFor.push(tag)); + } + + // If we go back from an exclusive gesture to a non-exclusive gesture, we want to reset the `waitFor` array + // to the previous state, siblings of the exclusive gesture are not exclusive with it. Since we use `push` method to + // add tags to the `waitFor` array, we can override `length` property to reset it to the previous state. + if ( + child.name === 'ExclusiveGesture' && + node.name !== 'ExclusiveGesture' + ) { + waitFor.length = length; + } + } + // This means that child is a leaf node. + else { + // In the leaf node, we only care about filling `waitFor` array. First we traverse the child... + dfs(child, waitFor, simultaneousHandlers); + + // ..and when we go back we add the tag of the child to the `waitFor` array. + if (node.name === 'ExclusiveGesture') { + waitFor.push(child.tag); + } + } + }); + }; + + dfs(gesture); + return ( {children} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts new file mode 100644 index 0000000000..8feb86a458 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -0,0 +1,91 @@ +import { + NativeGesture, + StateChangeEvent, + UpdateEvent, + TouchEvent, + ComposedGesture, +} from '../../types'; +import { isComposedGesture } from '../utils'; +import { tagMessage } from '../../../utils'; + +// TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) +// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks, @eslint-react/hooks-extra/no-unnecessary-use-prefix +export function useComposedGesture( + ...gestures: (NativeGesture | ComposedGesture)[] +): ComposedGesture { + const tags = gestures.flatMap((gesture) => + isComposedGesture(gesture) ? gesture.tags : gesture.tag + ); + + const config = { + shouldUseReanimated: gestures.some( + (gesture) => gesture.config.shouldUseReanimated + ), + dispatchesAnimatedEvents: gestures.some( + (gesture) => gesture.config.dispatchesAnimatedEvents + ), + }; + + if (config.shouldUseReanimated && config.dispatchesAnimatedEvents) { + throw new Error( + tagMessage( + 'Composed gestures cannot use both Reanimated and Animated events at the same time.' + ) + ); + } + + const onGestureHandlerStateChange = ( + event: StateChangeEvent> + ) => { + for (const gesture of gestures) { + if (gesture.gestureEvents.onGestureHandlerStateChange) { + gesture.gestureEvents.onGestureHandlerStateChange(event); + } + } + }; + + const onGestureHandlerEvent = ( + event: UpdateEvent> + ) => { + for (const gesture of gestures) { + if (gesture.gestureEvents.onGestureHandlerEvent) { + gesture.gestureEvents.onGestureHandlerEvent(event); + } + } + }; + + const onGestureHandlerTouchEvent = (event: TouchEvent) => { + for (const gesture of gestures) { + if (gesture.gestureEvents.onGestureHandlerTouchEvent) { + gesture.gestureEvents.onGestureHandlerTouchEvent(event); + } + } + }; + + let onGestureHandlerAnimatedEvent; + + for (const gesture of gestures) { + if (gesture.gestureEvents.onGestureHandlerAnimatedEvent) { + onGestureHandlerAnimatedEvent = + gesture.gestureEvents.onGestureHandlerAnimatedEvent; + + break; + } + } + + return { + tags, + name: 'ComposedGesture', + config, + gestureEvents: { + onGestureHandlerStateChange, + onGestureHandlerEvent, + onGestureHandlerTouchEvent, + onReanimatedStateChange: undefined, + onReanimatedUpdateEvent: undefined, + onReanimatedTouchEvent: undefined, + onGestureHandlerAnimatedEvent, + }, + gestures, + }; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts new file mode 100644 index 0000000000..38b8f72495 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts @@ -0,0 +1,10 @@ +import { NativeGesture, ComposedGesture } from '../../types'; +import { useComposedGesture } from './useComposedGesture'; + +export function useExclusive(...gestures: (NativeGesture | ComposedGesture)[]) { + const composedGesture = useComposedGesture(...gestures); + + composedGesture.name = 'ExclusiveGesture'; + + return composedGesture; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts new file mode 100644 index 0000000000..ae59b5a48e --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts @@ -0,0 +1,10 @@ +import { ComposedGesture, NativeGesture } from '../../types'; +import { useComposedGesture } from './useComposedGesture'; + +export function useRace(...gestures: (NativeGesture | ComposedGesture)[]) { + const composedGesture = useComposedGesture(...gestures); + + composedGesture.name = 'RaceGesture'; + + return composedGesture; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts new file mode 100644 index 0000000000..2975dc6fd7 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts @@ -0,0 +1,12 @@ +import { ComposedGesture, NativeGesture } from '../../types'; +import { useComposedGesture } from './useComposedGesture'; + +export function useSimultaneous( + ...gestures: (NativeGesture | ComposedGesture)[] +) { + const composedGesture = useComposedGesture(...gestures); + + composedGesture.name = 'SimultaneousGesture'; + + return composedGesture; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 897472600a..5b681d2a94 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -7,36 +7,8 @@ import { SharedValue, } from '../../handlers/gestures/reanimatedWrapper'; import { hash, prepareConfig, isAnimatedEvent } from './utils'; -import { AnimatedEvent } from '../types'; import { tagMessage } from '../../utils'; - -type GestureType = - | 'TapGestureHandler' - | 'LongPressGestureHandler' - | 'PanGestureHandler' - | 'PinchGestureHandler' - | 'RotationGestureHandler' - | 'FlingGestureHandler' - | 'ForceTouchGestureHandler' - | 'ManualGestureHandler' - | 'NativeViewGestureHandler'; - -type GestureEvents = { - onGestureHandlerStateChange: (event: any) => void; - onGestureHandlerEvent: undefined | ((event: any) => void); - onGestureHandlerTouchEvent: (event: any) => void; - onReanimatedStateChange: undefined | ((event: any) => void); - onReanimatedUpdateEvent: undefined | ((event: any) => void); - onReanimatedTouchEvent: undefined | ((event: any) => void); - onGestureHandlerAnimatedEvent: undefined | AnimatedEvent; -}; - -export interface NativeGesture { - tag: number; - name: GestureType; - config: Record; - gestureEvents: GestureEvents; -} +import { GestureType, NativeGesture } from '../types'; function hasWorkletEventHandlers(config: Record) { return Object.values(config).some( @@ -196,7 +168,7 @@ export function useGesture( }, [config, tag]); return { - tag: tag, + tag, name: type, config, gestureEvents: { @@ -208,5 +180,7 @@ export function useGesture( onReanimatedTouchEvent, onGestureHandlerAnimatedEvent, }, + simultaneousHandlers: [], + waitFor: [], }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index cbfce7a903..8484721829 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -8,6 +8,8 @@ import { GestureStateChangeEventWithData, GestureUpdateEventWithData, UpdateEvent, + NativeGesture, + ComposedGesture, } from '../types'; import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon'; import { tagMessage } from '../../utils'; @@ -182,3 +184,9 @@ export function hash(str: string) { } return h >>> 0; } + +export function isComposedGesture( + gesture: NativeGesture | ComposedGesture +): gesture is ComposedGesture { + return 'tags' in gesture; +} diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 3f04f28a9f..bc5de77f86 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -48,3 +48,63 @@ export type CallbackHandlers = Omit< export type AnimatedEvent = ((...args: any[]) => void) & { _argMapping?: unknown; }; + +export type GestureType = + | 'TapGestureHandler' + | 'LongPressGestureHandler' + | 'PanGestureHandler' + | 'PinchGestureHandler' + | 'RotationGestureHandler' + | 'FlingGestureHandler' + | 'ForceTouchGestureHandler' + | 'ManualGestureHandler' + | 'NativeViewGestureHandler'; + +export type ComposedGestureType = + | 'SimultaneousGesture' + | 'ExclusiveGesture' + | 'RaceGesture' + | 'ComposedGesture'; + +export type GestureEvents = { + onGestureHandlerStateChange: ( + event: StateChangeEvent> + ) => void; + onGestureHandlerEvent: + | undefined + | ((event: UpdateEvent>) => void); + onGestureHandlerTouchEvent: (event: TouchEvent) => void; + onReanimatedStateChange: + | undefined + | ((event: StateChangeEvent>) => void); + onReanimatedUpdateEvent: + | undefined + | ((event: UpdateEvent>) => void); + onReanimatedTouchEvent: undefined | ((event: TouchEvent) => void); + onGestureHandlerAnimatedEvent: undefined | AnimatedEvent; +}; + +export type GestureRelations = { + simultaneousGestures: number[]; + exclusiveGestures: number[]; +}; + +export type NativeGesture = { + tag: number; + name: GestureType; + config: Record; + gestureEvents: GestureEvents; + simultaneousHandlers: number[]; + waitFor: number[]; +}; + +export type ComposedGesture = { + tags: number[]; + name: ComposedGestureType; + config: { + shouldUseReanimated: boolean; + dispatchesAnimatedEvents: boolean; + }; + gestureEvents: GestureEvents; + gestures: (NativeGesture | ComposedGesture)[]; +}; From 92b4a3a150e3237e9b07a23ddad7fab3519947d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 Aug 2025 12:21:30 +0200 Subject: [PATCH 22/67] Implement module method for updating relations --- .../gesturehandler/react/RNGestureHandlerModule.kt | 11 +++++++++-- .../apple/RNGestureHandler.h | 1 + .../apple/RNGestureHandler.mm | 11 +++++++---- .../apple/RNGestureHandlerManager.h | 2 ++ .../apple/RNGestureHandlerManager.mm | 6 ++++++ .../apple/RNGestureHandlerModule.mm | 6 ++++++ .../src/specs/NativeRNGestureHandlerModule.ts | 2 ++ .../src/v3/NativeDetector.tsx | 9 ++++++++- 8 files changed, 41 insertions(+), 7 deletions(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index 075c1abcd7..f064f85f90 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -97,8 +97,6 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : val handler = registry.getHandler(handlerTag) ?: return val factory = RNGestureHandlerFactoryUtil.findFactoryForHandler(handler) ?: return - interactionManager.dropRelationsForHandlerWithTag(handlerTag) - interactionManager.configureInteractions(handler, config) factory.setConfig(handler, config) } @@ -111,6 +109,15 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : factory.updateConfig(handler, config) } + @ReactMethod + override fun configureRelations(handlerTagDouble: Double, relations: ReadableMap) { + val handlerTag = handlerTagDouble.toInt() + val handler = registry.getHandler(handlerTag) ?: return + + interactionManager.dropRelationsForHandlerWithTag(handlerTag) + interactionManager.configureInteractions(handler, relations) + } + @ReactMethod override fun dropGestureHandler(handlerTagDouble: Double) { val handlerTag = handlerTagDouble.toInt() diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.h b/packages/react-native-gesture-handler/apple/RNGestureHandler.h index 226130b6a0..3096ef95fa 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.h @@ -89,6 +89,7 @@ - (void)resetConfig NS_REQUIRES_SUPER; - (void)setConfig:(nullable NSDictionary *)config NS_REQUIRES_SUPER; - (void)updateConfig:(nullable NSDictionary *)config NS_REQUIRES_SUPER; +- (void)updateRelations:(nonnull NSDictionary *)relations; - (void)handleGesture:(nonnull id)recognizer; - (void)handleGesture:(nonnull id)recognizer inState:(RNGestureHandlerState)state; - (BOOL)containsPointInView; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index ace5c41b3a..c501ac009a 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -123,10 +123,6 @@ - (void)setConfig:(NSDictionary *)config - (void)updateConfig:(NSDictionary *)config { - _handlersToWaitFor = [RCTConvert NSNumberArray:config[@"waitFor"]]; - _simultaneousHandlers = [RCTConvert NSNumberArray:config[@"simultaneousHandlers"]]; - _handlersThatShouldWait = [RCTConvert NSNumberArray:config[@"blocksHandlers"]]; - id prop = config[@"enabled"]; if (prop != nil) { self.enabled = [RCTConvert BOOL:prop]; @@ -191,6 +187,13 @@ - (void)updateConfig:(NSDictionary *)config } } +- (void)updateRelations:(NSDictionary *)relations +{ + _handlersToWaitFor = [RCTConvert NSNumberArray:relations[@"waitFor"]]; + _simultaneousHandlers = [RCTConvert NSNumberArray:relations[@"simultaneousHandlers"]]; + _handlersThatShouldWait = [RCTConvert NSNumberArray:relations[@"blocksHandlers"]]; +} + - (void)setEnabled:(BOOL)enabled { _enabled = enabled; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h index fe4a950865..123d85cd54 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h @@ -28,6 +28,8 @@ - (void)updateGestureHandlerConfig:(nonnull NSNumber *)handlerTag config:(nonnull NSDictionary *)config; +- (void)updateGestureHandlerRelations:(nonnull NSNumber *)handlerTag relations:(nonnull NSDictionary *)relations; + - (void)dropGestureHandler:(nonnull NSNumber *)handlerTag; - (void)dropAllGestureHandlers; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm index 363d0bb3cc..046b7a54e5 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm @@ -191,6 +191,12 @@ - (void)updateGestureHandlerConfig:(NSNumber *)handlerTag config:(NSDictionary * [handler updateConfig:config]; } +- (void)updateGestureHandlerRelations:(NSNumber *)handlerTag relations:(NSDictionary *)relations +{ + RNGestureHandler *handler = [_registry handlerWithTag:handlerTag]; + [handler updateRelations:relations]; +} + - (void)dropGestureHandler:(NSNumber *)handlerTag { [_registry dropHandlerWithTag:handlerTag]; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index fab69544b7..c8052bc822 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -159,6 +159,12 @@ - (void)updateGestureHandlerConfig:(double)handlerTag newConfig:(NSDictionary *) }]; } +- (void)configureRelations:(double)handlerTag relations:(NSDictionary *)relations +{ + RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId]; + [manager updateGestureHandlerRelations:[NSNumber numberWithDouble:handlerTag] relations:relations]; +} + - (void)dropGestureHandler:(double)handlerTag { [self addOperationBlock:^(RNGestureHandlerManager *manager) { diff --git a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts index f3d3ae181a..25cf9733dd 100644 --- a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts +++ b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts @@ -22,6 +22,8 @@ export interface Spec extends TurboModule { setGestureHandlerConfig: (handlerTag: Double, newConfig: Object) => void; // eslint-disable-next-line @typescript-eslint/ban-types updateGestureHandlerConfig: (handlerTag: Double, newConfig: Object) => void; + // eslint-disable-next-line @typescript-eslint/ban-types + configureRelations: (handlerTag: Double, relations: Object) => void; dropGestureHandler: (handlerTag: Double) => void; flushOperations: () => void; } diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx index 0eb4d222aa..b0443ad962 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx @@ -6,6 +6,7 @@ import { Animated, StyleSheet } from 'react-native'; import HostGestureDetector from './HostGestureDetector'; import { tagMessage } from '../utils'; import { isComposedGesture } from './hooks/utils'; +import RNGestureHandlerModule from '../RNGestureHandlerModule'; export interface NativeDetectorProps { children?: React.ReactNode; @@ -47,12 +48,18 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { simultaneousHandlers: Set = new Set() ) => { // If we are in the leaf node, we want to fill gesture relations arrays with current - // waitFor and simultaneousHandlers. + // waitFor and simultaneousHandlers. We also want to configure relations on the native side. // TODO: handle `simultaneousWithExternalGesture`, `requreExternalGestureToFail`, `blocksExternalGesture` if (!isComposedGesture(node)) { node.simultaneousHandlers.push(...simultaneousHandlers); node.waitFor.push(...waitFor); + RNGestureHandlerModule.configureRelations(node.tag, { + waitFor, + simultaneousHandlers: Array.from(simultaneousHandlers), + blocksHandlers: node.blocksHandlers || [], // TODO: handle `blocksExternalGesture` + }); + return; } From e73ee579b3e1aa6fc297012fdc2d176723c5918e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 Aug 2025 17:37:17 +0200 Subject: [PATCH 23/67] Add blocksHandlers array --- packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts | 1 + packages/react-native-gesture-handler/src/v3/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 5b681d2a94..35dc6e27c1 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -182,5 +182,6 @@ export function useGesture( }, simultaneousHandlers: [], waitFor: [], + blocksHandlers: [], }; } diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index bc5de77f86..eafee6cfe2 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -96,6 +96,7 @@ export type NativeGesture = { gestureEvents: GestureEvents; simultaneousHandlers: number[]; waitFor: number[]; + blocksHandlers: number[]; }; export type ComposedGesture = { From 432aee01ff4bc891f6dce47a3612cfed242f3088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 Aug 2025 18:41:29 +0200 Subject: [PATCH 24/67] Handle Simultaneous as root --- .../src/v3/NativeDetector.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx index b0443ad962..238516835d 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx @@ -44,8 +44,8 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. const dfs = ( node: NativeGesture | ComposedGesture, - waitFor: number[] = [], - simultaneousHandlers: Set = new Set() + simultaneousHandlers: Set = new Set(), + waitFor: number[] = [] ) => { // If we are in the leaf node, we want to fill gesture relations arrays with current // waitFor and simultaneousHandlers. We also want to configure relations on the native side. @@ -94,7 +94,7 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { const length = waitFor.length; // We traverse the child, passing the current `waitFor` and `simultaneousHandlers`. - dfs(child, waitFor, simultaneousHandlers); + dfs(child, simultaneousHandlers, waitFor); // After traversing the child, we need to update `waitFor` and `simultaneousHandlers` @@ -137,7 +137,7 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { // This means that child is a leaf node. else { // In the leaf node, we only care about filling `waitFor` array. First we traverse the child... - dfs(child, waitFor, simultaneousHandlers); + dfs(child, simultaneousHandlers, waitFor); // ..and when we go back we add the tag of the child to the `waitFor` array. if (node.name === 'ExclusiveGesture') { @@ -147,7 +147,11 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { }); }; - dfs(gesture); + if (gesture.name === 'SimultaneousGesture') { + dfs(gesture, new Set(gesture.tags)); + } else { + dfs(gesture); + } return ( Date: Thu, 14 Aug 2025 18:56:51 +0200 Subject: [PATCH 25/67] Change name to enum --- .../react-native-gesture-handler/src/index.ts | 2 + .../src/v3/NativeDetector.tsx | 31 +++++++++------- .../v3/hooks/relations/useComposedGesture.ts | 3 +- .../src/v3/hooks/relations/useExclusive.ts | 8 +++- .../src/v3/hooks/relations/useRace.ts | 6 +-- .../src/v3/hooks/relations/useSimultaneous.ts | 8 +++- .../src/v3/hooks/useGesture.ts | 6 +-- .../src/v3/types.ts | 37 ++++++++++--------- 8 files changed, 57 insertions(+), 44 deletions(-) diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts index 1f6ad3b622..e1e040bbe6 100644 --- a/packages/react-native-gesture-handler/src/index.ts +++ b/packages/react-native-gesture-handler/src/index.ts @@ -167,4 +167,6 @@ export { NativeDetector } from './v3/NativeDetector'; export * from './v3/hooks/useGesture'; +export { HandlerType } from './v3/types'; + initialize(); diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx index 238516835d..561c66d825 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { NativeGesture, ComposedGesture } from './types'; +import { NativeGesture, ComposedGesture, ComposedGestureType } from './types'; import { Reanimated } from '../handlers/gestures/reanimatedWrapper'; import { Animated, StyleSheet } from 'react-native'; @@ -73,8 +73,8 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { // we add the tags of the simultaneous gesture to the `simultaneousHandlers`. // This way when we traverse the child, we already have the tags of the simultaneous gestures if ( - node.name !== 'SimultaneousGesture' && - child.name === 'SimultaneousGesture' + node.type !== ComposedGestureType.Simultaneous && + child.type === ComposedGestureType.Simultaneous ) { child.tags.forEach((tag) => simultaneousHandlers.add(tag)); } @@ -83,8 +83,8 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { // we remove the tags of the child gestures from the `simultaneousHandlers`, // as those are not simultaneous with each other. if ( - node.name === 'SimultaneousGesture' && - child.name !== 'SimultaneousGesture' + node.type === ComposedGestureType.Simultaneous && + child.type !== ComposedGestureType.Simultaneous ) { child.tags.forEach((tag) => simultaneousHandlers.delete(tag)); } @@ -102,8 +102,8 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { // we want to delete the tags of the simultaneous gesture from the `simultaneousHandlers` - // those gestures are not simultaneous with each other anymore. if ( - child.name === 'SimultaneousGesture' && - node.name !== 'SimultaneousGesture' + child.type === ComposedGestureType.Simultaneous && + node.type !== ComposedGestureType.Simultaneous ) { node.tags.forEach((tag) => simultaneousHandlers.delete(tag)); } @@ -112,15 +112,15 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { // we want to add the tags of the simultaneous gesture to the `simultaneousHandlers`, // as those gestures are simultaneous with other children of the current node. if ( - child.name !== 'SimultaneousGesture' && - node.name === 'SimultaneousGesture' + child.type !== ComposedGestureType.Simultaneous && + node.type === ComposedGestureType.Simultaneous ) { node.tags.forEach((tag) => simultaneousHandlers.add(tag)); } // If we go back to an exclusive gesture, we want to add the tags of the child gesture to the `waitFor` array. // This will allow us to pass exclusive gesture tags to the right subtree of the current node. - if (node.name === 'ExclusiveGesture') { + if (node.type === ComposedGestureType.Exclusive) { child.tags.forEach((tag) => waitFor.push(tag)); } @@ -128,8 +128,8 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { // to the previous state, siblings of the exclusive gesture are not exclusive with it. Since we use `push` method to // add tags to the `waitFor` array, we can override `length` property to reset it to the previous state. if ( - child.name === 'ExclusiveGesture' && - node.name !== 'ExclusiveGesture' + child.type === ComposedGestureType.Exclusive && + node.type !== ComposedGestureType.Exclusive ) { waitFor.length = length; } @@ -140,14 +140,17 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { dfs(child, simultaneousHandlers, waitFor); // ..and when we go back we add the tag of the child to the `waitFor` array. - if (node.name === 'ExclusiveGesture') { + if (node.type === ComposedGestureType.Exclusive) { waitFor.push(child.tag); } } }); }; - if (gesture.name === 'SimultaneousGesture') { + if ( + isComposedGesture(gesture) && + gesture.type === ComposedGestureType.Simultaneous + ) { dfs(gesture, new Set(gesture.tags)); } else { dfs(gesture); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 8feb86a458..9cd11ea044 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -4,6 +4,7 @@ import { UpdateEvent, TouchEvent, ComposedGesture, + ComposedGestureType, } from '../../types'; import { isComposedGesture } from '../utils'; import { tagMessage } from '../../../utils'; @@ -75,7 +76,7 @@ export function useComposedGesture( return { tags, - name: 'ComposedGesture', + type: ComposedGestureType.Race, config, gestureEvents: { onGestureHandlerStateChange, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts index 38b8f72495..b402c8080a 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts @@ -1,10 +1,14 @@ -import { NativeGesture, ComposedGesture } from '../../types'; +import { + NativeGesture, + ComposedGesture, + ComposedGestureType, +} from '../../types'; import { useComposedGesture } from './useComposedGesture'; export function useExclusive(...gestures: (NativeGesture | ComposedGesture)[]) { const composedGesture = useComposedGesture(...gestures); - composedGesture.name = 'ExclusiveGesture'; + composedGesture.type = ComposedGestureType.Exclusive; return composedGesture; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts index ae59b5a48e..a04e21b38d 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts @@ -2,9 +2,5 @@ import { ComposedGesture, NativeGesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; export function useRace(...gestures: (NativeGesture | ComposedGesture)[]) { - const composedGesture = useComposedGesture(...gestures); - - composedGesture.name = 'RaceGesture'; - - return composedGesture; + return useComposedGesture(...gestures); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts index 2975dc6fd7..f046b05ff7 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts @@ -1,4 +1,8 @@ -import { ComposedGesture, NativeGesture } from '../../types'; +import { + ComposedGesture, + ComposedGestureType, + NativeGesture, +} from '../../types'; import { useComposedGesture } from './useComposedGesture'; export function useSimultaneous( @@ -6,7 +10,7 @@ export function useSimultaneous( ) { const composedGesture = useComposedGesture(...gestures); - composedGesture.name = 'SimultaneousGesture'; + composedGesture.type = ComposedGestureType.Simultaneous; return composedGesture; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 35dc6e27c1..5fd39d54da 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -8,7 +8,7 @@ import { } from '../../handlers/gestures/reanimatedWrapper'; import { hash, prepareConfig, isAnimatedEvent } from './utils'; import { tagMessage } from '../../utils'; -import { GestureType, NativeGesture } from '../types'; +import { HandlerType, NativeGesture } from '../types'; function hasWorkletEventHandlers(config: Record) { return Object.values(config).some( @@ -79,7 +79,7 @@ function unbindSharedValues(config: any, handlerTag: number) { } export function useGesture( - type: GestureType, + type: HandlerType, config: Record ): NativeGesture { const tag = useMemo(() => getNextHandlerTag(), []); @@ -169,7 +169,7 @@ export function useGesture( return { tag, - name: type, + type, config, gestureEvents: { onGestureHandlerStateChange, diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index eafee6cfe2..ca899ba959 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -49,22 +49,25 @@ export type AnimatedEvent = ((...args: any[]) => void) & { _argMapping?: unknown; }; -export type GestureType = - | 'TapGestureHandler' - | 'LongPressGestureHandler' - | 'PanGestureHandler' - | 'PinchGestureHandler' - | 'RotationGestureHandler' - | 'FlingGestureHandler' - | 'ForceTouchGestureHandler' - | 'ManualGestureHandler' - | 'NativeViewGestureHandler'; +export enum SingleGestureType { + Tap = 'TapGestureHandler', + LongPress = 'LongPressGestureHandler', + Pan = 'PanGestureHandler', + Pinch = 'PinchGestureHandler', + Rotation = 'RotationGestureHandler', + Fling = 'FlingGestureHandler', + Manual = 'ManualGestureHandler', + Native = 'NativeGestureHandler', +} -export type ComposedGestureType = - | 'SimultaneousGesture' - | 'ExclusiveGesture' - | 'RaceGesture' - | 'ComposedGesture'; +export enum ComposedGestureType { + Simultaneous = 'SimultaneousGesture', + Exclusive = 'ExclusiveGesture', + Race = 'RaceGesture', +} + +// TODO: Find better name +export type HandlerType = SingleGestureType | ComposedGestureType; export type GestureEvents = { onGestureHandlerStateChange: ( @@ -91,7 +94,7 @@ export type GestureRelations = { export type NativeGesture = { tag: number; - name: GestureType; + type: HandlerType; config: Record; gestureEvents: GestureEvents; simultaneousHandlers: number[]; @@ -101,7 +104,7 @@ export type NativeGesture = { export type ComposedGesture = { tags: number[]; - name: ComposedGestureType; + type: ComposedGestureType; config: { shouldUseReanimated: boolean; dispatchesAnimatedEvents: boolean; From db5d261880e2b4be6488c14c9d025f9bfb870168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 Aug 2025 19:03:15 +0200 Subject: [PATCH 26/67] Move DFS to other file --- .../react-native-gesture-handler/src/index.ts | 4 +- .../src/v3/HostGestureDetector.tsx | 3 - .../src/v3/NativeDetector.tsx | 200 ------------------ .../v3/NativeDetector/HostGestureDetector.tsx | 3 + .../HostGestureDetector.web.tsx | 8 +- .../src/v3/NativeDetector/NativeDetector.tsx | 89 ++++++++ .../src/v3/NativeDetector/utils.ts | 115 ++++++++++ 7 files changed, 213 insertions(+), 209 deletions(-) delete mode 100644 packages/react-native-gesture-handler/src/v3/HostGestureDetector.tsx delete mode 100644 packages/react-native-gesture-handler/src/v3/NativeDetector.tsx create mode 100644 packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx rename packages/react-native-gesture-handler/src/v3/{ => NativeDetector}/HostGestureDetector.web.tsx (92%) create mode 100644 packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx create mode 100644 packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts index e1e040bbe6..95adbeca92 100644 --- a/packages/react-native-gesture-handler/src/index.ts +++ b/packages/react-native-gesture-handler/src/index.ts @@ -162,8 +162,8 @@ export type { } from './components/DrawerLayout'; export { default as DrawerLayout } from './components/DrawerLayout'; -export type { NativeDetectorProps } from './v3/NativeDetector'; -export { NativeDetector } from './v3/NativeDetector'; +export type { NativeDetectorProps } from './v3/NativeDetector/NativeDetector'; +export { NativeDetector } from './v3/NativeDetector/NativeDetector'; export * from './v3/hooks/useGesture'; diff --git a/packages/react-native-gesture-handler/src/v3/HostGestureDetector.tsx b/packages/react-native-gesture-handler/src/v3/HostGestureDetector.tsx deleted file mode 100644 index 1bc9860d9a..0000000000 --- a/packages/react-native-gesture-handler/src/v3/HostGestureDetector.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import RNGestureHandlerDetectorNativeComponent from '../specs/RNGestureHandlerDetectorNativeComponent'; -const HostGestureDetector = RNGestureHandlerDetectorNativeComponent; -export default HostGestureDetector; diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx deleted file mode 100644 index 561c66d825..0000000000 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import React from 'react'; -import { NativeGesture, ComposedGesture, ComposedGestureType } from './types'; -import { Reanimated } from '../handlers/gestures/reanimatedWrapper'; - -import { Animated, StyleSheet } from 'react-native'; -import HostGestureDetector from './HostGestureDetector'; -import { tagMessage } from '../utils'; -import { isComposedGesture } from './hooks/utils'; -import RNGestureHandlerModule from '../RNGestureHandlerModule'; - -export interface NativeDetectorProps { - children?: React.ReactNode; - gesture: NativeGesture | ComposedGesture; -} - -const AnimatedNativeDetector = - Animated.createAnimatedComponent(HostGestureDetector); - -const ReanimatedNativeDetector = - Reanimated?.default.createAnimatedComponent(HostGestureDetector); - -export function NativeDetector({ gesture, children }: NativeDetectorProps) { - const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents - ? AnimatedNativeDetector - : // TODO: Remove this cast when we properly type config - (gesture.config.shouldUseReanimated as boolean) - ? ReanimatedNativeDetector - : HostGestureDetector; - - // It might happen only with ReanimatedNativeDetector - if (!NativeDetectorComponent) { - throw new Error( - tagMessage( - 'Gesture expects to run on the UI thread, but failed to create the Reanimated NativeDetector.' - ) - ); - } - - // This piece of magic traverses the gesture tree and populates `waitFor` and `simultaneousHandlers` - // arrays for each gesture. It traverses the tree recursively using DFS. - // `waitFor` and `simultaneousHandlers` are global data structures that will be populated into each gesture. - // For `waitFor` we need array as order of the gestures matters. - // For `simultaneousHandlers` we use Set as the order doesn't matter. - // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. - const dfs = ( - node: NativeGesture | ComposedGesture, - simultaneousHandlers: Set = new Set(), - waitFor: number[] = [] - ) => { - // If we are in the leaf node, we want to fill gesture relations arrays with current - // waitFor and simultaneousHandlers. We also want to configure relations on the native side. - // TODO: handle `simultaneousWithExternalGesture`, `requreExternalGestureToFail`, `blocksExternalGesture` - if (!isComposedGesture(node)) { - node.simultaneousHandlers.push(...simultaneousHandlers); - node.waitFor.push(...waitFor); - - RNGestureHandlerModule.configureRelations(node.tag, { - waitFor, - simultaneousHandlers: Array.from(simultaneousHandlers), - blocksHandlers: node.blocksHandlers || [], // TODO: handle `blocksExternalGesture` - }); - - return; - } - - // If we are in the composed gesture, we want to traverse its children. - node.gestures.forEach((child) => { - // If child is composed gesture, we have to correctly fill `waitFor` and `simultaneousHandlers`. - if (isComposedGesture(child)) { - // We have to update `simultaneousHandlers` before traversing the child. - - // If we go from a non-simultaneous gesture to a simultaneous gesture, - // we add the tags of the simultaneous gesture to the `simultaneousHandlers`. - // This way when we traverse the child, we already have the tags of the simultaneous gestures - if ( - node.type !== ComposedGestureType.Simultaneous && - child.type === ComposedGestureType.Simultaneous - ) { - child.tags.forEach((tag) => simultaneousHandlers.add(tag)); - } - - // If we go from a simultaneous gesture to a non-simultaneous gesture, - // we remove the tags of the child gestures from the `simultaneousHandlers`, - // as those are not simultaneous with each other. - if ( - node.type === ComposedGestureType.Simultaneous && - child.type !== ComposedGestureType.Simultaneous - ) { - child.tags.forEach((tag) => simultaneousHandlers.delete(tag)); - } - - // We will keep the current length of `waitFor` to reset it to previous state - // after traversing the child. - const length = waitFor.length; - - // We traverse the child, passing the current `waitFor` and `simultaneousHandlers`. - dfs(child, simultaneousHandlers, waitFor); - - // After traversing the child, we need to update `waitFor` and `simultaneousHandlers` - - // If we go back from a simultaneous gesture to a non-simultaneous gesture, - // we want to delete the tags of the simultaneous gesture from the `simultaneousHandlers` - - // those gestures are not simultaneous with each other anymore. - if ( - child.type === ComposedGestureType.Simultaneous && - node.type !== ComposedGestureType.Simultaneous - ) { - node.tags.forEach((tag) => simultaneousHandlers.delete(tag)); - } - - // If we go back from a non-simultaneous gesture to a simultaneous gesture, - // we want to add the tags of the simultaneous gesture to the `simultaneousHandlers`, - // as those gestures are simultaneous with other children of the current node. - if ( - child.type !== ComposedGestureType.Simultaneous && - node.type === ComposedGestureType.Simultaneous - ) { - node.tags.forEach((tag) => simultaneousHandlers.add(tag)); - } - - // If we go back to an exclusive gesture, we want to add the tags of the child gesture to the `waitFor` array. - // This will allow us to pass exclusive gesture tags to the right subtree of the current node. - if (node.type === ComposedGestureType.Exclusive) { - child.tags.forEach((tag) => waitFor.push(tag)); - } - - // If we go back from an exclusive gesture to a non-exclusive gesture, we want to reset the `waitFor` array - // to the previous state, siblings of the exclusive gesture are not exclusive with it. Since we use `push` method to - // add tags to the `waitFor` array, we can override `length` property to reset it to the previous state. - if ( - child.type === ComposedGestureType.Exclusive && - node.type !== ComposedGestureType.Exclusive - ) { - waitFor.length = length; - } - } - // This means that child is a leaf node. - else { - // In the leaf node, we only care about filling `waitFor` array. First we traverse the child... - dfs(child, simultaneousHandlers, waitFor); - - // ..and when we go back we add the tag of the child to the `waitFor` array. - if (node.type === ComposedGestureType.Exclusive) { - waitFor.push(child.tag); - } - } - }); - }; - - if ( - isComposedGesture(gesture) && - gesture.type === ComposedGestureType.Simultaneous - ) { - dfs(gesture, new Set(gesture.tags)); - } else { - dfs(gesture); - } - - return ( - - {children} - - ); -} - -const styles = StyleSheet.create({ - detector: { - display: 'contents', - // TODO: remove, debug info only - backgroundColor: 'red', - }, -}); diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx new file mode 100644 index 0000000000..5b91d456e2 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx @@ -0,0 +1,3 @@ +import RNGestureHandlerDetectorNativeComponent from '../../specs/RNGestureHandlerDetectorNativeComponent'; +const HostGestureDetector = RNGestureHandlerDetectorNativeComponent; +export default HostGestureDetector; diff --git a/packages/react-native-gesture-handler/src/v3/HostGestureDetector.web.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx similarity index 92% rename from packages/react-native-gesture-handler/src/v3/HostGestureDetector.web.tsx rename to packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx index cbe2e77441..46f0064574 100644 --- a/packages/react-native-gesture-handler/src/v3/HostGestureDetector.web.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx @@ -1,9 +1,9 @@ import React, { Ref, useEffect, useRef } from 'react'; -import RNGestureHandlerModule from '../RNGestureHandlerModule.web'; -import { ActionType } from '../ActionType'; -import { PropsRef } from '../web/interfaces'; +import RNGestureHandlerModule from '../../RNGestureHandlerModule.web'; +import { ActionType } from '../../ActionType'; +import { PropsRef } from '../../web/interfaces'; import { View } from 'react-native'; -import { tagMessage } from '../utils'; +import { tagMessage } from '../../utils'; export interface GestureHandlerDetectorProps extends PropsRef { handlerTags: number[]; diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx new file mode 100644 index 0000000000..eb3a0d347c --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { NativeGesture, ComposedGesture, ComposedGestureType } from '../types'; +import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; + +import { Animated, StyleSheet } from 'react-native'; +import HostGestureDetector from './HostGestureDetector'; +import { tagMessage } from '../../utils'; +import { isComposedGesture } from '../hooks/utils'; +import { dfs } from './utils'; + +export interface NativeDetectorProps { + children?: React.ReactNode; + gesture: NativeGesture | ComposedGesture; +} + +const AnimatedNativeDetector = + Animated.createAnimatedComponent(HostGestureDetector); + +const ReanimatedNativeDetector = + Reanimated?.default.createAnimatedComponent(HostGestureDetector); + +export function NativeDetector({ gesture, children }: NativeDetectorProps) { + const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents + ? AnimatedNativeDetector + : // TODO: Remove this cast when we properly type config + (gesture.config.shouldUseReanimated as boolean) + ? ReanimatedNativeDetector + : HostGestureDetector; + + // It might happen only with ReanimatedNativeDetector + if (!NativeDetectorComponent) { + throw new Error( + tagMessage( + 'Gesture expects to run on the UI thread, but failed to create the Reanimated NativeDetector.' + ) + ); + } + + if ( + isComposedGesture(gesture) && + gesture.type === ComposedGestureType.Simultaneous + ) { + dfs(gesture, new Set(gesture.tags)); + } else { + dfs(gesture); + } + + return ( + + {children} + + ); +} + +const styles = StyleSheet.create({ + detector: { + display: 'contents', + // TODO: remove, debug info only + backgroundColor: 'red', + }, +}); diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts new file mode 100644 index 0000000000..04e860e19d --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -0,0 +1,115 @@ +// This piece of magic traverses the gesture tree and populates `waitFor` and `simultaneousHandlers` +// arrays for each gesture. It traverses the tree recursively using DFS. +// `waitFor` and `simultaneousHandlers` are global data structures that will be populated into each gesture. +// For `waitFor` we need array as order of the gestures matters. +// For `simultaneousHandlers` we use Set as the order doesn't matter. + +import RNGestureHandlerModule from '../../RNGestureHandlerModule'; +import { isComposedGesture } from '../hooks/utils'; +import { ComposedGesture, ComposedGestureType, NativeGesture } from '../types'; + +// The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. +export const dfs = ( + node: NativeGesture | ComposedGesture, + simultaneousHandlers: Set = new Set(), + waitFor: number[] = [] +) => { + // If we are in the leaf node, we want to fill gesture relations arrays with current + // waitFor and simultaneousHandlers. We also want to configure relations on the native side. + // TODO: handle `simultaneousWithExternalGesture`, `requreExternalGestureToFail`, `blocksExternalGesture` + if (!isComposedGesture(node)) { + node.simultaneousHandlers.push(...simultaneousHandlers); + node.waitFor.push(...waitFor); + + RNGestureHandlerModule.configureRelations(node.tag, { + waitFor, + simultaneousHandlers: Array.from(simultaneousHandlers), + blocksHandlers: node.blocksHandlers || [], // TODO: handle `blocksExternalGesture` + }); + + return; + } + + // If we are in the composed gesture, we want to traverse its children. + node.gestures.forEach((child) => { + // If child is composed gesture, we have to correctly fill `waitFor` and `simultaneousHandlers`. + if (isComposedGesture(child)) { + // We have to update `simultaneousHandlers` before traversing the child. + + // If we go from a non-simultaneous gesture to a simultaneous gesture, + // we add the tags of the simultaneous gesture to the `simultaneousHandlers`. + // This way when we traverse the child, we already have the tags of the simultaneous gestures + if ( + node.type !== ComposedGestureType.Simultaneous && + child.type === ComposedGestureType.Simultaneous + ) { + child.tags.forEach((tag) => simultaneousHandlers.add(tag)); + } + + // If we go from a simultaneous gesture to a non-simultaneous gesture, + // we remove the tags of the child gestures from the `simultaneousHandlers`, + // as those are not simultaneous with each other. + if ( + node.type === ComposedGestureType.Simultaneous && + child.type !== ComposedGestureType.Simultaneous + ) { + child.tags.forEach((tag) => simultaneousHandlers.delete(tag)); + } + + // We will keep the current length of `waitFor` to reset it to previous state + // after traversing the child. + const length = waitFor.length; + + // We traverse the child, passing the current `waitFor` and `simultaneousHandlers`. + dfs(child, simultaneousHandlers, waitFor); + + // After traversing the child, we need to update `waitFor` and `simultaneousHandlers` + + // If we go back from a simultaneous gesture to a non-simultaneous gesture, + // we want to delete the tags of the simultaneous gesture from the `simultaneousHandlers` - + // those gestures are not simultaneous with each other anymore. + if ( + child.type === ComposedGestureType.Simultaneous && + node.type !== ComposedGestureType.Simultaneous + ) { + node.tags.forEach((tag) => simultaneousHandlers.delete(tag)); + } + + // If we go back from a non-simultaneous gesture to a simultaneous gesture, + // we want to add the tags of the simultaneous gesture to the `simultaneousHandlers`, + // as those gestures are simultaneous with other children of the current node. + if ( + child.type !== ComposedGestureType.Simultaneous && + node.type === ComposedGestureType.Simultaneous + ) { + node.tags.forEach((tag) => simultaneousHandlers.add(tag)); + } + + // If we go back to an exclusive gesture, we want to add the tags of the child gesture to the `waitFor` array. + // This will allow us to pass exclusive gesture tags to the right subtree of the current node. + if (node.type === ComposedGestureType.Exclusive) { + child.tags.forEach((tag) => waitFor.push(tag)); + } + + // If we go back from an exclusive gesture to a non-exclusive gesture, we want to reset the `waitFor` array + // to the previous state, siblings of the exclusive gesture are not exclusive with it. Since we use `push` method to + // add tags to the `waitFor` array, we can override `length` property to reset it to the previous state. + if ( + child.type === ComposedGestureType.Exclusive && + node.type !== ComposedGestureType.Exclusive + ) { + waitFor.length = length; + } + } + // This means that child is a leaf node. + else { + // In the leaf node, we only care about filling `waitFor` array. First we traverse the child... + dfs(child, simultaneousHandlers, waitFor); + + // ..and when we go back we add the tag of the child to the `waitFor` array. + if (node.type === ComposedGestureType.Exclusive) { + waitFor.push(child.tag); + } + } + }); +}; From 43212af6974bdfbee5251f8e26da57d119b1c487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 18 Aug 2025 08:14:11 +0200 Subject: [PATCH 27/67] Do not traverse single gestures --- .../src/v3/NativeDetector/NativeDetector.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index eb3a0d347c..c56878dea1 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -36,13 +36,12 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { ); } - if ( - isComposedGesture(gesture) && - gesture.type === ComposedGestureType.Simultaneous - ) { - dfs(gesture, new Set(gesture.tags)); - } else { - dfs(gesture); + if (isComposedGesture(gesture)) { + if (gesture.type === ComposedGestureType.Simultaneous) { + dfs(gesture, new Set(gesture.tags)); + } else { + dfs(gesture); + } } return ( From 4ec26c012b34fbfdd6912c8a377b808d8a694905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 18 Aug 2025 10:35:06 +0200 Subject: [PATCH 28/67] Handle external relations --- .../src/v3/NativeDetector/NativeDetector.tsx | 7 +++ .../src/v3/NativeDetector/utils.ts | 10 ++-- .../src/v3/hooks/useGesture.ts | 49 +++++++++++++++-- .../src/v3/types.ts | 53 ++++++++++--------- 4 files changed, 85 insertions(+), 34 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index c56878dea1..ac2ccae495 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -7,6 +7,7 @@ import HostGestureDetector from './HostGestureDetector'; import { tagMessage } from '../../utils'; import { isComposedGesture } from '../hooks/utils'; import { dfs } from './utils'; +import RNGestureHandlerModule from '../../RNGestureHandlerModule'; export interface NativeDetectorProps { children?: React.ReactNode; @@ -42,6 +43,12 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { } else { dfs(gesture); } + } else { + RNGestureHandlerModule.configureRelations(gesture.tag, { + waitFor: gesture.gestureRelations.waitFor, + simultaneousHandlers: gesture.gestureRelations.simultaneousHandlers, + blocksHandlers: gesture.gestureRelations.blocksHandlers, + }); } return ( diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 04e860e19d..251f24b9e3 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -18,13 +18,13 @@ export const dfs = ( // waitFor and simultaneousHandlers. We also want to configure relations on the native side. // TODO: handle `simultaneousWithExternalGesture`, `requreExternalGestureToFail`, `blocksExternalGesture` if (!isComposedGesture(node)) { - node.simultaneousHandlers.push(...simultaneousHandlers); - node.waitFor.push(...waitFor); + node.gestureRelations.simultaneousHandlers.push(...simultaneousHandlers); + node.gestureRelations.waitFor.push(...waitFor); RNGestureHandlerModule.configureRelations(node.tag, { - waitFor, - simultaneousHandlers: Array.from(simultaneousHandlers), - blocksHandlers: node.blocksHandlers || [], // TODO: handle `blocksExternalGesture` + waitFor: node.gestureRelations.waitFor, + simultaneousHandlers: node.gestureRelations.simultaneousHandlers, + blocksHandlers: node.gestureRelations.blocksHandlers, }); return; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 5fd39d54da..27f2688330 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -8,7 +8,15 @@ import { } from '../../handlers/gestures/reanimatedWrapper'; import { hash, prepareConfig, isAnimatedEvent } from './utils'; import { tagMessage } from '../../utils'; -import { HandlerType, NativeGesture } from '../types'; +import { + GestureRelations, + HandlerType, + NativeGesture, + ComposedGesture, + Gesture, +} from '../types'; +import { isComposedGesture } from './utils'; +import { ValueOf } from '../../typeUtils'; function hasWorkletEventHandlers(config: Record) { return Object.values(config).some( @@ -78,8 +86,39 @@ function unbindSharedValues(config: any, handlerTag: number) { } } +function prepareRelations(config: any): GestureRelations { + const extractHandlerTags = (otherHandler: Gesture | Gesture[]): number[] => { + if (!otherHandler) { + return []; + } + + let otherTags: number[]; + + if (Array.isArray(otherHandler)) { + otherTags = otherHandler.flatMap( + (gesture: NativeGesture | ComposedGesture) => + isComposedGesture(gesture) ? gesture.tags : gesture.tag + ); + } else { + otherTags = isComposedGesture(otherHandler) + ? otherHandler.tags + : [otherHandler.tag]; + } + + return otherTags; + }; + + return { + simultaneousHandlers: extractHandlerTags( + config.simultaneousWithExternalGesture + ), + waitFor: extractHandlerTags(config.requireExternalGestureToFail), + blocksHandlers: extractHandlerTags(config.blocksExternalGesture), + }; +} + export function useGesture( - type: HandlerType, + type: ValueOf, config: Record ): NativeGesture { const tag = useMemo(() => getNextHandlerTag(), []); @@ -139,6 +178,8 @@ export function useGesture( throw new Error(tagMessage('Failed to create reanimated event handlers.')); } + const gestureRelations = prepareRelations(config); + useMemo(() => { RNGestureHandlerModule.createGestureHandler(type, tag, {}); RNGestureHandlerModule.flushOperations(); @@ -180,8 +221,6 @@ export function useGesture( onReanimatedTouchEvent, onGestureHandlerAnimatedEvent, }, - simultaneousHandlers: [], - waitFor: [], - blocksHandlers: [], + gestureRelations, }; } diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index ca899ba959..9c50624bd8 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -5,6 +5,7 @@ import { HandlerStateChangeEventPayload, } from '../handlers/gestureHandlerCommon'; import { HandlerCallbacks } from '../handlers/gestures/gesture'; +import { ValueOf } from '../typeUtils'; export type GestureUpdateEventWithData = GestureEventPayload & { handlerData: T; @@ -49,25 +50,28 @@ export type AnimatedEvent = ((...args: any[]) => void) & { _argMapping?: unknown; }; -export enum SingleGestureType { - Tap = 'TapGestureHandler', - LongPress = 'LongPressGestureHandler', - Pan = 'PanGestureHandler', - Pinch = 'PinchGestureHandler', - Rotation = 'RotationGestureHandler', - Fling = 'FlingGestureHandler', - Manual = 'ManualGestureHandler', - Native = 'NativeGestureHandler', -} - -export enum ComposedGestureType { - Simultaneous = 'SimultaneousGesture', - Exclusive = 'ExclusiveGesture', - Race = 'RaceGesture', -} +export const SingleGestureType = { + Tap: 'TapGestureHandler', + LongPress: 'LongPressGestureHandler', + Pan: 'PanGestureHandler', + Pinch: 'PinchGestureHandler', + Rotation: 'RotationGestureHandler', + Fling: 'FlingGestureHandler', + Manual: 'ManualGestureHandler', + Native: 'NativeGestureHandler', +} as const; + +export const ComposedGestureType = { + Simultaneous: 'SimultaneousGesture', + Exclusive: 'ExclusiveGesture', + Race: 'RaceGesture', +} as const; // TODO: Find better name -export type HandlerType = SingleGestureType | ComposedGestureType; +export const HandlerType = { + ...SingleGestureType, + ...ComposedGestureType, +} as const; export type GestureEvents = { onGestureHandlerStateChange: ( @@ -88,23 +92,22 @@ export type GestureEvents = { }; export type GestureRelations = { - simultaneousGestures: number[]; - exclusiveGestures: number[]; + simultaneousHandlers: number[]; + waitFor: number[]; + blocksHandlers: number[]; }; export type NativeGesture = { tag: number; - type: HandlerType; + type: ValueOf; config: Record; gestureEvents: GestureEvents; - simultaneousHandlers: number[]; - waitFor: number[]; - blocksHandlers: number[]; + gestureRelations: GestureRelations; }; export type ComposedGesture = { tags: number[]; - type: ComposedGestureType; + type: ValueOf; config: { shouldUseReanimated: boolean; dispatchesAnimatedEvents: boolean; @@ -112,3 +115,5 @@ export type ComposedGesture = { gestureEvents: GestureEvents; gestures: (NativeGesture | ComposedGesture)[]; }; + +export type Gesture = NativeGesture | ComposedGesture; From fa599af28de9c77067e971684b13b9e0be4bd2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 20 Aug 2025 10:52:24 +0200 Subject: [PATCH 29/67] Use operation block --- .../apple/RNGestureHandlerModule.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index c8052bc822..965c9cd837 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -157,6 +157,8 @@ - (void)updateGestureHandlerConfig:(double)handlerTag newConfig:(NSDictionary *) [self addOperationBlock:^(RNGestureHandlerManager *manager) { [manager updateGestureHandlerConfig:[NSNumber numberWithDouble:handlerTag] config:config]; }]; + + [self flushOperations]; } - (void)configureRelations:(double)handlerTag relations:(NSDictionary *)relations From 42fc8c2b023a68e2c0facf7b8ed1acc121b47cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 20 Aug 2025 11:46:21 +0200 Subject: [PATCH 30/67] Move flushOperations into JS --- .../apple/RNGestureHandlerModule.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index 965c9cd837..c8052bc822 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -157,8 +157,6 @@ - (void)updateGestureHandlerConfig:(double)handlerTag newConfig:(NSDictionary *) [self addOperationBlock:^(RNGestureHandlerManager *manager) { [manager updateGestureHandlerConfig:[NSNumber numberWithDouble:handlerTag] config:config]; }]; - - [self flushOperations]; } - (void)configureRelations:(double)handlerTag relations:(NSDictionary *)relations From f62a6df0b8e09dab1ede4e0350044b56e60104ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 20 Aug 2025 12:08:00 +0200 Subject: [PATCH 31/67] Add exports for type --- .../src/v3/hooks/useGesture.ts | 13 ++++++++----- .../react-native-gesture-handler/src/v3/types.ts | 13 +++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 27f2688330..f01a7a9ebd 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -6,17 +6,20 @@ import { Reanimated, SharedValue, } from '../../handlers/gestures/reanimatedWrapper'; -import { hash, prepareConfig, isAnimatedEvent } from './utils'; +import { + hash, + prepareConfig, + isAnimatedEvent, + isComposedGesture, +} from './utils'; import { tagMessage } from '../../utils'; import { GestureRelations, - HandlerType, NativeGesture, ComposedGesture, Gesture, + SingleGestureType, } from '../types'; -import { isComposedGesture } from './utils'; -import { ValueOf } from '../../typeUtils'; function hasWorkletEventHandlers(config: Record) { return Object.values(config).some( @@ -118,7 +121,7 @@ function prepareRelations(config: any): GestureRelations { } export function useGesture( - type: ValueOf, + type: SingleGestureType, config: Record ): NativeGesture { const tag = useMemo(() => getNextHandlerTag(), []); diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 9c50624bd8..015cb2c7ac 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -61,18 +61,27 @@ export const SingleGestureType = { Native: 'NativeGestureHandler', } as const; +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type SingleGestureType = ValueOf; + export const ComposedGestureType = { Simultaneous: 'SimultaneousGesture', Exclusive: 'ExclusiveGesture', Race: 'RaceGesture', } as const; +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ComposedGestureType = ValueOf; + // TODO: Find better name export const HandlerType = { ...SingleGestureType, ...ComposedGestureType, } as const; +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type HandlerType = ValueOf; + export type GestureEvents = { onGestureHandlerStateChange: ( event: StateChangeEvent> @@ -99,7 +108,7 @@ export type GestureRelations = { export type NativeGesture = { tag: number; - type: ValueOf; + type: HandlerType; config: Record; gestureEvents: GestureEvents; gestureRelations: GestureRelations; @@ -107,7 +116,7 @@ export type NativeGesture = { export type ComposedGesture = { tags: number[]; - type: ValueOf; + type: ComposedGestureType; config: { shouldUseReanimated: boolean; dispatchesAnimatedEvents: boolean; From 504feae0f6fefb6f1bf760d9121f891d55696508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 Aug 2025 12:48:02 +0200 Subject: [PATCH 32/67] Add composed handler --- .../handlers/gestures/reanimatedWrapper.ts | 3 +++ .../react-native-gesture-handler/src/index.ts | 1 + .../src/v3/hooks/relations/index.ts | 3 +++ .../v3/hooks/relations/useComposedGesture.ts | 26 ++++++++++++++++--- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/relations/index.ts diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts index 483e401ecd..9875a7f710 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -59,6 +59,9 @@ let Reanimated: >( value: unknown ): value is WorkletFunction; + useComposedEventHandler( + handlers: (((event: T) => void) | null)[] + ): (event: T) => void; runOnUI( fn: (...args: A) => R ): (...args: Parameters) => void; diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts index 95adbeca92..870a12d980 100644 --- a/packages/react-native-gesture-handler/src/index.ts +++ b/packages/react-native-gesture-handler/src/index.ts @@ -166,6 +166,7 @@ export type { NativeDetectorProps } from './v3/NativeDetector/NativeDetector'; export { NativeDetector } from './v3/NativeDetector/NativeDetector'; export * from './v3/hooks/useGesture'; +export * from './v3/hooks/relations'; export { HandlerType } from './v3/types'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/index.ts new file mode 100644 index 0000000000..6c1019125c --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/index.ts @@ -0,0 +1,3 @@ +export { useSimultaneous } from './useSimultaneous'; +export { useExclusive } from './useExclusive'; +export { useRace } from './useRace'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 9cd11ea044..0e4a7f91e1 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -8,9 +8,9 @@ import { } from '../../types'; import { isComposedGesture } from '../utils'; import { tagMessage } from '../../../utils'; +import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; // TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) -// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks, @eslint-react/hooks-extra/no-unnecessary-use-prefix export function useComposedGesture( ...gestures: (NativeGesture | ComposedGesture)[] ): ComposedGesture { @@ -63,6 +63,24 @@ export function useComposedGesture( } }; + const onReanimatedStateChange = Reanimated?.useComposedEventHandler( + gestures.map( + (gesture) => gesture.gestureEvents.onReanimatedStateChange || null + ) + ); + + const onReanimatedUpdateEvent = Reanimated?.useComposedEventHandler( + gestures.map( + (gesture) => gesture.gestureEvents.onReanimatedUpdateEvent || null + ) + ); + + const onReanimatedTouchEvent = Reanimated?.useComposedEventHandler( + gestures.map( + (gesture) => gesture.gestureEvents.onReanimatedTouchEvent || null + ) + ); + let onGestureHandlerAnimatedEvent; for (const gesture of gestures) { @@ -82,9 +100,9 @@ export function useComposedGesture( onGestureHandlerStateChange, onGestureHandlerEvent, onGestureHandlerTouchEvent, - onReanimatedStateChange: undefined, - onReanimatedUpdateEvent: undefined, - onReanimatedTouchEvent: undefined, + onReanimatedStateChange, + onReanimatedUpdateEvent, + onReanimatedTouchEvent, onGestureHandlerAnimatedEvent, }, gestures, From e229d8435f107a9cf11b0d0704c13eb8f9239410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 Aug 2025 15:47:19 +0200 Subject: [PATCH 33/67] Rename dfs --- .../src/v3/NativeDetector/NativeDetector.tsx | 14 ++++++++------ .../src/v3/NativeDetector/utils.ts | 8 ++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index ac2ccae495..d27606a09b 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -6,7 +6,7 @@ import { Animated, StyleSheet } from 'react-native'; import HostGestureDetector from './HostGestureDetector'; import { tagMessage } from '../../utils'; import { isComposedGesture } from '../hooks/utils'; -import { dfs } from './utils'; +import { traverseGestureRelations } from './utils'; import RNGestureHandlerModule from '../../RNGestureHandlerModule'; export interface NativeDetectorProps { @@ -38,11 +38,13 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { } if (isComposedGesture(gesture)) { - if (gesture.type === ComposedGestureType.Simultaneous) { - dfs(gesture, new Set(gesture.tags)); - } else { - dfs(gesture); - } + traverseGestureRelations( + gesture, + new Set( + // If root is simultaneous, we want to add its tags to the set + gesture.type === ComposedGestureType.Simultaneous ? gesture.tags : [] + ) + ); } else { RNGestureHandlerModule.configureRelations(gesture.tag, { waitFor: gesture.gestureRelations.waitFor, diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 251f24b9e3..f81df5d7ef 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -9,9 +9,9 @@ import { isComposedGesture } from '../hooks/utils'; import { ComposedGesture, ComposedGestureType, NativeGesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. -export const dfs = ( +export const traverseGestureRelations = ( node: NativeGesture | ComposedGesture, - simultaneousHandlers: Set = new Set(), + simultaneousHandlers: Set, waitFor: number[] = [] ) => { // If we are in the leaf node, we want to fill gesture relations arrays with current @@ -61,7 +61,7 @@ export const dfs = ( const length = waitFor.length; // We traverse the child, passing the current `waitFor` and `simultaneousHandlers`. - dfs(child, simultaneousHandlers, waitFor); + traverseGestureRelations(child, simultaneousHandlers, waitFor); // After traversing the child, we need to update `waitFor` and `simultaneousHandlers` @@ -104,7 +104,7 @@ export const dfs = ( // This means that child is a leaf node. else { // In the leaf node, we only care about filling `waitFor` array. First we traverse the child... - dfs(child, simultaneousHandlers, waitFor); + traverseGestureRelations(child, simultaneousHandlers, waitFor); // ..and when we go back we add the tag of the child to the `waitFor` array. if (node.type === ComposedGestureType.Exclusive) { From fac31e25f018ae43e26402576ec593d78cb70686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 Aug 2025 16:08:37 +0200 Subject: [PATCH 34/67] Split utils --- .../src/v3/NativeDetector/NativeDetector.tsx | 2 +- .../src/v3/NativeDetector/utils.ts | 2 +- .../GestureHandlerEventWorkletHandler.ts | 3 +- ...GestureHandlerStateChangeWorkletHandler.ts | 3 +- .../GestureHandlerTouchEventWorkletHandler.ts | 9 +- .../js/useGestureStateChangeEvent.ts | 2 +- .../callbacks/js/useGestureTouchEvent.ts | 2 +- .../callbacks/js/useGestureUpdateEvent.ts | 3 +- .../useReanimatedStateChangeEvent.ts | 2 +- .../reanimated/useReanimatedTouchEvent.ts | 2 +- .../reanimated/useReanimatedUpdateEvent.ts | 2 +- .../v3/hooks/relations/useComposedGesture.ts | 2 +- .../src/v3/hooks/useGesture.ts | 119 ++-------------- .../src/v3/hooks/utils.ts | 127 +----------------- .../src/v3/hooks/utils/EventHandlersUtils.ts | 106 +++++++++++++++ .../src/v3/hooks/utils/ReanimatedUtils.ts | 77 +++++++++++ .../src/v3/hooks/utils/RelationUtils.ts | 43 ++++++ 17 files changed, 259 insertions(+), 247 deletions(-) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/utils/EventHandlersUtils.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/utils/ReanimatedUtils.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/utils/RelationUtils.ts diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index d27606a09b..3c971d03d9 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -5,9 +5,9 @@ import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; import { Animated, StyleSheet } from 'react-native'; import HostGestureDetector from './HostGestureDetector'; import { tagMessage } from '../../utils'; -import { isComposedGesture } from '../hooks/utils'; import { traverseGestureRelations } from './utils'; import RNGestureHandlerModule from '../../RNGestureHandlerModule'; +import { isComposedGesture } from '../hooks/utils/RelationUtils'; export interface NativeDetectorProps { children?: React.ReactNode; diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index f81df5d7ef..040a35ef78 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -5,7 +5,7 @@ // For `simultaneousHandlers` we use Set as the order doesn't matter. import RNGestureHandlerModule from '../../RNGestureHandlerModule'; -import { isComposedGesture } from '../hooks/utils'; +import { isComposedGesture } from '../hooks/utils/RelationUtils'; import { ComposedGesture, ComposedGestureType, NativeGesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts index 50e0538b61..fd3a33c89e 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts @@ -2,7 +2,8 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { tagMessage } from '../../../utils'; import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper'; import { CallbackHandlers, UpdateEvent } from '../../types'; -import { isEventForHandlerWithTag, runCallback } from '../utils'; +import { isEventForHandlerWithTag } from '../utils'; +import { runCallback } from '../utils/EventHandlersUtils'; export function getGestureHandlerEventWorkletHandler( handlerTag: number, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts index 0238317b3c..b26e428457 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts @@ -1,7 +1,8 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { State } from '../../../State'; import { CallbackHandlers, StateChangeEvent } from '../../types'; -import { isEventForHandlerWithTag, isNativeEvent, runCallback } from '../utils'; +import { isEventForHandlerWithTag, isNativeEvent } from '../utils'; +import { runCallback } from '../utils/EventHandlersUtils'; export function getGestureHandlerStateChangeWorkletHandler( handlerTag: number, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts index 4d29125912..2d5ed23ebd 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts @@ -1,13 +1,12 @@ import { NativeSyntheticEvent } from 'react-native'; import { CallbackHandlers, TouchEvent } from '../../types'; +import { isEventForHandlerWithTag, isNativeEvent } from '../utils'; +import { TouchEventType } from '../../../TouchEventType'; +import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; import { - isEventForHandlerWithTag, - isNativeEvent, runCallback, touchEventTypeToCallbackType, -} from '../utils'; -import { TouchEventType } from '../../../TouchEventType'; -import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; +} from '../utils/EventHandlersUtils'; export function getGestureHandlerTouchEventWorkletHandler( handlerTag: number, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index b27f04e244..1f865e1e6f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,4 +1,4 @@ -import { extractStateChangeHandlers } from '../../utils'; +import { extractStateChangeHandlers } from '../../utils/EventHandlersUtils'; import { getGestureHandlerStateChangeWorkletHandler } from '../GestureHandlerStateChangeWorkletHandler'; // eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index 9a2b921243..9fdfeacba1 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,4 +1,4 @@ -import { extractTouchHandlers } from '../../utils'; +import { extractTouchHandlers } from '../../utils/EventHandlersUtils'; import { getGestureHandlerTouchEventWorkletHandler } from '../GestureHandlerTouchEventWorkletHandler'; // eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index ab1c3b65cb..818fdef133 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -1,6 +1,7 @@ -import { extractUpdateHandlers, isAnimatedEvent } from '../../utils'; +import { isAnimatedEvent } from '../../utils'; import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; import { getGestureHandlerEventWorkletHandler } from '../GestureHandlerEventWorkletHandler'; +import { extractUpdateHandlers } from '../../utils/EventHandlersUtils'; // eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks export function useGestureUpdateEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts index 62eb38f0d2..a95da0f004 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts @@ -1,5 +1,5 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { extractStateChangeHandlers } from '../../utils'; +import { extractStateChangeHandlers } from '../../utils/EventHandlersUtils'; import { getGestureHandlerStateChangeWorkletHandler } from '../GestureHandlerStateChangeWorkletHandler'; export function useReanimatedStateChangeEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts index 906f897df8..7c29655d8f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts @@ -1,5 +1,5 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { extractTouchHandlers } from '../../utils'; +import { extractTouchHandlers } from '../../utils/EventHandlersUtils'; import { getGestureHandlerTouchEventWorkletHandler } from '../GestureHandlerTouchEventWorkletHandler'; export function useReanimatedTouchEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts index 8546764ebd..b9f33eb55b 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts @@ -1,5 +1,5 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { extractUpdateHandlers } from '../../utils'; +import { extractUpdateHandlers } from '../../utils/EventHandlersUtils'; import { getGestureHandlerEventWorkletHandler } from '../GestureHandlerEventWorkletHandler'; export function useReanimatedUpdateEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 0e4a7f91e1..2204007aaa 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -6,9 +6,9 @@ import { ComposedGesture, ComposedGestureType, } from '../../types'; -import { isComposedGesture } from '../utils'; import { tagMessage } from '../../../utils'; import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; +import { isComposedGesture } from '../utils/RelationUtils'; // TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) export function useComposedGesture( diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index f01a7a9ebd..d9486e6976 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -2,123 +2,20 @@ import { useEffect, useMemo } from 'react'; import { getNextHandlerTag } from '../../handlers/getNextHandlerTag'; import RNGestureHandlerModule from '../../RNGestureHandlerModule'; import { useGestureCallbacks } from './useGestureCallbacks'; +import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; import { - Reanimated, - SharedValue, -} from '../../handlers/gestures/reanimatedWrapper'; -import { - hash, prepareConfig, isAnimatedEvent, - isComposedGesture, + shouldHandleTouchEvents, } from './utils'; import { tagMessage } from '../../utils'; +import { NativeGesture, SingleGestureType } from '../types'; import { - GestureRelations, - NativeGesture, - ComposedGesture, - Gesture, - SingleGestureType, -} from '../types'; - -function hasWorkletEventHandlers(config: Record) { - return Object.values(config).some( - (prop) => typeof prop === 'function' && '__workletHash' in prop - ); -} - -function shouldHandleTouchEvents(config: Record) { - return ( - !!config.onTouchesDown || - !!config.onTouchesMove || - !!config.onTouchesUp || - !!config.onTouchesCancelled - ); -} - -const SHARED_VALUE_OFFSET = 1.618; - -// This is used to obtain HostFunction that can be executed on the UI thread -const { updateGestureHandlerConfig, flushOperations } = RNGestureHandlerModule; - -function bindSharedValues(config: any, handlerTag: number) { - if (Reanimated === undefined) { - return; - } - - const baseListenerId = handlerTag + SHARED_VALUE_OFFSET; - - const attachListener = (sharedValue: SharedValue, configKey: string) => { - 'worklet'; - const keyHash = hash(configKey); - const listenerId = baseListenerId + keyHash; - - sharedValue.addListener(listenerId, (value) => { - updateGestureHandlerConfig(handlerTag, { [configKey]: value }); - flushOperations(); - }); - }; - - for (const [key, maybeSharedValue] of Object.entries(config)) { - if (!Reanimated.isSharedValue(maybeSharedValue)) { - continue; - } - - Reanimated.runOnUI(attachListener)(maybeSharedValue, key); - } -} - -function unbindSharedValues(config: any, handlerTag: number) { - if (Reanimated === undefined) { - return; - } - - const baseListenerId = handlerTag + SHARED_VALUE_OFFSET; - - for (const [key, maybeSharedValue] of Object.entries(config)) { - if (!Reanimated.isSharedValue(maybeSharedValue)) { - continue; - } - - const keyHash = hash(key); - const listenerId = baseListenerId + keyHash; - - Reanimated.runOnUI(() => { - maybeSharedValue.removeListener(listenerId); - })(); - } -} - -function prepareRelations(config: any): GestureRelations { - const extractHandlerTags = (otherHandler: Gesture | Gesture[]): number[] => { - if (!otherHandler) { - return []; - } - - let otherTags: number[]; - - if (Array.isArray(otherHandler)) { - otherTags = otherHandler.flatMap( - (gesture: NativeGesture | ComposedGesture) => - isComposedGesture(gesture) ? gesture.tags : gesture.tag - ); - } else { - otherTags = isComposedGesture(otherHandler) - ? otherHandler.tags - : [otherHandler.tag]; - } - - return otherTags; - }; - - return { - simultaneousHandlers: extractHandlerTags( - config.simultaneousWithExternalGesture - ), - waitFor: extractHandlerTags(config.requireExternalGestureToFail), - blocksHandlers: extractHandlerTags(config.blocksExternalGesture), - }; -} + bindSharedValues, + hasWorkletEventHandlers, + unbindSharedValues, +} from './utils/ReanimatedUtils'; +import { prepareRelations } from './utils/RelationUtils'; export function useGesture( type: SingleGestureType, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index 8484721829..b48c5d3cfd 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -1,60 +1,14 @@ import { Animated, NativeSyntheticEvent } from 'react-native'; -import { CALLBACK_TYPE } from '../../handlers/gestures/gesture'; -import { TouchEventType } from '../../TouchEventType'; import { AnimatedEvent, - CallbackHandlers, GestureHandlerEvent, GestureStateChangeEventWithData, GestureUpdateEventWithData, - UpdateEvent, - NativeGesture, - ComposedGesture, } from '../types'; import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon'; import { tagMessage } from '../../utils'; import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; -export function getHandler(type: CALLBACK_TYPE, config: CallbackHandlers) { - 'worklet'; - switch (type) { - case CALLBACK_TYPE.BEGAN: - return config.onBegin; - case CALLBACK_TYPE.START: - return config.onStart; - case CALLBACK_TYPE.UPDATE: - return config.onUpdate; - case CALLBACK_TYPE.END: - return config.onEnd; - case CALLBACK_TYPE.FINALIZE: - return config.onFinalize; - case CALLBACK_TYPE.TOUCHES_DOWN: - return config.onTouchesDown; - case CALLBACK_TYPE.TOUCHES_MOVE: - return config.onTouchesMove; - case CALLBACK_TYPE.TOUCHES_UP: - return config.onTouchesUp; - case CALLBACK_TYPE.TOUCHES_CANCELLED: - return config.onTouchesCancelled; - } -} - -export function touchEventTypeToCallbackType( - eventType: TouchEventType -): CALLBACK_TYPE { - 'worklet'; - switch (eventType) { - case TouchEventType.TOUCHES_DOWN: - return CALLBACK_TYPE.TOUCHES_DOWN; - case TouchEventType.TOUCHES_MOVE: - return CALLBACK_TYPE.TOUCHES_MOVE; - case TouchEventType.TOUCHES_UP: - return CALLBACK_TYPE.TOUCHES_UP; - case TouchEventType.TOUCHES_CANCELLED: - return CALLBACK_TYPE.TOUCHES_CANCELLED; - } - return CALLBACK_TYPE.UNDEFINED; -} export function isNativeEvent( event: GestureHandlerEvent ): event is @@ -66,20 +20,6 @@ export function isNativeEvent( return 'nativeEvent' in event; } -export function runCallback( - type: CALLBACK_TYPE, - config: CallbackHandlers, - event: GestureHandlerEvent>, - ...args: unknown[] -) { - 'worklet'; - const handler = getHandler(type, config); - - // TODO: add proper types (likely boolean) - // @ts-ignore It works, duh - handler?.(isNativeEvent(event) ? event.nativeEvent : event, ...args); -} - export function isEventForHandlerWithTag( handlerTag: number, event: GestureHandlerEvent> @@ -113,49 +53,6 @@ export function checkMappingForChangeProperties(obj: Animated.Mapping) { } } -export function extractStateChangeHandlers(config: any): CallbackHandlers { - 'worklet'; - const { onBegin, onStart, onEnd, onFinalize } = config; - - const handlers: CallbackHandlers = { - ...(onBegin ? { onBegin } : {}), - ...(onStart ? { onStart } : {}), - ...(onEnd ? { onEnd } : {}), - ...(onFinalize ? { onFinalize } : {}), - }; - - return handlers; -} - -export function extractUpdateHandlers(config: any): { - handlers: CallbackHandlers; - changeEventCalculator?: ( - current: UpdateEvent>, - previous?: UpdateEvent> - ) => UpdateEvent>; -} { - 'worklet'; - const { onUpdate, changeEventCalculator } = config; - - const handlers: CallbackHandlers = { ...(onUpdate ? { onUpdate } : {}) }; - - return { handlers, changeEventCalculator }; -} - -export function extractTouchHandlers(config: any): CallbackHandlers { - const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = - config; - - const handlers: CallbackHandlers = { - ...(onTouchesDown ? { onTouchesDown } : {}), - ...(onTouchesMove ? { onTouchesMove } : {}), - ...(onTouchesUp ? { onTouchesUp } : {}), - ...(onTouchesCancelled ? { onTouchesCancelled } : {}), - }; - - return handlers; -} - export function prepareConfig(config: any) { const copy = { ...config }; @@ -172,21 +69,11 @@ export function prepareConfig(config: any) { return copy; } -// Variant of djb2 hash function. -// Taken from https://gist.github.com/eplawless/52813b1d8ad9af510d85?permalink_comment_id=3367765#gistcomment-3367765 -export function hash(str: string) { - 'worklet'; - const len = str.length; - let h = 5381; - - for (let i = 0; i < len; i++) { - h = (h * 33) ^ str.charCodeAt(i); - } - return h >>> 0; -} - -export function isComposedGesture( - gesture: NativeGesture | ComposedGesture -): gesture is ComposedGesture { - return 'tags' in gesture; +export function shouldHandleTouchEvents(config: Record) { + return ( + !!config.onTouchesDown || + !!config.onTouchesMove || + !!config.onTouchesUp || + !!config.onTouchesCancelled + ); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/EventHandlersUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/EventHandlersUtils.ts new file mode 100644 index 0000000000..963e5c5ecd --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/EventHandlersUtils.ts @@ -0,0 +1,106 @@ +import { TouchEventType } from '../../../TouchEventType'; +import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; +import { + CallbackHandlers, + GestureHandlerEvent, + UpdateEvent, +} from '../../types'; +import { isNativeEvent } from '../utils'; + +export function extractStateChangeHandlers(config: any): CallbackHandlers { + 'worklet'; + const { onBegin, onStart, onEnd, onFinalize } = config; + + const handlers: CallbackHandlers = { + ...(onBegin ? { onBegin } : {}), + ...(onStart ? { onStart } : {}), + ...(onEnd ? { onEnd } : {}), + ...(onFinalize ? { onFinalize } : {}), + }; + + return handlers; +} + +export function extractUpdateHandlers(config: any): { + handlers: CallbackHandlers; + changeEventCalculator?: ( + current: UpdateEvent>, + previous?: UpdateEvent> + ) => UpdateEvent>; +} { + 'worklet'; + const { onUpdate, changeEventCalculator } = config; + + const handlers: CallbackHandlers = { ...(onUpdate ? { onUpdate } : {}) }; + + return { handlers, changeEventCalculator }; +} + +export function extractTouchHandlers(config: any): CallbackHandlers { + const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = + config; + + const handlers: CallbackHandlers = { + ...(onTouchesDown ? { onTouchesDown } : {}), + ...(onTouchesMove ? { onTouchesMove } : {}), + ...(onTouchesUp ? { onTouchesUp } : {}), + ...(onTouchesCancelled ? { onTouchesCancelled } : {}), + }; + + return handlers; +} + +export function getHandler(type: CALLBACK_TYPE, config: CallbackHandlers) { + 'worklet'; + switch (type) { + case CALLBACK_TYPE.BEGAN: + return config.onBegin; + case CALLBACK_TYPE.START: + return config.onStart; + case CALLBACK_TYPE.UPDATE: + return config.onUpdate; + case CALLBACK_TYPE.END: + return config.onEnd; + case CALLBACK_TYPE.FINALIZE: + return config.onFinalize; + case CALLBACK_TYPE.TOUCHES_DOWN: + return config.onTouchesDown; + case CALLBACK_TYPE.TOUCHES_MOVE: + return config.onTouchesMove; + case CALLBACK_TYPE.TOUCHES_UP: + return config.onTouchesUp; + case CALLBACK_TYPE.TOUCHES_CANCELLED: + return config.onTouchesCancelled; + } +} + +export function touchEventTypeToCallbackType( + eventType: TouchEventType +): CALLBACK_TYPE { + 'worklet'; + switch (eventType) { + case TouchEventType.TOUCHES_DOWN: + return CALLBACK_TYPE.TOUCHES_DOWN; + case TouchEventType.TOUCHES_MOVE: + return CALLBACK_TYPE.TOUCHES_MOVE; + case TouchEventType.TOUCHES_UP: + return CALLBACK_TYPE.TOUCHES_UP; + case TouchEventType.TOUCHES_CANCELLED: + return CALLBACK_TYPE.TOUCHES_CANCELLED; + } + return CALLBACK_TYPE.UNDEFINED; +} + +export function runCallback( + type: CALLBACK_TYPE, + config: CallbackHandlers, + event: GestureHandlerEvent>, + ...args: unknown[] +) { + 'worklet'; + const handler = getHandler(type, config); + + // TODO: add proper types (likely boolean) + // @ts-ignore It works, duh + handler?.(isNativeEvent(event) ? event.nativeEvent : event, ...args); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/ReanimatedUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/ReanimatedUtils.ts new file mode 100644 index 0000000000..c0cf19cc81 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/ReanimatedUtils.ts @@ -0,0 +1,77 @@ +import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; +import { + Reanimated, + SharedValue, +} from '../../../handlers/gestures/reanimatedWrapper'; + +// Variant of djb2 hash function. +// Taken from https://gist.github.com/eplawless/52813b1d8ad9af510d85?permalink_comment_id=3367765#gistcomment-3367765 +function hash(str: string) { + 'worklet'; + const len = str.length; + let h = 5381; + + for (let i = 0; i < len; i++) { + h = (h * 33) ^ str.charCodeAt(i); + } + return h >>> 0; +} + +const SHARED_VALUE_OFFSET = 1.618; + +// This is used to obtain HostFunction that can be executed on the UI thread +const { updateGestureHandlerConfig, flushOperations } = RNGestureHandlerModule; + +export function bindSharedValues(config: any, handlerTag: number) { + if (Reanimated === undefined) { + return; + } + + const baseListenerId = handlerTag + SHARED_VALUE_OFFSET; + + const attachListener = (sharedValue: SharedValue, configKey: string) => { + 'worklet'; + const keyHash = hash(configKey); + const listenerId = baseListenerId + keyHash; + + sharedValue.addListener(listenerId, (value) => { + updateGestureHandlerConfig(handlerTag, { [configKey]: value }); + flushOperations(); + }); + }; + + for (const [key, maybeSharedValue] of Object.entries(config)) { + if (!Reanimated.isSharedValue(maybeSharedValue)) { + continue; + } + + Reanimated.runOnUI(attachListener)(maybeSharedValue, key); + } +} + +export function unbindSharedValues(config: any, handlerTag: number) { + if (Reanimated === undefined) { + return; + } + + const baseListenerId = handlerTag + SHARED_VALUE_OFFSET; + + for (const [key, maybeSharedValue] of Object.entries(config)) { + if (!Reanimated.isSharedValue(maybeSharedValue)) { + continue; + } + + const keyHash = hash(key); + const listenerId = baseListenerId + keyHash; + + Reanimated.runOnUI(() => { + maybeSharedValue.removeListener(listenerId); + })(); + } +} + +export function hasWorkletEventHandlers(config: Record) { + return Object.values(config).some( + (prop) => typeof prop === 'function' && '__workletHash' in prop + ); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/RelationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/RelationUtils.ts new file mode 100644 index 0000000000..79bc4e0ec2 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/RelationUtils.ts @@ -0,0 +1,43 @@ +import { + ComposedGesture, + Gesture, + GestureRelations, + NativeGesture, +} from '../../types'; + +export function isComposedGesture( + gesture: NativeGesture | ComposedGesture +): gesture is ComposedGesture { + return 'tags' in gesture; +} + +export function prepareRelations(config: any): GestureRelations { + const extractHandlerTags = (otherHandler: Gesture | Gesture[]): number[] => { + if (!otherHandler) { + return []; + } + + let otherTags: number[]; + + if (Array.isArray(otherHandler)) { + otherTags = otherHandler.flatMap( + (gesture: NativeGesture | ComposedGesture) => + isComposedGesture(gesture) ? gesture.tags : gesture.tag + ); + } else { + otherTags = isComposedGesture(otherHandler) + ? otherHandler.tags + : [otherHandler.tag]; + } + + return otherTags; + }; + + return { + simultaneousHandlers: extractHandlerTags( + config.simultaneousWithExternalGesture + ), + waitFor: extractHandlerTags(config.requireExternalGestureToFail), + blocksHandlers: extractHandlerTags(config.blocksExternalGesture), + }; +} From fb88c8cdf660982114e34cab96ad7a49d9370b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 4 Sep 2025 10:52:13 +0200 Subject: [PATCH 35/67] Change hook eslint rule to warning --- .eslintrc.json | 1 + .../src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts | 1 - .../src/v3/hooks/callbacks/js/useGestureTouchEvent.ts | 1 - .../src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 166c283d52..8d7a7e9287 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -71,6 +71,7 @@ "@eslint-react/no-array-index-key": "warn", "@eslint-react/hooks-extra/no-direct-set-state-in-use-effect": "warn", "@eslint-react/hooks-extra/prefer-use-state-lazy-initialization": "warn", + "@eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks": "warn", "no-redeclare": "off", "@typescript-eslint/no-redeclare": "error", "no-use-before-define": "off", diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index b27f04e244..9af705fc21 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,7 +1,6 @@ import { extractStateChangeHandlers } from '../../utils'; import { getGestureHandlerStateChangeWorkletHandler } from '../GestureHandlerStateChangeWorkletHandler'; -// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks export function useGestureStateChangeEvent(handlerTag: number, config: any) { const handlers = extractStateChangeHandlers(config); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index 9a2b921243..13b061d6e8 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,7 +1,6 @@ import { extractTouchHandlers } from '../../utils'; import { getGestureHandlerTouchEventWorkletHandler } from '../GestureHandlerTouchEventWorkletHandler'; -// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks export function useGestureTouchEvent(handlerTag: number, config: any) { const handlers = extractTouchHandlers(config); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index ab1c3b65cb..5e907ba42e 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -2,7 +2,6 @@ import { extractUpdateHandlers, isAnimatedEvent } from '../../utils'; import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; import { getGestureHandlerEventWorkletHandler } from '../GestureHandlerEventWorkletHandler'; -// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks export function useGestureUpdateEvent(handlerTag: number, config: any) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); From 8e5f07e2ca844c774cdbcb73fce87d8e2e7732e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 4 Sep 2025 12:51:00 +0200 Subject: [PATCH 36/67] Extract choosing handler type into function --- .../apple/RNGestureHandler.mm | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index ace5c41b3a..d63c736675 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -313,6 +313,13 @@ - (void)handleGesture:(UIGestureRecognizer *)recognizer inState:(RNGestureHandle [self sendEventsInState:self.state forViewWithTag:tag withExtraData:eventData]; } +- (RNGestureHandlerEventHandlerType)chooseEventHandlerType +{ + return _dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated + : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated + : RNGestureHandlerEventHandlerTypeJS; +} + - (void)sendEventsInState:(RNGestureHandlerState)state forViewWithTag:(nonnull NSNumber *)reactTag withExtraData:(RNGestureHandlerEventExtraData *)extraData @@ -360,15 +367,12 @@ - (void)sendEventsInState:(RNGestureHandlerState)state } if (state == RNGestureHandlerStateActive) { - id touchEvent = [[RNGestureHandlerEvent alloc] - initWithReactTag:reactTag - handlerTag:_tag - state:state - extraData:extraData - forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated - : RNGestureHandlerEventHandlerTypeJS - coalescingKey:self->_eventCoalescingKey]; + id touchEvent = [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag + handlerTag:_tag + state:state + extraData:extraData + forHandlerType:[self chooseEventHandlerType] + coalescingKey:self->_eventCoalescingKey]; [self sendEvent:touchEvent]; } } @@ -385,42 +389,32 @@ - (void)sendEvent:(RNGestureHandlerStateChange *)event { [self.emitter sendEvent:event withActionType:self.actionType - forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated - : RNGestureHandlerEventHandlerTypeJS + forHandlerType:[self chooseEventHandlerType] forView:[self findViewForEvents]]; } - (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(NSNumber *)reactTag { if (_actionType == RNGestureHandlerActionTypeNativeDetector) { - [self.emitter - sendNativeTouchEventForGestureHandler:self - withPointerType:_pointerType - forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated - : RNGestureHandlerEventHandlerTypeJS]; + [self.emitter sendNativeTouchEventForGestureHandler:self + withPointerType:_pointerType + forHandlerType:[self chooseEventHandlerType]]; } else { id extraData = [RNGestureHandlerEventExtraData forEventType:_pointerTracker.eventType withChangedPointers:_pointerTracker.changedPointersData withAllPointers:_pointerTracker.allPointersData withNumberOfTouches:_pointerTracker.trackedPointersCount withPointerType:_pointerType]; - id event = [[RNGestureHandlerEvent alloc] - initWithReactTag:reactTag - handlerTag:_tag - state:state - extraData:extraData - forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated - : RNGestureHandlerEventHandlerTypeJS - coalescingKey:[_tag intValue]]; + id event = [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag + handlerTag:_tag + state:state + extraData:extraData + forHandlerType:[self chooseEventHandlerType] + coalescingKey:[_tag intValue]]; [self.emitter sendEvent:event withActionType:self.actionType - forHandlerType:_dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated - : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated - : RNGestureHandlerEventHandlerTypeJS + forHandlerType:[self chooseEventHandlerType] forView:self.recognizer.view]; } } From feaa1672580df65add84469c5f3e74d8730bc43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 4 Sep 2025 13:12:57 +0200 Subject: [PATCH 37/67] Rename event handlers --- .../v3/hooks/callbacks/js/useGestureStateChangeEvent.ts | 4 ++-- .../src/v3/hooks/callbacks/js/useGestureTouchEvent.ts | 4 ++-- .../src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts | 9 ++------- .../reanimated/useReanimatedStateChangeEvent.ts | 7 ++----- .../callbacks/reanimated/useReanimatedTouchEvent.ts | 7 ++----- .../callbacks/reanimated/useReanimatedUpdateEvent.ts | 4 ++-- ...tateChangeWorkletHandler.ts => stateChangeHandler.ts} | 2 +- ...rTouchEventWorkletHandler.ts => touchEventHandler.ts} | 2 +- ...ureHandlerEventWorkletHandler.ts => updateHandler.ts} | 2 +- 9 files changed, 15 insertions(+), 26 deletions(-) rename packages/react-native-gesture-handler/src/v3/hooks/callbacks/{GestureHandlerStateChangeWorkletHandler.ts => stateChangeHandler.ts} (96%) rename packages/react-native-gesture-handler/src/v3/hooks/callbacks/{GestureHandlerTouchEventWorkletHandler.ts => touchEventHandler.ts} (95%) rename packages/react-native-gesture-handler/src/v3/hooks/callbacks/{GestureHandlerEventWorkletHandler.ts => updateHandler.ts} (95%) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index 9af705fc21..4bc8ae7a5c 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,8 +1,8 @@ import { extractStateChangeHandlers } from '../../utils'; -import { getGestureHandlerStateChangeWorkletHandler } from '../GestureHandlerStateChangeWorkletHandler'; +import { getStateChangeHandler } from '../stateChangeHandler'; export function useGestureStateChangeEvent(handlerTag: number, config: any) { const handlers = extractStateChangeHandlers(config); - return getGestureHandlerStateChangeWorkletHandler(handlerTag, handlers); + return getStateChangeHandler(handlerTag, handlers); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index 13b061d6e8..c234cfb475 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,8 +1,8 @@ import { extractTouchHandlers } from '../../utils'; -import { getGestureHandlerTouchEventWorkletHandler } from '../GestureHandlerTouchEventWorkletHandler'; +import { getTouchEventHandler } from '../touchEventHandler'; export function useGestureTouchEvent(handlerTag: number, config: any) { const handlers = extractTouchHandlers(config); - return getGestureHandlerTouchEventWorkletHandler(handlerTag, handlers); + return getTouchEventHandler(handlerTag, handlers); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index 5e907ba42e..81cbac361d 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -1,6 +1,6 @@ import { extractUpdateHandlers, isAnimatedEvent } from '../../utils'; import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; -import { getGestureHandlerEventWorkletHandler } from '../GestureHandlerEventWorkletHandler'; +import { getUpdateHandler } from '../updateHandler'; export function useGestureUpdateEvent(handlerTag: number, config: any) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); @@ -11,10 +11,5 @@ export function useGestureUpdateEvent(handlerTag: number, config: any) { return isAnimatedEvent(config.onUpdate) ? undefined - : getGestureHandlerEventWorkletHandler( - handlerTag, - handlers, - jsContext, - changeEventCalculator - ); + : getUpdateHandler(handlerTag, handlers, jsContext, changeEventCalculator); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts index 62eb38f0d2..615384ed52 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts @@ -1,14 +1,11 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; import { extractStateChangeHandlers } from '../../utils'; -import { getGestureHandlerStateChangeWorkletHandler } from '../GestureHandlerStateChangeWorkletHandler'; +import { getStateChangeHandler } from '../stateChangeHandler'; export function useReanimatedStateChangeEvent(handlerTag: number, config: any) { const handlers = extractStateChangeHandlers(config); - const callback = getGestureHandlerStateChangeWorkletHandler( - handlerTag, - handlers - ); + const callback = getStateChangeHandler(handlerTag, handlers); const reanimatedHandler = Reanimated?.useHandler(handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts index 906f897df8..62f8684145 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts @@ -1,14 +1,11 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; import { extractTouchHandlers } from '../../utils'; -import { getGestureHandlerTouchEventWorkletHandler } from '../GestureHandlerTouchEventWorkletHandler'; +import { getTouchEventHandler } from '../touchEventHandler'; export function useReanimatedTouchEvent(handlerTag: number, config: any) { const handlers = extractTouchHandlers(config); - const callback = getGestureHandlerTouchEventWorkletHandler( - handlerTag, - handlers - ); + const callback = getTouchEventHandler(handlerTag, handlers); const reanimatedHandler = Reanimated?.useHandler(handlers); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts index 8546764ebd..0808ebbf42 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts @@ -1,6 +1,6 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; import { extractUpdateHandlers } from '../../utils'; -import { getGestureHandlerEventWorkletHandler } from '../GestureHandlerEventWorkletHandler'; +import { getUpdateHandler } from '../updateHandler'; export function useReanimatedUpdateEvent(handlerTag: number, config: any) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); @@ -18,7 +18,7 @@ export function useReanimatedUpdateEvent(handlerTag: number, config: any) { const reanimatedHandler = Reanimated?.useHandler(handlers); - const callback = getGestureHandlerEventWorkletHandler( + const callback = getUpdateHandler( handlerTag, handlers, reanimatedHandler?.context, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/stateChangeHandler.ts similarity index 96% rename from packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/stateChangeHandler.ts index 0238317b3c..c593a6532a 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerStateChangeWorkletHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/stateChangeHandler.ts @@ -3,7 +3,7 @@ import { State } from '../../../State'; import { CallbackHandlers, StateChangeEvent } from '../../types'; import { isEventForHandlerWithTag, isNativeEvent, runCallback } from '../utils'; -export function getGestureHandlerStateChangeWorkletHandler( +export function getStateChangeHandler( handlerTag: number, callbacks: CallbackHandlers ) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/touchEventHandler.ts similarity index 95% rename from packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/touchEventHandler.ts index 4d29125912..4367174fa4 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerTouchEventWorkletHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/touchEventHandler.ts @@ -9,7 +9,7 @@ import { import { TouchEventType } from '../../../TouchEventType'; import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; -export function getGestureHandlerTouchEventWorkletHandler( +export function getTouchEventHandler( handlerTag: number, callbacks: CallbackHandlers ) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/updateHandler.ts similarity index 95% rename from packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts rename to packages/react-native-gesture-handler/src/v3/hooks/callbacks/updateHandler.ts index 50e0538b61..55ed7e5b31 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/GestureHandlerEventWorkletHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/updateHandler.ts @@ -4,7 +4,7 @@ import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper' import { CallbackHandlers, UpdateEvent } from '../../types'; import { isEventForHandlerWithTag, runCallback } from '../utils'; -export function getGestureHandlerEventWorkletHandler( +export function getUpdateHandler( handlerTag: number, callbacks: CallbackHandlers, context: ReanimatedContext | undefined, From 3f2ea8c8b62e28a2f2b93d153c7f0c4d9df51e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 4 Sep 2025 13:24:58 +0200 Subject: [PATCH 38/67] Improve error message --- .../react-native-gesture-handler/src/v3/hooks/useGesture.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 897472600a..eaa0c2d619 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -132,7 +132,9 @@ export function useGesture( if (config.dispatchesAnimatedEvents && config.shouldUseReanimated) { throw new Error( - tagMessage('Cannot use Reanimated and Animated events at the same time.') + tagMessage( + `${type}: You cannot use Animated.Event together with callbacks running on the UI thread. Either remove Animated.Event from onUpdate, or set runOnJS property to true on the gesture.` + ) ); } From 4022b9600f36858c94fdf4276c5f0e8f00e2dbc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 4 Sep 2025 14:08:08 +0200 Subject: [PATCH 39/67] Move for loop inside function --- .../src/v3/hooks/useGestureCallbacks.ts | 5 +--- .../src/v3/hooks/utils.ts | 25 +++++++++++-------- .../src/v3/types.ts | 4 +-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts index 5f477a196b..37c3b641e1 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts @@ -30,10 +30,7 @@ export function useGestureCallbacks(handlerTag: number, config: any) { let onGestureHandlerAnimatedEvent: AnimatedEvent | undefined; if (isAnimatedEvent(config.onUpdate)) { - for (const mapping of config.onUpdate._argMapping) { - checkMappingForChangeProperties(mapping); - } - + checkMappingForChangeProperties(config.onUpdate); // TODO: Remove cast when config is properly typed. onGestureHandlerAnimatedEvent = config.onUpdate as AnimatedEvent; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index cbfce7a903..c55c121f97 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -1,4 +1,4 @@ -import { Animated, NativeSyntheticEvent } from 'react-native'; +import { NativeSyntheticEvent } from 'react-native'; import { CALLBACK_TYPE } from '../../handlers/gestures/gesture'; import { TouchEventType } from '../../TouchEventType'; import { @@ -97,16 +97,21 @@ export function isAnimatedEvent( return !!callback && '_argMapping' in callback; } -export function checkMappingForChangeProperties(obj: Animated.Mapping) { - if (!('nativeEvent' in obj) || !('handlerData' in obj.nativeEvent)) { - return; - } +export function checkMappingForChangeProperties(animatedEvent: AnimatedEvent) { + for (const mapping of animatedEvent._argMapping) { + if ( + !mapping || + !('nativeEvent' in mapping && 'handlerData' in mapping.nativeEvent) + ) { + continue; + } - for (const key in obj.nativeEvent.handlerData) { - if (key.startsWith('change')) { - throw new Error( - tagMessage(`${key} is not available when using Animated.Event.`) - ); + for (const key in mapping.nativeEvent.handlerData) { + if (key.startsWith('change')) { + throw new Error( + tagMessage(`${key} is not available when using Animated.Event.`) + ); + } } } } diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 3f04f28a9f..03611a4157 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -1,4 +1,4 @@ -import { NativeSyntheticEvent } from 'react-native'; +import { Animated, NativeSyntheticEvent } from 'react-native'; import { GestureEventPayload, GestureTouchEvent, @@ -46,5 +46,5 @@ export type CallbackHandlers = Omit< // 1. Distinguish it from a regular function, // 2. Have access to the _argMapping property to check for usage of `change*` callbacks. export type AnimatedEvent = ((...args: any[]) => void) & { - _argMapping?: unknown; + _argMapping: (Animated.Mapping | null)[]; }; From 2dc44b8b43e9fe4b359e43efe670e2f0ba48ce2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 4 Sep 2025 18:38:02 +0200 Subject: [PATCH 40/67] Do not drop relataions on config change --- .../react-native-gesture-handler/apple/RNGestureHandler.mm | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index 1ce1fb6dae..a5d5180e1f 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -103,9 +103,6 @@ - (void)resetConfig self.enabled = YES; self.manualActivation = NO; _shouldCancelWhenOutside = NO; - _handlersToWaitFor = nil; - _simultaneousHandlers = nil; - _handlersThatShouldWait = nil; _hitSlop = RNGHHitSlopEmpty; _needsPointerData = NO; _dispatchesAnimatedEvents = NO; From fab60e5f5893122a612cfee4f8cde8b2a03789bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 5 Sep 2025 09:49:00 +0200 Subject: [PATCH 41/67] Rename method --- .../apple/RNGestureHandler.mm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index d63c736675..12f563ff0e 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -313,7 +313,7 @@ - (void)handleGesture:(UIGestureRecognizer *)recognizer inState:(RNGestureHandle [self sendEventsInState:self.state forViewWithTag:tag withExtraData:eventData]; } -- (RNGestureHandlerEventHandlerType)chooseEventHandlerType +- (RNGestureHandlerEventHandlerType)eventHandlerType { return _dispatchesAnimatedEvents ? RNGestureHandlerEventHandlerTypeAnimated : _dispatchesReanimatedEvents ? RNGestureHandlerEventHandlerTypeReanimated @@ -371,7 +371,7 @@ - (void)sendEventsInState:(RNGestureHandlerState)state handlerTag:_tag state:state extraData:extraData - forHandlerType:[self chooseEventHandlerType] + forHandlerType:[self eventHandlerType] coalescingKey:self->_eventCoalescingKey]; [self sendEvent:touchEvent]; } @@ -389,7 +389,7 @@ - (void)sendEvent:(RNGestureHandlerStateChange *)event { [self.emitter sendEvent:event withActionType:self.actionType - forHandlerType:[self chooseEventHandlerType] + forHandlerType:[self eventHandlerType] forView:[self findViewForEvents]]; } @@ -398,7 +398,7 @@ - (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(NSNum if (_actionType == RNGestureHandlerActionTypeNativeDetector) { [self.emitter sendNativeTouchEventForGestureHandler:self withPointerType:_pointerType - forHandlerType:[self chooseEventHandlerType]]; + forHandlerType:[self eventHandlerType]]; } else { id extraData = [RNGestureHandlerEventExtraData forEventType:_pointerTracker.eventType withChangedPointers:_pointerTracker.changedPointersData @@ -409,12 +409,12 @@ - (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(NSNum handlerTag:_tag state:state extraData:extraData - forHandlerType:[self chooseEventHandlerType] + forHandlerType:[self eventHandlerType] coalescingKey:[_tag intValue]]; [self.emitter sendEvent:event withActionType:self.actionType - forHandlerType:[self chooseEventHandlerType] + forHandlerType:[self eventHandlerType] forView:self.recognizer.view]; } } From 832ffb041acfdd6fd21bf10f8aa50b707f5c1728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 5 Sep 2025 10:08:17 +0200 Subject: [PATCH 42/67] Rename files to camelCase --- .../src/v3/NativeDetector/NativeDetector.tsx | 2 +- .../src/v3/NativeDetector/utils.ts | 2 +- .../src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts | 2 +- .../src/v3/hooks/callbacks/js/useGestureTouchEvent.ts | 2 +- .../src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts | 2 +- .../callbacks/reanimated/useReanimatedStateChangeEvent.ts | 2 +- .../v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts | 2 +- .../v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts | 2 +- .../src/v3/hooks/callbacks/stateChangeHandler.ts | 2 +- .../src/v3/hooks/callbacks/touchEventHandler.ts | 2 +- .../src/v3/hooks/callbacks/updateHandler.ts | 2 +- .../src/v3/hooks/relations/useComposedGesture.ts | 2 +- .../react-native-gesture-handler/src/v3/hooks/useGesture.ts | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index 3c971d03d9..4532d31497 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -7,7 +7,7 @@ import HostGestureDetector from './HostGestureDetector'; import { tagMessage } from '../../utils'; import { traverseGestureRelations } from './utils'; import RNGestureHandlerModule from '../../RNGestureHandlerModule'; -import { isComposedGesture } from '../hooks/utils/RelationUtils'; +import { isComposedGesture } from '../hooks/utils/relationUtils'; export interface NativeDetectorProps { children?: React.ReactNode; diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 040a35ef78..4c1ac71522 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -5,7 +5,7 @@ // For `simultaneousHandlers` we use Set as the order doesn't matter. import RNGestureHandlerModule from '../../RNGestureHandlerModule'; -import { isComposedGesture } from '../hooks/utils/RelationUtils'; +import { isComposedGesture } from '../hooks/utils/relationUtils'; import { ComposedGesture, ComposedGestureType, NativeGesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts index 5ca4b7f8b4..699bb29c46 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureStateChangeEvent.ts @@ -1,4 +1,4 @@ -import { extractStateChangeHandlers } from '../../utils/EventHandlersUtils'; +import { extractStateChangeHandlers } from '../../utils/eventHandlersUtils'; import { getStateChangeHandler } from '../stateChangeHandler'; export function useGestureStateChangeEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts index d59a353290..f98068ab68 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureTouchEvent.ts @@ -1,4 +1,4 @@ -import { extractTouchHandlers } from '../../utils/EventHandlersUtils'; +import { extractTouchHandlers } from '../../utils/eventHandlersUtils'; import { getTouchEventHandler } from '../touchEventHandler'; export function useGestureTouchEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts index cfbc4ae3b0..5cc938339f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/js/useGestureUpdateEvent.ts @@ -1,6 +1,6 @@ import { isAnimatedEvent } from '../../utils'; import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; -import { extractUpdateHandlers } from '../../utils/EventHandlersUtils'; +import { extractUpdateHandlers } from '../../utils/eventHandlersUtils'; import { getUpdateHandler } from '../updateHandler'; export function useGestureUpdateEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts index 73f0d6b3a9..86253ae056 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedStateChangeEvent.ts @@ -1,5 +1,5 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { extractStateChangeHandlers } from '../../utils/EventHandlersUtils'; +import { extractStateChangeHandlers } from '../../utils/eventHandlersUtils'; import { getStateChangeHandler } from '../stateChangeHandler'; export function useReanimatedStateChangeEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts index 5f17948682..81e0e3f204 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedTouchEvent.ts @@ -1,5 +1,5 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { extractTouchHandlers } from '../../utils/EventHandlersUtils'; +import { extractTouchHandlers } from '../../utils/eventHandlersUtils'; import { getTouchEventHandler } from '../touchEventHandler'; export function useReanimatedTouchEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts index 908b12a2bc..7f4f0083d1 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/reanimated/useReanimatedUpdateEvent.ts @@ -1,5 +1,5 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; -import { extractUpdateHandlers } from '../../utils/EventHandlersUtils'; +import { extractUpdateHandlers } from '../../utils/eventHandlersUtils'; import { getUpdateHandler } from '../updateHandler'; export function useReanimatedUpdateEvent(handlerTag: number, config: any) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/stateChangeHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/stateChangeHandler.ts index 8c2183bdfb..ad6a0ef511 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/stateChangeHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/stateChangeHandler.ts @@ -2,7 +2,7 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { State } from '../../../State'; import { CallbackHandlers, StateChangeEvent } from '../../types'; import { isEventForHandlerWithTag, isNativeEvent } from '../utils'; -import { runCallback } from '../utils/EventHandlersUtils'; +import { runCallback } from '../utils/eventHandlersUtils'; export function getStateChangeHandler( handlerTag: number, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/touchEventHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/touchEventHandler.ts index a792a3997f..7f58c888ef 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/touchEventHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/touchEventHandler.ts @@ -6,7 +6,7 @@ import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; import { runCallback, touchEventTypeToCallbackType, -} from '../utils/EventHandlersUtils'; +} from '../utils/eventHandlersUtils'; export function getTouchEventHandler( handlerTag: number, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/updateHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/updateHandler.ts index cdcd01e51f..e18dd31c8c 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/updateHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/updateHandler.ts @@ -3,7 +3,7 @@ import { tagMessage } from '../../../utils'; import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper'; import { CallbackHandlers, UpdateEvent } from '../../types'; import { isEventForHandlerWithTag } from '../utils'; -import { runCallback } from '../utils/EventHandlersUtils'; +import { runCallback } from '../utils/eventHandlersUtils'; export function getUpdateHandler( handlerTag: number, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 2204007aaa..d14eeea363 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -8,7 +8,7 @@ import { } from '../../types'; import { tagMessage } from '../../../utils'; import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; -import { isComposedGesture } from '../utils/RelationUtils'; +import { isComposedGesture } from '../utils/relationUtils'; // TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) export function useComposedGesture( diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index d37f7ba172..94b97533f6 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -14,8 +14,8 @@ import { bindSharedValues, hasWorkletEventHandlers, unbindSharedValues, -} from './utils/ReanimatedUtils'; -import { prepareRelations } from './utils/RelationUtils'; +} from './utils/reanimatedUtils'; +import { prepareRelations } from './utils/relationUtils'; export function useGesture( type: SingleGestureType, From fb8c8e3d2254351b57711003fd9f1634341ac66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bert?= <63123542+m-bert@users.noreply.github.com> Date: Fri, 5 Sep 2025 10:32:33 +0200 Subject: [PATCH 43/67] Rename files to camelCase --- .../hooks/utils/{EventHandlersUtils.ts => eventHandlersUtils.ts} | 0 .../src/v3/hooks/utils/{ReanimatedUtils.ts => reanimatedUtils.ts} | 0 .../src/v3/hooks/utils/{RelationUtils.ts => relationUtils.ts} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename packages/react-native-gesture-handler/src/v3/hooks/utils/{EventHandlersUtils.ts => eventHandlersUtils.ts} (100%) rename packages/react-native-gesture-handler/src/v3/hooks/utils/{ReanimatedUtils.ts => reanimatedUtils.ts} (100%) rename packages/react-native-gesture-handler/src/v3/hooks/utils/{RelationUtils.ts => relationUtils.ts} (100%) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/EventHandlersUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts similarity index 100% rename from packages/react-native-gesture-handler/src/v3/hooks/utils/EventHandlersUtils.ts rename to packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/ReanimatedUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts similarity index 100% rename from packages/react-native-gesture-handler/src/v3/hooks/utils/ReanimatedUtils.ts rename to packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/RelationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts similarity index 100% rename from packages/react-native-gesture-handler/src/v3/hooks/utils/RelationUtils.ts rename to packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts From 192556e7acf4c31ccbac8f251ee984c261adc160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 5 Sep 2025 10:45:00 +0200 Subject: [PATCH 44/67] Remove todo --- .../react-native-gesture-handler/src/v3/NativeDetector/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 4c1ac71522..46b851f4c4 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -16,7 +16,6 @@ export const traverseGestureRelations = ( ) => { // If we are in the leaf node, we want to fill gesture relations arrays with current // waitFor and simultaneousHandlers. We also want to configure relations on the native side. - // TODO: handle `simultaneousWithExternalGesture`, `requreExternalGestureToFail`, `blocksExternalGesture` if (!isComposedGesture(node)) { node.gestureRelations.simultaneousHandlers.push(...simultaneousHandlers); node.gestureRelations.waitFor.push(...waitFor); From 7c7876bc869349e68e39272a11ba49d7252fab20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 5 Sep 2025 14:41:39 +0200 Subject: [PATCH 45/67] Make simultaneousWithExternalGesture symmetric --- .../src/v3/hooks/useGesture.ts | 2 +- .../src/v3/hooks/utils/relationUtils.ts | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 94b97533f6..a432d993e3 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -80,7 +80,7 @@ export function useGesture( throw new Error(tagMessage('Failed to create reanimated event handlers.')); } - const gestureRelations = prepareRelations(config); + const gestureRelations = prepareRelations(config, tag); useMemo(() => { RNGestureHandlerModule.createGestureHandler(type, tag, {}); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index 79bc4e0ec2..78e5a543c0 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -11,7 +11,10 @@ export function isComposedGesture( return 'tags' in gesture; } -export function prepareRelations(config: any): GestureRelations { +export function prepareRelations( + config: any, + handlerTag: number +): GestureRelations { const extractHandlerTags = (otherHandler: Gesture | Gesture[]): number[] => { if (!otherHandler) { return []; @@ -33,6 +36,18 @@ export function prepareRelations(config: any): GestureRelations { return otherTags; }; + if (config.simultaneousWithExternalGesture) { + if (Array.isArray(config.simultaneousWithExternalGesture)) { + for (const gesture of config.simultaneousWithExternalGesture) { + gesture.gestureRelations.simultaneousHandlers.push(handlerTag); + } + } else { + config.simultaneousWithExternalGesture.gestureRelations.simultaneousHandlers.push( + handlerTag + ); + } + } + return { simultaneousHandlers: extractHandlerTags( config.simultaneousWithExternalGesture From 3e2b661979ed110030284d94d2e00924ce2d7bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 5 Sep 2025 15:04:17 +0200 Subject: [PATCH 46/67] Fix crash with animated --- packages/react-native-gesture-handler/src/v3/hooks/utils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index 8f2dc1cf5c..57c5bee592 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -70,6 +70,9 @@ export function prepareConfig(config: any) { // TODO: Filter changes - passing functions (and possibly other types) // causes a native crash copy.onUpdate = null; + copy.simultaneousWithExternalGesture = null; + copy.requireExternalGestureToFail = null; + copy.blocksExternalGesture = null; return copy; } From 8cd19ff51d2a7327758aa5817a3510fcb58a4cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 5 Sep 2025 16:25:07 +0200 Subject: [PATCH 47/67] Add todo --- .../src/v3/hooks/utils/relationUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index 78e5a543c0..e94e0a6c96 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -15,6 +15,7 @@ export function prepareRelations( config: any, handlerTag: number ): GestureRelations { + // TODO: Handle composed gestures passed into external relations const extractHandlerTags = (otherHandler: Gesture | Gesture[]): number[] => { if (!otherHandler) { return []; From 619aed94bdcc70d14affca67b5fbef5414f0f68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 8 Sep 2025 12:51:39 +0200 Subject: [PATCH 48/67] Use enums --- apps/basic-example/src/NativeDetector.tsx | 3 +- .../react-native-gesture-handler/src/index.ts | 2 +- .../src/v3/types.ts | 50 +++++++------------ 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/apps/basic-example/src/NativeDetector.tsx b/apps/basic-example/src/NativeDetector.tsx index 94f54bc4e7..356b178f64 100644 --- a/apps/basic-example/src/NativeDetector.tsx +++ b/apps/basic-example/src/NativeDetector.tsx @@ -3,6 +3,7 @@ import { Animated, Button, useAnimatedValue } from 'react-native'; import { GestureHandlerRootView, NativeDetector, + SingleGestureType, useGesture, } from 'react-native-gesture-handler'; @@ -17,7 +18,7 @@ export default function App() { } ); - const gesture = useGesture('PanGestureHandler', { + const gesture = useGesture(SingleGestureType.Pan, { onUpdate: event, }); diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts index 870a12d980..aa1d6d5c67 100644 --- a/packages/react-native-gesture-handler/src/index.ts +++ b/packages/react-native-gesture-handler/src/index.ts @@ -168,6 +168,6 @@ export { NativeDetector } from './v3/NativeDetector/NativeDetector'; export * from './v3/hooks/useGesture'; export * from './v3/hooks/relations'; -export { HandlerType } from './v3/types'; +export { SingleGestureType } from './v3/types'; initialize(); diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 3118cf66c1..3014a7b66f 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -5,7 +5,6 @@ import { HandlerStateChangeEventPayload, } from '../handlers/gestureHandlerCommon'; import { HandlerCallbacks } from '../handlers/gestures/gesture'; -import { ValueOf } from '../typeUtils'; export type GestureUpdateEventWithData = GestureEventPayload & { handlerData: T; @@ -50,37 +49,22 @@ export type AnimatedEvent = ((...args: any[]) => void) & { _argMapping: (Animated.Mapping | null)[]; }; -export const SingleGestureType = { - Tap: 'TapGestureHandler', - LongPress: 'LongPressGestureHandler', - Pan: 'PanGestureHandler', - Pinch: 'PinchGestureHandler', - Rotation: 'RotationGestureHandler', - Fling: 'FlingGestureHandler', - Manual: 'ManualGestureHandler', - Native: 'NativeGestureHandler', -} as const; - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type SingleGestureType = ValueOf; - -export const ComposedGestureType = { - Simultaneous: 'SimultaneousGesture', - Exclusive: 'ExclusiveGesture', - Race: 'RaceGesture', -} as const; - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type ComposedGestureType = ValueOf; - -// TODO: Find better name -export const HandlerType = { - ...SingleGestureType, - ...ComposedGestureType, -} as const; - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type HandlerType = ValueOf; +export enum SingleGestureType { + Tap = 'TapGestureHandler', + LongPress = 'LongPressGestureHandler', + Pan = 'PanGestureHandler', + Pinch = 'PinchGestureHandler', + Rotation = 'RotationGestureHandler', + Fling = 'FlingGestureHandler', + Manual = 'ManualGestureHandler', + Native = 'NativeGestureHandler', +} + +export enum ComposedGestureType { + Simultaneous = 'SimultaneousGesture', + Exclusive = 'ExclusiveGesture', + Race = 'RaceGesture', +} export type GestureEvents = { onGestureHandlerStateChange: ( @@ -108,7 +92,7 @@ export type GestureRelations = { export type NativeGesture = { tag: number; - type: HandlerType; + type: SingleGestureType; config: Record; gestureEvents: GestureEvents; gestureRelations: GestureRelations; From b12824bfa9e444af68219abccd99b84813b23492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 8 Sep 2025 13:03:30 +0200 Subject: [PATCH 49/67] Use named type --- packages/react-native-gesture-handler/src/v3/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 3014a7b66f..e1a48ee8de 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -106,7 +106,7 @@ export type ComposedGesture = { dispatchesAnimatedEvents: boolean; }; gestureEvents: GestureEvents; - gestures: (NativeGesture | ComposedGesture)[]; + gestures: Gesture[]; }; export type Gesture = NativeGesture | ComposedGesture; From 4ad7f5b5f4c4c41dbf70b87a5641a6be3bc5ad52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 8 Sep 2025 14:01:39 +0200 Subject: [PATCH 50/67] Extract dfs call to utils --- .../src/v3/NativeDetector/NativeDetector.tsx | 22 +++------------- .../src/v3/NativeDetector/utils.ts | 25 ++++++++++++++++++- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index 4532d31497..29974946b3 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -1,12 +1,10 @@ import React from 'react'; -import { NativeGesture, ComposedGesture, ComposedGestureType } from '../types'; +import { NativeGesture, ComposedGesture } from '../types'; import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; - import { Animated, StyleSheet } from 'react-native'; import HostGestureDetector from './HostGestureDetector'; import { tagMessage } from '../../utils'; -import { traverseGestureRelations } from './utils'; -import RNGestureHandlerModule from '../../RNGestureHandlerModule'; +import { configureRelations } from './utils'; import { isComposedGesture } from '../hooks/utils/relationUtils'; export interface NativeDetectorProps { @@ -37,21 +35,7 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { ); } - if (isComposedGesture(gesture)) { - traverseGestureRelations( - gesture, - new Set( - // If root is simultaneous, we want to add its tags to the set - gesture.type === ComposedGestureType.Simultaneous ? gesture.tags : [] - ) - ); - } else { - RNGestureHandlerModule.configureRelations(gesture.tag, { - waitFor: gesture.gestureRelations.waitFor, - simultaneousHandlers: gesture.gestureRelations.simultaneousHandlers, - blocksHandlers: gesture.gestureRelations.blocksHandlers, - }); - } + configureRelations(gesture); return ( Date: Mon, 8 Sep 2025 14:09:19 +0200 Subject: [PATCH 51/67] Pass composed type as argument --- .../v3/hooks/relations/useComposedGesture.ts | 3 ++- .../src/v3/hooks/relations/useExclusive.ts | 13 ++++++------- .../src/v3/hooks/relations/useRace.ts | 6 +++--- .../src/v3/hooks/relations/useSimultaneous.ts | 17 ++++++----------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index d14eeea363..058101dbff 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -12,6 +12,7 @@ import { isComposedGesture } from '../utils/relationUtils'; // TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) export function useComposedGesture( + type: ComposedGestureType, ...gestures: (NativeGesture | ComposedGesture)[] ): ComposedGesture { const tags = gestures.flatMap((gesture) => @@ -94,7 +95,7 @@ export function useComposedGesture( return { tags, - type: ComposedGestureType.Race, + type, config, gestureEvents: { onGestureHandlerStateChange, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts index b402c8080a..b2e14c3519 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts @@ -1,12 +1,11 @@ -import { - NativeGesture, - ComposedGesture, - ComposedGestureType, -} from '../../types'; +import { ComposedGestureType, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; -export function useExclusive(...gestures: (NativeGesture | ComposedGesture)[]) { - const composedGesture = useComposedGesture(...gestures); +export function useExclusive(...gestures: Gesture[]) { + const composedGesture = useComposedGesture( + ComposedGestureType.Exclusive, + ...gestures + ); composedGesture.type = ComposedGestureType.Exclusive; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts index a04e21b38d..eca1778893 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts @@ -1,6 +1,6 @@ -import { ComposedGesture, NativeGesture } from '../../types'; +import { ComposedGestureType, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; -export function useRace(...gestures: (NativeGesture | ComposedGesture)[]) { - return useComposedGesture(...gestures); +export function useRace(...gestures: Gesture[]) { + return useComposedGesture(ComposedGestureType.Race, ...gestures); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts index f046b05ff7..6882b914f7 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts @@ -1,16 +1,11 @@ -import { - ComposedGesture, - ComposedGestureType, - NativeGesture, -} from '../../types'; +import { ComposedGestureType, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; -export function useSimultaneous( - ...gestures: (NativeGesture | ComposedGesture)[] -) { - const composedGesture = useComposedGesture(...gestures); - - composedGesture.type = ComposedGestureType.Simultaneous; +export function useSimultaneous(...gestures: Gesture[]) { + const composedGesture = useComposedGesture( + ComposedGestureType.Simultaneous, + ...gestures + ); return composedGesture; } From 761e22abba890a5edf1588eeabc423b4e7b4f7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 8 Sep 2025 14:23:20 +0200 Subject: [PATCH 52/67] Move function to global scope --- .../src/v3/hooks/utils/relationUtils.ts | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index e94e0a6c96..b62616878b 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -1,42 +1,36 @@ -import { - ComposedGesture, - Gesture, - GestureRelations, - NativeGesture, -} from '../../types'; +import { ComposedGesture, Gesture, GestureRelations } from '../../types'; export function isComposedGesture( - gesture: NativeGesture | ComposedGesture + gesture: Gesture ): gesture is ComposedGesture { return 'tags' in gesture; } -export function prepareRelations( - config: any, - handlerTag: number -): GestureRelations { - // TODO: Handle composed gestures passed into external relations - const extractHandlerTags = (otherHandler: Gesture | Gesture[]): number[] => { - if (!otherHandler) { - return []; - } +function extractHandlerTags(otherHandler: Gesture | Gesture[]): number[] { + if (!otherHandler) { + return []; + } - let otherTags: number[]; + let otherTags: number[]; - if (Array.isArray(otherHandler)) { - otherTags = otherHandler.flatMap( - (gesture: NativeGesture | ComposedGesture) => - isComposedGesture(gesture) ? gesture.tags : gesture.tag - ); - } else { - otherTags = isComposedGesture(otherHandler) - ? otherHandler.tags - : [otherHandler.tag]; - } + if (Array.isArray(otherHandler)) { + otherTags = otherHandler.flatMap((gesture: Gesture) => + isComposedGesture(gesture) ? gesture.tags : gesture.tag + ); + } else { + otherTags = isComposedGesture(otherHandler) + ? otherHandler.tags + : [otherHandler.tag]; + } - return otherTags; - }; + return otherTags; +} +// TODO: Handle composed gestures passed into external relations +export function prepareRelations( + config: any, + handlerTag: number +): GestureRelations { if (config.simultaneousWithExternalGesture) { if (Array.isArray(config.simultaneousWithExternalGesture)) { for (const gesture of config.simultaneousWithExternalGesture) { From 648f8629fe996d1507bbdfcb15dc6e2adb01baf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 8 Sep 2025 14:36:28 +0200 Subject: [PATCH 53/67] Update comment --- .../src/v3/NativeDetector/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 414d6c94e5..1cdebd6b01 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -38,7 +38,9 @@ export const traverseGestureRelations = ( node.gestures.forEach((child) => { // If child is composed gesture, we have to correctly fill `waitFor` and `simultaneousHandlers`. if (isComposedGesture(child)) { - // We have to update `simultaneousHandlers` before traversing the child. + // We have to update `simultaneousHandlers` before traversing the child (going top-down). + // Simultaneous is an all-to-all relation - it needs to be prepared when entering the node. + // Exclusive is a one-to-many relation - gesture depends on the preceding ones and not on itself - it should be prepared when leaving the node (bottom-up). // If we go from a non-simultaneous gesture to a simultaneous gesture, // we add the tags of the simultaneous gesture to the `simultaneousHandlers`. From ce2de67a53388e265b5b4c99055345e699b38ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 9 Sep 2025 09:31:10 +0200 Subject: [PATCH 54/67] Assign instead of only push --- .../src/v3/NativeDetector/utils.ts | 16 ++++++++++------ .../src/v3/hooks/useGesture.ts | 9 +++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 1cdebd6b01..33e3df3ccd 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -5,7 +5,10 @@ // For `simultaneousHandlers` we use Set as the order doesn't matter. import RNGestureHandlerModule from '../../RNGestureHandlerModule'; -import { isComposedGesture } from '../hooks/utils/relationUtils'; +import { + isComposedGesture, + prepareRelations, +} from '../hooks/utils/relationUtils'; import { ComposedGesture, ComposedGestureType, @@ -22,6 +25,8 @@ export const traverseGestureRelations = ( // If we are in the leaf node, we want to fill gesture relations arrays with current // waitFor and simultaneousHandlers. We also want to configure relations on the native side. if (!isComposedGesture(node)) { + node.gestureRelations = prepareRelations(node.config, node.tag); + node.gestureRelations.simultaneousHandlers.push(...simultaneousHandlers); node.gestureRelations.waitFor.push(...waitFor); @@ -130,10 +135,9 @@ export function configureRelations(gesture: Gesture) { ) ); } else { - RNGestureHandlerModule.configureRelations(gesture.tag, { - waitFor: gesture.gestureRelations.waitFor, - simultaneousHandlers: gesture.gestureRelations.simultaneousHandlers, - blocksHandlers: gesture.gestureRelations.blocksHandlers, - }); + RNGestureHandlerModule.configureRelations( + gesture.tag, + prepareRelations(gesture.config, gesture.tag) + ); } } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index a432d993e3..807006081e 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -15,7 +15,6 @@ import { hasWorkletEventHandlers, unbindSharedValues, } from './utils/reanimatedUtils'; -import { prepareRelations } from './utils/relationUtils'; export function useGesture( type: SingleGestureType, @@ -80,8 +79,6 @@ export function useGesture( throw new Error(tagMessage('Failed to create reanimated event handlers.')); } - const gestureRelations = prepareRelations(config, tag); - useMemo(() => { RNGestureHandlerModule.createGestureHandler(type, tag, {}); RNGestureHandlerModule.flushOperations(); @@ -123,6 +120,10 @@ export function useGesture( onReanimatedTouchEvent, onGestureHandlerAnimatedEvent, }, - gestureRelations, + gestureRelations: { + simultaneousHandlers: [], + waitFor: [], + blocksHandlers: [], + }, }; } From ec85bb5a7647e73439081ef63881f914c73b138f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 9 Sep 2025 09:58:07 +0200 Subject: [PATCH 55/67] Add warning about the same gesture passed multiple times --- .../src/v3/hooks/relations/useComposedGesture.ts | 10 +++++++++- .../src/v3/hooks/utils/relationUtils.ts | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 058101dbff..3033e1703d 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -8,7 +8,7 @@ import { } from '../../types'; import { tagMessage } from '../../../utils'; import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; -import { isComposedGesture } from '../utils/relationUtils'; +import { containsDuplicates, isComposedGesture } from '../utils/relationUtils'; // TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) export function useComposedGesture( @@ -19,6 +19,14 @@ export function useComposedGesture( isComposedGesture(gesture) ? gesture.tags : gesture.tag ); + if (containsDuplicates(tags)) { + console.warn( + tagMessage( + 'Using the same gesture more than once in gesture composition can lead to unexpected behavior.' + ) + ); + } + const config = { shouldUseReanimated: gestures.some( (gesture) => gesture.config.shouldUseReanimated diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index b62616878b..e9dd19324b 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -51,3 +51,9 @@ export function prepareRelations( blocksHandlers: extractHandlerTags(config.blocksExternalGesture), }; } + +export function containsDuplicates(tags: number[]) { + const tagSet = new Set(tags); + + return tagSet.size !== tags.length; +} From 2e994810caae3edd45e0e369c5e9bce1edb97cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 9 Sep 2025 12:14:28 +0200 Subject: [PATCH 56/67] Do not store set in variable --- .../src/v3/hooks/utils/relationUtils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index e9dd19324b..a826262ab8 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -53,7 +53,5 @@ export function prepareRelations( } export function containsDuplicates(tags: number[]) { - const tagSet = new Set(tags); - - return tagSet.size !== tags.length; + return new Set(tags).size !== tags.length; } From adfada714777e3c92cb6fc6fce693997391e8076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 9 Sep 2025 14:12:06 +0200 Subject: [PATCH 57/67] Change warning to error --- .../src/v3/hooks/relations/useComposedGesture.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 3033e1703d..d97a00dfd9 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -20,9 +20,9 @@ export function useComposedGesture( ); if (containsDuplicates(tags)) { - console.warn( + throw new Error( tagMessage( - 'Using the same gesture more than once in gesture composition can lead to unexpected behavior.' + 'Each gesture can be used only once in the gesture composition.' ) ); } From 7ab9b3bebae01ffb3696c70bfcfde6fd5912baef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 9 Sep 2025 20:24:08 +0200 Subject: [PATCH 58/67] Use warn about multiple animated events --- .../v3/hooks/relations/useComposedGesture.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index d97a00dfd9..199b437278 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -92,12 +92,21 @@ export function useComposedGesture( let onGestureHandlerAnimatedEvent; - for (const gesture of gestures) { - if (gesture.gestureEvents.onGestureHandlerAnimatedEvent) { - onGestureHandlerAnimatedEvent = - gesture.gestureEvents.onGestureHandlerAnimatedEvent; + const gesturesWithAnimatedEvent = gestures.filter( + (gesture) => + gesture.gestureEvents.onGestureHandlerAnimatedEvent !== undefined + ); + + if (gesturesWithAnimatedEvent.length > 0) { + onGestureHandlerAnimatedEvent = + gesturesWithAnimatedEvent[0].gestureEvents.onGestureHandlerAnimatedEvent; - break; + if (__DEV__ && gesturesWithAnimatedEvent.length > 1) { + console.warn( + tagMessage( + 'Composed gesture can handle only one Animated event. The first one will be used, others will be ignored.' + ) + ); } } From c2c88cc35d32dd6189b4694f0ed3b9ecaa419d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 10 Sep 2025 14:15:07 +0200 Subject: [PATCH 59/67] Do not add repeating tags with external relations into simultaneous handlers --- .../src/v3/NativeDetector/utils.ts | 9 +++++---- .../src/v3/hooks/utils/relationUtils.ts | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 33e3df3ccd..16b9a5aa7b 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -135,9 +135,10 @@ export function configureRelations(gesture: Gesture) { ) ); } else { - RNGestureHandlerModule.configureRelations( - gesture.tag, - prepareRelations(gesture.config, gesture.tag) - ); + const relations = prepareRelations(gesture.config, gesture.tag); + + gesture.gestureRelations = relations; + + RNGestureHandlerModule.configureRelations(gesture.tag, relations); } } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index a826262ab8..1424b6f3f0 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -34,12 +34,21 @@ export function prepareRelations( if (config.simultaneousWithExternalGesture) { if (Array.isArray(config.simultaneousWithExternalGesture)) { for (const gesture of config.simultaneousWithExternalGesture) { - gesture.gestureRelations.simultaneousHandlers.push(handlerTag); + const simultaneousHandlers = + gesture.gestureRelations.simultaneousHandlers; + + if (!simultaneousHandlers.includes(handlerTag)) { + simultaneousHandlers.push(handlerTag); + } } } else { - config.simultaneousWithExternalGesture.gestureRelations.simultaneousHandlers.push( - handlerTag - ); + const simultaneousHandlers = + config.simultaneousWithExternalGesture.gestureRelations + .simultaneousHandlers; + + if (!simultaneousHandlers.includes(handlerTag)) { + simultaneousHandlers.push(handlerTag); + } } } From 4baf2cbbee2bae2ac561fd6ebbaad6cecd96a641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 10 Sep 2025 14:56:59 +0200 Subject: [PATCH 60/67] Do not mark gesture as simultaneous with itself --- .../src/v3/NativeDetector/utils.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 16b9a5aa7b..f9a36212f8 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -114,10 +114,16 @@ export const traverseGestureRelations = ( } // This means that child is a leaf node. else { - // In the leaf node, we only care about filling `waitFor` array. First we traverse the child... + // We don't want to mark gesture as simultaneous with itself, so we remove its tag from the set. + const hasRemovedTag = simultaneousHandlers.delete(child.tag); + traverseGestureRelations(child, simultaneousHandlers, waitFor); - // ..and when we go back we add the tag of the child to the `waitFor` array. + if (hasRemovedTag) { + simultaneousHandlers.add(child.tag); + } + + // In the leaf node, we only care about filling `waitFor` array. if (node.type === ComposedGestureType.Exclusive) { waitFor.push(child.tag); } From db2e974fb2cc1029659b06de7064c0a1ab6a47e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 11 Sep 2025 16:35:58 +0200 Subject: [PATCH 61/67] Handle external simultaneous composition --- .../src/v3/NativeDetector/utils.ts | 15 +++--- .../v3/hooks/relations/useComposedGesture.ts | 1 + .../src/v3/hooks/utils/relationUtils.ts | 49 +++++++++++-------- .../src/v3/types.ts | 1 + 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index f9a36212f8..c784d95da6 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -133,13 +133,16 @@ export const traverseGestureRelations = ( export function configureRelations(gesture: Gesture) { if (isComposedGesture(gesture)) { - traverseGestureRelations( - gesture, - new Set( - // If root is simultaneous, we want to add its tags to the set - gesture.type === ComposedGestureType.Simultaneous ? gesture.tags : [] - ) + const simultaneousHandlers = new Set( + gesture.externalSimultaneousHandlers ); + + // If root is simultaneous, we want to add its tags to the set + if (gesture.type === ComposedGestureType.Simultaneous) { + gesture.tags.forEach((tag) => simultaneousHandlers.add(tag)); + } + + traverseGestureRelations(gesture, simultaneousHandlers); } else { const relations = prepareRelations(gesture.config, gesture.tag); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 199b437278..480161bfe2 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -123,6 +123,7 @@ export function useComposedGesture( onReanimatedTouchEvent, onGestureHandlerAnimatedEvent, }, + externalSimultaneousHandlers: [], gestures, }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index 1424b6f3f0..372a23aea4 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -26,31 +26,40 @@ function extractHandlerTags(otherHandler: Gesture | Gesture[]): number[] { return otherTags; } -// TODO: Handle composed gestures passed into external relations -export function prepareRelations( - config: any, +function makeSimultaneousWithExternalGestureSymmetric( + otherHandler: Gesture | Gesture[], handlerTag: number -): GestureRelations { - if (config.simultaneousWithExternalGesture) { - if (Array.isArray(config.simultaneousWithExternalGesture)) { - for (const gesture of config.simultaneousWithExternalGesture) { - const simultaneousHandlers = - gesture.gestureRelations.simultaneousHandlers; +) { + if (!otherHandler) { + return; + } - if (!simultaneousHandlers.includes(handlerTag)) { - simultaneousHandlers.push(handlerTag); - } - } - } else { - const simultaneousHandlers = - config.simultaneousWithExternalGesture.gestureRelations - .simultaneousHandlers; + const processSingleGesture = (gesture: Gesture) => { + const simultaneousHandlers = isComposedGesture(gesture) + ? gesture.externalSimultaneousHandlers + : gesture.gestureRelations.simultaneousHandlers; - if (!simultaneousHandlers.includes(handlerTag)) { - simultaneousHandlers.push(handlerTag); - } + if (!simultaneousHandlers.includes(handlerTag)) { + simultaneousHandlers.push(handlerTag); } + }; + + if (Array.isArray(otherHandler)) { + otherHandler.forEach(processSingleGesture); + } else { + processSingleGesture(otherHandler); } +} + +// TODO: Handle composed gestures passed into external relations +export function prepareRelations( + config: any, + handlerTag: number +): GestureRelations { + makeSimultaneousWithExternalGestureSymmetric( + config.simultaneousWithExternalGesture, + handlerTag + ); return { simultaneousHandlers: extractHandlerTags( diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index e1a48ee8de..2ca45fd060 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -106,6 +106,7 @@ export type ComposedGesture = { dispatchesAnimatedEvents: boolean; }; gestureEvents: GestureEvents; + externalSimultaneousHandlers: number[]; gestures: Gesture[]; }; From 6fe39a57346a3ddaeadb6b6aa91a36cd9da03ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 11 Sep 2025 16:39:32 +0200 Subject: [PATCH 62/67] Remove todo --- .../src/v3/hooks/utils/relationUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index 372a23aea4..3f3c236026 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -51,7 +51,6 @@ function makeSimultaneousWithExternalGestureSymmetric( } } -// TODO: Handle composed gestures passed into external relations export function prepareRelations( config: any, handlerTag: number From 1429ecdc323d94f8505a6a8dce233be40faae59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 11 Sep 2025 16:44:22 +0200 Subject: [PATCH 63/67] Update types --- .../src/v3/NativeDetector/NativeDetector.tsx | 4 ++-- .../src/v3/NativeDetector/utils.ts | 9 ++------- .../src/v3/hooks/relations/useComposedGesture.ts | 4 ++-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index 29974946b3..a4ec84720f 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { NativeGesture, ComposedGesture } from '../types'; +import { Gesture } from '../types'; import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; import { Animated, StyleSheet } from 'react-native'; import HostGestureDetector from './HostGestureDetector'; @@ -9,7 +9,7 @@ import { isComposedGesture } from '../hooks/utils/relationUtils'; export interface NativeDetectorProps { children?: React.ReactNode; - gesture: NativeGesture | ComposedGesture; + gesture: Gesture; } const AnimatedNativeDetector = diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index c784d95da6..093897775f 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -9,16 +9,11 @@ import { isComposedGesture, prepareRelations, } from '../hooks/utils/relationUtils'; -import { - ComposedGesture, - ComposedGestureType, - Gesture, - NativeGesture, -} from '../types'; +import { ComposedGestureType, Gesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. export const traverseGestureRelations = ( - node: NativeGesture | ComposedGesture, + node: Gesture, simultaneousHandlers: Set, waitFor: number[] = [] ) => { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 480161bfe2..9e44b98ab5 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -1,10 +1,10 @@ import { - NativeGesture, StateChangeEvent, UpdateEvent, TouchEvent, ComposedGesture, ComposedGestureType, + Gesture, } from '../../types'; import { tagMessage } from '../../../utils'; import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; @@ -13,7 +13,7 @@ import { containsDuplicates, isComposedGesture } from '../utils/relationUtils'; // TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) export function useComposedGesture( type: ComposedGestureType, - ...gestures: (NativeGesture | ComposedGesture)[] + ...gestures: Gesture[] ): ComposedGesture { const tags = gestures.flatMap((gesture) => isComposedGesture(gesture) ? gesture.tags : gesture.tag From 81677c751b8c0761d5a6015884d88d25baa30e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 11 Sep 2025 16:46:35 +0200 Subject: [PATCH 64/67] Change dfs name --- .../src/v3/NativeDetector/utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 093897775f..3f14a33c84 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -12,7 +12,7 @@ import { import { ComposedGestureType, Gesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. -export const traverseGestureRelations = ( +export const traverseAndConfigureRelations = ( node: Gesture, simultaneousHandlers: Set, waitFor: number[] = [] @@ -67,7 +67,7 @@ export const traverseGestureRelations = ( const length = waitFor.length; // We traverse the child, passing the current `waitFor` and `simultaneousHandlers`. - traverseGestureRelations(child, simultaneousHandlers, waitFor); + traverseAndConfigureRelations(child, simultaneousHandlers, waitFor); // After traversing the child, we need to update `waitFor` and `simultaneousHandlers` @@ -112,7 +112,7 @@ export const traverseGestureRelations = ( // We don't want to mark gesture as simultaneous with itself, so we remove its tag from the set. const hasRemovedTag = simultaneousHandlers.delete(child.tag); - traverseGestureRelations(child, simultaneousHandlers, waitFor); + traverseAndConfigureRelations(child, simultaneousHandlers, waitFor); if (hasRemovedTag) { simultaneousHandlers.add(child.tag); @@ -137,7 +137,7 @@ export function configureRelations(gesture: Gesture) { gesture.tags.forEach((tag) => simultaneousHandlers.add(tag)); } - traverseGestureRelations(gesture, simultaneousHandlers); + traverseAndConfigureRelations(gesture, simultaneousHandlers); } else { const relations = prepareRelations(gesture.config, gesture.tag); From 759d02238e756af3dd31c1009491cde528453a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 11 Sep 2025 17:30:57 +0200 Subject: [PATCH 65/67] Prepare external relations in useGesture --- .../src/v3/NativeDetector/utils.ts | 9 ++++----- .../src/v3/hooks/useGesture.ts | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index 3f14a33c84..aa13df7784 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -139,10 +139,9 @@ export function configureRelations(gesture: Gesture) { traverseAndConfigureRelations(gesture, simultaneousHandlers); } else { - const relations = prepareRelations(gesture.config, gesture.tag); - - gesture.gestureRelations = relations; - - RNGestureHandlerModule.configureRelations(gesture.tag, relations); + RNGestureHandlerModule.configureRelations( + gesture.tag, + gesture.gestureRelations + ); } } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 807006081e..a432d993e3 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -15,6 +15,7 @@ import { hasWorkletEventHandlers, unbindSharedValues, } from './utils/reanimatedUtils'; +import { prepareRelations } from './utils/relationUtils'; export function useGesture( type: SingleGestureType, @@ -79,6 +80,8 @@ export function useGesture( throw new Error(tagMessage('Failed to create reanimated event handlers.')); } + const gestureRelations = prepareRelations(config, tag); + useMemo(() => { RNGestureHandlerModule.createGestureHandler(type, tag, {}); RNGestureHandlerModule.flushOperations(); @@ -120,10 +123,6 @@ export function useGesture( onReanimatedTouchEvent, onGestureHandlerAnimatedEvent, }, - gestureRelations: { - simultaneousHandlers: [], - waitFor: [], - blocksHandlers: [], - }, + gestureRelations, }; } From 7c001fe789367bd1e45c5e9d42f041497795e470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 12 Sep 2025 09:56:23 +0200 Subject: [PATCH 66/67] Rename types --- apps/basic-example/src/NativeDetector.tsx | 4 +-- .../react-native-gesture-handler/src/index.ts | 2 +- .../src/v3/NativeDetector/utils.ts | 28 +++++++++---------- .../v3/hooks/relations/useComposedGesture.ts | 4 +-- .../src/v3/hooks/relations/useExclusive.ts | 6 ++-- .../src/v3/hooks/relations/useRace.ts | 4 +-- .../src/v3/hooks/relations/useSimultaneous.ts | 4 +-- .../src/v3/hooks/useGesture.ts | 6 ++-- .../src/v3/types.ts | 12 ++++---- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/apps/basic-example/src/NativeDetector.tsx b/apps/basic-example/src/NativeDetector.tsx index 356b178f64..c6ee6ef8b8 100644 --- a/apps/basic-example/src/NativeDetector.tsx +++ b/apps/basic-example/src/NativeDetector.tsx @@ -3,7 +3,7 @@ import { Animated, Button, useAnimatedValue } from 'react-native'; import { GestureHandlerRootView, NativeDetector, - SingleGestureType, + SingleGestureName, useGesture, } from 'react-native-gesture-handler'; @@ -18,7 +18,7 @@ export default function App() { } ); - const gesture = useGesture(SingleGestureType.Pan, { + const gesture = useGesture(SingleGestureName.Pan, { onUpdate: event, }); diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts index aa1d6d5c67..e349eef13a 100644 --- a/packages/react-native-gesture-handler/src/index.ts +++ b/packages/react-native-gesture-handler/src/index.ts @@ -168,6 +168,6 @@ export { NativeDetector } from './v3/NativeDetector/NativeDetector'; export * from './v3/hooks/useGesture'; export * from './v3/hooks/relations'; -export { SingleGestureType } from './v3/types'; +export { SingleGestureName } from './v3/types'; initialize(); diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts index aa13df7784..6fc42163f6 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -9,7 +9,7 @@ import { isComposedGesture, prepareRelations, } from '../hooks/utils/relationUtils'; -import { ComposedGestureType, Gesture } from '../types'; +import { ComposedGestureName, Gesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. export const traverseAndConfigureRelations = ( @@ -46,8 +46,8 @@ export const traverseAndConfigureRelations = ( // we add the tags of the simultaneous gesture to the `simultaneousHandlers`. // This way when we traverse the child, we already have the tags of the simultaneous gestures if ( - node.type !== ComposedGestureType.Simultaneous && - child.type === ComposedGestureType.Simultaneous + node.type !== ComposedGestureName.Simultaneous && + child.type === ComposedGestureName.Simultaneous ) { child.tags.forEach((tag) => simultaneousHandlers.add(tag)); } @@ -56,8 +56,8 @@ export const traverseAndConfigureRelations = ( // we remove the tags of the child gestures from the `simultaneousHandlers`, // as those are not simultaneous with each other. if ( - node.type === ComposedGestureType.Simultaneous && - child.type !== ComposedGestureType.Simultaneous + node.type === ComposedGestureName.Simultaneous && + child.type !== ComposedGestureName.Simultaneous ) { child.tags.forEach((tag) => simultaneousHandlers.delete(tag)); } @@ -75,8 +75,8 @@ export const traverseAndConfigureRelations = ( // we want to delete the tags of the simultaneous gesture from the `simultaneousHandlers` - // those gestures are not simultaneous with each other anymore. if ( - child.type === ComposedGestureType.Simultaneous && - node.type !== ComposedGestureType.Simultaneous + child.type === ComposedGestureName.Simultaneous && + node.type !== ComposedGestureName.Simultaneous ) { node.tags.forEach((tag) => simultaneousHandlers.delete(tag)); } @@ -85,15 +85,15 @@ export const traverseAndConfigureRelations = ( // we want to add the tags of the simultaneous gesture to the `simultaneousHandlers`, // as those gestures are simultaneous with other children of the current node. if ( - child.type !== ComposedGestureType.Simultaneous && - node.type === ComposedGestureType.Simultaneous + child.type !== ComposedGestureName.Simultaneous && + node.type === ComposedGestureName.Simultaneous ) { node.tags.forEach((tag) => simultaneousHandlers.add(tag)); } // If we go back to an exclusive gesture, we want to add the tags of the child gesture to the `waitFor` array. // This will allow us to pass exclusive gesture tags to the right subtree of the current node. - if (node.type === ComposedGestureType.Exclusive) { + if (node.type === ComposedGestureName.Exclusive) { child.tags.forEach((tag) => waitFor.push(tag)); } @@ -101,8 +101,8 @@ export const traverseAndConfigureRelations = ( // to the previous state, siblings of the exclusive gesture are not exclusive with it. Since we use `push` method to // add tags to the `waitFor` array, we can override `length` property to reset it to the previous state. if ( - child.type === ComposedGestureType.Exclusive && - node.type !== ComposedGestureType.Exclusive + child.type === ComposedGestureName.Exclusive && + node.type !== ComposedGestureName.Exclusive ) { waitFor.length = length; } @@ -119,7 +119,7 @@ export const traverseAndConfigureRelations = ( } // In the leaf node, we only care about filling `waitFor` array. - if (node.type === ComposedGestureType.Exclusive) { + if (node.type === ComposedGestureName.Exclusive) { waitFor.push(child.tag); } } @@ -133,7 +133,7 @@ export function configureRelations(gesture: Gesture) { ); // If root is simultaneous, we want to add its tags to the set - if (gesture.type === ComposedGestureType.Simultaneous) { + if (gesture.type === ComposedGestureName.Simultaneous) { gesture.tags.forEach((tag) => simultaneousHandlers.add(tag)); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index 9e44b98ab5..689e965829 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -3,7 +3,7 @@ import { UpdateEvent, TouchEvent, ComposedGesture, - ComposedGestureType, + ComposedGestureName, Gesture, } from '../../types'; import { tagMessage } from '../../../utils'; @@ -12,7 +12,7 @@ import { containsDuplicates, isComposedGesture } from '../utils/relationUtils'; // TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) export function useComposedGesture( - type: ComposedGestureType, + type: ComposedGestureName, ...gestures: Gesture[] ): ComposedGesture { const tags = gestures.flatMap((gesture) => diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts index b2e14c3519..b0c07978f8 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts @@ -1,13 +1,13 @@ -import { ComposedGestureType, Gesture } from '../../types'; +import { ComposedGestureName, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; export function useExclusive(...gestures: Gesture[]) { const composedGesture = useComposedGesture( - ComposedGestureType.Exclusive, + ComposedGestureName.Exclusive, ...gestures ); - composedGesture.type = ComposedGestureType.Exclusive; + composedGesture.type = ComposedGestureName.Exclusive; return composedGesture; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts index eca1778893..8b1471f59e 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts @@ -1,6 +1,6 @@ -import { ComposedGestureType, Gesture } from '../../types'; +import { ComposedGestureName, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; export function useRace(...gestures: Gesture[]) { - return useComposedGesture(ComposedGestureType.Race, ...gestures); + return useComposedGesture(ComposedGestureName.Race, ...gestures); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts index 6882b914f7..d91fea87da 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts @@ -1,9 +1,9 @@ -import { ComposedGestureType, Gesture } from '../../types'; +import { ComposedGestureName, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; export function useSimultaneous(...gestures: Gesture[]) { const composedGesture = useComposedGesture( - ComposedGestureType.Simultaneous, + ComposedGestureName.Simultaneous, ...gestures ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index a432d993e3..2b6b99e303 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -9,7 +9,7 @@ import { shouldHandleTouchEvents, } from './utils'; import { tagMessage } from '../../utils'; -import { NativeGesture, SingleGestureType } from '../types'; +import { SingleGesture, SingleGestureName } from '../types'; import { bindSharedValues, hasWorkletEventHandlers, @@ -18,9 +18,9 @@ import { import { prepareRelations } from './utils/relationUtils'; export function useGesture( - type: SingleGestureType, + type: SingleGestureName, config: Record -): NativeGesture { +): SingleGesture { const tag = useMemo(() => getNextHandlerTag(), []); const disableReanimated = useMemo(() => config.disableReanimated, []); diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 2ca45fd060..0a3f157fa4 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -49,7 +49,7 @@ export type AnimatedEvent = ((...args: any[]) => void) & { _argMapping: (Animated.Mapping | null)[]; }; -export enum SingleGestureType { +export enum SingleGestureName { Tap = 'TapGestureHandler', LongPress = 'LongPressGestureHandler', Pan = 'PanGestureHandler', @@ -60,7 +60,7 @@ export enum SingleGestureType { Native = 'NativeGestureHandler', } -export enum ComposedGestureType { +export enum ComposedGestureName { Simultaneous = 'SimultaneousGesture', Exclusive = 'ExclusiveGesture', Race = 'RaceGesture', @@ -90,9 +90,9 @@ export type GestureRelations = { blocksHandlers: number[]; }; -export type NativeGesture = { +export type SingleGesture = { tag: number; - type: SingleGestureType; + type: SingleGestureName; config: Record; gestureEvents: GestureEvents; gestureRelations: GestureRelations; @@ -100,7 +100,7 @@ export type NativeGesture = { export type ComposedGesture = { tags: number[]; - type: ComposedGestureType; + type: ComposedGestureName; config: { shouldUseReanimated: boolean; dispatchesAnimatedEvents: boolean; @@ -110,4 +110,4 @@ export type ComposedGesture = { gestures: Gesture[]; }; -export type Gesture = NativeGesture | ComposedGesture; +export type Gesture = SingleGesture | ComposedGesture; From c3d63cc99d7b711c6f4acc0156478dd7ee2ec378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 12 Sep 2025 11:05:36 +0200 Subject: [PATCH 67/67] Mark otherHandlers as possibly undefined --- .../src/v3/hooks/utils/relationUtils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index 3f3c236026..9ac510b9cd 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -6,7 +6,9 @@ export function isComposedGesture( return 'tags' in gesture; } -function extractHandlerTags(otherHandler: Gesture | Gesture[]): number[] { +function extractHandlerTags( + otherHandler: Gesture | Gesture[] | undefined +): number[] { if (!otherHandler) { return []; } @@ -27,7 +29,7 @@ function extractHandlerTags(otherHandler: Gesture | Gesture[]): number[] { } function makeSimultaneousWithExternalGestureSymmetric( - otherHandler: Gesture | Gesture[], + otherHandler: Gesture | Gesture[] | undefined, handlerTag: number ) { if (!otherHandler) {