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 001/109] 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 002/109] 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 003/109] 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 004/109] 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 005/109] 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 006/109] 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 007/109] 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 008/109] 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 009/109] 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 010/109] 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 011/109] 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 012/109] 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 013/109] 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 014/109] 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 015/109] 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 016/109] 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 017/109] 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 018/109] 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 019/109] 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 020/109] 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 021/109] 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 022/109] 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 023/109] 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 024/109] 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 025/109] 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 026/109] 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 027/109] 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 028/109] 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 029/109] 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 030/109] 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 031/109] 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 032/109] 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 033/109] 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 034/109] 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 035/109] 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 036/109] 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 037/109] 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 038/109] 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 039/109] 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 040/109] 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 041/109] 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 042/109] 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 043/109] 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 044/109] 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 045/109] 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 046/109] 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 047/109] 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 74ec173f5d0d64ddfd3a9bce9af61f451c09c6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 5 Sep 2025 17:05:52 +0200 Subject: [PATCH 048/109] Type config --- .../handlers/gestures/reanimatedWrapper.ts | 4 +- .../js/useGestureStateChangeEvent.ts | 6 +- .../callbacks/js/useGestureTouchEvent.ts | 6 +- .../callbacks/js/useGestureUpdateEvent.ts | 6 +- .../useReanimatedStateChangeEvent.ts | 6 +- .../reanimated/useReanimatedTouchEvent.ts | 6 +- .../reanimated/useReanimatedUpdateEvent.ts | 6 +- .../v3/hooks/callbacks/stateChangeHandler.ts | 4 +- .../v3/hooks/callbacks/touchEventHandler.ts | 4 +- .../src/v3/hooks/callbacks/updateHandler.ts | 13 ++-- .../src/v3/hooks/useGesture.ts | 7 +-- .../src/v3/hooks/useGestureCallbacks.ts | 10 +-- .../src/v3/hooks/utils.ts | 26 ++++---- .../src/v3/hooks/utils/eventHandlersUtils.ts | 63 +++++++++++-------- .../src/v3/hooks/utils/reanimatedUtils.ts | 13 +++- .../src/v3/hooks/utils/relationUtils.ts | 9 ++- .../src/v3/types.ts | 53 +++++++++++++--- .../src/web/handlers/GestureHandler.ts | 9 +-- .../src/web/interfaces.ts | 9 +-- 19 files changed, 170 insertions(+), 90 deletions(-) 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 9875a7f710..e45a0cdec1 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -1,6 +1,6 @@ import { ComponentClass } from 'react'; import { tagMessage } from '../../utils'; -import { UpdateEvent } from '../../v3/types'; +import { GestureCallbacks, UpdateEvent } from '../../v3/types'; export interface SharedValue { value: Value; @@ -41,7 +41,7 @@ let Reanimated: options?: unknown ): ComponentClass

; }; - useHandler: (handlers: Record) => { + useHandler: (handlers: GestureCallbacks) => { doDependenciesDiffer: boolean; context: ReanimatedContext; }; 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 699bb29c46..37e4d72d53 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,11 @@ +import { BaseGestureConfig } from '../../../types'; import { extractStateChangeHandlers } from '../../utils/eventHandlersUtils'; import { getStateChangeHandler } from '../stateChangeHandler'; -export function useGestureStateChangeEvent(handlerTag: number, config: any) { +export function useGestureStateChangeEvent( + handlerTag: number, + config: BaseGestureConfig +) { const handlers = extractStateChangeHandlers(config); 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 f98068ab68..9c086c5922 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,11 @@ +import { BaseGestureConfig } from '../../../types'; import { extractTouchHandlers } from '../../utils/eventHandlersUtils'; import { getTouchEventHandler } from '../touchEventHandler'; -export function useGestureTouchEvent(handlerTag: number, config: any) { +export function useGestureTouchEvent( + handlerTag: number, + config: BaseGestureConfig +) { const handlers = extractTouchHandlers(config); 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 5cc938339f..470391d846 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,8 +2,12 @@ import { isAnimatedEvent } from '../../utils'; import { ReanimatedContext } from '../../../../handlers/gestures/reanimatedWrapper'; import { extractUpdateHandlers } from '../../utils/eventHandlersUtils'; import { getUpdateHandler } from '../updateHandler'; +import { BaseGestureConfig } from '../../../types'; -export function useGestureUpdateEvent(handlerTag: number, config: any) { +export function useGestureUpdateEvent( + handlerTag: number, + config: BaseGestureConfig +) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); const jsContext: ReanimatedContext = { 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 86253ae056..ec7bf29887 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,8 +1,12 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; +import { BaseGestureConfig } from '../../../types'; import { extractStateChangeHandlers } from '../../utils/eventHandlersUtils'; import { getStateChangeHandler } from '../stateChangeHandler'; -export function useReanimatedStateChangeEvent(handlerTag: number, config: any) { +export function useReanimatedStateChangeEvent( + handlerTag: number, + config: BaseGestureConfig +) { const handlers = extractStateChangeHandlers(config); const callback = getStateChangeHandler(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 81e0e3f204..a25932854b 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,8 +1,12 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; +import { BaseGestureConfig } from '../../../types'; import { extractTouchHandlers } from '../../utils/eventHandlersUtils'; import { getTouchEventHandler } from '../touchEventHandler'; -export function useReanimatedTouchEvent(handlerTag: number, config: any) { +export function useReanimatedTouchEvent( + handlerTag: number, + config: BaseGestureConfig +) { const handlers = extractTouchHandlers(config); const callback = getTouchEventHandler(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 7f4f0083d1..55ae397e48 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,8 +1,12 @@ import { Reanimated } from '../../../../handlers/gestures/reanimatedWrapper'; +import { BaseGestureConfig } from '../../../types'; import { extractUpdateHandlers } from '../../utils/eventHandlersUtils'; import { getUpdateHandler } from '../updateHandler'; -export function useReanimatedUpdateEvent(handlerTag: number, config: any) { +export function useReanimatedUpdateEvent( + handlerTag: number, + config: BaseGestureConfig +) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); // We don't want to call hooks conditionally, therefore `useHandler` and `useEvent` will be always called. 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 ad6a0ef511..323b5dee6c 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 @@ -1,12 +1,12 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { State } from '../../../State'; -import { CallbackHandlers, StateChangeEvent } from '../../types'; +import { GestureCallbacks, StateChangeEvent } from '../../types'; import { isEventForHandlerWithTag, isNativeEvent } from '../utils'; import { runCallback } from '../utils/eventHandlersUtils'; export function getStateChangeHandler( handlerTag: number, - callbacks: CallbackHandlers + callbacks: GestureCallbacks ) { return (event: StateChangeEvent>) => { 'worklet'; 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 7f58c888ef..6b333399f8 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 @@ -1,5 +1,5 @@ import { NativeSyntheticEvent } from 'react-native'; -import { CallbackHandlers, TouchEvent } from '../../types'; +import { GestureCallbacks, TouchEvent } from '../../types'; import { isEventForHandlerWithTag, isNativeEvent } from '../utils'; import { TouchEventType } from '../../../TouchEventType'; import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; @@ -10,7 +10,7 @@ import { export function getTouchEventHandler( handlerTag: number, - callbacks: CallbackHandlers + callbacks: GestureCallbacks ) { return (event: TouchEvent) => { 'worklet'; 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 e18dd31c8c..7ca87ea786 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 @@ -1,18 +1,19 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { tagMessage } from '../../../utils'; import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper'; -import { CallbackHandlers, UpdateEvent } from '../../types'; +import { + ChangeCalculatorType, + GestureCallbacks, + UpdateEvent, +} from '../../types'; import { isEventForHandlerWithTag } from '../utils'; import { runCallback } from '../utils/eventHandlersUtils'; export function getUpdateHandler( handlerTag: number, - callbacks: CallbackHandlers, + callbacks: GestureCallbacks, context: ReanimatedContext | undefined, - changeEventCalculator?: ( - current: UpdateEvent>, - previous?: UpdateEvent> - ) => UpdateEvent> + changeEventCalculator?: ChangeCalculatorType ) { return (event: UpdateEvent>) => { 'worklet'; 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..79f3140799 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 { BaseGestureConfig, NativeGesture, SingleGestureType } from '../types'; import { bindSharedValues, hasWorkletEventHandlers, @@ -19,7 +19,7 @@ import { prepareRelations } from './utils/relationUtils'; export function useGesture( type: SingleGestureType, - config: Record + config: BaseGestureConfig ): NativeGesture { const tag = useMemo(() => getNextHandlerTag(), []); const disableReanimated = useMemo(() => config.disableReanimated, []); @@ -38,8 +38,7 @@ export function useGesture( Reanimated !== undefined && hasWorkletEventHandlers(config); config.needsPointerData = shouldHandleTouchEvents(config); - // TODO: Remove this when we properly type config - config.dispatchesAnimatedEvents = isAnimatedEvent(config.onUpdate as any); + config.dispatchesAnimatedEvents = isAnimatedEvent(config.onUpdate); if (config.dispatchesAnimatedEvents && config.shouldUseReanimated) { throw new Error( 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 37c3b641e1..4557366503 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts @@ -1,13 +1,16 @@ import { useGestureStateChangeEvent } from './callbacks/js/useGestureStateChangeEvent'; import { useGestureUpdateEvent } from './callbacks/js/useGestureUpdateEvent'; import { useGestureTouchEvent } from './callbacks/js/useGestureTouchEvent'; -import { AnimatedEvent } from '../types'; +import { AnimatedEvent, BaseGestureConfig } 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) { +export function useGestureCallbacks( + handlerTag: number, + config: BaseGestureConfig +) { const onGestureHandlerStateChange = useGestureStateChangeEvent( handlerTag, config @@ -31,8 +34,7 @@ export function useGestureCallbacks(handlerTag: number, config: any) { let onGestureHandlerAnimatedEvent: AnimatedEvent | undefined; if (isAnimatedEvent(config.onUpdate)) { checkMappingForChangeProperties(config.onUpdate); - // TODO: Remove cast when config is properly typed. - onGestureHandlerAnimatedEvent = config.onUpdate as AnimatedEvent; + onGestureHandlerAnimatedEvent = config.onUpdate; } return { 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 57c5bee592..cc65f6cd47 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -1,9 +1,10 @@ import { NativeSyntheticEvent } from 'react-native'; import { AnimatedEvent, + BaseGestureConfig, GestureHandlerEvent, - GestureStateChangeEventWithData, - GestureUpdateEventWithData, + GestureStateChangeEvent, + GestureUpdateEvent, } from '../types'; import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon'; import { tagMessage } from '../../utils'; @@ -12,8 +13,8 @@ import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; export function isNativeEvent( event: GestureHandlerEvent ): event is - | NativeSyntheticEvent> - | NativeSyntheticEvent> + | NativeSyntheticEvent> + | NativeSyntheticEvent> | NativeSyntheticEvent { 'worklet'; @@ -32,7 +33,10 @@ export function isEventForHandlerWithTag( } export function isAnimatedEvent( - callback: ((event: any) => void) | AnimatedEvent | undefined + callback: + | ((event: GestureUpdateEvent) => void) + | AnimatedEvent + | undefined ): callback is AnimatedEvent { 'worklet'; @@ -58,7 +62,7 @@ export function checkMappingForChangeProperties(animatedEvent: AnimatedEvent) { } } -export function prepareConfig(config: any) { +export function prepareConfig(config: BaseGestureConfig) { const copy = { ...config }; for (const key in copy) { @@ -69,15 +73,15 @@ 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; + delete copy.onUpdate; + delete copy.simultaneousWithExternalGesture; + delete copy.requireExternalGestureToFail; + delete copy.blocksExternalGesture; return copy; } -export function shouldHandleTouchEvents(config: Record) { +export function shouldHandleTouchEvents(config: BaseGestureConfig) { return ( !!config.onTouchesDown || !!config.onTouchesMove || 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 index 963e5c5ecd..a881547873 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts @@ -1,17 +1,20 @@ import { TouchEventType } from '../../../TouchEventType'; import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { - CallbackHandlers, + BaseGestureConfig, + ChangeCalculatorType, + GestureCallbacks, GestureHandlerEvent, - UpdateEvent, } from '../../types'; import { isNativeEvent } from '../utils'; -export function extractStateChangeHandlers(config: any): CallbackHandlers { +export function extractStateChangeHandlers( + config: BaseGestureConfig +): GestureCallbacks { 'worklet'; const { onBegin, onStart, onEnd, onFinalize } = config; - const handlers: CallbackHandlers = { + const handlers: GestureCallbacks = { ...(onBegin ? { onBegin } : {}), ...(onStart ? { onStart } : {}), ...(onEnd ? { onEnd } : {}), @@ -21,26 +24,31 @@ export function extractStateChangeHandlers(config: any): CallbackHandlers { return handlers; } -export function extractUpdateHandlers(config: any): { - handlers: CallbackHandlers; - changeEventCalculator?: ( - current: UpdateEvent>, - previous?: UpdateEvent> - ) => UpdateEvent>; -} { +type UpdateHandlersReturnType = { + handlers: GestureCallbacks; + changeEventCalculator?: ChangeCalculatorType; +}; + +export function extractUpdateHandlers( + config: BaseGestureConfig +): UpdateHandlersReturnType { 'worklet'; const { onUpdate, changeEventCalculator } = config; - const handlers: CallbackHandlers = { ...(onUpdate ? { onUpdate } : {}) }; + const handlers: GestureCallbacks = { + ...(onUpdate ? { onUpdate } : {}), + }; return { handlers, changeEventCalculator }; } -export function extractTouchHandlers(config: any): CallbackHandlers { +export function extractTouchHandlers( + config: BaseGestureConfig +): GestureCallbacks { const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = config; - const handlers: CallbackHandlers = { + const handlers: GestureCallbacks = { ...(onTouchesDown ? { onTouchesDown } : {}), ...(onTouchesMove ? { onTouchesMove } : {}), ...(onTouchesUp ? { onTouchesUp } : {}), @@ -50,27 +58,30 @@ export function extractTouchHandlers(config: any): CallbackHandlers { return handlers; } -export function getHandler(type: CALLBACK_TYPE, config: CallbackHandlers) { +export function getHandler( + type: CALLBACK_TYPE, + callbacks: GestureCallbacks +) { 'worklet'; switch (type) { case CALLBACK_TYPE.BEGAN: - return config.onBegin; + return callbacks.onBegin; case CALLBACK_TYPE.START: - return config.onStart; + return callbacks.onStart; case CALLBACK_TYPE.UPDATE: - return config.onUpdate; + return callbacks.onUpdate; case CALLBACK_TYPE.END: - return config.onEnd; + return callbacks.onEnd; case CALLBACK_TYPE.FINALIZE: - return config.onFinalize; + return callbacks.onFinalize; case CALLBACK_TYPE.TOUCHES_DOWN: - return config.onTouchesDown; + return callbacks.onTouchesDown; case CALLBACK_TYPE.TOUCHES_MOVE: - return config.onTouchesMove; + return callbacks.onTouchesMove; case CALLBACK_TYPE.TOUCHES_UP: - return config.onTouchesUp; + return callbacks.onTouchesUp; case CALLBACK_TYPE.TOUCHES_CANCELLED: - return config.onTouchesCancelled; + return callbacks.onTouchesCancelled; } } @@ -93,12 +104,12 @@ export function touchEventTypeToCallbackType( export function runCallback( type: CALLBACK_TYPE, - config: CallbackHandlers, + callbacks: GestureCallbacks, event: GestureHandlerEvent>, ...args: unknown[] ) { 'worklet'; - const handler = getHandler(type, config); + const handler = getHandler(type, callbacks); // TODO: add proper types (likely boolean) // @ts-ignore It works, duh 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 index c0cf19cc81..4ff3c085db 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts @@ -3,6 +3,7 @@ import { Reanimated, SharedValue, } from '../../../handlers/gestures/reanimatedWrapper'; +import { BaseGestureConfig } from '../../types'; // Variant of djb2 hash function. // Taken from https://gist.github.com/eplawless/52813b1d8ad9af510d85?permalink_comment_id=3367765#gistcomment-3367765 @@ -22,7 +23,10 @@ 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) { +export function bindSharedValues( + config: BaseGestureConfig, + handlerTag: number +) { if (Reanimated === undefined) { return; } @@ -49,7 +53,10 @@ export function bindSharedValues(config: any, handlerTag: number) { } } -export function unbindSharedValues(config: any, handlerTag: number) { +export function unbindSharedValues( + config: BaseGestureConfig, + handlerTag: number +) { if (Reanimated === undefined) { return; } @@ -70,7 +77,7 @@ export function unbindSharedValues(config: any, handlerTag: number) { } } -export function hasWorkletEventHandlers(config: Record) { +export function hasWorkletEventHandlers(config: BaseGestureConfig) { 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 index e94e0a6c96..55b0328d7e 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,4 +1,5 @@ import { + BaseGestureConfig, ComposedGesture, Gesture, GestureRelations, @@ -12,11 +13,13 @@ export function isComposedGesture( } export function prepareRelations( - config: any, + config: BaseGestureConfig, handlerTag: number ): GestureRelations { // TODO: Handle composed gestures passed into external relations - const extractHandlerTags = (otherHandler: Gesture | Gesture[]): number[] => { + const extractHandlerTags = ( + otherHandler: Gesture | Gesture[] | undefined + ): number[] => { if (!otherHandler) { return []; } @@ -40,9 +43,11 @@ export function prepareRelations( if (config.simultaneousWithExternalGesture) { if (Array.isArray(config.simultaneousWithExternalGesture)) { for (const gesture of config.simultaneousWithExternalGesture) { + // @ts-ignore TODO: handle composed gestures gesture.gestureRelations.simultaneousHandlers.push(handlerTag); } } else { + // @ts-ignore TODO: handle composed gestures config.simultaneousWithExternalGesture.gestureRelations.simultaneousHandlers.push( handlerTag ); diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 3118cf66c1..6faf4a0ba2 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -7,14 +7,13 @@ import { import { HandlerCallbacks } from '../handlers/gestures/gesture'; import { ValueOf } from '../typeUtils'; -export type GestureUpdateEventWithData = GestureEventPayload & { +export type GestureUpdateEvent = GestureEventPayload & { handlerData: T; }; -export type GestureStateChangeEventWithData = - HandlerStateChangeEventPayload & { - handlerData: T; - }; +export type GestureStateChangeEvent = HandlerStateChangeEventPayload & { + handlerData: T; +}; export type GestureHandlerEvent = | UpdateEvent @@ -22,12 +21,12 @@ export type GestureHandlerEvent = | TouchEvent; export type UpdateEvent = - | GestureUpdateEventWithData - | NativeSyntheticEvent>; + | GestureUpdateEvent + | NativeSyntheticEvent>; export type StateChangeEvent = - | GestureStateChangeEventWithData - | NativeSyntheticEvent>; + | GestureStateChangeEvent + | NativeSyntheticEvent>; export type TouchEvent = | GestureTouchEvent @@ -109,7 +108,7 @@ export type GestureRelations = { export type NativeGesture = { tag: number; type: HandlerType; - config: Record; + config: BaseGestureConfig; gestureEvents: GestureEvents; gestureRelations: GestureRelations; }; @@ -125,4 +124,38 @@ export type ComposedGesture = { gestures: (NativeGesture | ComposedGesture)[]; }; +export type ChangeCalculatorType = ( + current: UpdateEvent>, + previous?: UpdateEvent> +) => UpdateEvent>; + export type Gesture = NativeGesture | ComposedGesture; + +interface ExternalRelations { + simultaneousWithExternalGesture?: Gesture | Gesture[]; + requireExternalGestureToFail?: Gesture | Gesture[]; + blocksExternalGesture?: Gesture | Gesture[]; +} + +export interface GestureCallbacks { + onBegin?: (event: GestureStateChangeEvent) => void; + onStart?: (event: GestureStateChangeEvent) => void; + onEnd?: (event: GestureStateChangeEvent, success: boolean) => void; + onFinalize?: (event: GestureStateChangeEvent, success: boolean) => void; + onUpdate?: (event: GestureUpdateEvent) => void | AnimatedEvent; + onTouchesDown?: (event: GestureTouchEvent) => void; + onTouchesMove?: (event: GestureTouchEvent) => void; + onTouchesUp?: (event: GestureTouchEvent) => void; + onTouchesCancelled?: (event: GestureTouchEvent) => void; +} + +export interface BaseGestureConfig + extends ExternalRelations, + GestureCallbacks, + Record { + disableReanimated?: boolean; + shouldUseReanimated?: boolean; + dispatchesAnimatedEvents?: boolean; + needsPointerData?: boolean; + changeEventCalculator?: ChangeCalculatorType; +} diff --git a/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts index 71df67ac4c..f1a17360db 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts @@ -26,10 +26,7 @@ import { PointerType } from '../../PointerType'; import { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate'; import { ActionType } from '../../ActionType'; import { tagMessage } from '../../utils'; -import { - GestureStateChangeEventWithData, - GestureUpdateEventWithData, -} from '../../v3/types'; +import { GestureStateChangeEvent, GestureUpdateEvent } from '../../v3/types'; import { TouchEventType } from '../../TouchEventType'; export default abstract class GestureHandler implements IGestureHandler { @@ -464,7 +461,7 @@ export default abstract class GestureHandler implements IGestureHandler { private transformStateChangeEvent( newState: State, oldState: State - ): ResultEvent> { + ): ResultEvent> { this.ensureViewRef(this.viewRef); return { nativeEvent: { @@ -487,7 +484,7 @@ export default abstract class GestureHandler implements IGestureHandler { private transformUpdateEvent( newState: State - ): ResultEvent> { + ): ResultEvent> { this.ensureViewRef(this.viewRef); return { nativeEvent: { diff --git a/packages/react-native-gesture-handler/src/web/interfaces.ts b/packages/react-native-gesture-handler/src/web/interfaces.ts index 49d0d81c76..614bb46b64 100644 --- a/packages/react-native-gesture-handler/src/web/interfaces.ts +++ b/packages/react-native-gesture-handler/src/web/interfaces.ts @@ -8,10 +8,7 @@ import { } from '../handlers/gestureHandlerCommon'; import { Directions } from '../Directions'; import { PointerType } from '../PointerType'; -import { - GestureStateChangeEventWithData, - GestureUpdateEventWithData, -} from '../v3/types'; +import { GestureStateChangeEvent, GestureUpdateEvent } from '../v3/types'; import { State } from '../State'; export interface HitSlop { @@ -116,8 +113,8 @@ export interface PointerData { // Native event has to stay for v2 compatibility type ResultEventType = - | GestureUpdateEventWithData - | GestureStateChangeEventWithData + | GestureUpdateEvent + | GestureStateChangeEvent | GestureTouchEvent | GestureHandlerNativeEvent; From 5c560e87f1bd4014fb3c536f493548f2a9e44c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 8 Sep 2025 12:06:25 +0200 Subject: [PATCH 049/109] Split types and handler data --- .../handlers/gestures/reanimatedWrapper.ts | 8 +- .../src/v3/NativeDetector/NativeDetector.tsx | 2 +- .../src/v3/NativeDetector/utils.ts | 2 +- .../js/useGestureStateChangeEvent.ts | 4 +- .../callbacks/js/useGestureTouchEvent.ts | 4 +- .../callbacks/js/useGestureUpdateEvent.ts | 6 +- .../useReanimatedStateChangeEvent.ts | 4 +- .../reanimated/useReanimatedTouchEvent.ts | 4 +- .../reanimated/useReanimatedUpdateEvent.ts | 4 +- .../v3/hooks/callbacks/stateChangeHandler.ts | 6 +- .../v3/hooks/callbacks/touchEventHandler.ts | 4 +- .../src/v3/hooks/callbacks/updateHandler.ts | 10 +- .../v3/hooks/relations/useComposedGesture.ts | 10 +- .../src/v3/hooks/relations/useExclusive.ts | 4 +- .../src/v3/hooks/relations/useRace.ts | 4 +- .../src/v3/hooks/relations/useSimultaneous.ts | 2 +- .../src/v3/hooks/useGesture.ts | 6 +- .../src/v3/hooks/useGestureCallbacks.ts | 4 +- .../src/v3/hooks/utils.ts | 26 ++-- .../src/v3/hooks/utils/eventHandlersUtils.ts | 40 +++--- .../src/v3/hooks/utils/reanimatedUtils.ts | 12 +- .../src/v3/hooks/utils/relationUtils.ts | 13 +- .../src/v3/types.ts | 123 +++++++++--------- 23 files changed, 157 insertions(+), 145 deletions(-) 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 e45a0cdec1..1d73f5b4de 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -14,8 +14,8 @@ export interface SharedValue { ) => void; } -export type ReanimatedContext = { - lastUpdateEvent: UpdateEvent> | undefined; +export type ReanimatedContext = { + lastUpdateEvent: UpdateEvent | undefined; }; interface WorkletProps { @@ -41,9 +41,9 @@ let Reanimated: options?: unknown ): ComponentClass

; }; - useHandler: (handlers: GestureCallbacks) => { + useHandler: (handlers: GestureCallbacks) => { doDependenciesDiffer: boolean; - context: ReanimatedContext; + context: ReanimatedContext; }; useEvent: ( callback: (event: T) => void, 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..e3026f0886 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -11,7 +11,7 @@ import { isComposedGesture } from '../hooks/utils/relationUtils'; export interface NativeDetectorProps { children?: React.ReactNode; - gesture: NativeGesture | ComposedGesture; + gesture: NativeGesture | ComposedGesture; } 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 46b851f4c4..2c7e42bd7d 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -10,7 +10,7 @@ import { ComposedGesture, ComposedGestureType, NativeGesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. export const traverseGestureRelations = ( - node: NativeGesture | ComposedGesture, + node: NativeGesture | ComposedGesture, simultaneousHandlers: Set, waitFor: 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 37e4d72d53..3c8fa64286 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 @@ -2,9 +2,9 @@ import { BaseGestureConfig } from '../../../types'; import { extractStateChangeHandlers } from '../../utils/eventHandlersUtils'; import { getStateChangeHandler } from '../stateChangeHandler'; -export function useGestureStateChangeEvent( +export function useGestureStateChangeEvent( handlerTag: number, - config: BaseGestureConfig + config: BaseGestureConfig ) { 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 9c086c5922..0271d2ad4d 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 @@ -2,9 +2,9 @@ import { BaseGestureConfig } from '../../../types'; import { extractTouchHandlers } from '../../utils/eventHandlersUtils'; import { getTouchEventHandler } from '../touchEventHandler'; -export function useGestureTouchEvent( +export function useGestureTouchEvent( handlerTag: number, - config: BaseGestureConfig + config: BaseGestureConfig ) { 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 470391d846..c3122a4224 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 @@ -4,13 +4,13 @@ import { extractUpdateHandlers } from '../../utils/eventHandlersUtils'; import { getUpdateHandler } from '../updateHandler'; import { BaseGestureConfig } from '../../../types'; -export function useGestureUpdateEvent( +export function useGestureUpdateEvent( handlerTag: number, - config: BaseGestureConfig + config: BaseGestureConfig ) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); - const jsContext: ReanimatedContext = { + 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 ec7bf29887..99863e9ea9 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 @@ -3,9 +3,9 @@ import { BaseGestureConfig } from '../../../types'; import { extractStateChangeHandlers } from '../../utils/eventHandlersUtils'; import { getStateChangeHandler } from '../stateChangeHandler'; -export function useReanimatedStateChangeEvent( +export function useReanimatedStateChangeEvent( handlerTag: number, - config: BaseGestureConfig + config: BaseGestureConfig ) { const handlers = extractStateChangeHandlers(config); 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 a25932854b..b1c27188f3 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 @@ -3,9 +3,9 @@ import { BaseGestureConfig } from '../../../types'; import { extractTouchHandlers } from '../../utils/eventHandlersUtils'; import { getTouchEventHandler } from '../touchEventHandler'; -export function useReanimatedTouchEvent( +export function useReanimatedTouchEvent( handlerTag: number, - config: BaseGestureConfig + config: BaseGestureConfig ) { const handlers = extractTouchHandlers(config); 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 55ae397e48..c86d790c20 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 @@ -3,9 +3,9 @@ import { BaseGestureConfig } from '../../../types'; import { extractUpdateHandlers } from '../../utils/eventHandlersUtils'; import { getUpdateHandler } from '../updateHandler'; -export function useReanimatedUpdateEvent( +export function useReanimatedUpdateEvent( handlerTag: number, - config: BaseGestureConfig + config: BaseGestureConfig ) { const { handlers, changeEventCalculator } = extractUpdateHandlers(config); 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 323b5dee6c..afd268e45b 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 @@ -4,11 +4,11 @@ import { GestureCallbacks, StateChangeEvent } from '../../types'; import { isEventForHandlerWithTag, isNativeEvent } from '../utils'; import { runCallback } from '../utils/eventHandlersUtils'; -export function getStateChangeHandler( +export function getStateChangeHandler( handlerTag: number, - callbacks: GestureCallbacks + callbacks: GestureCallbacks ) { - return (event: StateChangeEvent>) => { + return (event: StateChangeEvent) => { 'worklet'; if (!isEventForHandlerWithTag(handlerTag, event)) { 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 6b333399f8..6b39e82fb8 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 @@ -8,9 +8,9 @@ import { touchEventTypeToCallbackType, } from '../utils/eventHandlersUtils'; -export function getTouchEventHandler( +export function getTouchEventHandler( handlerTag: number, - callbacks: GestureCallbacks + callbacks: GestureCallbacks ) { return (event: TouchEvent) => { 'worklet'; 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 7ca87ea786..8f37633857 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 @@ -9,13 +9,13 @@ import { import { isEventForHandlerWithTag } from '../utils'; import { runCallback } from '../utils/eventHandlersUtils'; -export function getUpdateHandler( +export function getUpdateHandler( handlerTag: number, - callbacks: GestureCallbacks, - context: ReanimatedContext | undefined, - changeEventCalculator?: ChangeCalculatorType + callbacks: GestureCallbacks, + context: ReanimatedContext | undefined, + changeEventCalculator?: ChangeCalculatorType ) { - return (event: UpdateEvent>) => { + return (event: UpdateEvent) => { 'worklet'; if (!isEventForHandlerWithTag(handlerTag, event)) { 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..733fcfe99e 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,7 +12,7 @@ import { isComposedGesture } from '../utils/relationUtils'; // TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) export function useComposedGesture( - ...gestures: (NativeGesture | ComposedGesture)[] + ...gestures: (NativeGesture | ComposedGesture)[] ): ComposedGesture { const tags = gestures.flatMap((gesture) => isComposedGesture(gesture) ? gesture.tags : gesture.tag @@ -35,9 +35,7 @@ export function useComposedGesture( ); } - const onGestureHandlerStateChange = ( - event: StateChangeEvent> - ) => { + const onGestureHandlerStateChange = (event: StateChangeEvent) => { for (const gesture of gestures) { if (gesture.gestureEvents.onGestureHandlerStateChange) { gesture.gestureEvents.onGestureHandlerStateChange(event); @@ -45,9 +43,7 @@ export function useComposedGesture( } }; - const onGestureHandlerEvent = ( - event: UpdateEvent> - ) => { + const onGestureHandlerEvent = (event: UpdateEvent) => { for (const gesture of gestures) { if (gesture.gestureEvents.onGestureHandlerEvent) { gesture.gestureEvents.onGestureHandlerEvent(event); 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..e4fc064acb 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 @@ -5,7 +5,9 @@ import { } from '../../types'; import { useComposedGesture } from './useComposedGesture'; -export function useExclusive(...gestures: (NativeGesture | ComposedGesture)[]) { +export function useExclusive( + ...gestures: (NativeGesture | ComposedGesture)[] +) { const composedGesture = useComposedGesture(...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..dec6f1a717 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,8 @@ import { ComposedGesture, NativeGesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; -export function useRace(...gestures: (NativeGesture | ComposedGesture)[]) { +export function useRace( + ...gestures: (NativeGesture | 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 f046b05ff7..f8697bf5e6 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 @@ -6,7 +6,7 @@ import { import { useComposedGesture } from './useComposedGesture'; export function useSimultaneous( - ...gestures: (NativeGesture | ComposedGesture)[] + ...gestures: (NativeGesture | ComposedGesture)[] ) { const composedGesture = useComposedGesture(...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 79f3140799..6471deec9b 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -17,10 +17,10 @@ import { } from './utils/reanimatedUtils'; import { prepareRelations } from './utils/relationUtils'; -export function useGesture( +export function useGesture( type: SingleGestureType, - config: BaseGestureConfig -): NativeGesture { + config: BaseGestureConfig +): NativeGesture { const tag = useMemo(() => getNextHandlerTag(), []); const disableReanimated = useMemo(() => config.disableReanimated, []); 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 4557366503..b4ccedfd9a 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGestureCallbacks.ts @@ -7,9 +7,9 @@ import { useReanimatedStateChangeEvent } from './callbacks/reanimated/useReanima import { useReanimatedUpdateEvent } from './callbacks/reanimated/useReanimatedUpdateEvent'; import { useReanimatedTouchEvent } from './callbacks/reanimated/useReanimatedTouchEvent'; -export function useGestureCallbacks( +export function useGestureCallbacks( handlerTag: number, - config: BaseGestureConfig + config: BaseGestureConfig ) { const onGestureHandlerStateChange = useGestureStateChangeEvent( handlerTag, 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 cc65f6cd47..d90fc773d1 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -10,20 +10,20 @@ import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon'; import { tagMessage } from '../../utils'; import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; -export function isNativeEvent( - event: GestureHandlerEvent +export function isNativeEvent( + event: GestureHandlerEvent ): event is - | NativeSyntheticEvent> - | NativeSyntheticEvent> + | NativeSyntheticEvent> + | NativeSyntheticEvent> | NativeSyntheticEvent { 'worklet'; return 'nativeEvent' in event; } -export function isEventForHandlerWithTag( +export function isEventForHandlerWithTag( handlerTag: number, - event: GestureHandlerEvent> + event: GestureHandlerEvent ) { 'worklet'; @@ -32,9 +32,9 @@ export function isEventForHandlerWithTag( : event.handlerTag === handlerTag; } -export function isAnimatedEvent( +export function isAnimatedEvent( callback: - | ((event: GestureUpdateEvent) => void) + | ((event: GestureUpdateEvent) => void) | AnimatedEvent | undefined ): callback is AnimatedEvent { @@ -62,11 +62,15 @@ export function checkMappingForChangeProperties(animatedEvent: AnimatedEvent) { } } -export function prepareConfig(config: BaseGestureConfig) { +export function prepareConfig( + config: BaseGestureConfig +) { const copy = { ...config }; for (const key in copy) { + // @ts-ignore It is fine to use string as index if (Reanimated?.isSharedValue(copy[key])) { + // @ts-ignore It is fine to use string as index copy[key] = copy[key].value; } } @@ -81,7 +85,9 @@ export function prepareConfig(config: BaseGestureConfig) { return copy; } -export function shouldHandleTouchEvents(config: BaseGestureConfig) { +export function shouldHandleTouchEvents( + config: BaseGestureConfig +) { return ( !!config.onTouchesDown || !!config.onTouchesMove || 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 index a881547873..2503541d79 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts @@ -8,13 +8,13 @@ import { } from '../../types'; import { isNativeEvent } from '../utils'; -export function extractStateChangeHandlers( - config: BaseGestureConfig -): GestureCallbacks { +export function extractStateChangeHandlers( + config: BaseGestureConfig +): GestureCallbacks { 'worklet'; const { onBegin, onStart, onEnd, onFinalize } = config; - const handlers: GestureCallbacks = { + const handlers: GestureCallbacks = { ...(onBegin ? { onBegin } : {}), ...(onStart ? { onStart } : {}), ...(onEnd ? { onEnd } : {}), @@ -24,31 +24,31 @@ export function extractStateChangeHandlers( return handlers; } -type UpdateHandlersReturnType = { - handlers: GestureCallbacks; - changeEventCalculator?: ChangeCalculatorType; +type UpdateHandlersReturnType = { + handlers: GestureCallbacks; + changeEventCalculator?: ChangeCalculatorType; }; -export function extractUpdateHandlers( - config: BaseGestureConfig -): UpdateHandlersReturnType { +export function extractUpdateHandlers( + config: BaseGestureConfig +): UpdateHandlersReturnType { 'worklet'; const { onUpdate, changeEventCalculator } = config; - const handlers: GestureCallbacks = { + const handlers: GestureCallbacks = { ...(onUpdate ? { onUpdate } : {}), }; return { handlers, changeEventCalculator }; } -export function extractTouchHandlers( - config: BaseGestureConfig -): GestureCallbacks { +export function extractTouchHandlers( + config: BaseGestureConfig +): GestureCallbacks { const { onTouchesDown, onTouchesMove, onTouchesUp, onTouchesCancelled } = config; - const handlers: GestureCallbacks = { + const handlers: GestureCallbacks = { ...(onTouchesDown ? { onTouchesDown } : {}), ...(onTouchesMove ? { onTouchesMove } : {}), ...(onTouchesUp ? { onTouchesUp } : {}), @@ -58,9 +58,9 @@ export function extractTouchHandlers( return handlers; } -export function getHandler( +export function getHandler( type: CALLBACK_TYPE, - callbacks: GestureCallbacks + callbacks: GestureCallbacks ) { 'worklet'; switch (type) { @@ -102,10 +102,10 @@ export function touchEventTypeToCallbackType( return CALLBACK_TYPE.UNDEFINED; } -export function runCallback( +export function runCallback( type: CALLBACK_TYPE, - callbacks: GestureCallbacks, - event: GestureHandlerEvent>, + callbacks: GestureCallbacks, + event: GestureHandlerEvent, ...args: unknown[] ) { 'worklet'; 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 index 4ff3c085db..dd3c3b9a1e 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts @@ -23,8 +23,8 @@ 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: BaseGestureConfig, +export function bindSharedValues( + config: BaseGestureConfig, handlerTag: number ) { if (Reanimated === undefined) { @@ -53,8 +53,8 @@ export function bindSharedValues( } } -export function unbindSharedValues( - config: BaseGestureConfig, +export function unbindSharedValues( + config: BaseGestureConfig, handlerTag: number ) { if (Reanimated === undefined) { @@ -77,7 +77,9 @@ export function unbindSharedValues( } } -export function hasWorkletEventHandlers(config: BaseGestureConfig) { +export function hasWorkletEventHandlers( + config: BaseGestureConfig +) { 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 index 55b0328d7e..dc25217440 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 @@ -7,18 +7,21 @@ import { } from '../../types'; export function isComposedGesture( - gesture: NativeGesture | ComposedGesture + gesture: NativeGesture | ComposedGesture ): gesture is ComposedGesture { return 'tags' in gesture; } -export function prepareRelations( - config: BaseGestureConfig, +export function prepareRelations( + config: BaseGestureConfig, handlerTag: number ): GestureRelations { // TODO: Handle composed gestures passed into external relations const extractHandlerTags = ( - otherHandler: Gesture | Gesture[] | undefined + otherHandler: + | Gesture + | Gesture[] + | undefined ): number[] => { if (!otherHandler) { return []; @@ -28,7 +31,7 @@ export function prepareRelations( if (Array.isArray(otherHandler)) { otherTags = otherHandler.flatMap( - (gesture: NativeGesture | ComposedGesture) => + (gesture: NativeGesture | ComposedGesture) => isComposedGesture(gesture) ? gesture.tags : gesture.tag ); } else { diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 6faf4a0ba2..fe13524693 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -4,44 +4,34 @@ import { GestureTouchEvent, HandlerStateChangeEventPayload, } from '../handlers/gestureHandlerCommon'; -import { HandlerCallbacks } from '../handlers/gestures/gesture'; import { ValueOf } from '../typeUtils'; -export type GestureUpdateEvent = GestureEventPayload & { - handlerData: T; +export type GestureUpdateEvent = GestureEventPayload & { + handlerData: THandlerData; }; -export type GestureStateChangeEvent = HandlerStateChangeEventPayload & { - handlerData: T; -}; +export type GestureStateChangeEvent = + HandlerStateChangeEventPayload & { + handlerData: THandlerData; + }; -export type GestureHandlerEvent = - | UpdateEvent - | StateChangeEvent +export type GestureHandlerEvent = + | UpdateEvent + | StateChangeEvent | TouchEvent; -export type UpdateEvent = - | GestureUpdateEvent - | NativeSyntheticEvent>; +export type UpdateEvent = + | GestureUpdateEvent + | NativeSyntheticEvent>; -export type StateChangeEvent = - | GestureStateChangeEvent - | NativeSyntheticEvent>; +export type StateChangeEvent = + | GestureStateChangeEvent + | NativeSyntheticEvent>; export type TouchEvent = | GestureTouchEvent | NativeSyntheticEvent; -// TODO: Replace with v3 specific types -export type CallbackHandlers = Omit< - HandlerCallbacks>, - | 'gestureId' - | 'handlerTag' - | 'isWorklet' - | 'changeEventCalculator' - | 'onChange' ->; - // This is almost how Animated.event is typed in React Native. We add _argMapping in order to: // 1. Distinguish it from a regular function, // 2. Have access to the _argMapping property to check for usage of `change*` callbacks. @@ -81,20 +71,18 @@ export const HandlerType = { // eslint-disable-next-line @typescript-eslint/no-redeclare export type HandlerType = ValueOf; -export type GestureEvents = { - onGestureHandlerStateChange: ( - event: StateChangeEvent> - ) => void; +export type GestureEvents = { + onGestureHandlerStateChange: (event: StateChangeEvent) => void; onGestureHandlerEvent: | undefined - | ((event: UpdateEvent>) => void); + | ((event: UpdateEvent) => void); onGestureHandlerTouchEvent: (event: TouchEvent) => void; onReanimatedStateChange: | undefined - | ((event: StateChangeEvent>) => void); + | ((event: StateChangeEvent) => void); onReanimatedUpdateEvent: | undefined - | ((event: UpdateEvent>) => void); + | ((event: UpdateEvent) => void); onReanimatedTouchEvent: undefined | ((event: TouchEvent) => void); onGestureHandlerAnimatedEvent: undefined | AnimatedEvent; }; @@ -105,11 +93,11 @@ export type GestureRelations = { blocksHandlers: number[]; }; -export type NativeGesture = { +export type NativeGesture = { tag: number; type: HandlerType; - config: BaseGestureConfig; - gestureEvents: GestureEvents; + config: BaseGestureConfig; + gestureEvents: GestureEvents; gestureRelations: GestureRelations; }; @@ -120,42 +108,55 @@ export type ComposedGesture = { shouldUseReanimated: boolean; dispatchesAnimatedEvents: boolean; }; - gestureEvents: GestureEvents; - gestures: (NativeGesture | ComposedGesture)[]; + gestureEvents: GestureEvents; + gestures: (NativeGesture | ComposedGesture)[]; }; -export type ChangeCalculatorType = ( - current: UpdateEvent>, - previous?: UpdateEvent> -) => UpdateEvent>; +export type ChangeCalculatorType = ( + current: UpdateEvent, + previous?: UpdateEvent +) => UpdateEvent; -export type Gesture = NativeGesture | ComposedGesture; +export type Gesture = + | NativeGesture + | ComposedGesture; interface ExternalRelations { - simultaneousWithExternalGesture?: Gesture | Gesture[]; - requireExternalGestureToFail?: Gesture | Gesture[]; - blocksExternalGesture?: Gesture | Gesture[]; + simultaneousWithExternalGesture?: + | Gesture + | Gesture[]; + requireExternalGestureToFail?: + | Gesture + | Gesture[]; + blocksExternalGesture?: + | Gesture + | Gesture[]; } -export interface GestureCallbacks { - onBegin?: (event: GestureStateChangeEvent) => void; - onStart?: (event: GestureStateChangeEvent) => void; - onEnd?: (event: GestureStateChangeEvent, success: boolean) => void; - onFinalize?: (event: GestureStateChangeEvent, success: boolean) => void; - onUpdate?: (event: GestureUpdateEvent) => void | AnimatedEvent; +export interface GestureCallbacks { + onBegin?: (event: GestureStateChangeEvent) => void; + onStart?: (event: GestureStateChangeEvent) => void; + onEnd?: ( + event: GestureStateChangeEvent, + success: boolean + ) => void; + onFinalize?: ( + event: GestureStateChangeEvent, + success: boolean + ) => void; + onUpdate?: (event: GestureUpdateEvent) => void | AnimatedEvent; onTouchesDown?: (event: GestureTouchEvent) => void; onTouchesMove?: (event: GestureTouchEvent) => void; onTouchesUp?: (event: GestureTouchEvent) => void; onTouchesCancelled?: (event: GestureTouchEvent) => void; } -export interface BaseGestureConfig - extends ExternalRelations, - GestureCallbacks, - Record { - disableReanimated?: boolean; - shouldUseReanimated?: boolean; - dispatchesAnimatedEvents?: boolean; - needsPointerData?: boolean; - changeEventCalculator?: ChangeCalculatorType; -} +export type BaseGestureConfig = ExternalRelations & + GestureCallbacks & + TConfig & { + disableReanimated?: boolean; + shouldUseReanimated?: boolean; + dispatchesAnimatedEvents?: boolean; + needsPointerData?: boolean; + changeEventCalculator?: ChangeCalculatorType; + }; 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 050/109] 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 051/109] 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 052/109] 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 053/109] 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 054/109] 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 055/109] 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 056/109] 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 057/109] 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 058/109] 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 059/109] 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 060/109] 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 061/109] 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 062/109] 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 063/109] 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 064/109] 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 065/109] 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 066/109] 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 067/109] 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 068/109] 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 069/109] 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) { From d20025b68e9f6ee8505bebb5ab61fc59a1ad814c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 12 Sep 2025 12:19:50 +0200 Subject: [PATCH 070/109] Add default parameters to Gesture type --- .../src/v3/NativeDetector/NativeDetector.tsx | 2 +- .../src/v3/NativeDetector/utils.ts | 4 ++-- .../src/v3/hooks/relations/useComposedGesture.ts | 2 +- .../src/v3/hooks/relations/useExclusive.ts | 2 +- .../src/v3/hooks/relations/useRace.ts | 2 +- .../src/v3/hooks/relations/useSimultaneous.ts | 2 +- .../src/v3/hooks/utils/relationUtils.ts | 16 +++++----------- .../react-native-gesture-handler/src/v3/types.ts | 16 +++++----------- 8 files changed, 17 insertions(+), 29 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 66a8280b95..a4ec84720f 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -9,7 +9,7 @@ import { isComposedGesture } from '../hooks/utils/relationUtils'; export interface NativeDetectorProps { children?: React.ReactNode; - gesture: Gesture; + 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 35d7f916e1..6fc42163f6 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -13,7 +13,7 @@ import { ComposedGestureName, Gesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. export const traverseAndConfigureRelations = ( - node: Gesture, + node: Gesture, simultaneousHandlers: Set, waitFor: number[] = [] ) => { @@ -126,7 +126,7 @@ export const traverseAndConfigureRelations = ( }); }; -export function configureRelations(gesture: Gesture) { +export function configureRelations(gesture: Gesture) { if (isComposedGesture(gesture)) { const simultaneousHandlers = new Set( gesture.externalSimultaneousHandlers 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 80f7991c68..bdb8d11f2c 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 @@ -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: ComposedGestureName, - ...gestures: Gesture[] + ...gestures: Gesture[] ): ComposedGesture { const tags = gestures.flatMap((gesture) => isComposedGesture(gesture) ? gesture.tags : gesture.tag 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 850e9303f3..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,7 +1,7 @@ import { ComposedGestureName, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; -export function useExclusive(...gestures: Gesture[]) { +export function useExclusive(...gestures: Gesture[]) { const composedGesture = useComposedGesture( ComposedGestureName.Exclusive, ...gestures 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 9652b2206b..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 { ComposedGestureName, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; -export function useRace(...gestures: Gesture[]) { +export function useRace(...gestures: Gesture[]) { 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 eac8ddf6d5..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,7 +1,7 @@ import { ComposedGestureName, Gesture } from '../../types'; import { useComposedGesture } from './useComposedGesture'; -export function useSimultaneous(...gestures: Gesture[]) { +export function useSimultaneous(...gestures: Gesture[]) { const composedGesture = useComposedGesture( ComposedGestureName.Simultaneous, ...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 2ed55f9662..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 @@ -1,16 +1,13 @@ import { ComposedGesture, Gesture, GestureRelations } from '../../types'; export function isComposedGesture( - gesture: Gesture + gesture: Gesture ): gesture is ComposedGesture { return 'tags' in gesture; } function extractHandlerTags( - otherHandler: - | Gesture - | Gesture[] - | undefined + otherHandler: Gesture | Gesture[] | undefined ): number[] { if (!otherHandler) { return []; @@ -19,7 +16,7 @@ function extractHandlerTags( let otherTags: number[]; if (Array.isArray(otherHandler)) { - otherTags = otherHandler.flatMap((gesture: Gesture) => + otherTags = otherHandler.flatMap((gesture: Gesture) => isComposedGesture(gesture) ? gesture.tags : gesture.tag ); } else { @@ -32,17 +29,14 @@ function extractHandlerTags( } function makeSimultaneousWithExternalGestureSymmetric( - otherHandler: - | Gesture - | Gesture[] - | undefined, + otherHandler: Gesture | Gesture[] | undefined, handlerTag: number ) { if (!otherHandler) { return; } - const processSingleGesture = (gesture: Gesture) => { + const processSingleGesture = (gesture: Gesture) => { const simultaneousHandlers = isComposedGesture(gesture) ? gesture.externalSimultaneousHandlers : gesture.gestureRelations.simultaneousHandlers; diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 9404c3381b..8c2bc7451f 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -94,7 +94,7 @@ export type ComposedGesture = { }; gestureEvents: GestureEvents; externalSimultaneousHandlers: number[]; - gestures: Gesture[]; + gestures: Gesture[]; }; export type ChangeCalculatorType = ( @@ -102,20 +102,14 @@ export type ChangeCalculatorType = ( previous?: UpdateEvent ) => UpdateEvent; -export type Gesture = +export type Gesture = | SingleGesture | ComposedGesture; interface ExternalRelations { - simultaneousWithExternalGesture?: - | Gesture - | Gesture[]; - requireExternalGestureToFail?: - | Gesture - | Gesture[]; - blocksExternalGesture?: - | Gesture - | Gesture[]; + simultaneousWithExternalGesture?: Gesture | Gesture[]; + requireExternalGestureToFail?: Gesture | Gesture[]; + blocksExternalGesture?: Gesture | Gesture[]; } export interface GestureCallbacks { From 100e1aac1ac615322dc7b713ffa9ee1dd5fd2556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 10:17:54 +0200 Subject: [PATCH 071/109] useTap --- .../react-native-gesture-handler/src/index.ts | 2 + .../src/v3/hooks/gestures/index.ts | 2 + .../src/v3/hooks/gestures/useTap.ts | 99 +++++++++++++++++++ .../src/v3/hooks/utils.ts | 21 ++++ .../src/v3/types.ts | 19 +++- 5 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts index e349eef13a..82392a25a7 100644 --- a/packages/react-native-gesture-handler/src/index.ts +++ b/packages/react-native-gesture-handler/src/index.ts @@ -170,4 +170,6 @@ export * from './v3/hooks/relations'; export { SingleGestureName } from './v3/types'; +export * from './v3/hooks/gestures'; + initialize(); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts new file mode 100644 index 0000000000..09b2fa2420 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -0,0 +1,2 @@ +export type { TapGestureConfig } from './useTap'; +export { useTap } from './useTap'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts new file mode 100644 index 0000000000..6a30c9c69b --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts @@ -0,0 +1,99 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { remapProps } from '../utils'; + +type TapGestureHandlerProps = { + /** + * Minimum number of pointers (fingers) required to be placed before the + * handler activates. Should be a positive integer. + * The default value is 1. + */ + minPointers?: number; + + /** + * Maximum time, expressed in milliseconds, that defines how fast a finger + * must be released after a touch. The default value is 500. + */ + maxDuration?: number; + + /** + * Maximum time, expressed in milliseconds, that can pass before the next tap + * if many taps are required. The default value is 500. + */ + maxDelay?: number; + + /** + * Number of tap gestures required to activate the handler. The default value + * is 1. + */ + numberOfTaps?: number; + + /** + * Maximum distance, expressed in points, that defines how far the finger is + * allowed to travel along the X axis during a tap gesture. If the finger + * travels further than the defined distance along the X axis and the handler + * hasn't yet activated, it will fail to recognize the gesture. + */ + maxDeltaX?: number; + + /** + * Maximum distance, expressed in points, that defines how far the finger is + * allowed to travel along the Y axis during a tap gesture. If the finger + * travels further than the defined distance along the Y axis and the handler + * hasn't yet activated, it will fail to recognize the gesture. + */ + maxDeltaY?: number; + + /** + * Maximum distance, expressed in points, that defines how far the finger is + * allowed to travel during a tap gesture. If the finger travels further than + * the defined distance and the handler hasn't yet + * activated, it will fail to recognize the gesture. + */ + maxDistance?: number; +}; + +type TapGestureHandlerPropsInternal = { + minPointers?: number; + numberOfTaps?: number; + maxDeltaX?: number; + maxDeltaY?: number; + maxDurationMs?: number; + maxDelayMs?: number; + maxDist?: number; +}; + +type TapHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; +}; + +export type TapGestureConfig = ExcludeInternalConfigProps< + BaseGestureConfig +>; + +type TapGestureInternalConfig = BaseGestureConfig< + TapHandlerData, + TapGestureHandlerPropsInternal +>; + +const TapPropsMapping = new Map([ + ['maxDistance', 'maxDist'], + ['maxDuration', 'maxDurationMs'], + ['maxDelay', 'maxDelayMs'], +]); + +export function useTap(config: TapGestureConfig) { + const tapConfig = remapProps( + config, + TapPropsMapping + ); + + return useGesture(SingleGestureName.Tap, tapConfig); +} 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 d90fc773d1..d464344d8e 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -95,3 +95,24 @@ export function shouldHandleTouchEvents( !!config.onTouchesCancelled ); } + +export function remapProps( + config: TConfig, + propsMapping: Map +): TInternalConfig { + type MergedConfig = TConfig & TInternalConfig; + + const newConfig = { ...config } as MergedConfig; + + propsMapping.forEach((internalKey, key) => { + if (key in newConfig) { + newConfig[internalKey as keyof MergedConfig] = + newConfig[key as keyof MergedConfig]; + + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete newConfig[key as keyof MergedConfig]; + } + }); + + return newConfig; +} diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 8c2bc7451f..1ba10cec02 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -130,12 +130,21 @@ export interface GestureCallbacks { onTouchesCancelled?: (event: GestureTouchEvent) => void; } +export type InternalConfigProps = { + shouldUseReanimated?: boolean; + dispatchesAnimatedEvents?: boolean; + needsPointerData?: boolean; + changeEventCalculator?: ChangeCalculatorType; +}; + export type BaseGestureConfig = ExternalRelations & GestureCallbacks & - TConfig & { + TConfig & + InternalConfigProps & { disableReanimated?: boolean; - shouldUseReanimated?: boolean; - dispatchesAnimatedEvents?: boolean; - needsPointerData?: boolean; - changeEventCalculator?: ChangeCalculatorType; }; + +export type ExcludeInternalConfigProps = Omit< + T, + keyof InternalConfigProps +>; From 944cadabedf9dd5b5577bf1ff9a59b212cdd6af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 10:18:43 +0200 Subject: [PATCH 072/109] Make NativeDetector generic --- .../src/v3/NativeDetector/NativeDetector.tsx | 9 ++++++--- .../src/v3/NativeDetector/utils.ts | 4 +++- .../src/v3/hooks/utils/relationUtils.ts | 4 ++-- 3 files changed, 11 insertions(+), 6 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 a4ec84720f..156c4c2716 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -7,9 +7,9 @@ import { tagMessage } from '../../utils'; import { configureRelations } from './utils'; import { isComposedGesture } from '../hooks/utils/relationUtils'; -export interface NativeDetectorProps { +export interface NativeDetectorProps { children?: React.ReactNode; - gesture: Gesture; + gesture: Gesture; } const AnimatedNativeDetector = @@ -18,7 +18,10 @@ const AnimatedNativeDetector = const ReanimatedNativeDetector = Reanimated?.default.createAnimatedComponent(HostGestureDetector); -export function NativeDetector({ gesture, children }: NativeDetectorProps) { +export function NativeDetector({ + gesture, + children, +}: NativeDetectorProps) { const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents ? AnimatedNativeDetector : // TODO: Remove this cast when we properly type config 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 6fc42163f6..05a2936b61 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -126,7 +126,9 @@ export const traverseAndConfigureRelations = ( }); }; -export function configureRelations(gesture: Gesture) { +export function configureRelations( + gesture: Gesture +) { if (isComposedGesture(gesture)) { const simultaneousHandlers = new Set( gesture.externalSimultaneousHandlers 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 9ac510b9cd..44de198e8b 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,7 +1,7 @@ import { ComposedGesture, Gesture, GestureRelations } from '../../types'; -export function isComposedGesture( - gesture: Gesture +export function isComposedGesture( + gesture: Gesture ): gesture is ComposedGesture { return 'tags' in gesture; } From a9392ebcd6485b9b220aecc2364d5e13e2a3ee69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 11:11:26 +0200 Subject: [PATCH 073/109] useFling --- .../src/v3/hooks/gestures/index.ts | 3 ++ .../src/v3/hooks/gestures/useFling.ts | 53 +++++++++++++++++++ .../src/v3/hooks/utils.ts | 7 +++ 3 files changed, 63 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts index 09b2fa2420..6d92db60c5 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -1,2 +1,5 @@ export type { TapGestureConfig } from './useTap'; export { useTap } from './useTap'; + +export type { FlingGestureConfig } from './useFling'; +export { useFling } from './useFling'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts new file mode 100644 index 0000000000..a5b5bdfc91 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts @@ -0,0 +1,53 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type FlingGestureHandlerProps = { + /** + * Expressed allowed direction of movement. It's possible to pass one or many + * directions in one parameter: + * + * ```js + * direction={Directions.RIGHT | Directions.LEFT} + * ``` + * + * or + * + * ```js + * direction={Directions.DOWN} + * ``` + */ + direction?: number; + + /** + * Determine exact number of points required to handle the fling gesture. + */ + numberOfPointers?: number; +}; + +type FlingHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; +}; + +type FlingGestureInternalConfig = BaseGestureConfig< + FlingHandlerData, + FlingGestureHandlerProps +>; + +export type FlingGestureConfig = + ExcludeInternalConfigProps; + +export function useFling(config: FlingGestureConfig) { + const flingConfig = cloneConfig( + config + ); + + return useGesture(SingleGestureName.Fling, flingConfig); +} 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 d464344d8e..7059eeaed5 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -2,6 +2,7 @@ import { NativeSyntheticEvent } from 'react-native'; import { AnimatedEvent, BaseGestureConfig, + ExcludeInternalConfigProps, GestureHandlerEvent, GestureStateChangeEvent, GestureUpdateEvent, @@ -96,6 +97,12 @@ export function shouldHandleTouchEvents( ); } +export function cloneConfig( + config: ExcludeInternalConfigProps> +): BaseGestureConfig { + return { ...config } as BaseGestureConfig; +} + export function remapProps( config: TConfig, propsMapping: Map From 72118e7c197d982fbebcd0e8f01159b86738dc43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 11:23:32 +0200 Subject: [PATCH 074/109] useLongPress --- .../src/v3/hooks/gestures/index.ts | 3 + .../src/v3/hooks/gestures/useLongPress.ts | 71 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts index 6d92db60c5..af3a4b7305 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -3,3 +3,6 @@ export { useTap } from './useTap'; export type { FlingGestureConfig } from './useFling'; export { useFling } from './useFling'; + +export type { LongPressGestureConfig } from './useLongPress'; +export { useLongPress } from './useLongPress'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts new file mode 100644 index 0000000000..acc52af6ca --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts @@ -0,0 +1,71 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { remapProps } from '../utils'; + +export interface LongPressGestureHandlerProps { + /** + * Minimum time, expressed in milliseconds, that a finger must remain pressed on + * the corresponding view. The default value is 500. + */ + minDuration?: number; + + /** + * Maximum distance, expressed in points, that defines how far the finger is + * allowed to travel during a long press gesture. If the finger travels + * further than the defined distance and the handler hasn't yet activated, it + * will fail to recognize the gesture. The default value is 10. + */ + maxDistance?: number; + + /** + * Determine exact number of points required to handle the long press gesture. + */ + numberOfPointers?: number; +} + +type LongPressGestureHandlerPropsInternal = { + minDurationMs?: number; + maxDist?: number; + numberOfPointers?: number; +}; + +type LongPressHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; + duration: number; +}; + +export type LongPressGestureConfig = ExcludeInternalConfigProps< + BaseGestureConfig +>; + +type LongPressGestureInternalConfig = BaseGestureConfig< + LongPressHandlerData, + LongPressGestureHandlerPropsInternal +>; + +const LongPressPropsMapping = new Map< + keyof LongPressGestureHandlerProps, + keyof LongPressGestureHandlerPropsInternal +>([ + ['minDuration', 'minDurationMs'], + ['maxDistance', 'maxDist'], +]); + +export function useLongPress(config: LongPressGestureConfig) { + const longPressConfig = remapProps< + LongPressGestureConfig, + LongPressGestureInternalConfig + >(config, LongPressPropsMapping); + + return useGesture( + SingleGestureName.LongPress, + longPressConfig + ); +} From 0688821195ada012acb083e6aa5dc0daf073080a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 11:24:47 +0200 Subject: [PATCH 075/109] Small tap improvements --- .../src/v3/hooks/gestures/useTap.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts index 6a30c9c69b..01eed8fb0d 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts @@ -83,7 +83,10 @@ type TapGestureInternalConfig = BaseGestureConfig< TapGestureHandlerPropsInternal >; -const TapPropsMapping = new Map([ +const TapPropsMapping = new Map< + keyof TapGestureHandlerProps, + keyof TapGestureHandlerPropsInternal +>([ ['maxDistance', 'maxDist'], ['maxDuration', 'maxDurationMs'], ['maxDelay', 'maxDelayMs'], @@ -95,5 +98,8 @@ export function useTap(config: TapGestureConfig) { TapPropsMapping ); - return useGesture(SingleGestureName.Tap, tapConfig); + return useGesture( + SingleGestureName.Tap, + tapConfig + ); } From d3a7c328be277418c6fbe6df9ec93847784772fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 11:31:56 +0200 Subject: [PATCH 076/109] usePinch --- .../src/v3/hooks/gestures/index.ts | 3 ++ .../src/v3/hooks/gestures/usePinch.ts | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts index af3a4b7305..01f1dab6d9 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -6,3 +6,6 @@ export { useFling } from './useFling'; export type { LongPressGestureConfig } from './useLongPress'; export { useLongPress } from './useLongPress'; + +export type { PinchGestureConfig } from './usePinch'; +export { usePinch } from './usePinch'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts new file mode 100644 index 0000000000..55e3c1ba04 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -0,0 +1,32 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type PinchGestureHandlerProps = {}; + +type PinchHandlerData = { + scale: number; + focalX: number; + focalY: number; + velocity: number; +}; + +type PinchGestureInternalConfig = BaseGestureConfig< + PinchHandlerData, + PinchGestureHandlerProps +>; + +export type PinchGestureConfig = + ExcludeInternalConfigProps; + +export function usePinch(config: PinchGestureConfig) { + const pinchConfig = cloneConfig( + config + ); + + return useGesture(SingleGestureName.Pinch, pinchConfig); +} From bc3f1e5ca37bb0742783120b2cf2c685682011e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 11:37:14 +0200 Subject: [PATCH 077/109] useRotation --- .../src/v3/hooks/gestures/index.ts | 3 ++ .../src/v3/hooks/gestures/useRotation.ts | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts index 01f1dab6d9..32a7d1a873 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -9,3 +9,6 @@ export { useLongPress } from './useLongPress'; export type { PinchGestureConfig } from './usePinch'; export { usePinch } from './usePinch'; + +export type { RotationGestureConfig } from './useRotation'; +export { useRotation } from './useRotation'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts new file mode 100644 index 0000000000..872cd3ca9f --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -0,0 +1,33 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type RotationGestureHandlerProps = {}; + +type RotationHandlerData = { + rotation: number; + anchorX: number; + anchorY: number; + velocity: number; +}; + +type RotationGestureInternalConfig = BaseGestureConfig< + RotationHandlerData, + RotationGestureHandlerProps +>; + +export type RotationGestureConfig = + ExcludeInternalConfigProps; + +export function useRotation(config: RotationGestureConfig) { + const rotationConfig = cloneConfig< + RotationHandlerData, + RotationGestureHandlerProps + >(config); + + return useGesture(SingleGestureName.Rotation, rotationConfig); +} From 3d3198ccd6bfc69c652479c34b10584f5316f47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 11:41:48 +0200 Subject: [PATCH 078/109] Change interface to type --- .../src/v3/hooks/gestures/useLongPress.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts index acc52af6ca..0e6ecd19c9 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { remapProps } from '../utils'; -export interface LongPressGestureHandlerProps { +type LongPressGestureHandlerProps = { /** * Minimum time, expressed in milliseconds, that a finger must remain pressed on * the corresponding view. The default value is 500. @@ -25,7 +25,7 @@ export interface LongPressGestureHandlerProps { * Determine exact number of points required to handle the long press gesture. */ numberOfPointers?: number; -} +}; type LongPressGestureHandlerPropsInternal = { minDurationMs?: number; From 2d746d30b3944b93717b2cee7d2d4b5369415192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 11:49:17 +0200 Subject: [PATCH 079/109] useHover --- .../src/v3/hooks/gestures/index.ts | 3 ++ .../src/v3/hooks/gestures/useHover.ts | 46 +++++++++++++++++++ .../src/v3/types.ts | 1 + 3 files changed, 50 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts index 32a7d1a873..ccb2486ad5 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -12,3 +12,6 @@ export { usePinch } from './usePinch'; export type { RotationGestureConfig } from './useRotation'; export { useRotation } from './useRotation'; + +export type { HoverGestureConfig } from './useHover'; +export { useHover } from './useHover'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts new file mode 100644 index 0000000000..fe4da7e357 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -0,0 +1,46 @@ +import { StylusData } from '../../../handlers/gestureHandlerCommon'; +import { HoverEffect } from '../../../handlers/gestures/hoverGesture'; +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type HoverGestureHandlerProps = { + /** + * Visual effect applied to the view while the view is hovered. The possible values are: + * + * - `HoverEffect.None` + * - `HoverEffect.Lift` + * - `HoverEffect.Highlight` + * + * Defaults to `HoverEffect.None` + */ + hoverEffect?: HoverEffect; +}; + +type HoverHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; + stylusData: StylusData; +}; + +type HoverGestureInternalConfig = BaseGestureConfig< + HoverHandlerData, + HoverGestureHandlerProps +>; + +export type HoverGestureConfig = + ExcludeInternalConfigProps; + +export function useHover(config: HoverGestureConfig) { + const hoverConfig = cloneConfig( + config + ); + + return useGesture(SingleGestureName.Hover, hoverConfig); +} diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 1ba10cec02..3d761a7c67 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -47,6 +47,7 @@ export enum SingleGestureName { Fling = 'FlingGestureHandler', Manual = 'ManualGestureHandler', Native = 'NativeGestureHandler', + Hover = 'HoverGestureHandler', } export enum ComposedGestureName { From ed4af90328998750029dd00d3c526447e4c8dfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 11:54:03 +0200 Subject: [PATCH 080/109] useManual --- .../src/v3/hooks/gestures/index.ts | 3 ++ .../src/v3/hooks/gestures/useManual.ts | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts index ccb2486ad5..d87eebb6cc 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -15,3 +15,6 @@ export { useRotation } from './useRotation'; export type { HoverGestureConfig } from './useHover'; export { useHover } from './useHover'; + +export type { ManualGestureConfig } from './useManual'; +export { useManual } from './useManual'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts new file mode 100644 index 0000000000..f33e4e659c --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts @@ -0,0 +1,28 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type ManualGestureHandlerProps = {}; + +type ManualHandlerData = {}; + +type ManualGestureInternalConfig = BaseGestureConfig< + ManualHandlerData, + ManualGestureHandlerProps +>; + +export type ManualGestureConfig = + ExcludeInternalConfigProps; + +export function useManual(config: ManualGestureConfig) { + const manualConfig = cloneConfig< + ManualHandlerData, + ManualGestureHandlerProps + >(config); + + return useGesture(SingleGestureName.Manual, manualConfig); +} From 7871cc677cd9e266166c29419dbc1eb08b96befc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 12:14:19 +0200 Subject: [PATCH 081/109] useNative --- .../src/v3/hooks/gestures/index.ts | 3 ++ .../src/v3/hooks/gestures/useNative.ts | 44 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts index d87eebb6cc..607ea1c923 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -18,3 +18,6 @@ export { useHover } from './useHover'; export type { ManualGestureConfig } from './useManual'; export { useManual } from './useManual'; + +export type { NativeViewGestureConfig } from './useNative'; +export { useNative } from './useNative'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts new file mode 100644 index 0000000000..261e3034f5 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts @@ -0,0 +1,44 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type NativeViewGestureHandlerProps = { + /** + * Android only. + * + * Determines whether the handler should check for an existing touch event on + * instantiation. + */ + shouldActivateOnStart?: boolean; + + /** + * When `true`, cancels all other gesture handlers when this + * `NativeViewGestureHandler` receives an `ACTIVE` state event. + */ + disallowInterruption?: boolean; +}; + +type NativeViewHandlerData = { + pointerInside: boolean; +}; + +type NativeViewGestureHandlerInternalConfig = BaseGestureConfig< + NativeViewHandlerData, + NativeViewGestureHandlerProps +>; + +export type NativeViewGestureConfig = + ExcludeInternalConfigProps; + +export function useNative(config: NativeViewGestureConfig) { + const nativeConfig = cloneConfig< + NativeViewHandlerData, + NativeViewGestureHandlerProps + >(config); + + return useGesture(SingleGestureName.Native, nativeConfig); +} From 28455b363080c730636eb0d1a7ffaaee3daa1b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 12:46:22 +0200 Subject: [PATCH 082/109] usePan --- .../src/v3/hooks/gestures/index.ts | 3 + .../src/v3/hooks/gestures/usePan.ts | 143 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts index 607ea1c923..64e175e259 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -21,3 +21,6 @@ export { useManual } from './useManual'; export type { NativeViewGestureConfig } from './useNative'; export { useNative } from './useNative'; + +export type { PanGestureConfig } from './usePan'; +export { usePan } from './usePan'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts new file mode 100644 index 0000000000..6568331458 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -0,0 +1,143 @@ +import { StylusData } from '../../../handlers/gestureHandlerCommon'; +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { remapProps } from '../utils'; + +type CommonPanProps = { + /** + * Minimum distance the finger (or multiple finger) need to travel before the + * handler activates. Expressed in points. + */ + minDistance?: number; + + /** + * Android only. + */ + avgTouches?: boolean; + + /** + * Enables two-finger gestures on supported devices, for example iPads with + * trackpads. If not enabled the gesture will require click + drag, with + * enableTrackpadTwoFingerGesture swiping with two fingers will also trigger + * the gesture. + */ + enableTrackpadTwoFingerGesture?: boolean; + + /** + * A number of fingers that is required to be placed before handler can + * activate. Should be a higher or equal to 0 integer. + */ + minPointers?: number; + + /** + * When the given number of fingers is placed on the screen and handler hasn't + * yet activated it will fail recognizing the gesture. Should be a higher or + * equal to 0 integer. + */ + maxPointers?: number; + + minVelocity?: number; + minVelocityX?: number; + minVelocityY?: number; + activateAfterLongPress?: number; +}; + +export type PanGestureHandlerProps = CommonPanProps & { + /** + * Range along X axis (in points) where fingers travels without activation of + * handler. Moving outside of this range implies activation of handler. Range + * can be given as an array or a single number. If range is set as an array, + * first value must be lower or equal to 0, a the second one higher or equal + * to 0. If only one number `p` is given a range of `(-inf, p)` will be used + * if `p` is higher or equal to 0 and `(-p, inf)` otherwise. + */ + activeOffsetY?: + | number + | [activeOffsetYStart: number, activeOffsetYEnd: number]; + + /** + * Range along X axis (in points) where fingers travels without activation of + * handler. Moving outside of this range implies activation of handler. Range + * can be given as an array or a single number. If range is set as an array, + * first value must be lower or equal to 0, a the second one higher or equal + * to 0. If only one number `p` is given a range of `(-inf, p)` will be used + * if `p` is higher or equal to 0 and `(-p, inf)` otherwise. + */ + activeOffsetX?: + | number + | [activeOffsetXStart: number, activeOffsetXEnd: number]; + + /** + * When the finger moves outside this range (in points) along Y axis and + * handler hasn't yet activated it will fail recognizing the gesture. Range + * can be given as an array or a single number. If range is set as an array, + * first value must be lower or equal to 0, a the second one higher or equal + * to 0. If only one number `p` is given a range of `(-inf, p)` will be used + * if `p` is higher or equal to 0 and `(-p, inf)` otherwise. + */ + failOffsetY?: number | [failOffsetYStart: number, failOffsetYEnd: number]; + + /** + * When the finger moves outside this range (in points) along X axis and + * handler hasn't yet activated it will fail recognizing the gesture. Range + * can be given as an array or a single number. If range is set as an array, + * first value must be lower or equal to 0, a the second one higher or equal + * to 0. If only one number `p` is given a range of `(-inf, p)` will be used + * if `p` is higher or equal to 0 and `(-p, inf)` otherwise. + */ + failOffsetX?: number | [failOffsetXStart: number, failOffsetXEnd: number]; +}; + +type PanGestureInternalProps = CommonPanProps & { + minDist?: number; + activeOffsetYStart?: number; + activeOffsetYEnd?: number; + activeOffsetXStart?: number; + activeOffsetXEnd?: number; + failOffsetYStart?: number; + failOffsetYEnd?: number; + failOffsetXStart?: number; + failOffsetXEnd?: number; +}; + +type PanHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; + translationX: number; + translationY: number; + velocityX: number; + velocityY: number; + stylusData: StylusData; +}; + +export type PanGestureConfig = ExcludeInternalConfigProps< + BaseGestureConfig +>; + +type PanGestureInternalConfig = BaseGestureConfig< + PanHandlerData, + PanGestureInternalProps +>; + +const PanPropsMapping = new Map< + keyof PanGestureHandlerProps, + keyof PanGestureInternalProps +>([['minDistance', 'minDist']]); + +export function usePan(config: PanGestureConfig) { + const panConfig = remapProps( + config, + PanPropsMapping + ); + + return useGesture( + SingleGestureName.Pan, + panConfig + ); +} From c21c05576955e866e4d5d2a11c67b67e5f28f3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 12:54:05 +0200 Subject: [PATCH 083/109] Rename types --- .../src/v3/hooks/gestures/useFling.ts | 8 +++----- .../src/v3/hooks/gestures/useHover.ts | 8 +++----- .../src/v3/hooks/gestures/useLongPress.ts | 14 +++++++------- .../src/v3/hooks/gestures/useManual.ts | 11 +++++------ .../src/v3/hooks/gestures/useNative.ts | 10 +++++----- .../src/v3/hooks/gestures/usePan.ts | 10 +++++----- .../src/v3/hooks/gestures/usePinch.ts | 8 +++----- .../src/v3/hooks/gestures/useRotation.ts | 11 +++++------ .../src/v3/hooks/gestures/useTap.ts | 14 +++++++------- 9 files changed, 43 insertions(+), 51 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts index a5b5bdfc91..b20e873d49 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type FlingGestureHandlerProps = { +type FlingGestureProps = { /** * Expressed allowed direction of movement. It's possible to pass one or many * directions in one parameter: @@ -38,16 +38,14 @@ type FlingHandlerData = { type FlingGestureInternalConfig = BaseGestureConfig< FlingHandlerData, - FlingGestureHandlerProps + FlingGestureProps >; export type FlingGestureConfig = ExcludeInternalConfigProps; export function useFling(config: FlingGestureConfig) { - const flingConfig = cloneConfig( - config - ); + const flingConfig = cloneConfig(config); return useGesture(SingleGestureName.Fling, flingConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts index fe4da7e357..67a5dfa6ea 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -8,7 +8,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type HoverGestureHandlerProps = { +type HoverGestureProps = { /** * Visual effect applied to the view while the view is hovered. The possible values are: * @@ -31,16 +31,14 @@ type HoverHandlerData = { type HoverGestureInternalConfig = BaseGestureConfig< HoverHandlerData, - HoverGestureHandlerProps + HoverGestureProps >; export type HoverGestureConfig = ExcludeInternalConfigProps; export function useHover(config: HoverGestureConfig) { - const hoverConfig = cloneConfig( - config - ); + const hoverConfig = cloneConfig(config); return useGesture(SingleGestureName.Hover, hoverConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts index 0e6ecd19c9..4f280ff609 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { remapProps } from '../utils'; -type LongPressGestureHandlerProps = { +type LongPressGestureProps = { /** * Minimum time, expressed in milliseconds, that a finger must remain pressed on * the corresponding view. The default value is 500. @@ -27,7 +27,7 @@ type LongPressGestureHandlerProps = { numberOfPointers?: number; }; -type LongPressGestureHandlerPropsInternal = { +type LongPressGesturePropsInternal = { minDurationMs?: number; maxDist?: number; numberOfPointers?: number; @@ -42,17 +42,17 @@ type LongPressHandlerData = { }; export type LongPressGestureConfig = ExcludeInternalConfigProps< - BaseGestureConfig + BaseGestureConfig >; type LongPressGestureInternalConfig = BaseGestureConfig< LongPressHandlerData, - LongPressGestureHandlerPropsInternal + LongPressGesturePropsInternal >; const LongPressPropsMapping = new Map< - keyof LongPressGestureHandlerProps, - keyof LongPressGestureHandlerPropsInternal + keyof LongPressGestureProps, + keyof LongPressGesturePropsInternal >([ ['minDuration', 'minDurationMs'], ['maxDistance', 'maxDist'], @@ -64,7 +64,7 @@ export function useLongPress(config: LongPressGestureConfig) { LongPressGestureInternalConfig >(config, LongPressPropsMapping); - return useGesture( + return useGesture( SingleGestureName.LongPress, longPressConfig ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts index f33e4e659c..e18e933a1a 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts @@ -6,23 +6,22 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type ManualGestureHandlerProps = {}; +type ManualGestureProps = {}; type ManualHandlerData = {}; type ManualGestureInternalConfig = BaseGestureConfig< ManualHandlerData, - ManualGestureHandlerProps + ManualGestureProps >; export type ManualGestureConfig = ExcludeInternalConfigProps; export function useManual(config: ManualGestureConfig) { - const manualConfig = cloneConfig< - ManualHandlerData, - ManualGestureHandlerProps - >(config); + const manualConfig = cloneConfig( + config + ); return useGesture(SingleGestureName.Manual, manualConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts index 261e3034f5..124d6556cf 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type NativeViewGestureHandlerProps = { +type NativeViewGestureProps = { /** * Android only. * @@ -26,18 +26,18 @@ type NativeViewHandlerData = { pointerInside: boolean; }; -type NativeViewGestureHandlerInternalConfig = BaseGestureConfig< +type NativeViewGestureInternalConfig = BaseGestureConfig< NativeViewHandlerData, - NativeViewGestureHandlerProps + NativeViewGestureProps >; export type NativeViewGestureConfig = - ExcludeInternalConfigProps; + ExcludeInternalConfigProps; export function useNative(config: NativeViewGestureConfig) { const nativeConfig = cloneConfig< NativeViewHandlerData, - NativeViewGestureHandlerProps + NativeViewGestureProps >(config); return useGesture(SingleGestureName.Native, nativeConfig); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index 6568331458..b4d41510b3 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -7,7 +7,7 @@ import { import { useGesture } from '../useGesture'; import { remapProps } from '../utils'; -type CommonPanProps = { +type CommonPanGestureProps = { /** * Minimum distance the finger (or multiple finger) need to travel before the * handler activates. Expressed in points. @@ -46,7 +46,7 @@ type CommonPanProps = { activateAfterLongPress?: number; }; -export type PanGestureHandlerProps = CommonPanProps & { +export type PanGestureProps = CommonPanGestureProps & { /** * Range along X axis (in points) where fingers travels without activation of * handler. Moving outside of this range implies activation of handler. Range @@ -92,7 +92,7 @@ export type PanGestureHandlerProps = CommonPanProps & { failOffsetX?: number | [failOffsetXStart: number, failOffsetXEnd: number]; }; -type PanGestureInternalProps = CommonPanProps & { +type PanGestureInternalProps = CommonPanGestureProps & { minDist?: number; activeOffsetYStart?: number; activeOffsetYEnd?: number; @@ -117,7 +117,7 @@ type PanHandlerData = { }; export type PanGestureConfig = ExcludeInternalConfigProps< - BaseGestureConfig + BaseGestureConfig >; type PanGestureInternalConfig = BaseGestureConfig< @@ -126,7 +126,7 @@ type PanGestureInternalConfig = BaseGestureConfig< >; const PanPropsMapping = new Map< - keyof PanGestureHandlerProps, + keyof PanGestureProps, keyof PanGestureInternalProps >([['minDistance', 'minDist']]); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts index 55e3c1ba04..8bfd9248bc 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type PinchGestureHandlerProps = {}; +type PinchGestureProps = {}; type PinchHandlerData = { scale: number; @@ -17,16 +17,14 @@ type PinchHandlerData = { type PinchGestureInternalConfig = BaseGestureConfig< PinchHandlerData, - PinchGestureHandlerProps + PinchGestureProps >; export type PinchGestureConfig = ExcludeInternalConfigProps; export function usePinch(config: PinchGestureConfig) { - const pinchConfig = cloneConfig( - config - ); + const pinchConfig = cloneConfig(config); return useGesture(SingleGestureName.Pinch, pinchConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts index 872cd3ca9f..50480fd0d8 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type RotationGestureHandlerProps = {}; +type RotationGestureProps = {}; type RotationHandlerData = { rotation: number; @@ -17,17 +17,16 @@ type RotationHandlerData = { type RotationGestureInternalConfig = BaseGestureConfig< RotationHandlerData, - RotationGestureHandlerProps + RotationGestureProps >; export type RotationGestureConfig = ExcludeInternalConfigProps; export function useRotation(config: RotationGestureConfig) { - const rotationConfig = cloneConfig< - RotationHandlerData, - RotationGestureHandlerProps - >(config); + const rotationConfig = cloneConfig( + config + ); return useGesture(SingleGestureName.Rotation, rotationConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts index 01eed8fb0d..ae9fed8da1 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { remapProps } from '../utils'; -type TapGestureHandlerProps = { +type TapGestureProps = { /** * Minimum number of pointers (fingers) required to be placed before the * handler activates. Should be a positive integer. @@ -57,7 +57,7 @@ type TapGestureHandlerProps = { maxDistance?: number; }; -type TapGestureHandlerPropsInternal = { +type TapGestureInternalProps = { minPointers?: number; numberOfTaps?: number; maxDeltaX?: number; @@ -75,17 +75,17 @@ type TapHandlerData = { }; export type TapGestureConfig = ExcludeInternalConfigProps< - BaseGestureConfig + BaseGestureConfig >; type TapGestureInternalConfig = BaseGestureConfig< TapHandlerData, - TapGestureHandlerPropsInternal + TapGestureInternalProps >; const TapPropsMapping = new Map< - keyof TapGestureHandlerProps, - keyof TapGestureHandlerPropsInternal + keyof TapGestureProps, + keyof TapGestureInternalProps >([ ['maxDistance', 'maxDist'], ['maxDuration', 'maxDurationMs'], @@ -98,7 +98,7 @@ export function useTap(config: TapGestureConfig) { TapPropsMapping ); - return useGesture( + return useGesture( SingleGestureName.Tap, tapConfig ); From 0bf03e16a5f8de122aeea558e5c088188c953f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 13:36:49 +0200 Subject: [PATCH 084/109] Add common props to HandlerData --- .../react-native-gesture-handler/src/v3/types.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 3d761a7c67..840419408b 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -4,14 +4,22 @@ import { GestureTouchEvent, HandlerStateChangeEventPayload, } from '../handlers/gestureHandlerCommon'; +import { PointerType } from '../PointerType'; + +export type BaseHandlerData = { + numberOfPointers: number; + pointerType: PointerType; +}; + +export type HandlerData = BaseHandlerData & T; export type GestureUpdateEvent = GestureEventPayload & { - handlerData: THandlerData; + handlerData: HandlerData; }; export type GestureStateChangeEvent = HandlerStateChangeEventPayload & { - handlerData: THandlerData; + handlerData: HandlerData; }; export type GestureHandlerEvent = From 37f392ee0d4a7123d66f6d701ebbd59908d8fd5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 13:51:12 +0200 Subject: [PATCH 085/109] Long press default behavior --- .../src/v3/hooks/gestures/useLongPress.ts | 4 ++++ .../react-native-gesture-handler/src/v3/types.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts index 4f280ff609..5981189bfc 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts @@ -64,6 +64,10 @@ export function useLongPress(config: LongPressGestureConfig) { LongPressGestureInternalConfig >(config, LongPressPropsMapping); + if (longPressConfig.shouldCancelWhenOutside === undefined) { + longPressConfig.shouldCancelWhenOutside = true; + } + return useGesture( SingleGestureName.LongPress, longPressConfig diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 840419408b..d77676a540 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -1,8 +1,13 @@ import { Animated, NativeSyntheticEvent } from 'react-native'; import { + ActiveCursor, GestureEventPayload, GestureTouchEvent, HandlerStateChangeEventPayload, + HitSlop, + MouseButton, + TouchAction, + UserSelect, } from '../handlers/gestureHandlerCommon'; import { PointerType } from '../PointerType'; @@ -151,6 +156,14 @@ export type BaseGestureConfig = ExternalRelations & TConfig & InternalConfigProps & { disableReanimated?: boolean; + enabled?: boolean; + shouldCancelWhenOutside?: boolean; + hitSlop?: HitSlop; + userSelect?: UserSelect; + activeCursor?: ActiveCursor; + mouseButton?: MouseButton; + enableContextMenu?: boolean; + touchAction?: TouchAction; }; export type ExcludeInternalConfigProps = Omit< From d3ad70f0dfda137f9378dad99870adf72bd43110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 14:09:37 +0200 Subject: [PATCH 086/109] Pan props validation --- .../src/v3/hooks/gestures/usePan.ts | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index b4d41510b3..cd39d1baee 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -130,12 +130,124 @@ const PanPropsMapping = new Map< keyof PanGestureInternalProps >([['minDistance', 'minDist']]); +function validatePanConfig(config: PanGestureConfig) { + if ( + Array.isArray(config.activeOffsetX) && + (config.activeOffsetX[0] > 0 || config.activeOffsetX[1] < 0) + ) { + throw new Error( + `First element of activeOffsetX should be negative, and the second one should be positive` + ); + } + + if ( + Array.isArray(config.activeOffsetY) && + (config.activeOffsetY[0] > 0 || config.activeOffsetY[1] < 0) + ) { + throw new Error( + `First element of activeOffsetY should be negative, and the second one should be positive` + ); + } + + if ( + Array.isArray(config.failOffsetX) && + (config.failOffsetX[0] > 0 || config.failOffsetX[1] < 0) + ) { + throw new Error( + `First element of failOffsetX should be negative, and the second one should be positive` + ); + } + + if ( + Array.isArray(config.failOffsetY) && + (config.failOffsetY[0] > 0 || config.failOffsetY[1] < 0) + ) { + throw new Error( + `First element of failOffsetY should be negative, and the second one should be positive` + ); + } + + if (config.minDistance && (config.failOffsetX || config.failOffsetY)) { + throw new Error( + `It is not supported to use minDistance with failOffsetX or failOffsetY, use activeOffsetX and activeOffsetY instead` + ); + } + + if (config.minDistance && (config.activeOffsetX || config.activeOffsetY)) { + throw new Error( + `It is not supported to use minDistance with activeOffsetX or activeOffsetY` + ); + } +} + +function transformPanProps( + config: PanGestureConfig & PanGestureInternalConfig +) { + if (config.activeOffsetX !== undefined) { + if (Array.isArray(config.activeOffsetX)) { + config.activeOffsetXStart = config.activeOffsetX[0]; + config.activeOffsetXEnd = config.activeOffsetX[1]; + } else if (config.activeOffsetX < 0) { + config.activeOffsetXStart = config.activeOffsetX; + } else { + config.activeOffsetXEnd = config.activeOffsetX; + } + + delete config.activeOffsetX; + } + + if (config.activeOffsetY !== undefined) { + if (Array.isArray(config.activeOffsetY)) { + config.activeOffsetYStart = config.activeOffsetY[0]; + config.activeOffsetYEnd = config.activeOffsetY[1]; + } else if (config.activeOffsetY < 0) { + config.activeOffsetYStart = config.activeOffsetY; + } else { + config.activeOffsetYEnd = config.activeOffsetY; + } + + delete config.activeOffsetY; + } + + if (config.failOffsetX !== undefined) { + if (Array.isArray(config.failOffsetX)) { + config.failOffsetXStart = config.failOffsetX[0]; + config.failOffsetXEnd = config.failOffsetX[1]; + } else if (config.failOffsetX < 0) { + config.failOffsetXStart = config.failOffsetX; + } else { + config.failOffsetXEnd = config.failOffsetX; + } + + delete config.failOffsetX; + } + + if (config.failOffsetY !== undefined) { + if (Array.isArray(config.failOffsetY)) { + config.failOffsetYStart = config.failOffsetY[0]; + config.failOffsetYEnd = config.failOffsetY[1]; + } else if (config.failOffsetY < 0) { + config.failOffsetYStart = config.failOffsetY; + } else { + config.failOffsetYEnd = config.failOffsetY; + } + + delete config.failOffsetY; + } +} + export function usePan(config: PanGestureConfig) { + if (__DEV__) { + validatePanConfig(config); + } + const panConfig = remapProps( config, PanPropsMapping ); + transformPanProps(panConfig); + return useGesture( SingleGestureName.Pan, panConfig From b8f6921929fb52053d1107c89ad0ab8d38503e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 15:46:17 +0200 Subject: [PATCH 087/109] Extract nativeEvent earlier --- .../handlers/gestures/reanimatedWrapper.ts | 4 +- .../v3/hooks/callbacks/stateChangeHandler.ts | 42 +++++++++---------- .../v3/hooks/callbacks/touchEventHandler.ts | 25 +++-------- .../src/v3/hooks/callbacks/updateHandler.ts | 9 +++- .../src/v3/hooks/utils.ts | 15 +++++-- .../src/v3/hooks/utils/eventHandlersUtils.ts | 7 ++-- .../src/v3/types.ts | 11 +++-- 7 files changed, 57 insertions(+), 56 deletions(-) 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 1d73f5b4de..30d03130d7 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -1,6 +1,6 @@ import { ComponentClass } from 'react'; import { tagMessage } from '../../utils'; -import { GestureCallbacks, UpdateEvent } from '../../v3/types'; +import { GestureCallbacks, GestureUpdateEvent } from '../../v3/types'; export interface SharedValue { value: Value; @@ -15,7 +15,7 @@ export interface SharedValue { } export type ReanimatedContext = { - lastUpdateEvent: UpdateEvent | undefined; + lastUpdateEvent: GestureUpdateEvent | undefined; }; interface WorkletProps { 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 afd268e45b..b8852bb505 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 @@ -1,48 +1,46 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { State } from '../../../State'; -import { GestureCallbacks, StateChangeEvent } from '../../types'; -import { isEventForHandlerWithTag, isNativeEvent } from '../utils'; +import { + GestureCallbacks, + GestureStateChangeEvent, + StateChangeEvent, +} from '../../types'; +import { isEventForHandlerWithTag, maybeExtractNativeEvent } from '../utils'; import { runCallback } from '../utils/eventHandlersUtils'; export function getStateChangeHandler( handlerTag: number, callbacks: GestureCallbacks ) { - return (event: StateChangeEvent) => { + return (sourceEvent: StateChangeEvent) => { 'worklet'; + const event = maybeExtractNativeEvent( + sourceEvent + ) as GestureStateChangeEvent; + 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) { + if (event.oldState === State.UNDETERMINED && event.state === State.BEGAN) { runCallback(CALLBACK_TYPE.BEGAN, callbacks, event); } else if ( - (oldState === State.BEGAN || oldState === State.UNDETERMINED) && - state === State.ACTIVE + (event.oldState === State.BEGAN || + event.oldState === State.UNDETERMINED) && + event.state === State.ACTIVE ) { runCallback(CALLBACK_TYPE.START, callbacks, event); - } else if (oldState !== state && state === State.END) { - if (oldState === State.ACTIVE) { + } else if (event.oldState !== event.state && event.state === State.END) { + if (event.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 + (event.state === State.FAILED || event.state === State.CANCELLED) && + event.state !== event.oldState ) { - if (oldState === State.ACTIVE) { + if (event.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/touchEventHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/touchEventHandler.ts index 6b39e82fb8..65f915d269 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 @@ -1,6 +1,5 @@ -import { NativeSyntheticEvent } from 'react-native'; import { GestureCallbacks, TouchEvent } from '../../types'; -import { isEventForHandlerWithTag, isNativeEvent } from '../utils'; +import { isEventForHandlerWithTag, maybeExtractNativeEvent } from '../utils'; import { TouchEventType } from '../../../TouchEventType'; import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon'; import { @@ -12,30 +11,18 @@ export function getTouchEventHandler( handlerTag: number, callbacks: GestureCallbacks ) { - return (event: TouchEvent) => { + return (sourceEvent: TouchEvent) => { 'worklet'; + const event = maybeExtractNativeEvent(sourceEvent) as GestureTouchEvent; + 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 - ) { + if (event.eventType !== TouchEventType.UNDETERMINED) { runCallback( - touchEventTypeToCallbackType((event as GestureTouchEvent).eventType), + touchEventTypeToCallbackType(event.eventType), callbacks, event ); 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 8f37633857..6dd80657de 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 @@ -4,9 +4,10 @@ import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper' import { ChangeCalculatorType, GestureCallbacks, + GestureUpdateEvent, UpdateEvent, } from '../../types'; -import { isEventForHandlerWithTag } from '../utils'; +import { isEventForHandlerWithTag, maybeExtractNativeEvent } from '../utils'; import { runCallback } from '../utils/eventHandlersUtils'; export function getUpdateHandler( @@ -15,9 +16,13 @@ export function getUpdateHandler( context: ReanimatedContext | undefined, changeEventCalculator?: ChangeCalculatorType ) { - return (event: UpdateEvent) => { + return (sourceEvent: UpdateEvent) => { 'worklet'; + const event = maybeExtractNativeEvent( + sourceEvent + ) as GestureUpdateEvent; + if (!isEventForHandlerWithTag(handlerTag, event)) { return; } 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 7059eeaed5..b65b673070 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -3,6 +3,7 @@ import { AnimatedEvent, BaseGestureConfig, ExcludeInternalConfigProps, + ExtractedGestureHandlerEvent, GestureHandlerEvent, GestureStateChangeEvent, GestureUpdateEvent, @@ -22,15 +23,21 @@ export function isNativeEvent( return 'nativeEvent' in event; } +export function maybeExtractNativeEvent( + event: GestureHandlerEvent +) { + 'worklet'; + + return isNativeEvent(event) ? event.nativeEvent : event; +} + export function isEventForHandlerWithTag( handlerTag: number, - event: GestureHandlerEvent + event: ExtractedGestureHandlerEvent ) { 'worklet'; - return isNativeEvent(event) - ? event.nativeEvent.handlerTag === handlerTag - : event.handlerTag === handlerTag; + return event.handlerTag === handlerTag; } export function isAnimatedEvent( 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 index 2503541d79..629a0183cc 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts @@ -3,10 +3,9 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { BaseGestureConfig, ChangeCalculatorType, + ExtractedGestureHandlerEvent, GestureCallbacks, - GestureHandlerEvent, } from '../../types'; -import { isNativeEvent } from '../utils'; export function extractStateChangeHandlers( config: BaseGestureConfig @@ -105,7 +104,7 @@ export function touchEventTypeToCallbackType( export function runCallback( type: CALLBACK_TYPE, callbacks: GestureCallbacks, - event: GestureHandlerEvent, + event: ExtractedGestureHandlerEvent, ...args: unknown[] ) { 'worklet'; @@ -113,5 +112,5 @@ export function runCallback( // TODO: add proper types (likely boolean) // @ts-ignore It works, duh - handler?.(isNativeEvent(event) ? event.nativeEvent : event, ...args); + handler?.(event, ...args); } diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index d77676a540..210c3fb49a 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -32,6 +32,11 @@ export type GestureHandlerEvent = | StateChangeEvent | TouchEvent; +export type ExtractedGestureHandlerEvent = + | GestureUpdateEvent + | GestureStateChangeEvent + | GestureTouchEvent; + export type UpdateEvent = | GestureUpdateEvent | NativeSyntheticEvent>; @@ -112,9 +117,9 @@ export type ComposedGesture = { }; export type ChangeCalculatorType = ( - current: UpdateEvent, - previous?: UpdateEvent -) => UpdateEvent; + current: GestureUpdateEvent, + previous?: GestureUpdateEvent +) => GestureUpdateEvent; export type Gesture = | SingleGesture From ab23cf59ce41971a77ca7b0f92040272b3f14c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 15:52:30 +0200 Subject: [PATCH 088/109] Hover change calculator --- .../src/v3/hooks/gestures/useHover.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts index 67a5dfa6ea..33d4ffc0f0 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -3,6 +3,7 @@ import { HoverEffect } from '../../../handlers/gestures/hoverGesture'; import { BaseGestureConfig, ExcludeInternalConfigProps, + GestureUpdateEvent, SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; @@ -27,6 +28,8 @@ type HoverHandlerData = { absoluteX: number; absoluteY: number; stylusData: StylusData; + changeX?: number; + changeY?: number; }; type HoverGestureInternalConfig = BaseGestureConfig< @@ -37,8 +40,34 @@ type HoverGestureInternalConfig = BaseGestureConfig< export type HoverGestureConfig = ExcludeInternalConfigProps; +function changeEventCalculator( + current: GestureUpdateEvent, + previous?: GestureUpdateEvent +): GestureUpdateEvent { + 'worklet'; + + const currentEventData = current.handlerData; + const previousEventData = previous ? previous.handlerData : null; + + const changePayload = previousEventData + ? { + changeX: currentEventData.x - previousEventData.x, + changeY: currentEventData.y - previousEventData.y, + } + : { + changeX: currentEventData.x, + changeY: currentEventData.y, + }; + + const resultEvent = { ...current }; + resultEvent.handlerData = { ...currentEventData, ...changePayload }; + + return resultEvent; +} + export function useHover(config: HoverGestureConfig) { const hoverConfig = cloneConfig(config); + hoverConfig.changeEventCalculator = changeEventCalculator; return useGesture(SingleGestureName.Hover, hoverConfig); } From 313b3b3f124c3da786890e2e2730f21cd175da0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 15:55:50 +0200 Subject: [PATCH 089/109] Pan change calculator --- .../src/v3/hooks/gestures/usePan.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index cd39d1baee..6b2205451d 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -2,6 +2,7 @@ import { StylusData } from '../../../handlers/gestureHandlerCommon'; import { BaseGestureConfig, ExcludeInternalConfigProps, + GestureUpdateEvent, SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; @@ -114,6 +115,8 @@ type PanHandlerData = { velocityX: number; velocityY: number; stylusData: StylusData; + changeX?: number; + changeY?: number; }; export type PanGestureConfig = ExcludeInternalConfigProps< @@ -236,6 +239,31 @@ function transformPanProps( } } +function changeEventCalculator( + current: GestureUpdateEvent, + previous?: GestureUpdateEvent +): GestureUpdateEvent { + 'worklet'; + + const currentEventData = current.handlerData; + const previousEventData = previous ? previous.handlerData : null; + + const changePayload = previousEventData + ? { + changeX: currentEventData.translationX - previousEventData.translationX, + changeY: currentEventData.translationY - previousEventData.translationY, + } + : { + changeX: currentEventData.translationX, + changeY: currentEventData.translationY, + }; + + const resultEvent = { ...current }; + resultEvent.handlerData = { ...currentEventData, ...changePayload }; + + return resultEvent; +} + export function usePan(config: PanGestureConfig) { if (__DEV__) { validatePanConfig(config); @@ -248,6 +276,8 @@ export function usePan(config: PanGestureConfig) { transformPanProps(panConfig); + panConfig.changeEventCalculator = changeEventCalculator; + return useGesture( SingleGestureName.Pan, panConfig From 7bd1ac1f837adcd9204bada87b823c2fee2bad90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 16:00:27 +0200 Subject: [PATCH 090/109] Pinch change calculator --- .../src/v3/hooks/gestures/usePinch.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts index 8bfd9248bc..1e881a533d 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -1,6 +1,7 @@ import { BaseGestureConfig, ExcludeInternalConfigProps, + GestureUpdateEvent, SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; @@ -13,6 +14,7 @@ type PinchHandlerData = { focalX: number; focalY: number; velocity: number; + scaleChange?: number; }; type PinchGestureInternalConfig = BaseGestureConfig< @@ -23,8 +25,31 @@ type PinchGestureInternalConfig = BaseGestureConfig< export type PinchGestureConfig = ExcludeInternalConfigProps; +function changeEventCalculator( + current: GestureUpdateEvent, + previous?: GestureUpdateEvent +): GestureUpdateEvent { + 'worklet'; + + const currentEventData = current.handlerData; + const previousEventData = previous ? previous.handlerData : null; + + const changePayload = { + scaleChange: previousEventData + ? currentEventData.scale / previousEventData.scale + : currentEventData.scale, + }; + + const resultEvent = { ...current }; + resultEvent.handlerData = { ...currentEventData, ...changePayload }; + + return resultEvent; +} + export function usePinch(config: PinchGestureConfig) { const pinchConfig = cloneConfig(config); + pinchConfig.changeEventCalculator = changeEventCalculator; + return useGesture(SingleGestureName.Pinch, pinchConfig); } From 029fba86bf5f632747be4e51789477f7fa6715e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 16:02:18 +0200 Subject: [PATCH 091/109] Rotation change calculator --- .../src/v3/hooks/gestures/useRotation.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts index 50480fd0d8..d2da3ac3a6 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -1,6 +1,7 @@ import { BaseGestureConfig, ExcludeInternalConfigProps, + GestureUpdateEvent, SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; @@ -13,6 +14,7 @@ type RotationHandlerData = { anchorX: number; anchorY: number; velocity: number; + rotationChange?: number; }; type RotationGestureInternalConfig = BaseGestureConfig< @@ -23,10 +25,33 @@ type RotationGestureInternalConfig = BaseGestureConfig< export type RotationGestureConfig = ExcludeInternalConfigProps; +function changeEventCalculator( + current: GestureUpdateEvent, + previous?: GestureUpdateEvent +): GestureUpdateEvent { + 'worklet'; + + const currentEventData = current.handlerData; + const previousEventData = previous ? previous.handlerData : null; + + const changePayload = { + rotationChange: previousEventData + ? currentEventData.rotation - previousEventData.rotation + : currentEventData.rotation, + }; + + const resultEvent = { ...current }; + resultEvent.handlerData = { ...currentEventData, ...changePayload }; + + return resultEvent; +} + export function useRotation(config: RotationGestureConfig) { const rotationConfig = cloneConfig( config ); + rotationConfig.changeEventCalculator = changeEventCalculator; + return useGesture(SingleGestureName.Rotation, rotationConfig); } From 787db88d7dcdde8e221aff34da994fd5fe740934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 15 Sep 2025 16:21:15 +0200 Subject: [PATCH 092/109] Extract diff calculations --- .../src/v3/hooks/gestures/useHover.ts | 37 ++++++----------- .../src/v3/hooks/gestures/usePan.ts | 40 +++++++------------ .../src/v3/hooks/gestures/usePinch.ts | 29 +++++--------- .../src/v3/hooks/gestures/useRotation.ts | 32 ++++++--------- .../src/v3/hooks/utils.ts | 22 ++++++++++ .../src/v3/types.ts | 5 +++ 6 files changed, 75 insertions(+), 90 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts index 33d4ffc0f0..025ddba0d4 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -3,11 +3,11 @@ import { HoverEffect } from '../../../handlers/gestures/hoverGesture'; import { BaseGestureConfig, ExcludeInternalConfigProps, - GestureUpdateEvent, + HandlerData, SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; -import { cloneConfig } from '../utils'; +import { cloneConfig, getChangeEventCalculator } from '../utils'; type HoverGestureProps = { /** @@ -40,34 +40,21 @@ type HoverGestureInternalConfig = BaseGestureConfig< export type HoverGestureConfig = ExcludeInternalConfigProps; -function changeEventCalculator( - current: GestureUpdateEvent, - previous?: GestureUpdateEvent -): GestureUpdateEvent { +function diffCalculator( + current: HandlerData, + previous: HandlerData | null +) { 'worklet'; - - const currentEventData = current.handlerData; - const previousEventData = previous ? previous.handlerData : null; - - const changePayload = previousEventData - ? { - changeX: currentEventData.x - previousEventData.x, - changeY: currentEventData.y - previousEventData.y, - } - : { - changeX: currentEventData.x, - changeY: currentEventData.y, - }; - - const resultEvent = { ...current }; - resultEvent.handlerData = { ...currentEventData, ...changePayload }; - - return resultEvent; + return { + changeX: previous ? current.x - previous.x : current.x, + changeY: previous ? current.y - previous.y : current.y, + }; } export function useHover(config: HoverGestureConfig) { const hoverConfig = cloneConfig(config); - hoverConfig.changeEventCalculator = changeEventCalculator; + + hoverConfig.changeEventCalculator = getChangeEventCalculator(diffCalculator); return useGesture(SingleGestureName.Hover, hoverConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index 6b2205451d..eba2bbe950 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -2,11 +2,11 @@ import { StylusData } from '../../../handlers/gestureHandlerCommon'; import { BaseGestureConfig, ExcludeInternalConfigProps, - GestureUpdateEvent, + HandlerData, SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; -import { remapProps } from '../utils'; +import { getChangeEventCalculator, remapProps } from '../utils'; type CommonPanGestureProps = { /** @@ -239,29 +239,19 @@ function transformPanProps( } } -function changeEventCalculator( - current: GestureUpdateEvent, - previous?: GestureUpdateEvent -): GestureUpdateEvent { +function diffCalculator( + current: HandlerData, + previous: HandlerData | null +) { 'worklet'; - - const currentEventData = current.handlerData; - const previousEventData = previous ? previous.handlerData : null; - - const changePayload = previousEventData - ? { - changeX: currentEventData.translationX - previousEventData.translationX, - changeY: currentEventData.translationY - previousEventData.translationY, - } - : { - changeX: currentEventData.translationX, - changeY: currentEventData.translationY, - }; - - const resultEvent = { ...current }; - resultEvent.handlerData = { ...currentEventData, ...changePayload }; - - return resultEvent; + return { + changeX: previous + ? current.translationX - previous.translationX + : current.translationX, + changeY: previous + ? current.translationY - previous.translationY + : current.translationY, + }; } export function usePan(config: PanGestureConfig) { @@ -276,7 +266,7 @@ export function usePan(config: PanGestureConfig) { transformPanProps(panConfig); - panConfig.changeEventCalculator = changeEventCalculator; + panConfig.changeEventCalculator = getChangeEventCalculator(diffCalculator); return useGesture( SingleGestureName.Pan, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts index 1e881a533d..64884190da 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -1,11 +1,11 @@ import { BaseGestureConfig, ExcludeInternalConfigProps, - GestureUpdateEvent, + HandlerData, SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; -import { cloneConfig } from '../utils'; +import { cloneConfig, getChangeEventCalculator } from '../utils'; type PinchGestureProps = {}; @@ -25,31 +25,20 @@ type PinchGestureInternalConfig = BaseGestureConfig< export type PinchGestureConfig = ExcludeInternalConfigProps; -function changeEventCalculator( - current: GestureUpdateEvent, - previous?: GestureUpdateEvent -): GestureUpdateEvent { +function diffCalculator( + current: HandlerData, + previous: HandlerData | null +) { 'worklet'; - - const currentEventData = current.handlerData; - const previousEventData = previous ? previous.handlerData : null; - - const changePayload = { - scaleChange: previousEventData - ? currentEventData.scale / previousEventData.scale - : currentEventData.scale, + return { + scaleChange: previous ? current.scale / previous.scale : current.scale, }; - - const resultEvent = { ...current }; - resultEvent.handlerData = { ...currentEventData, ...changePayload }; - - return resultEvent; } export function usePinch(config: PinchGestureConfig) { const pinchConfig = cloneConfig(config); - pinchConfig.changeEventCalculator = changeEventCalculator; + pinchConfig.changeEventCalculator = getChangeEventCalculator(diffCalculator); return useGesture(SingleGestureName.Pinch, pinchConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts index d2da3ac3a6..910eb17726 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -1,11 +1,11 @@ import { BaseGestureConfig, ExcludeInternalConfigProps, - GestureUpdateEvent, + HandlerData, SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; -import { cloneConfig } from '../utils'; +import { cloneConfig, getChangeEventCalculator } from '../utils'; type RotationGestureProps = {}; @@ -25,25 +25,16 @@ type RotationGestureInternalConfig = BaseGestureConfig< export type RotationGestureConfig = ExcludeInternalConfigProps; -function changeEventCalculator( - current: GestureUpdateEvent, - previous?: GestureUpdateEvent -): GestureUpdateEvent { +function diffCalculator( + current: HandlerData, + previous: HandlerData | null +) { 'worklet'; - - const currentEventData = current.handlerData; - const previousEventData = previous ? previous.handlerData : null; - - const changePayload = { - rotationChange: previousEventData - ? currentEventData.rotation - previousEventData.rotation - : currentEventData.rotation, + return { + rotationChange: previous + ? current.rotation - previous.rotation + : current.rotation, }; - - const resultEvent = { ...current }; - resultEvent.handlerData = { ...currentEventData, ...changePayload }; - - return resultEvent; } export function useRotation(config: RotationGestureConfig) { @@ -51,7 +42,8 @@ export function useRotation(config: RotationGestureConfig) { config ); - rotationConfig.changeEventCalculator = changeEventCalculator; + rotationConfig.changeEventCalculator = + getChangeEventCalculator(diffCalculator); return useGesture(SingleGestureName.Rotation, rotationConfig); } 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 b65b673070..cb2da3a96f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -2,6 +2,8 @@ import { NativeSyntheticEvent } from 'react-native'; import { AnimatedEvent, BaseGestureConfig, + ChangeCalculatorType, + DiffCalculatorType, ExcludeInternalConfigProps, ExtractedGestureHandlerEvent, GestureHandlerEvent, @@ -130,3 +132,23 @@ export function remapProps( return newConfig; } + +export function getChangeEventCalculator( + diffCalculator: DiffCalculatorType +): ChangeCalculatorType { + 'worklet'; + return ( + current: GestureUpdateEvent, + previous?: GestureUpdateEvent + ) => { + const currentEventData = current.handlerData; + const previousEventData = previous ? previous.handlerData : null; + + const changePayload = diffCalculator(currentEventData, previousEventData); + + const resultEvent = { ...current }; + resultEvent.handlerData = { ...currentEventData, ...changePayload }; + + return resultEvent; + }; +} diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 210c3fb49a..41cbe93289 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -121,6 +121,11 @@ export type ChangeCalculatorType = ( previous?: GestureUpdateEvent ) => GestureUpdateEvent; +export type DiffCalculatorType = ( + current: HandlerData, + previous: HandlerData | null +) => Partial>; + export type Gesture = | SingleGesture | ComposedGesture; From d2ff3a43a8bc3044df2acca7632e238f2bc83737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 16 Sep 2025 12:42:44 +0200 Subject: [PATCH 093/109] Remove unnecexxary export keywords --- packages/react-native-gesture-handler/src/v3/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index cfc7417605..59ab10a29c 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -11,11 +11,11 @@ import { PointerType } from '../PointerType'; import { State } from '../State'; -export interface EventPayload { +interface EventPayload { handlerTag: number; state: State; } -export interface StateChangeEventPayload extends EventPayload { +interface StateChangeEventPayload extends EventPayload { oldState: State; } From c10d2e293a3f856a041050ad90219d52db7077d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 16 Sep 2025 12:57:47 +0200 Subject: [PATCH 094/109] Add ts-ignore explanations in NativeDetector --- .../src/v3/NativeDetector/NativeDetector.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 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 fd8e528d90..01af531761 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -41,25 +41,25 @@ export function NativeDetector({ return ( Date: Tue, 16 Sep 2025 13:12:42 +0200 Subject: [PATCH 095/109] Add worklet directive --- packages/react-native-gesture-handler/src/v3/hooks/utils.ts | 1 + 1 file changed, 1 insertion(+) 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 cb2da3a96f..29eec43163 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -141,6 +141,7 @@ export function getChangeEventCalculator( current: GestureUpdateEvent, previous?: GestureUpdateEvent ) => { + 'worklet'; const currentEventData = current.handlerData; const previousEventData = previous ? previous.handlerData : null; From 51c55a1710c364624fd72c9027638ed26dc36a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 16 Sep 2025 13:13:02 +0200 Subject: [PATCH 096/109] Remove todo --- .../src/v3/hooks/callbacks/updateHandler.ts | 1 - 1 file changed, 1 deletion(-) 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 6dd80657de..032f666e60 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 @@ -41,7 +41,6 @@ export function getUpdateHandler( : event ); - // TODO: Investigate why this is always undefined context.lastUpdateEvent = event; }; } From e2ab4496420ddbf9f37299d17ca8be201a91a65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 16 Sep 2025 13:54:46 +0200 Subject: [PATCH 097/109] Switch order in type name --- .../src/v3/hooks/gestures/useLongPress.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts index 5981189bfc..7154c31580 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts @@ -27,7 +27,7 @@ type LongPressGestureProps = { numberOfPointers?: number; }; -type LongPressGesturePropsInternal = { +type LongPressGestureInternalProps = { minDurationMs?: number; maxDist?: number; numberOfPointers?: number; @@ -47,12 +47,12 @@ export type LongPressGestureConfig = ExcludeInternalConfigProps< type LongPressGestureInternalConfig = BaseGestureConfig< LongPressHandlerData, - LongPressGesturePropsInternal + LongPressGestureInternalProps >; const LongPressPropsMapping = new Map< keyof LongPressGestureProps, - keyof LongPressGesturePropsInternal + keyof LongPressGestureInternalProps >([ ['minDuration', 'minDurationMs'], ['maxDistance', 'maxDist'], @@ -68,7 +68,7 @@ export function useLongPress(config: LongPressGestureConfig) { longPressConfig.shouldCancelWhenOutside = true; } - return useGesture( + return useGesture( SingleGestureName.LongPress, longPressConfig ); From d77bd448d34870ab16eff11d8839759c5f77c5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 16 Sep 2025 14:38:35 +0200 Subject: [PATCH 098/109] Update types --- .../src/v3/hooks/gestures/useManual.ts | 5 ++--- .../src/v3/hooks/gestures/usePinch.ts | 2 +- .../src/v3/hooks/gestures/useRotation.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts index e18e933a1a..d8feb12c77 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts @@ -6,9 +6,8 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type ManualGestureProps = {}; - -type ManualHandlerData = {}; +type ManualGestureProps = Record; +type ManualHandlerData = Record; type ManualGestureInternalConfig = BaseGestureConfig< ManualHandlerData, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts index 8bfd9248bc..a41dd9560d 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type PinchGestureProps = {}; +type PinchGestureProps = Record; type PinchHandlerData = { scale: number; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts index 50480fd0d8..c06ae8d265 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type RotationGestureProps = {}; +type RotationGestureProps = Record; type RotationHandlerData = { rotation: number; From 4b406a8bbfccdf0ab15ba8f683b5166fe9b36248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 16 Sep 2025 15:49:05 +0200 Subject: [PATCH 099/109] Fix NativeViewGestureHandler name --- 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 59ab10a29c..ac9e44970e 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -66,7 +66,7 @@ export enum SingleGestureName { Rotation = 'RotationGestureHandler', Fling = 'FlingGestureHandler', Manual = 'ManualGestureHandler', - Native = 'NativeGestureHandler', + Native = 'NativeViewGestureHandler', Hover = 'HoverGestureHandler', } From 68dd1e66f8941ab5e6e2da491231ff313f996279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 16 Sep 2025 15:51:43 +0200 Subject: [PATCH 100/109] Fix types --- .../react-native-gesture-handler/src/v3/types.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 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 ac9e44970e..1f31a5c693 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -160,7 +160,7 @@ export type InternalConfigProps = { export type BaseGestureConfig = ExternalRelations & GestureCallbacks & - TConfig & + FilterNeverProperties & InternalConfigProps & { disableReanimated?: boolean; enabled?: boolean; @@ -177,3 +177,15 @@ export type ExcludeInternalConfigProps = Omit< T, keyof InternalConfigProps >; + +// Some handlers do not have specific properties (e.g. `Pinch`), therefore we mark those prop types as `Record`. +// Doing intersection with those types results in type which cannot have any property. In order to fix that, +// we filter out properties with `never` values. +// +// This piece of magic works like this: +// 1. We iterate over all keys of T using `keyof T` +// 2. We check if the type of property is `never` using conditional types (`T[K] extends never ? never : K`). +// If it is, we replace the key with `never`, i.e. we delete it. Otherwise we keep it as is. +type FilterNeverProperties = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +}; From 78a8f1a9abd28989d4386ba4b2e42439b5c3a182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 18 Sep 2025 15:00:34 +0200 Subject: [PATCH 101/109] Change type name to only T --- packages/react-native-gesture-handler/src/v3/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 1f31a5c693..110edade40 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -26,12 +26,12 @@ export type BaseHandlerData = { export type HandlerData = BaseHandlerData & T; -export type GestureUpdateEvent = EventPayload & { - handlerData: HandlerData; +export type GestureUpdateEvent = EventPayload & { + handlerData: HandlerData; }; -export type GestureStateChangeEvent = StateChangeEventPayload & { - handlerData: HandlerData; +export type GestureStateChangeEvent = StateChangeEventPayload & { + handlerData: HandlerData; }; export type GestureHandlerEvent = From 5d0ef4d5a5fcbd4906c586558e1aa884d1cb63da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 18 Sep 2025 16:46:08 +0200 Subject: [PATCH 102/109] Add cloneConfig to other gestures --- .../src/v3/hooks/gestures/useLongPress.ts | 15 ++++++++++----- .../src/v3/hooks/gestures/usePan.ts | 10 +++++++--- .../src/v3/hooks/gestures/useTap.ts | 10 +++++++--- .../src/v3/hooks/utils.ts | 14 ++++++-------- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts index 7154c31580..619395d0e0 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts @@ -4,7 +4,7 @@ import { SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; -import { remapProps } from '../utils'; +import { cloneConfig, remapProps } from '../utils'; type LongPressGestureProps = { /** @@ -59,10 +59,15 @@ const LongPressPropsMapping = new Map< ]); export function useLongPress(config: LongPressGestureConfig) { - const longPressConfig = remapProps< - LongPressGestureConfig, - LongPressGestureInternalConfig - >(config, LongPressPropsMapping); + const longPressConfig = cloneConfig< + LongPressHandlerData, + LongPressGestureInternalProps + >(config); + + remapProps( + longPressConfig, + LongPressPropsMapping + ); if (longPressConfig.shouldCancelWhenOutside === undefined) { longPressConfig.shouldCancelWhenOutside = true; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index cd39d1baee..d8e981d1d6 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -5,7 +5,7 @@ import { SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; -import { remapProps } from '../utils'; +import { cloneConfig, remapProps } from '../utils'; type CommonPanGestureProps = { /** @@ -241,8 +241,12 @@ export function usePan(config: PanGestureConfig) { validatePanConfig(config); } - const panConfig = remapProps( - config, + const panConfig = cloneConfig( + config + ); + + remapProps( + panConfig, PanPropsMapping ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts index ae9fed8da1..856eae64c5 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts @@ -4,7 +4,7 @@ import { SingleGestureName, } from '../../types'; import { useGesture } from '../useGesture'; -import { remapProps } from '../utils'; +import { cloneConfig, remapProps } from '../utils'; type TapGestureProps = { /** @@ -93,8 +93,12 @@ const TapPropsMapping = new Map< ]); export function useTap(config: TapGestureConfig) { - const tapConfig = remapProps( - config, + const tapConfig = cloneConfig( + config + ); + + remapProps( + tapConfig, TapPropsMapping ); 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 7059eeaed5..37a6fddee7 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -104,22 +104,20 @@ export function cloneConfig( } export function remapProps( - config: TConfig, + config: TConfig & TInternalConfig, propsMapping: Map ): TInternalConfig { type MergedConfig = TConfig & TInternalConfig; - const newConfig = { ...config } as MergedConfig; - propsMapping.forEach((internalKey, key) => { - if (key in newConfig) { - newConfig[internalKey as keyof MergedConfig] = - newConfig[key as keyof MergedConfig]; + if (key in config) { + config[internalKey as keyof MergedConfig] = + config[key as keyof MergedConfig]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete newConfig[key as keyof MergedConfig]; + delete config[key as keyof MergedConfig]; } }); - return newConfig; + return config; } From dc472e11930d46f0717413e12f5c240a7b56acf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 18 Sep 2025 16:50:59 +0200 Subject: [PATCH 103/109] Rename Props to Properties --- .../src/v3/hooks/gestures/useFling.ts | 8 +++++--- .../src/v3/hooks/gestures/useHover.ts | 8 +++++--- .../src/v3/hooks/gestures/useLongPress.ts | 16 ++++++++-------- .../src/v3/hooks/gestures/useManual.ts | 6 +++--- .../src/v3/hooks/gestures/useNative.ts | 6 +++--- .../src/v3/hooks/gestures/usePan.ts | 18 +++++++++--------- .../src/v3/hooks/gestures/usePinch.ts | 8 +++++--- .../src/v3/hooks/gestures/useRotation.ts | 11 ++++++----- .../src/v3/hooks/gestures/useTap.ts | 16 ++++++++-------- 9 files changed, 52 insertions(+), 45 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts index b20e873d49..f1e6d2981b 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type FlingGestureProps = { +type FlingGestureProperties = { /** * Expressed allowed direction of movement. It's possible to pass one or many * directions in one parameter: @@ -38,14 +38,16 @@ type FlingHandlerData = { type FlingGestureInternalConfig = BaseGestureConfig< FlingHandlerData, - FlingGestureProps + FlingGestureProperties >; export type FlingGestureConfig = ExcludeInternalConfigProps; export function useFling(config: FlingGestureConfig) { - const flingConfig = cloneConfig(config); + const flingConfig = cloneConfig( + config + ); return useGesture(SingleGestureName.Fling, flingConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts index 67a5dfa6ea..cbcf16d4b3 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -8,7 +8,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type HoverGestureProps = { +type HoverGestureProperties = { /** * Visual effect applied to the view while the view is hovered. The possible values are: * @@ -31,14 +31,16 @@ type HoverHandlerData = { type HoverGestureInternalConfig = BaseGestureConfig< HoverHandlerData, - HoverGestureProps + HoverGestureProperties >; export type HoverGestureConfig = ExcludeInternalConfigProps; export function useHover(config: HoverGestureConfig) { - const hoverConfig = cloneConfig(config); + const hoverConfig = cloneConfig( + config + ); return useGesture(SingleGestureName.Hover, hoverConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts index 619395d0e0..723c9a598c 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig, remapProps } from '../utils'; -type LongPressGestureProps = { +type LongPressGestureProperties = { /** * Minimum time, expressed in milliseconds, that a finger must remain pressed on * the corresponding view. The default value is 500. @@ -27,7 +27,7 @@ type LongPressGestureProps = { numberOfPointers?: number; }; -type LongPressGestureInternalProps = { +type LongPressGestureInternalProperties = { minDurationMs?: number; maxDist?: number; numberOfPointers?: number; @@ -42,17 +42,17 @@ type LongPressHandlerData = { }; export type LongPressGestureConfig = ExcludeInternalConfigProps< - BaseGestureConfig + BaseGestureConfig >; type LongPressGestureInternalConfig = BaseGestureConfig< LongPressHandlerData, - LongPressGestureInternalProps + LongPressGestureInternalProperties >; const LongPressPropsMapping = new Map< - keyof LongPressGestureProps, - keyof LongPressGestureInternalProps + keyof LongPressGestureProperties, + keyof LongPressGestureInternalProperties >([ ['minDuration', 'minDurationMs'], ['maxDistance', 'maxDist'], @@ -61,7 +61,7 @@ const LongPressPropsMapping = new Map< export function useLongPress(config: LongPressGestureConfig) { const longPressConfig = cloneConfig< LongPressHandlerData, - LongPressGestureInternalProps + LongPressGestureInternalProperties >(config); remapProps( @@ -73,7 +73,7 @@ export function useLongPress(config: LongPressGestureConfig) { longPressConfig.shouldCancelWhenOutside = true; } - return useGesture( + return useGesture( SingleGestureName.LongPress, longPressConfig ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts index d8feb12c77..5d32f85e71 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts @@ -6,19 +6,19 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type ManualGestureProps = Record; +type ManualGestureProperties = Record; type ManualHandlerData = Record; type ManualGestureInternalConfig = BaseGestureConfig< ManualHandlerData, - ManualGestureProps + ManualGestureProperties >; export type ManualGestureConfig = ExcludeInternalConfigProps; export function useManual(config: ManualGestureConfig) { - const manualConfig = cloneConfig( + const manualConfig = cloneConfig( config ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts index 124d6556cf..0ed71b4d83 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type NativeViewGestureProps = { +type NativeViewGestureProperties = { /** * Android only. * @@ -28,7 +28,7 @@ type NativeViewHandlerData = { type NativeViewGestureInternalConfig = BaseGestureConfig< NativeViewHandlerData, - NativeViewGestureProps + NativeViewGestureProperties >; export type NativeViewGestureConfig = @@ -37,7 +37,7 @@ export type NativeViewGestureConfig = export function useNative(config: NativeViewGestureConfig) { const nativeConfig = cloneConfig< NativeViewHandlerData, - NativeViewGestureProps + NativeViewGestureProperties >(config); return useGesture(SingleGestureName.Native, nativeConfig); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index d8e981d1d6..c79dc9cb12 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -7,7 +7,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig, remapProps } from '../utils'; -type CommonPanGestureProps = { +type CommonPanGestureProperties = { /** * Minimum distance the finger (or multiple finger) need to travel before the * handler activates. Expressed in points. @@ -46,7 +46,7 @@ type CommonPanGestureProps = { activateAfterLongPress?: number; }; -export type PanGestureProps = CommonPanGestureProps & { +export type PanGestureProperties = CommonPanGestureProperties & { /** * Range along X axis (in points) where fingers travels without activation of * handler. Moving outside of this range implies activation of handler. Range @@ -92,7 +92,7 @@ export type PanGestureProps = CommonPanGestureProps & { failOffsetX?: number | [failOffsetXStart: number, failOffsetXEnd: number]; }; -type PanGestureInternalProps = CommonPanGestureProps & { +type PanGestureInternalProperties = CommonPanGestureProperties & { minDist?: number; activeOffsetYStart?: number; activeOffsetYEnd?: number; @@ -117,17 +117,17 @@ type PanHandlerData = { }; export type PanGestureConfig = ExcludeInternalConfigProps< - BaseGestureConfig + BaseGestureConfig >; type PanGestureInternalConfig = BaseGestureConfig< PanHandlerData, - PanGestureInternalProps + PanGestureInternalProperties >; const PanPropsMapping = new Map< - keyof PanGestureProps, - keyof PanGestureInternalProps + keyof PanGestureProperties, + keyof PanGestureInternalProperties >([['minDistance', 'minDist']]); function validatePanConfig(config: PanGestureConfig) { @@ -241,7 +241,7 @@ export function usePan(config: PanGestureConfig) { validatePanConfig(config); } - const panConfig = cloneConfig( + const panConfig = cloneConfig( config ); @@ -252,7 +252,7 @@ export function usePan(config: PanGestureConfig) { transformPanProps(panConfig); - return useGesture( + return useGesture( SingleGestureName.Pan, panConfig ); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts index a41dd9560d..ed61b0202c 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type PinchGestureProps = Record; +type PinchGestureProperties = Record; type PinchHandlerData = { scale: number; @@ -17,14 +17,16 @@ type PinchHandlerData = { type PinchGestureInternalConfig = BaseGestureConfig< PinchHandlerData, - PinchGestureProps + PinchGestureProperties >; export type PinchGestureConfig = ExcludeInternalConfigProps; export function usePinch(config: PinchGestureConfig) { - const pinchConfig = cloneConfig(config); + const pinchConfig = cloneConfig( + config + ); return useGesture(SingleGestureName.Pinch, pinchConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts index c06ae8d265..542def7146 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig } from '../utils'; -type RotationGestureProps = Record; +type RotationGestureProperties = Record; type RotationHandlerData = { rotation: number; @@ -17,16 +17,17 @@ type RotationHandlerData = { type RotationGestureInternalConfig = BaseGestureConfig< RotationHandlerData, - RotationGestureProps + RotationGestureProperties >; export type RotationGestureConfig = ExcludeInternalConfigProps; export function useRotation(config: RotationGestureConfig) { - const rotationConfig = cloneConfig( - config - ); + const rotationConfig = cloneConfig< + RotationHandlerData, + RotationGestureProperties + >(config); return useGesture(SingleGestureName.Rotation, rotationConfig); } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts index 856eae64c5..d45f8ae99e 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts @@ -6,7 +6,7 @@ import { import { useGesture } from '../useGesture'; import { cloneConfig, remapProps } from '../utils'; -type TapGestureProps = { +type TapGestureProperties = { /** * Minimum number of pointers (fingers) required to be placed before the * handler activates. Should be a positive integer. @@ -57,7 +57,7 @@ type TapGestureProps = { maxDistance?: number; }; -type TapGestureInternalProps = { +type TapGestureInternalProperties = { minPointers?: number; numberOfTaps?: number; maxDeltaX?: number; @@ -75,17 +75,17 @@ type TapHandlerData = { }; export type TapGestureConfig = ExcludeInternalConfigProps< - BaseGestureConfig + BaseGestureConfig >; type TapGestureInternalConfig = BaseGestureConfig< TapHandlerData, - TapGestureInternalProps + TapGestureInternalProperties >; const TapPropsMapping = new Map< - keyof TapGestureProps, - keyof TapGestureInternalProps + keyof TapGestureProperties, + keyof TapGestureInternalProperties >([ ['maxDistance', 'maxDist'], ['maxDuration', 'maxDurationMs'], @@ -93,7 +93,7 @@ const TapPropsMapping = new Map< ]); export function useTap(config: TapGestureConfig) { - const tapConfig = cloneConfig( + const tapConfig = cloneConfig( config ); @@ -102,7 +102,7 @@ export function useTap(config: TapGestureConfig) { TapPropsMapping ); - return useGesture( + return useGesture( SingleGestureName.Tap, tapConfig ); From 68a24caae4bc0f3b369d77d1c4e76347bdebaf0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 29 Sep 2025 16:01:24 +0200 Subject: [PATCH 104/109] Make change properties non-nullable --- .../src/v3/hooks/gestures/useHover.ts | 4 ++-- .../src/v3/hooks/gestures/usePan.ts | 4 ++-- .../src/v3/hooks/gestures/usePinch.ts | 2 +- .../src/v3/hooks/gestures/useRotation.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts index 956aa6400b..cbd0d4d6c3 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -28,8 +28,8 @@ type HoverHandlerData = { absoluteX: number; absoluteY: number; stylusData: StylusData; - changeX?: number; - changeY?: number; + changeX: number; + changeY: number; }; type HoverGestureInternalConfig = BaseGestureConfig< diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index f91393f0f8..4030b63add 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -115,8 +115,8 @@ type PanHandlerData = { velocityX: number; velocityY: number; stylusData: StylusData; - changeX?: number; - changeY?: number; + changeX: number; + changeY: number; }; export type PanGestureConfig = ExcludeInternalConfigProps< diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts index 5481090009..1d654f7496 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -14,7 +14,7 @@ type PinchHandlerData = { focalX: number; focalY: number; velocity: number; - scaleChange?: number; + scaleChange: number; }; type PinchGestureInternalConfig = BaseGestureConfig< diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts index f5db8218c5..6d43ea0f3b 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -14,7 +14,7 @@ type RotationHandlerData = { anchorX: number; anchorY: number; velocity: number; - rotationChange?: number; + rotationChange: number; }; type RotationGestureInternalConfig = BaseGestureConfig< From 16b1cc22d14694ab36384e7f09285c0bd861824c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 29 Sep 2025 16:05:12 +0200 Subject: [PATCH 105/109] Change Extacted to Unpacked --- packages/react-native-gesture-handler/src/v3/hooks/utils.ts | 4 ++-- .../src/v3/hooks/utils/eventHandlersUtils.ts | 4 ++-- packages/react-native-gesture-handler/src/v3/types.ts | 2 +- 3 files changed, 5 insertions(+), 5 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 1dd160797c..0edcef753f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -5,7 +5,7 @@ import { ChangeCalculatorType, DiffCalculatorType, ExcludeInternalConfigProps, - ExtractedGestureHandlerEvent, + UnpackedGestureHandlerEvent, GestureHandlerEvent, GestureStateChangeEvent, GestureUpdateEvent, @@ -35,7 +35,7 @@ export function maybeExtractNativeEvent( export function isEventForHandlerWithTag( handlerTag: number, - event: ExtractedGestureHandlerEvent + event: UnpackedGestureHandlerEvent ) { 'worklet'; 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 index 629a0183cc..459588f4a6 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/eventHandlersUtils.ts @@ -3,7 +3,7 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import { BaseGestureConfig, ChangeCalculatorType, - ExtractedGestureHandlerEvent, + UnpackedGestureHandlerEvent, GestureCallbacks, } from '../../types'; @@ -104,7 +104,7 @@ export function touchEventTypeToCallbackType( export function runCallback( type: CALLBACK_TYPE, callbacks: GestureCallbacks, - event: ExtractedGestureHandlerEvent, + event: UnpackedGestureHandlerEvent, ...args: unknown[] ) { 'worklet'; diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index bc3855126e..acb55bb23d 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -39,7 +39,7 @@ export type GestureHandlerEvent = | StateChangeEvent | TouchEvent; -export type ExtractedGestureHandlerEvent = +export type UnpackedGestureHandlerEvent = | GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent; From 312c9541b1a2de86eeba1850174d2e57d7682f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 29 Sep 2025 16:12:12 +0200 Subject: [PATCH 106/109] Use 0 instead of current values --- .../src/v3/hooks/gestures/useHover.ts | 4 ++-- .../src/v3/hooks/gestures/usePan.ts | 8 ++------ .../src/v3/hooks/gestures/usePinch.ts | 2 +- .../src/v3/hooks/gestures/useRotation.ts | 4 +--- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts index cbd0d4d6c3..948ec66445 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -46,8 +46,8 @@ function diffCalculator( ) { 'worklet'; return { - changeX: previous ? current.x - previous.x : current.x, - changeY: previous ? current.y - previous.y : current.y, + changeX: previous ? current.x - previous.x : 0, + changeY: previous ? current.y - previous.y : 0, }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index 4030b63add..86c94b8837 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -245,12 +245,8 @@ function diffCalculator( ) { 'worklet'; return { - changeX: previous - ? current.translationX - previous.translationX - : current.translationX, - changeY: previous - ? current.translationY - previous.translationY - : current.translationY, + changeX: previous ? current.translationX - previous.translationX : 0, + changeY: previous ? current.translationY - previous.translationY : 0, }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts index 1d654f7496..b19fefa6f6 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -31,7 +31,7 @@ function diffCalculator( ) { 'worklet'; return { - scaleChange: previous ? current.scale / previous.scale : current.scale, + scaleChange: previous ? current.scale / previous.scale : 0, }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts index 6d43ea0f3b..8a04051e01 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -31,9 +31,7 @@ function diffCalculator( ) { 'worklet'; return { - rotationChange: previous - ? current.rotation - previous.rotation - : current.rotation, + rotationChange: previous ? current.rotation - previous.rotation : 0, }; } From a28e2c991eec1e3670b19671c36480994eee47d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 29 Sep 2025 16:41:14 +0200 Subject: [PATCH 107/109] Do not copy event --- packages/react-native-gesture-handler/src/v3/hooks/utils.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 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 0edcef753f..48ed475203 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -145,9 +145,8 @@ export function getChangeEventCalculator( const changePayload = diffCalculator(currentEventData, previousEventData); - const resultEvent = { ...current }; - resultEvent.handlerData = { ...currentEventData, ...changePayload }; + current.handlerData = { ...currentEventData, ...changePayload }; - return resultEvent; + return current; }; } From 771e25cb4822b2683a5d5ba2f8fa4aa6780b0ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 2 Oct 2025 15:41:18 +0200 Subject: [PATCH 108/109] Revert "Use 0 instead of current values" This reverts commit 312c9541b1a2de86eeba1850174d2e57d7682f63. --- .../src/v3/hooks/gestures/useHover.ts | 4 ++-- .../src/v3/hooks/gestures/usePan.ts | 8 ++++++-- .../src/v3/hooks/gestures/usePinch.ts | 2 +- .../src/v3/hooks/gestures/useRotation.ts | 4 +++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts index 948ec66445..cbd0d4d6c3 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -46,8 +46,8 @@ function diffCalculator( ) { 'worklet'; return { - changeX: previous ? current.x - previous.x : 0, - changeY: previous ? current.y - previous.y : 0, + changeX: previous ? current.x - previous.x : current.x, + changeY: previous ? current.y - previous.y : current.y, }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts index 86c94b8837..4030b63add 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -245,8 +245,12 @@ function diffCalculator( ) { 'worklet'; return { - changeX: previous ? current.translationX - previous.translationX : 0, - changeY: previous ? current.translationY - previous.translationY : 0, + changeX: previous + ? current.translationX - previous.translationX + : current.translationX, + changeY: previous + ? current.translationY - previous.translationY + : current.translationY, }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts index b19fefa6f6..1d654f7496 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -31,7 +31,7 @@ function diffCalculator( ) { 'worklet'; return { - scaleChange: previous ? current.scale / previous.scale : 0, + scaleChange: previous ? current.scale / previous.scale : current.scale, }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts index 8a04051e01..6d43ea0f3b 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -31,7 +31,9 @@ function diffCalculator( ) { 'worklet'; return { - rotationChange: previous ? current.rotation - previous.rotation : 0, + rotationChange: previous + ? current.rotation - previous.rotation + : current.rotation, }; } From f399d361bbc562b81f056b75b0f5f7ed4d4615fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 2 Oct 2025 15:42:26 +0200 Subject: [PATCH 109/109] Use 0 instead of current values in Hover --- .../src/v3/hooks/gestures/useHover.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts index cbd0d4d6c3..948ec66445 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -46,8 +46,8 @@ function diffCalculator( ) { 'worklet'; return { - changeX: previous ? current.x - previous.x : current.x, - changeY: previous ? current.y - previous.y : current.y, + changeX: previous ? current.x - previous.x : 0, + changeY: previous ? current.y - previous.y : 0, }; }