diff --git a/apps/basic-example/Gemfile.lock b/apps/basic-example/Gemfile.lock
index 3e46c77c15..9f9cbc5984 100644
--- a/apps/basic-example/Gemfile.lock
+++ b/apps/basic-example/Gemfile.lock
@@ -5,15 +5,18 @@ GEM
base64
nkf
rexml
- activesupport (7.1.4.2)
+ activesupport (7.1.5.1)
base64
+ benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
+ logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
+ securerandom (>= 0.3)
tzinfo (~> 2.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
@@ -21,9 +24,9 @@ GEM
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
- base64 (0.2.0)
- benchmark (0.4.0)
- bigdecimal (3.1.9)
+ base64 (0.3.0)
+ benchmark (0.4.1)
+ bigdecimal (3.2.2)
claide (1.1.0)
cocoapods (1.15.2)
addressable (~> 2.8)
@@ -64,8 +67,8 @@ GEM
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.3.3)
- connection_pool (2.5.1)
- drb (2.2.1)
+ connection_pool (2.5.3)
+ drb (2.2.3)
escape (0.0.4)
ethon (0.16.0)
ffi (>= 1.15.0)
@@ -77,7 +80,7 @@ GEM
mutex_m
i18n (1.14.7)
concurrent-ruby (~> 1.0)
- json (2.10.2)
+ json (2.12.2)
logger (1.7.0)
minitest (5.25.5)
molinillo (0.8.0)
@@ -89,6 +92,7 @@ GEM
public_suffix (4.0.7)
rexml (3.4.1)
ruby-macho (2.5.1)
+ securerandom (0.3.2)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
diff --git a/apps/basic-example/ios/Podfile.lock b/apps/basic-example/ios/Podfile.lock
index 27ba11a5e7..d784023016 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: 0c4b7d2aacc910a1f702694fa86be830386f4ceb
+ Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e
PODFILE CHECKSUM: d05778d3a61b8d49242579ea0aa864580fbb1f64
diff --git a/apps/basic-example/src/App.tsx b/apps/basic-example/src/App.tsx
index 8f5193ee0c..fceb9a2ba2 100644
--- a/apps/basic-example/src/App.tsx
+++ b/apps/basic-example/src/App.tsx
@@ -4,55 +4,47 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler';
import Navigator from './Navigator';
-import ComponentsScreen from './ComponentsScreen';
-import FinalScreen from './FinalScreen';
-import GestureCompositionScreen from './GestureCompositionScreen';
-import HomeScreen from './HomeScreen';
-import ViewFlatteningScreen from './ViewFlatteningScreen';
+import NativeDetector from './NativeDetector';
+import RuntimeDecoration from './RuntimeDecoration';
-const Stack = Navigator.create();
-
-Stack.setRoutes({
- home: {
- component: HomeScreen,
- title: 'RNGH FabricExample',
- rightButtonAction: () => {
- Stack.navigateTo('gestureComposition');
- },
- },
- gestureComposition: {
- component: GestureCompositionScreen,
- title: 'Gesture Composition',
- rightButtonAction: () => {
- Stack.navigateTo('components');
- },
- },
- components: {
- component: ComponentsScreen,
- title: 'Components',
- rightButtonAction: () => {
- Stack.navigateTo('viewFlattening');
- },
+const EXAMPLES = [
+ {
+ name: 'Runtime Decoration',
+ component: RuntimeDecoration,
},
- viewFlattening: {
- component: ViewFlatteningScreen,
- title: 'View Flattening',
- rightButtonAction: () => {
- Stack.navigateTo('final');
- },
+ {
+ name: 'Native Detector',
+ component: NativeDetector,
},
- final: {
- component: FinalScreen,
- title: 'Final Screen',
- },
-});
+];
+
+const Stack = Navigator.create();
+Stack.setRoutes(
+ Object.fromEntries(
+ EXAMPLES.map((example, index) => [
+ example.name.toLowerCase().replace(/\s+/g, ''),
+ {
+ component: example.component,
+ title: example.name,
+ rightButtonAction:
+ index === EXAMPLES.length - 1
+ ? undefined
+ : () => {
+ Stack.navigateTo(
+ EXAMPLES[index + 1].name.toLowerCase().replace(/\s+/g, '')
+ );
+ },
+ },
+ ])
+ )
+);
export default function App() {
return (
-
+
);
diff --git a/apps/basic-example/src/ComponentsScreen.tsx b/apps/basic-example/src/ComponentsScreen.tsx
deleted file mode 100644
index 8a3eb59fd9..0000000000
--- a/apps/basic-example/src/ComponentsScreen.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-import React from 'react';
-import {
- FlatList,
- Gesture,
- GestureDetector,
- ScrollView,
- Switch,
- TextInput,
- TouchableNativeFeedback,
- TouchableOpacity,
-} from 'react-native-gesture-handler';
-import { StyleSheet, Text, View } from 'react-native';
-
-import { COLORS } from './colors';
-
-function SwitchDemo() {
- const [value, setValue] = React.useState(false);
-
- const pan = Gesture.Pan()
- .onBegin(() => console.log('onBegin'))
- .onUpdate(() => console.log('onUpdate')) // doesn't work on iOS
- .onFinalize(() => console.log('onFinalize'));
-
- return (
-
- RNGH Switch
-
- setValue(!value)} />
-
-
- );
-}
-
-function TextInputDemo() {
- const [value, setValue] = React.useState('Hello!');
-
- const pan = Gesture.Pan()
- .onBegin(() => console.log('onBegin'))
- .onUpdate(() => console.log('onUpdate'))
- .onFinalize(() => console.log('onFinalize'));
-
- return (
-
- RNGH TextInput
-
-
-
-
- );
-}
-
-function TouchableNativeFeedbackDemo() {
- return (
-
- RNGH TouchableNativeFeedback
- console.log('onPressIn')}
- onPressOut={() => console.log('onPressOut')}
- onLongPress={() => console.log('onLongPress')}>
-
-
-
- );
-}
-
-function TouchableOpacityDemo() {
- return (
-
- RNGH TouchableOpacity
- console.log('onPressIn')}
- onPressOut={() => console.log('onPressOut')}
- onLongPress={() => console.log('onLongPress')}>
-
-
-
- );
-}
-
-function ScrollViewDemo() {
- const pan = Gesture.Pan().onUpdate((e) => console.log('onUpdate', e.x, e.y));
-
- return (
-
- RNGH ScrollView
-
-
- {Object.values(COLORS).map((color) => (
-
- ))}
-
-
-
- Dragging here should not scroll
-
-
-
-
-
-
- );
-}
-
-function FlatListDemo() {
- return (
-
- RNGH FlatList
-
- (
-
-
-
- )}
- keyExtractor={(e) => e}
- />
-
-
- );
-}
-
-export default function ComponentsScreen() {
- return (
-
-
- Gesture Handler also provides a set of components that support gestures.
-
-
-
-
-
-
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- },
- bold: {
- fontWeight: 'bold',
- textAlign: 'center',
- marginHorizontal: 20,
- },
- textInput: {
- borderWidth: 1,
- borderStyle: 'solid',
- borderColor: 'darkgray',
- width: 135,
- padding: 10,
- },
- text: {
- marginVertical: 3,
- },
- demo: {
- marginVertical: 3,
- alignItems: 'center',
- },
- smallBox: {
- width: 50,
- height: 50,
- },
- largeBox: {
- width: 100,
- height: 100,
- },
- panBox: {
- width: 100,
- height: 75,
- backgroundColor: 'lightgray',
- alignItems: 'center',
- justifyContent: 'center',
- },
- panText: {
- textAlign: 'center',
- },
-});
diff --git a/apps/basic-example/src/FinalScreen.tsx b/apps/basic-example/src/FinalScreen.tsx
deleted file mode 100644
index 9e65cf8135..0000000000
--- a/apps/basic-example/src/FinalScreen.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React from 'react';
-import { Image, StyleSheet, Text, View } from 'react-native';
-// @ts-ignore TODO: remove once there is a .d.ts file with definitions
-import openURLInBrowser from 'react-native/Libraries/Core/Devtools/openURLInBrowser';
-
-import { COLORS } from './colors';
-
-const SOFTWARE_MANSION_LOGO_URL =
- 'https://pbs.twimg.com/profile_images/1243176655172009986/Jgdl2m15_400x400.jpg';
-const SOFTWARE_MANSION_TWITTER_URL = 'https://twitter.com/swmansion/';
-
-export default function FinalScreen() {
- return (
-
-
- React Native Gesture Handler is developed by Software Mansion.
-
-
- We are actively porting React Native libraries to Fabric, including
- React Native Screens and Reanimated.
-
-
- Follow us on Twitter and stay tuned! @swmansion
-
- openURLInBrowser(SOFTWARE_MANSION_TWITTER_URL)}>
-
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: COLORS.NAVY,
- },
- text: {
- textAlign: 'center',
- color: 'white',
- fontSize: 21,
- marginHorizontal: 20,
- marginVertical: 10,
- },
- bold: {
- fontWeight: 'bold',
- },
- logo: {
- width: 250,
- height: 250,
- },
-});
diff --git a/apps/basic-example/src/GestureCompositionScreen.tsx b/apps/basic-example/src/GestureCompositionScreen.tsx
deleted file mode 100644
index 2eb920c62f..0000000000
--- a/apps/basic-example/src/GestureCompositionScreen.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import React from 'react';
-import { StyleSheet, Text, View } from 'react-native';
-import { Gesture, GestureDetector } from 'react-native-gesture-handler';
-
-import { COLORS } from './colors';
-
-function RaceDemo() {
- const pan = Gesture.Pan()
- .onStart(() => console.log('pan onStart'))
- .onUpdate(() => console.log('pan onUpdate'))
- .onEnd(() => console.log('pan onEnd'));
-
- const longPress = Gesture.LongPress()
- .onStart(() => console.log('longPress onStart'))
- .onEnd(() => console.log('longPress onEnd'));
-
- return (
-
-
- Gesture.Race(pan, longPress) - the first gesture that meets its
- activation criteria will activate
-
-
-
-
-
- );
-}
-
-function ExclusiveDemo() {
- const singleTap = Gesture.Tap().onStart(() => console.log('single tap!'));
- const doubleTap = Gesture.Tap()
- .onStart(() => console.log('double tap!'))
- .numberOfTaps(2);
-
- return (
-
-
- Gesture.Exclusive(doubleTap, singleTap) - the second gesture will wait
- for the failure of the first one
-
-
-
-
-
- );
-}
-
-function SimultaneousDemo() {
- const pinch = Gesture.Pinch()
- .onStart(() => console.log('pinch onStart'))
- .onUpdate(() => console.log('pinch onUpdate'))
- .onEnd(() => console.log('pinch onEnd'));
- const rotation = Gesture.Rotation()
- .onStart(() => console.log('rotation onStart'))
- .onUpdate(() => console.log('rotation onUpdate'))
- .onEnd(() => console.log('rotation onEnd'));
-
- return (
-
-
- Gesture.Simultaneous(pinch, rotation) - both gestures can activate and
- process touches at the same time
-
-
-
-
-
- );
-}
-
-export default function ComponentsScreen() {
- return (
-
-
- Gesture Handler provides a simple API for using multiple gestures at
- once in different configurations.
-
-
-
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- },
- bold: {
- fontWeight: 'bold',
- textAlign: 'center',
- marginHorizontal: 20,
- },
- text: {
- marginVertical: 3,
- marginHorizontal: 10,
- textAlign: 'center',
- },
- demo: {
- marginVertical: 3,
- alignItems: 'center',
- },
- largeBox: {
- width: 100,
- height: 100,
- },
- largerBox: {
- width: 150,
- height: 150,
- },
-});
diff --git a/apps/basic-example/src/NativeDetector.tsx b/apps/basic-example/src/NativeDetector.tsx
new file mode 100644
index 0000000000..b5cfc7b3b3
--- /dev/null
+++ b/apps/basic-example/src/NativeDetector.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react';
+import { Animated, Button, useAnimatedValue } from 'react-native';
+import {
+ GestureHandlerRootView,
+ NativeDetector,
+ useGesture,
+} from 'react-native-gesture-handler';
+
+export default function App() {
+ const [visible, setVisible] = React.useState(true);
+
+ const value = useAnimatedValue(0);
+ const event = Animated.event(
+ [{ nativeEvent: { handlerData: { translationX: value } } }],
+ {
+ useNativeDriver: true,
+ }
+ );
+
+ const gesture = useGesture('PanGestureHandler', {
+ onGestureHandlerAnimatedEvent: event,
+ onGestureHandlerEvent: (e: any) =>
+ console.log('onGestureHandlerEvent', e.nativeEvent),
+ });
+
+ return (
+
+
+ );
+}
diff --git a/apps/basic-example/src/Navigator.tsx b/apps/basic-example/src/Navigator.tsx
index 0a8ad50b78..4949d2b936 100644
--- a/apps/basic-example/src/Navigator.tsx
+++ b/apps/basic-example/src/Navigator.tsx
@@ -10,7 +10,7 @@ export interface RouteInfo {
export type NavigatorRoutes = Record;
export interface NavigatorProps {
- initialRouteName: string;
+ initialRouteName?: string;
}
export interface ButtonProps {
@@ -74,11 +74,13 @@ export default class Navigator {
};
Navigator = (props: NavigatorProps) => {
- const [currentRoute, setCurrentRoute] = useState(props.initialRouteName);
+ const [currentRoute, setCurrentRoute] = useState(
+ props.initialRouteName ?? Object.keys(this.routes)[0]
+ );
this.setCurrentRoute = setCurrentRoute;
useEffect(() => {
- // eslint-disable-next-line @typescript-eslint/unbound-method
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @eslint-react/web-api/no-leaked-event-listener
return BackHandler.addEventListener('hardwareBackPress', this.backHandler)
.remove;
}, []);
diff --git a/apps/basic-example/src/HomeScreen.tsx b/apps/basic-example/src/RuntimeDecoration.tsx
similarity index 72%
rename from apps/basic-example/src/HomeScreen.tsx
rename to apps/basic-example/src/RuntimeDecoration.tsx
index d729810f01..d3baa9eb3c 100644
--- a/apps/basic-example/src/HomeScreen.tsx
+++ b/apps/basic-example/src/RuntimeDecoration.tsx
@@ -1,20 +1,27 @@
import * as React from 'react';
-import { StyleSheet, View } from 'react-native';
+import { StyleSheet, Text, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { COLORS } from './colors';
import Animated, {
+ runOnJS,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
+import { useState } from 'react';
-export default function HomeScreen() {
+export function RuntimeChecker({
+ runGestureOnJS,
+}: {
+ runGestureOnJS: boolean;
+}) {
const pressed = useSharedValue(false);
const active = useSharedValue(false);
const posX = useSharedValue(0);
const posY = useSharedValue(0);
const start = useSharedValue({ x: 0, y: 0 });
+ const [isUIRuntime, setIsUIRuntime] = useState(null);
const style = useAnimatedStyle(() => {
return {
@@ -28,6 +35,7 @@ export default function HomeScreen() {
});
const gesture = Gesture.Manual()
+ .runOnJS(runGestureOnJS)
.onTouchesDown((e) => {
if (!pressed.value) {
pressed.value = true;
@@ -36,7 +44,7 @@ export default function HomeScreen() {
y: e.allTouches[0].absoluteY,
};
}
- console.log(_WORKLET);
+ runOnJS(setIsUIRuntime)(_WORKLET ?? false);
})
.onTouchesMove((e, state) => {
const dist = Math.sqrt(
@@ -63,17 +71,20 @@ export default function HomeScreen() {
}
})
.onStart(() => {
- console.log('Gesture started');
active.value = true;
})
.onFinalize(() => {
- console.log('Gesture finalized');
pressed.value = false;
active.value = false;
});
return (
+
+ {isUIRuntime === null
+ ? 'Start the gesture to check the runtime'
+ : `Running on the ${isUIRuntime ? 'UI' : 'JS'} runtime`}
+
+
+
+
+
+ );
+}
+
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
+ gap: 8,
},
box: {
width: 50,
height: 50,
},
+ separator: {
+ width: '100%',
+ height: 1,
+ backgroundColor: 'black',
+ },
});
diff --git a/apps/basic-example/src/ViewFlatteningScreen.tsx b/apps/basic-example/src/ViewFlatteningScreen.tsx
deleted file mode 100644
index dd3aaa52f9..0000000000
--- a/apps/basic-example/src/ViewFlatteningScreen.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import React from 'react';
-import { Animated, StyleSheet, Text, View } from 'react-native';
-import { Gesture, GestureDetector } from 'react-native-gesture-handler';
-
-import { COLORS } from './colors';
-
-declare const performance: {
- now: () => number;
-};
-
-function ParentViewFlattenedDemo() {
- const tap = Gesture.Tap().onStart(() =>
- console.log(performance.now(), 'tap!')
- );
-
- return (
- <>
- Parent view flattened
-
-
-
-
-
- >
- );
-}
-
-function InnerFlattenedParent() {
- return (
-
- {/* parent view will be collapsed */}
-
-
- );
-}
-
-function ParentViewNotFlattenedCollapsableTrueDemo() {
- const tap = Gesture.Tap().onStart(() => {
- console.log(performance.now(), 'tap!');
- });
-
- return (
- <>
- Parent view not flattened, collapsable=true
- (child moved to the same level as parent)
-
-
-
-
-
- >
- );
-}
-
-function InnerNotFlattenedParentCollapsable() {
- return (
-
-
-
- );
-}
-
-function ParentViewNotFlattenedCollapsableFalseDemo() {
- const tap = Gesture.Tap().onStart(() =>
- console.log(performance.now(), 'tap!')
- );
-
- return (
- <>
- Parent view not flattened, collapsable=false
- (structure doesn't change)
-
-
-
-
-
- >
- );
-}
-
-function InnerNotFlattenedParentNotCollapsable() {
- return (
-
-
-
- );
-}
-
-function ParentAnimatedViewDemo() {
- const tap = Gesture.Tap().onStart(() =>
- console.log(performance.now(), 'tap!')
- );
-
- return (
- <>
- Parent is Animated.View
-
-
-
-
-
- >
- );
-}
-
-function InnerParentAnimatedView() {
- return (
-
-
-
- );
-}
-
-export default function ViewFlatteningScreen() {
- return (
-
-
- Fabric comes with a brand new advanced view flattening mechanism. Not
- only some of the views may gets collapsed but also the structure of
- native views hierarchy may differ from the React component tree.
-
-
-
-
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- },
- bold: {
- fontWeight: 'bold',
- textAlign: 'center',
- marginHorizontal: 5,
- marginBottom: 10,
- },
- borderedBox: {
- borderWidth: 2,
- borderStyle: 'dashed',
- borderColor: 'gray',
- margin: 5,
- },
- boxWrapper: {
- padding: 17,
- margin: 15,
- },
- box: {
- width: 25,
- height: 25,
- },
-});
diff --git a/apps/basic-example/src/utils.ts b/apps/basic-example/src/utils.ts
deleted file mode 100644
index 8776c3ed46..0000000000
--- a/apps/basic-example/src/utils.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export function isHermes(): boolean {
- // @ts-expect-error HermesInternal is not yet included in the RN types
- return !!global?.HermesInternal;
-}
-
-export function isFabric(): boolean {
- // @ts-expect-error nativeFabricUIManager is not yet included in the RN types
- return !!global?.nativeFabricUIManager;
-}
diff --git a/apps/macos-example/macos/Podfile.lock b/apps/macos-example/macos/Podfile.lock
index 35c6fe377c..00d0439d7f 100644
--- a/apps/macos-example/macos/Podfile.lock
+++ b/apps/macos-example/macos/Podfile.lock
@@ -1504,6 +1504,31 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
+ - RNGestureHandler/shared (= 2.25.0)
+ - Yoga
+ - RNGestureHandler/shared (2.25.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.11.18.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-hermes
+ - React-ImageManager
+ - React-jsi
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-renderercss
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
- Yoga
- RNReanimated (3.18.0):
- DoubleConversion
diff --git a/packages/react-native-gesture-handler/android/CMakeLists.txt b/packages/react-native-gesture-handler/android/CMakeLists.txt
new file mode 100644
index 0000000000..3499b7420e
--- /dev/null
+++ b/packages/react-native-gesture-handler/android/CMakeLists.txt
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.13)
+project(GestureHandlerCodegen)
+
+set(CMAKE_VERBOSE_MAKEFILE on)
+set(CMAKE_CXX_STANDARD 20)
+
+file(GLOB_RECURSE rn_gesture_handler_SRCS CONFIGURE_DEPENDS ../shared/shadowNodes/*.cpp)
+file(GLOB_RECURSE rn_gesture_handler_codegen_SRCS CONFIGURE_DEPENDS ./build/generated/source/codegen/jni/*.cpp)
+
+add_library(
+ react_codegen_rngesturehandler_codegen
+ SHARED
+ ${rn_gesture_handler_SRCS}
+ ${rn_gesture_handler_codegen_SRCS}
+)
+
+target_include_directories(react_codegen_rngesturehandler_codegen PUBLIC ../shared/shadowNodes)
+target_include_directories(react_codegen_rngesturehandler_codegen PUBLIC ./build/generated/source/codegen/jni)
+
+target_link_libraries(
+ react_codegen_rngesturehandler_codegen
+ fbjni
+ jsi
+ reactnative
+)
diff --git a/packages/react-native-gesture-handler/android/paper/src/main/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerDelegate.java b/packages/react-native-gesture-handler/android/paper/src/main/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerDelegate.java
index 99dfe019d0..03524008f0 100644
--- a/packages/react-native-gesture-handler/android/paper/src/main/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerDelegate.java
+++ b/packages/react-native-gesture-handler/android/paper/src/main/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerDelegate.java
@@ -21,6 +21,12 @@ public RNGestureHandlerRootViewManagerDelegate(U viewManager) {
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
- super.setProperty(view, propName, value);
+ switch (propName) {
+ case "moduleId":
+ mViewManager.setModuleId(view, value == null ? 0 : ((Double) value).intValue());
+ break;
+ default:
+ super.setProperty(view, propName, value);
+ }
}
}
diff --git a/packages/react-native-gesture-handler/android/paper/src/main/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerInterface.java b/packages/react-native-gesture-handler/android/paper/src/main/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerInterface.java
index c94080ea6c..8dc0586aec 100644
--- a/packages/react-native-gesture-handler/android/paper/src/main/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerInterface.java
+++ b/packages/react-native-gesture-handler/android/paper/src/main/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerInterface.java
@@ -13,5 +13,5 @@
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNGestureHandlerRootViewManagerInterface extends ViewManagerWithGeneratedInterface {
- // No props
+ void setModuleId(T view, int value);
}
diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt
index e271a3e2f7..95f071671f 100644
--- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt
@@ -11,6 +11,7 @@ import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.uimanager.ViewManager
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
+import com.swmansion.gesturehandler.react.RNGestureHandlerDetectorViewManager
import com.swmansion.gesturehandler.react.RNGestureHandlerModule
import com.swmansion.gesturehandler.react.RNGestureHandlerRootViewManager
@@ -30,11 +31,17 @@ class RNGestureHandlerPackage :
RNGestureHandlerButtonViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec {
RNGestureHandlerButtonViewManager()
},
+ RNGestureHandlerDetectorViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec {
+ RNGestureHandlerDetectorViewManager()
+ },
)
}
- override fun createViewManagers(reactContext: ReactApplicationContext) =
- listOf>(RNGestureHandlerRootViewManager(), RNGestureHandlerButtonViewManager())
+ override fun createViewManagers(reactContext: ReactApplicationContext) = listOf>(
+ RNGestureHandlerRootViewManager(),
+ RNGestureHandlerButtonViewManager(),
+ RNGestureHandlerDetectorViewManager(),
+ )
override fun getViewManagerNames(reactContext: ReactApplicationContext) = viewManagers.keys.toList()
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 819498986c..55e0cdcbb0 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
@@ -967,6 +967,8 @@ open class GestureHandler {
const val ACTION_TYPE_NATIVE_ANIMATED_EVENT = 2
const val ACTION_TYPE_JS_FUNCTION_OLD_API = 3
const val ACTION_TYPE_JS_FUNCTION_NEW_API = 4
+ const val ACTION_TYPE_NATIVE_DETECTOR = 5
+ const val ACTION_TYPE_NATIVE_DETECTOR_ANIMATED_EVENT = 6
const val POINTER_TYPE_TOUCH = 0
const val POINTER_TYPE_STYLUS = 1
const val POINTER_TYPE_MOUSE = 2
diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt
new file mode 100644
index 0000000000..83770fe3b3
--- /dev/null
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt
@@ -0,0 +1,87 @@
+package com.swmansion.gesturehandler.react
+
+import android.content.Context
+import com.facebook.react.bridge.ReadableArray
+import com.facebook.react.uimanager.ThemedReactContext
+import com.facebook.react.uimanager.UIManagerHelper
+import com.facebook.react.uimanager.events.Event
+import com.facebook.react.views.view.ReactViewGroup
+import com.swmansion.gesturehandler.core.GestureHandler
+
+class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
+ private val reactContext: ThemedReactContext
+ get() = context as ThemedReactContext
+ private var handlersToAttach: List? = null
+ private var attachedHandlers = listOf()
+ private var moduleId: Int = -1
+ private var dispatchesAnimatedEvents: Boolean = false
+
+ fun setHandlerTags(handlerTags: ReadableArray?) {
+ val newHandlers = handlerTags?.toArrayList()?.map { (it as Double).toInt() } ?: emptyList()
+ if (moduleId == -1) {
+ // It's possible that handlerTags will be set before module id. In that case, store
+ // the handler ids and attach them after setting module id.
+ handlersToAttach = newHandlers
+ return
+ }
+
+ attachHandlers(newHandlers)
+ }
+
+ fun setModuleId(id: Int) {
+ assert(this.moduleId == -1) { "Tried to change moduleId of a native detector" }
+
+ this.moduleId = id
+ this.attachHandlers(handlersToAttach ?: return)
+ handlersToAttach = null
+ }
+
+ fun setDispatchesAnimatedEvents(dispatchesAnimatedEvents: Boolean) {
+ this.dispatchesAnimatedEvents = dispatchesAnimatedEvents
+ }
+
+ private fun attachHandlers(newHandlers: List) {
+ val registry = RNGestureHandlerModule.registries[moduleId]
+ ?: throw Exception("Tried to access a non-existent registry")
+
+ val changes = mutableMapOf()
+
+ for (tag in attachedHandlers) {
+ changes[tag] = GestureHandlerMutation.Detach
+ }
+
+ for (tag in newHandlers) {
+ changes[tag] = if (changes.containsKey(tag)) GestureHandlerMutation.Keep else GestureHandlerMutation.Attach
+ }
+
+ for (entry in changes) {
+ if (entry.value == GestureHandlerMutation.Attach) {
+ // TODO: Attach to the child when attached gesture is a NativeGestureHandler, track children changes then
+ registry.attachHandlerToView(
+ entry.key,
+ this.id,
+ if (dispatchesAnimatedEvents) {
+ GestureHandler.ACTION_TYPE_NATIVE_DETECTOR_ANIMATED_EVENT
+ } else {
+ GestureHandler.ACTION_TYPE_NATIVE_DETECTOR
+ },
+ )
+ } else if (entry.value == GestureHandlerMutation.Detach) {
+ registry.detachHandler(entry.key)
+ }
+ }
+ }
+
+ fun dispatchEvent(event: Event<*>) {
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
+ eventDispatcher?.dispatchEvent(event)
+ }
+
+ companion object {
+ private enum class GestureHandlerMutation {
+ Attach,
+ Detach,
+ Keep,
+ }
+ }
+}
diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorViewManager.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorViewManager.kt
new file mode 100644
index 0000000000..548afef608
--- /dev/null
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorViewManager.kt
@@ -0,0 +1,43 @@
+package com.swmansion.gesturehandler.react
+
+import com.facebook.react.bridge.ReadableArray
+import com.facebook.react.module.annotations.ReactModule
+import com.facebook.react.uimanager.ThemedReactContext
+import com.facebook.react.uimanager.ViewGroupManager
+import com.facebook.react.uimanager.ViewManagerDelegate
+import com.facebook.react.viewmanagers.RNGestureHandlerDetectorManagerDelegate
+import com.facebook.react.viewmanagers.RNGestureHandlerDetectorManagerInterface
+
+@ReactModule(name = RNGestureHandlerDetectorViewManager.REACT_CLASS)
+class RNGestureHandlerDetectorViewManager :
+ ViewGroupManager(),
+ RNGestureHandlerDetectorManagerInterface {
+ private val mDelegate: ViewManagerDelegate
+
+ init {
+ mDelegate =
+ RNGestureHandlerDetectorManagerDelegate(this)
+ }
+
+ override fun getDelegate(): ViewManagerDelegate = mDelegate
+
+ override fun getName() = REACT_CLASS
+
+ override fun createViewInstance(reactContext: ThemedReactContext) = RNGestureHandlerDetectorView(reactContext)
+
+ companion object {
+ const val REACT_CLASS = "RNGestureHandlerDetector"
+ }
+
+ override fun setHandlerTags(view: RNGestureHandlerDetectorView, value: ReadableArray?) {
+ view.setHandlerTags(value)
+ }
+
+ override fun setModuleId(view: RNGestureHandlerDetectorView, value: Int) {
+ view.setModuleId(value)
+ }
+
+ override fun setDispatchesAnimatedEvents(view: RNGestureHandlerDetectorView, value: Boolean) {
+ view.setDispatchesAnimatedEvents(value)
+ }
+}
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/RNGestureHandlerEvent.kt
index a46ab52c75..47df2598da 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/RNGestureHandlerEvent.kt
@@ -17,6 +17,7 @@ import com.swmansion.gesturehandler.react.eventbuilders.GestureHandlerEventDataB
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
// 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
@@ -27,11 +28,13 @@ class RNGestureHandlerEvent private constructor() : Event
private fun init(
handler: T,
+ actionType: Int,
dataBuilder: GestureHandlerEventDataBuilder,
useNativeAnimatedName: Boolean,
) {
val view = handler.view!!
super.init(UIManagerHelper.getSurfaceId(view), view.id)
+ this.actionType = actionType
this.dataBuilder = dataBuilder
this.useTopPrefixedName = useNativeAnimatedName
coalescingKey = handler.eventCoalescingKey
@@ -42,7 +45,9 @@ class RNGestureHandlerEvent private constructor() : Event
EVENTS_POOL.release(this)
}
- override fun getEventName() = if (useTopPrefixedName) {
+ override fun getEventName() = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR_ANIMATED_EVENT) {
+ NATIVE_DETECTOR_ANIMATED_EVENT_NAME
+ } else if (useTopPrefixedName) {
NATIVE_ANIMATED_EVENT_NAME
} else {
EVENT_NAME
@@ -52,24 +57,46 @@ class RNGestureHandlerEvent private constructor() : Event
override fun getCoalescingKey() = coalescingKey
- override fun getEventData(): WritableMap = createEventData(dataBuilder!!)
+ override fun getEventData(): WritableMap = if (actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR ||
+ actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR_ANIMATED_EVENT
+ ) {
+ createNativeEventData(dataBuilder!!)
+ } else {
+ createEventData(dataBuilder!!)
+ }
companion object {
const val EVENT_NAME = "onGestureHandlerEvent"
const val NATIVE_ANIMATED_EVENT_NAME = "topGestureHandlerEvent"
+ const val NATIVE_DETECTOR_ANIMATED_EVENT_NAME = "topGestureHandlerAnimatedEvent"
private const val TOUCH_EVENTS_POOL_SIZE = 7 // magic
private val EVENTS_POOL = Pools.SynchronizedPool(TOUCH_EVENTS_POOL_SIZE)
fun obtain(
handler: T,
+ actionType: Int,
dataBuilder: GestureHandlerEventDataBuilder,
useTopPrefixedName: Boolean = false,
): RNGestureHandlerEvent = (EVENTS_POOL.acquire() ?: RNGestureHandlerEvent()).apply {
- init(handler, dataBuilder, useTopPrefixedName)
+ init(handler, actionType, dataBuilder, useTopPrefixedName)
}
fun createEventData(dataBuilder: GestureHandlerEventDataBuilder<*>): WritableMap = Arguments.createMap().apply {
dataBuilder.buildEventData(this)
+ putInt("handlerTag", dataBuilder.handlerTag)
+ putInt("state", dataBuilder.state)
}
+
+ fun createNativeEventData(dataBuilder: GestureHandlerEventDataBuilder<*>): WritableMap =
+ Arguments.createMap().apply {
+ putMap(
+ "handlerData",
+ Arguments.createMap().apply {
+ dataBuilder.buildEventData(this)
+ },
+ )
+ putInt("handlerTag", dataBuilder.handlerTag)
+ putInt("state", dataBuilder.state)
+ }
}
}
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/RNGestureHandlerEventDispatcher.kt
index 923747dbbd..d545c3bf96 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/RNGestureHandlerEventDispatcher.kt
@@ -40,6 +40,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
// Reanimated worklet
val event = RNGestureHandlerEvent.obtain(
handler,
+ handler.actionType,
handlerFactory.createEventBuilder(handler),
)
sendEventForReanimated(event)
@@ -48,6 +49,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
// Animated with useNativeDriver: true
val event = RNGestureHandlerEvent.obtain(
handler,
+ handler.actionType,
handlerFactory.createEventBuilder(handler),
true,
)
@@ -63,6 +65,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
} else {
val event = RNGestureHandlerEvent.obtain(
handler,
+ handler.actionType,
handlerFactory.createEventBuilder(handler),
)
sendEventForDirectEvent(event)
@@ -74,6 +77,37 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
RNGestureHandlerEvent.createEventData(handlerFactory.createEventBuilder(handler))
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
}
+ GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> {
+ val view = handler.view
+ if (view is RNGestureHandlerDetectorView) {
+ val event = RNGestureHandlerEvent.obtain(
+ handler,
+ handler.actionType,
+ handlerFactory.createEventBuilder(handler),
+ )
+ view.dispatchEvent(event)
+ }
+ }
+ // In case of a native detector with animated event listener, dispatch the event twice:
+ // once for the animated event, second for any JS callbacks
+ GestureHandler.ACTION_TYPE_NATIVE_DETECTOR_ANIMATED_EVENT -> {
+ val view = handler.view
+ if (view is RNGestureHandlerDetectorView) {
+ val animatedEvent = RNGestureHandlerEvent.obtain(
+ handler,
+ handler.actionType,
+ handlerFactory.createEventBuilder(handler),
+ )
+ view.dispatchEvent(animatedEvent)
+
+ val event = RNGestureHandlerEvent.obtain(
+ handler,
+ GestureHandler.ACTION_TYPE_NATIVE_DETECTOR,
+ handlerFactory.createEventBuilder(handler),
+ )
+ view.dispatchEvent(event)
+ }
+ }
}
}
@@ -93,10 +127,12 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
handler,
newState,
oldState,
+ handler.actionType,
handlerFactory.createEventBuilder(handler),
)
sendEventForReanimated(event)
}
+
GestureHandler.ACTION_TYPE_NATIVE_ANIMATED_EVENT, GestureHandler.ACTION_TYPE_JS_FUNCTION_OLD_API -> {
// JS function or Animated.event with useNativeDriver: false with old API
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
@@ -111,11 +147,13 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
handler,
newState,
oldState,
+ handler.actionType,
handlerFactory.createEventBuilder(handler),
)
sendEventForDirectEvent(event)
}
}
+
GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API -> {
// JS function or Animated.event with useNativeDriver: false with new API
val data = RNGestureHandlerStateChangeEvent.createEventData(
@@ -125,6 +163,20 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
)
sendEventForDeviceEvent(RNGestureHandlerStateChangeEvent.EVENT_NAME, data)
}
+
+ GestureHandler.ACTION_TYPE_NATIVE_DETECTOR, GestureHandler.ACTION_TYPE_NATIVE_DETECTOR_ANIMATED_EVENT -> {
+ val view = handler.view
+ if (view is RNGestureHandlerDetectorView) {
+ val event = RNGestureHandlerStateChangeEvent.obtain(
+ handler,
+ newState,
+ oldState,
+ handler.actionType,
+ handlerFactory.createEventBuilder(handler),
+ )
+ view.dispatchEvent(event)
+ }
+ }
}
}
@@ -147,7 +199,7 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
when (handler.actionType) {
GestureHandler.ACTION_TYPE_REANIMATED_WORKLET -> {
// Reanimated worklet
- val event = RNGestureHandlerTouchEvent.obtain(handler)
+ val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType)
sendEventForReanimated(event)
}
GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API -> {
@@ -155,6 +207,13 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
val data = RNGestureHandlerTouchEvent.createEventData(handler)
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
}
+ GestureHandler.ACTION_TYPE_NATIVE_DETECTOR, GestureHandler.ACTION_TYPE_NATIVE_DETECTOR_ANIMATED_EVENT -> {
+ val view = handler.view
+ if (view is RNGestureHandlerDetectorView) {
+ val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType)
+ view.dispatchEvent(event)
+ }
+ }
}
}
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 8b781ac0ed..7a5f06c611 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
@@ -24,7 +24,7 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
NativeRNGestureHandlerModuleSpec(reactContext),
TurboModuleWithJSIBindings {
- val registry: RNGestureHandlerRegistry = RNGestureHandlerRegistry()
+ private val moduleId = nextModuleId++
private val eventDispatcher = RNGestureHandlerEventDispatcher(reactApplicationContext)
private val interactionManager = RNGestureHandlerInteractionManager()
private val roots: MutableList = ArrayList()
@@ -33,6 +33,12 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
@Suppress("unused")
private var mHybridData: HybridData = initHybrid()
private var uiRuntimeDecorated = false
+ private val registry: RNGestureHandlerRegistry
+ get() = registries[moduleId]!!
+
+ init {
+ registries[moduleId] = RNGestureHandlerRegistry()
+ }
override fun getName() = NAME
@@ -166,6 +172,7 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
}
}
}
+ registries.remove(moduleId)
invalidateNative()
super.invalidate()
}
@@ -198,6 +205,9 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
companion object {
const val NAME = "RNGestureHandlerModule"
+ private var nextModuleId = 0
+ val registries: MutableMap = mutableMapOf()
+
init {
SoLoader.loadLibrary("gesturehandler")
}
diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.kt
index bfc9c433d6..e9691c3b8b 100644
--- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.kt
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.kt
@@ -24,7 +24,7 @@ class RNGestureHandlerRegistry : GestureHandlerRegistry {
fun attachHandlerToView(handlerTag: Int, viewTag: Int, actionType: Int): Boolean {
val handler = handlers[handlerTag]
return handler?.let {
- detachHandler(handler)
+ detachHandlerInternal(handler)
handler.actionType = actionType
registerHandlerForViewWithTag(viewTag, handler)
true
@@ -48,7 +48,7 @@ class RNGestureHandlerRegistry : GestureHandlerRegistry {
}
@Synchronized
- private fun detachHandler(handler: GestureHandler) {
+ private fun detachHandlerInternal(handler: GestureHandler) {
val attachedToView = attachedTo[handler.tag]
if (attachedToView != null) {
attachedTo.remove(handler.tag)
@@ -71,10 +71,17 @@ class RNGestureHandlerRegistry : GestureHandlerRegistry {
}
}
+ @Synchronized
+ fun detachHandler(handlerTag: Int) {
+ handlers[handlerTag]?.let {
+ detachHandlerInternal(it)
+ }
+ }
+
@Synchronized
fun dropHandler(handlerTag: Int) {
handlers[handlerTag]?.let {
- detachHandler(it)
+ detachHandlerInternal(it)
handlers.remove(handlerTag)
}
}
diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
index d8f8fd63c2..bfaa88a1d9 100644
--- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt
@@ -14,7 +14,7 @@ import com.facebook.react.uimanager.ThemedReactContext
import com.swmansion.gesturehandler.core.GestureHandler
import com.swmansion.gesturehandler.core.GestureHandlerOrchestrator
-class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: ViewGroup) {
+class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: ViewGroup, private val moduleId: Int) {
private val orchestrator: GestureHandlerOrchestrator?
private val jsGestureHandler: GestureHandler?
val rootView: ViewGroup
@@ -22,11 +22,13 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView:
private var passingTouch = false
init {
+ val registry =
+ RNGestureHandlerModule.registries[moduleId] ?: throw Exception("Tried to access a non-existent registry")
+
UiThreadUtil.assertOnUiThread()
val wrappedViewTag = wrappedView.id
assert(wrappedViewTag >= 1) { "Expect view tag to be set for $wrappedView" }
val module = context.getNativeModule(RNGestureHandlerModule::class.java)!!
- val registry = module.registry
rootView = findRootViewTag(wrappedView)
Log.i(
ReactConstants.TAG,
@@ -46,6 +48,9 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView:
}
fun tearDown() {
+ val registry =
+ RNGestureHandlerModule.registries[moduleId] ?: throw Exception("Tried to access a non-existent registry")
+
Log.i(
ReactConstants.TAG,
"[GESTURE HANDLER] Tearing down gesture handler registered for root view $rootView",
diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt
index ebce5f9fa3..96f225a9e9 100644
--- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt
@@ -12,6 +12,7 @@ import com.facebook.react.uimanager.RootView
import com.facebook.react.views.view.ReactViewGroup
class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) {
+ private var moduleId: Int = -1
private var rootViewEnabled = false
private var rootHelper: RNGestureHandlerRootHelper? = null // TODO: resettable lateinit
override fun onAttachedToWindow() {
@@ -24,10 +25,14 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) {
)
}
if (rootViewEnabled && rootHelper == null) {
- rootHelper = RNGestureHandlerRootHelper(context as ReactContext, this)
+ rootHelper = RNGestureHandlerRootHelper(context as ReactContext, this, moduleId)
}
}
+ fun setModuleId(id: Int) {
+ this.moduleId = id
+ }
+
fun tearDown() {
rootHelper?.tearDown()
}
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 381d31d3ab..c8eedb803a 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
@@ -32,6 +32,10 @@ class RNGestureHandlerRootViewManager :
view.tearDown()
}
+ override fun setModuleId(view: RNGestureHandlerRootView, value: Int) {
+ view.setModuleId(value)
+ }
+
/**
* The following event configuration is necessary even if you are not using
* GestureHandlerRootView component directly.
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/RNGestureHandlerStateChangeEvent.kt
index 77d5fa7930..dced0bcd76 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/RNGestureHandlerStateChangeEvent.kt
@@ -18,11 +18,13 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event? = 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 fun init(
handler: T,
newState: Int,
oldState: Int,
+ actionType: Int,
dataBuilder: GestureHandlerEventDataBuilder,
) {
val view = handler.view!!
@@ -30,6 +32,7 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event,
): RNGestureHandlerStateChangeEvent = (
EVENTS_POOL.acquire()
?: RNGestureHandlerStateChangeEvent()
).apply {
- init(handler, newState, oldState, dataBuilder)
+ init(handler, newState, oldState, actionType, dataBuilder)
}
fun createEventData(dataBuilder: GestureHandlerEventDataBuilder<*>, newState: Int, oldState: Int): WritableMap =
Arguments.createMap().apply {
dataBuilder.buildEventData(this)
+ putInt("handlerTag", dataBuilder.handlerTag)
putInt("state", newState)
putInt("oldState", oldState)
}
+
+ fun createNativeEventData(
+ dataBuilder: GestureHandlerEventDataBuilder<*>,
+ newState: Int,
+ oldState: Int,
+ ): WritableMap = Arguments.createMap().apply {
+ putMap(
+ "handlerData",
+ Arguments.createMap().apply {
+ dataBuilder.buildEventData(this)
+ },
+ )
+ putInt("handlerTag", dataBuilder.handlerTag)
+ putInt("state", newState)
+ putInt("oldState", oldState)
+ }
}
}
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/RNGestureHandlerTouchEvent.kt
index 69876ecd48..362342fed4 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/RNGestureHandlerTouchEvent.kt
@@ -10,11 +10,14 @@ import com.swmansion.gesturehandler.core.GestureHandler
class RNGestureHandlerTouchEvent private constructor() : Event() {
private var extraData: WritableMap? = null
private var coalescingKey: Short = 0
- private fun init(handler: T) {
+ private var actionType = GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API
+
+ private fun init(handler: T, actionType: Int) {
val view = handler.view!!
super.init(UIManagerHelper.getSurfaceId(view), view.id)
extraData = createEventData(handler)
coalescingKey = handler.eventCoalescingKey
+ this.actionType = actionType
}
override fun onDispose() {
@@ -22,7 +25,14 @@ class RNGestureHandlerTouchEvent private constructor() : Event(
TOUCH_EVENTS_POOL_SIZE,
)
- fun obtain(handler: T): RNGestureHandlerTouchEvent =
+ fun obtain(handler: T, actionType: Int): RNGestureHandlerTouchEvent =
(EVENTS_POOL.acquire() ?: RNGestureHandlerTouchEvent()).apply {
- init(handler)
+ init(handler, actionType)
}
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/GestureHandlerEventDataBuilder.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/eventbuilders/GestureHandlerEventDataBuilder.kt
index 007cefc230..51c1e1355a 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/eventbuilders/GestureHandlerEventDataBuilder.kt
@@ -4,15 +4,13 @@ import com.facebook.react.bridge.WritableMap
import com.swmansion.gesturehandler.core.GestureHandler
abstract class GestureHandlerEventDataBuilder(handler: T) {
- private val handlerTag: Int = handler.tag
- private val state: Int = handler.state
+ public val handlerTag: Int = handler.tag
+ public val state: Int = handler.state
private val pointerType: Int = handler.pointerType
private val numberOfPointers: Int = handler.numberOfPointers
open fun buildEventData(eventData: WritableMap) {
eventData.putInt("numberOfPointers", numberOfPointers)
- eventData.putInt("handlerTag", handlerTag)
- eventData.putInt("state", state)
eventData.putInt("pointerType", pointerType)
}
}
diff --git a/packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt b/packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt
index 7b9566f4bd..a039a2fdb3 100644
--- a/packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt
+++ b/packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt
@@ -12,8 +12,8 @@ set(PACKAGE_NAME "gesturehandler")
set(RNGH_DIR "${CMAKE_SOURCE_DIR}/../../../../")
set(REACT_ANDROID_DIR "${REACT_NATIVE_DIR}/ReactAndroid")
-file(GLOB_RECURSE gesture_handler_SRCS CONFIGURE_DEPENDS "./*.cpp")
-file(GLOB_RECURSE gesture_handler_shared_SRCS CONFIGURE_DEPENDS "${RNGH_DIR}/shared/*.cpp")
+file(GLOB_RECURSE gesture_handler_SRCS CONFIGURE_DEPENDS ./*.cpp)
+file(GLOB_RECURSE gesture_handler_shared_SRCS CONFIGURE_DEPENDS "${RNGH_DIR}/shared/runtime/*.cpp")
include(${REACT_ANDROID_DIR}/cmake-utils/folly-flags.cmake)
add_compile_options(${folly_FLAGS})
@@ -28,7 +28,7 @@ target_include_directories(
${PACKAGE_NAME}
PUBLIC
"${CMAKE_SOURCE_DIR}"
- "${RNGH_DIR}/shared"
+ "${RNGH_DIR}/shared/runtime"
PRIVATE
"${REACT_NATIVE_DIR}/ReactCommon"
)
diff --git a/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp b/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp
index faa2f2cd18..476fe8dc3b 100644
--- a/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp
+++ b/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp
@@ -36,7 +36,7 @@ RNGestureHandlerModule::getBindingsInstallerCxx() {
[&, this](jsi::Runtime &runtime) {
this->rnRuntime_ = &runtime;
RNGHRuntimeDecorator::installRNRuntimeBindings(
- runtime, [&](int handlerTag, int state) {
+ runtime, getModuleId(), [&](int handlerTag, int state) {
setGestureState(handlerTag, state);
});
}));
@@ -53,7 +53,7 @@ void RNGestureHandlerModule::setGestureState(
bool RNGestureHandlerModule::decorateUIRuntime() {
return RNGHRuntimeDecorator::installUIRuntimeBindings(
- *rnRuntime_, [&](int handlerTag, int state) {
+ *rnRuntime_, getModuleId(), [&](int handlerTag, int state) {
this->setGestureState(handlerTag, state);
});
}
@@ -64,4 +64,14 @@ void RNGestureHandlerModule::invalidateNative() {
javaPart_ = nullptr;
}
+int RNGestureHandlerModule::getModuleId() {
+ auto jthis = javaPart_;
+
+ auto cls = jthis->getClass();
+ auto fieldId = cls->getField("moduleId");
+
+ jint value = jthis->getFieldValue(fieldId);
+ return value;
+}
+
} // namespace gesturehandler
diff --git a/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h b/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h
index 09749dac3b..d8e96afa2e 100644
--- a/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h
+++ b/packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h
@@ -27,8 +27,8 @@ class RNGestureHandlerModule : public jni::HybridClass {
jni::local_ref getBindingsInstallerCxx();
void setGestureState(const int handlerTag, const int state);
- void decorateRuntime(jsi::Runtime &runtime);
bool decorateUIRuntime();
void invalidateNative();
+ int getModuleId();
};
} // namespace gesturehandler
diff --git a/packages/react-native-gesture-handler/apple/Handlers/RNFlingHandler.m b/packages/react-native-gesture-handler/apple/Handlers/RNFlingHandler.m
index 554aba88e4..4e39548326 100644
--- a/packages/react-native-gesture-handler/apple/Handlers/RNFlingHandler.m
+++ b/packages/react-native-gesture-handler/apple/Handlers/RNFlingHandler.m
@@ -169,7 +169,8 @@ - (void)mouseDragged:(NSEvent *)event
double timeDelta = currentTime - startTime;
- Vector *velocityVector = [Vector fromVelocityX:(distance.x / timeDelta) withVelocityY:(distance.y / timeDelta)];
+ RNGHVector *velocityVector = [RNGHVector fromVelocityX:(distance.x / timeDelta)
+ withVelocityY:(distance.y / timeDelta)];
[self tryActivate:velocityVector];
}
@@ -184,7 +185,7 @@ - (void)mouseUp:(NSEvent *)event
self.state == NSGestureRecognizerStateChanged ? NSGestureRecognizerStateEnded : NSGestureRecognizerStateFailed;
}
-- (void)tryActivate:(Vector *)velocityVector
+- (void)tryActivate:(RNGHVector *)velocityVector
{
bool isAligned = NO;
@@ -217,9 +218,9 @@ - (void)tryActivate:(Vector *)velocityVector
- (BOOL)getAlignment:(RNGestureHandlerDirection)direction
withMinimalAlignmentCosine:(double)minimalAlignmentCosine
- withVelocityVector:(Vector *)velocityVector
+ withVelocityVector:(RNGHVector *)velocityVector
{
- Vector *directionVector = [Vector fromDirection:direction];
+ RNGHVector *directionVector = [RNGHVector fromDirection:direction];
return ((self.direction & direction) == direction) &&
[velocityVector isSimilar:directionVector withThreshold:minimalAlignmentCosine];
}
diff --git a/packages/react-native-gesture-handler/apple/RNGHVector.h b/packages/react-native-gesture-handler/apple/RNGHVector.h
index c120111470..d2b76e2f3d 100644
--- a/packages/react-native-gesture-handler/apple/RNGHVector.h
+++ b/packages/react-native-gesture-handler/apple/RNGHVector.h
@@ -10,7 +10,7 @@
#ifndef RNGHVector_h
#define RNGHVector_h
-@interface Vector : NSObject
+@interface RNGHVector : NSObject
@property (atomic, readonly, assign) double x;
@property (atomic, readonly, assign) double y;
@@ -18,11 +18,11 @@
@property (atomic, readonly, assign) double unitY;
@property (atomic, readonly, assign) double magnitude;
-+ (Vector *_Nonnull)fromDirection:(RNGestureHandlerDirection)direction;
-+ (Vector *_Nonnull)fromVelocityX:(double)vx withVelocityY:(double)vy;
++ (RNGHVector *_Nonnull)fromDirection:(RNGestureHandlerDirection)direction;
++ (RNGHVector *_Nonnull)fromVelocityX:(double)vx withVelocityY:(double)vy;
- (nonnull instancetype)initWithX:(double)x withY:(double)y;
-- (double)computeSimilarity:(Vector *_Nonnull)other;
-- (BOOL)isSimilar:(Vector *_Nonnull)other withThreshold:(double)threshold;
+- (double)computeSimilarity:(RNGHVector *_Nonnull)other;
+- (BOOL)isSimilar:(RNGHVector *_Nonnull)other withThreshold:(double)threshold;
@end
diff --git a/packages/react-native-gesture-handler/apple/RNGHVector.m b/packages/react-native-gesture-handler/apple/RNGHVector.m
index caf825fe24..27ccb7e62a 100644
--- a/packages/react-native-gesture-handler/apple/RNGHVector.m
+++ b/packages/react-native-gesture-handler/apple/RNGHVector.m
@@ -8,7 +8,7 @@
#import "RNGHVector.h"
#import
-@implementation Vector
+@implementation RNGHVector
- (id)initWithX:(double)x withY:(double)y
{
@@ -25,41 +25,41 @@ - (id)initWithX:(double)x withY:(double)y
return self;
}
-+ (Vector *)fromDirection:(RNGestureHandlerDirection)direction
++ (RNGHVector *)fromDirection:(RNGestureHandlerDirection)direction
{
switch (direction) {
case RNGestureHandlerDirectionRight:
- return [[Vector alloc] initWithX:1 withY:0];
+ return [[RNGHVector alloc] initWithX:1 withY:0];
case RNGestureHandlerDirectionLeft:
- return [[Vector alloc] initWithX:-1 withY:0];
+ return [[RNGHVector alloc] initWithX:-1 withY:0];
case RNGestureHandlerDirectionUp:
- return [[Vector alloc] initWithX:0 withY:1];
+ return [[RNGHVector alloc] initWithX:0 withY:1];
case RNGestureHandlerDirectionDown:
- return [[Vector alloc] initWithX:0 withY:-1];
+ return [[RNGHVector alloc] initWithX:0 withY:-1];
case RNGestureHandlerDirectionUpLeft:
- return [[Vector alloc] initWithX:-1 withY:1];
+ return [[RNGHVector alloc] initWithX:-1 withY:1];
case RNGestureHandlerDirectionUpRight:
- return [[Vector alloc] initWithX:1 withY:1];
+ return [[RNGHVector alloc] initWithX:1 withY:1];
case RNGestureHandlerDirectionDownLeft:
- return [[Vector alloc] initWithX:-1 withY:-1];
+ return [[RNGHVector alloc] initWithX:-1 withY:-1];
case RNGestureHandlerDirectionDownRight:
- return [[Vector alloc] initWithX:1 withY:-1];
+ return [[RNGHVector alloc] initWithX:1 withY:-1];
default:
- return [[Vector alloc] initWithX:0 withY:0];
+ return [[RNGHVector alloc] initWithX:0 withY:0];
}
}
-+ (Vector *)fromVelocityX:(double)vx withVelocityY:(double)vy;
++ (RNGHVector *)fromVelocityX:(double)vx withVelocityY:(double)vy;
{
- return [[Vector alloc] initWithX:vx withY:vy];
+ return [[RNGHVector alloc] initWithX:vx withY:vy];
}
-- (double)computeSimilarity:(Vector *)other
+- (double)computeSimilarity:(RNGHVector *)other
{
return self.unitX * other.unitX + self.unitY * other.unitY;
}
-- (BOOL)isSimilar:(Vector *)other withThreshold:(double)threshold
+- (BOOL)isSimilar:(RNGHVector *)other withThreshold:(double)threshold
{
return [self computeSimilarity:other] > threshold;
}
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.h b/packages/react-native-gesture-handler/apple/RNGestureHandler.h
index b8e021d4db..276f1fdb09 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandler.h
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.h
@@ -38,7 +38,11 @@
@protocol RNGestureHandlerEventEmitter
-- (void)sendEvent:(nonnull RNGestureHandlerStateChange *)event withActionType:(RNGestureHandlerActionType)actionType;
+- (void)sendEvent:(nonnull RNGestureHandlerStateChange *)event
+ withActionType:(RNGestureHandlerActionType)actionType
+ forRecognizer:(UIGestureRecognizer *)recognizer;
+
+- (void)sendNativeTouchEventForGestureHandler:(RNGestureHandler *)handler withPointerType:(NSInteger)pointerType;
@end
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm
index 708172f47a..bc36fa8951 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm
@@ -287,7 +287,8 @@ - (void)handleGesture:(UIGestureRecognizer *)recognizer
// it may happen that the gesture recognizer is reset after it's been unbound from the view,
// it that recognizer tried to send event, the app would crash because the target of the event
// would be nil.
- if (view.reactTag == nil) {
+ if (view.reactTag == nil && _actionType != RNGestureHandlerActionTypeNativeDetector &&
+ _actionType != RNGestureHandlerActionTypeNativeDetectorAnimatedEvent) {
return;
}
@@ -298,10 +299,17 @@ - (void)handleGesture:(UIGestureRecognizer *)recognizer
- (void)handleGesture:(UIGestureRecognizer *)recognizer inState:(RNGestureHandlerState)state
{
_state = state;
+
RNGestureHandlerEventExtraData *eventData = [self eventExtraData:recognizer];
- RNGHUIView *view = [self chooseViewForInteraction:recognizer];
- [self sendEventsInState:self.state forViewWithTag:view.reactTag withExtraData:eventData];
+ NSNumber *tag = [self chooseViewForInteraction:recognizer].reactTag;
+ if (tag == nil &&
+ (_actionType == RNGestureHandlerActionTypeNativeDetector ||
+ _actionType == RNGestureHandlerActionTypeNativeDetectorAnimatedEvent)) {
+ tag = @(recognizer.view.tag);
+ }
+
+ [self sendEventsInState:self.state forViewWithTag:tag withExtraData:eventData];
}
- (void)sendEventsInState:(RNGestureHandlerState)state
@@ -355,6 +363,7 @@ - (void)sendEventsInState:(RNGestureHandlerState)state
handlerTag:_tag
state:state
extraData:extraData
+ forActionType:_actionType
coalescingKey:self->_eventCoalescingKey];
[self sendEvent:touchEvent];
}
@@ -362,23 +371,28 @@ - (void)sendEventsInState:(RNGestureHandlerState)state
- (void)sendEvent:(RNGestureHandlerStateChange *)event
{
- [self.emitter sendEvent:event withActionType:self.actionType];
+ [self.emitter sendEvent:event withActionType:self.actionType forRecognizer:self.recognizer];
}
- (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(NSNumber *)reactTag
{
- 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
- coalescingKey:[_tag intValue]];
-
- [self.emitter sendEvent:event withActionType:self.actionType];
+ if (self.actionType == RNGestureHandlerActionTypeNativeDetector) {
+ [self.emitter sendNativeTouchEventForGestureHandler:self withPointerType:_pointerType];
+ } 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
+ forActionType:_actionType
+ coalescingKey:[_tag intValue]];
+
+ [self.emitter sendEvent:event withActionType:self.actionType forRecognizer:self.recognizer];
+ }
}
- (RNGestureHandlerState)recognizerState
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerActionType.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerActionType.h
index 26adddc9ab..236fcdb166 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerActionType.h
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerActionType.h
@@ -7,4 +7,6 @@ typedef NS_ENUM(NSInteger, RNGestureHandlerActionType) {
// RNGH API
RNGestureHandlerActionTypeJSFunctionNewAPI, // JS function or Animated.event with useNativeDriver: false using new
// RNGH API
+ RNGestureHandlerActionTypeNativeDetector,
+ RNGestureHandlerActionTypeNativeDetectorAnimatedEvent,
};
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.h
new file mode 100644
index 0000000000..48ab0551e8
--- /dev/null
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.h
@@ -0,0 +1,25 @@
+#if !TARGET_OS_OSX
+#import
+#else
+#import
+#endif
+
+#import
+
+#import
+
+using namespace facebook::react;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RNGestureHandlerDetector : RCTViewComponentView
+
+- (void)dispatchStateChangeEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerStateChange)event;
+
+- (void)dispatchGestureEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerEvent)event;
+
+- (void)dispatchTouchEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerTouchEvent)event;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm
new file mode 100644
index 0000000000..5e47ea7a50
--- /dev/null
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm
@@ -0,0 +1,147 @@
+#import "RNGestureHandlerDetector.h"
+#import "RNGestureHandlerDetectorComponentDescriptor.h"
+#import "RNGestureHandlerModule.h"
+
+#import
+#import
+
+#import
+#import
+#import
+
+#include
+
+@interface RNGestureHandlerDetector ()
+@end
+
+typedef NS_ENUM(NSInteger, RNGestureHandlerMutation) {
+ RNGestureHandlerMutationAttach = 1,
+ RNGestureHandlerMutationDetach,
+ RNGestureHandlerMutationKeep,
+};
+
+@implementation RNGestureHandlerDetector {
+ int _moduleId;
+}
+
+#if TARGET_OS_OSX
++ (BOOL)shouldBeRecycled
+{
+ return NO;
+}
+#endif
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ if (self = [super initWithFrame:frame]) {
+ static const auto defaultProps = std::make_shared();
+ _props = defaultProps;
+ _moduleId = -1;
+ }
+
+ return self;
+}
+
+// TODO: I'm not sure whether this is the correct place for cleanup
+// Possibly allowing recycling and doing this in prepareForRecycle would be better
+- (void)willMoveToWindow:(UIWindow *)newWindow
+{
+ if (newWindow == nil) {
+ RNGestureHandlerManager *handlerManager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId];
+ react_native_assert(handlerManager != nullptr && "Tried to access a non-existent handler manager")
+ const auto &props = *std::static_pointer_cast(_props);
+
+ for (const auto handler : props.handlerTags) {
+ NSNumber *handlerTag = [NSNumber numberWithInt:handler];
+ [handlerManager.registry detachHandlerWithTag:handlerTag];
+ }
+ }
+}
+
+- (void)dispatchStateChangeEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerStateChange)event
+{
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(_eventEmitter)
+ ->onGestureHandlerStateChange(event);
+ }
+}
+
+- (void)dispatchGestureEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerEvent)event
+{
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(_eventEmitter)->onGestureHandlerEvent(event);
+ }
+}
+
+- (void)dispatchTouchEvent:(RNGestureHandlerDetectorEventEmitter::OnGestureHandlerTouchEvent)event
+{
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(_eventEmitter)
+ ->onGestureHandlerTouchEvent(event);
+ }
+}
+
+#pragma mark - RCTComponentViewProtocol
+
++ (ComponentDescriptorProvider)componentDescriptorProvider
+{
+ return concreteComponentDescriptorProvider();
+}
+
+- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
+ oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
+{
+ auto newLayoutMetrics = layoutMetrics;
+ // Override to force hittesting to work outside bounds
+ newLayoutMetrics.overflowInset = {.left = 1, .right = 1, .top = 1, .bottom = 1};
+
+ [super updateLayoutMetrics:newLayoutMetrics oldLayoutMetrics:oldLayoutMetrics];
+}
+
+- (void)updateProps:(const Props::Shared &)propsBase oldProps:(const Props::Shared &)oldPropsBase
+{
+ const auto &newProps = *std::static_pointer_cast(propsBase);
+ const auto &oldProps = *std::static_pointer_cast(oldPropsBase);
+
+ _moduleId = newProps.moduleId;
+ RNGestureHandlerManager *handlerManager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId];
+ react_native_assert(handlerManager != nullptr && "Tried to access a non-existent handler manager")
+
+ std::unordered_map
+ changes;
+
+ if (oldPropsBase != nullptr) {
+ for (const auto oldHandler : oldProps.handlerTags) {
+ changes[oldHandler] = RNGestureHandlerMutationDetach;
+ }
+ }
+
+ for (const auto newHandler : newProps.handlerTags) {
+ changes[newHandler] = changes.contains(newHandler) ? RNGestureHandlerMutationKeep : RNGestureHandlerMutationAttach;
+ }
+
+ for (const auto handlerChange : changes) {
+ NSNumber *handlerTag = [NSNumber numberWithInt:handlerChange.first];
+
+ if (handlerChange.second == RNGestureHandlerMutationAttach) {
+ // TODO: Attach to the child when attached gesture is a NativeGestureHandler, track children changes then
+ [handlerManager.registry
+ attachHandlerWithTag:handlerTag
+ toView:self
+ withActionType:newProps.dispatchesAnimatedEvents ? RNGestureHandlerActionTypeNativeDetectorAnimatedEvent
+ : RNGestureHandlerActionTypeNativeDetector];
+ } else if (handlerChange.second == RNGestureHandlerMutationDetach) {
+ [handlerManager.registry detachHandlerWithTag:handlerTag];
+ }
+ }
+
+ [super updateProps:propsBase oldProps:oldPropsBase];
+ // Override to force hittesting to work outside bounds
+ self.clipsToBounds = NO;
+}
+@end
+
+Class RNGestureHandlerDetectorCls(void)
+{
+ return RNGestureHandlerDetector.class;
+}
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h
index 6b7bfa2089..d6d6e09190 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.h
@@ -5,11 +5,12 @@
#import "RNGHStylusData.h"
#import "RNGHTouchEventType.h"
#import "RNGHUIKit.h"
+#import "RNGestureHandlerActionType.h"
#import "RNGestureHandlerState.h"
@interface RNGestureHandlerEventExtraData : NSObject
-@property (readonly) NSDictionary *data;
+@property (readonly) NSDictionary *data;
- (instancetype)initWithData:(NSDictionary *)data;
@@ -57,16 +58,28 @@
@interface RNGestureHandlerEvent : NSObject
+@property (nonatomic, strong, readonly) NSNumber *reactTag;
+@property (nonatomic, strong, readonly) NSNumber *handlerTag;
+@property (nonatomic, strong, readonly) RNGestureHandlerEventExtraData *extraData;
+@property (nonatomic, readonly) RNGestureHandlerState state;
+
- (instancetype)initWithReactTag:(NSNumber *)reactTag
handlerTag:(NSNumber *)handlerTag
state:(RNGestureHandlerState)state
extraData:(RNGestureHandlerEventExtraData *)extraData
+ forActionType:(NSInteger)actionType
coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER;
@end
@interface RNGestureHandlerStateChange : NSObject
+@property (nonatomic, strong, readonly) NSNumber *reactTag;
+@property (nonatomic, strong, readonly) NSNumber *handlerTag;
+@property (nonatomic, strong, readonly) RNGestureHandlerEventExtraData *extraData;
+@property (nonatomic, readonly) RNGestureHandlerState state;
+@property (nonatomic, readonly) RNGestureHandlerState previousState;
+
- (instancetype)initWithReactTag:(NSNumber *)reactTag
handlerTag:(NSNumber *)handlerTag
state:(RNGestureHandlerState)state
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.m b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm
similarity index 87%
rename from packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.m
rename to packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm
index 4d01cf1010..2379928218 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.m
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerEvents.mm
@@ -165,9 +165,7 @@ + (RNGestureHandlerEventExtraData *)forPointerInside:(BOOL)pointerInside withPoi
@end
@implementation RNGestureHandlerEvent {
- NSNumber *_handlerTag;
- RNGestureHandlerState _state;
- RNGestureHandlerEventExtraData *_extraData;
+ NSInteger _actionType;
}
@synthesize viewTag = _viewTag;
@@ -177,6 +175,7 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag
handlerTag:(NSNumber *)handlerTag
state:(RNGestureHandlerState)state
extraData:(RNGestureHandlerEventExtraData *)extraData
+ forActionType:(NSInteger)actionType
coalescingKey:(uint16_t)coalescingKey
{
if ((self = [super init])) {
@@ -185,6 +184,7 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag
_state = state;
_extraData = extraData;
_coalescingKey = coalescingKey;
+ _actionType = actionType;
}
return self;
}
@@ -193,7 +193,8 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag
- (NSString *)eventName
{
- return @"onGestureHandlerEvent";
+ return _actionType == RNGestureHandlerActionTypeNativeDetectorAnimatedEvent ? @"onGestureHandlerAnimatedEvent"
+ : @"onGestureHandlerEvent";
}
- (BOOL)canCoalesce
@@ -213,21 +214,25 @@ + (NSString *)moduleDotMethod
- (NSArray *)arguments
{
- NSMutableDictionary *body = [NSMutableDictionary dictionaryWithDictionary:_extraData.data];
- [body setObject:_viewTag forKey:@"target"];
- [body setObject:_handlerTag forKey:@"handlerTag"];
- [body setObject:@(_state) forKey:@"state"];
- return @[ self.viewTag, @"onGestureHandlerEvent", body ];
+ if (_actionType == RNGestureHandlerActionTypeNativeDetectorAnimatedEvent) {
+ NSMutableDictionary *body = [[NSMutableDictionary alloc] init];
+ [body setObject:_viewTag forKey:@"target"];
+ [body setObject:_handlerTag forKey:@"handlerTag"];
+ [body setObject:@(_state) forKey:@"state"];
+ [body setObject:_extraData.data forKey:@"handlerData"];
+ return @[ self.viewTag, @"onGestureHandlerAnimatedEvent", body ];
+ } else {
+ NSMutableDictionary *body = [NSMutableDictionary dictionaryWithDictionary:_extraData.data];
+ [body setObject:_viewTag forKey:@"target"];
+ [body setObject:_handlerTag forKey:@"handlerTag"];
+ [body setObject:@(_state) forKey:@"state"];
+ return @[ self.viewTag, @"onGestureHandlerEvent", body ];
+ }
}
@end
-@implementation RNGestureHandlerStateChange {
- NSNumber *_handlerTag;
- RNGestureHandlerState _state;
- RNGestureHandlerState _prevState;
- RNGestureHandlerEventExtraData *_extraData;
-}
+@implementation RNGestureHandlerStateChange
@synthesize viewTag = _viewTag;
@synthesize coalescingKey = _coalescingKey;
@@ -243,7 +248,7 @@ - (instancetype)initWithReactTag:(NSNumber *)reactTag
_viewTag = reactTag;
_handlerTag = handlerTag;
_state = state;
- _prevState = prevState;
+ _previousState = prevState;
_extraData = extraData;
_coalescingKey = coalescingKey++;
}
@@ -279,7 +284,7 @@ - (NSArray *)arguments
[body setObject:_viewTag forKey:@"target"];
[body setObject:_handlerTag forKey:@"handlerTag"];
[body setObject:@(_state) forKey:@"state"];
- [body setObject:@(_prevState) forKey:@"oldState"];
+ [body setObject:@(_previousState) forKey:@"oldState"];
return @[ self.viewTag, @"onGestureHandlerStateChange", body ];
}
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h
index a191817419..7f3d9ceaef 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h
@@ -3,6 +3,8 @@
#import
#import "RNGestureHandler.h"
+#import "RNGestureHandlerDetector.h"
+#import "RNGestureHandlerRegistry.h"
@class RCTUIManager;
@class RCTEventDispatcher;
@@ -10,6 +12,8 @@
@interface RNGestureHandlerManager : NSObject
#ifdef RCT_NEW_ARCH_ENABLED
+@property (nonatomic, strong, readonly, nonnull) RNGestureHandlerRegistry *registry;
+
- (nonnull instancetype)initWithModuleRegistry:(nonnull RCTModuleRegistry *)moduleRegistry
viewRegistry:(nonnull RCTViewRegistry *)viewRegistry;
#else
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm
index 7c4821410a..327ded2000 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm
@@ -11,7 +11,7 @@
#import "RNGestureHandler.h"
#import "RNGestureHandlerActionType.h"
-#import "RNGestureHandlerRegistry.h"
+#import "RNGestureHandlerNativeEventUtils.h"
#import "RNGestureHandlerState.h"
#import "RNRootViewGestureRecognizer.h"
@@ -63,6 +63,11 @@ @implementation RNGestureHandlerManager {
}
#ifdef RCT_NEW_ARCH_ENABLED
+- (RNGestureHandlerRegistry *)registry
+{
+ return _registry;
+}
+
- (instancetype)initWithModuleRegistry:(RCTModuleRegistry *)moduleRegistry viewRegistry:(RCTViewRegistry *)viewRegistry
{
if ((self = [super init])) {
@@ -347,9 +352,29 @@ - (void)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
#pragma mark Events
-- (void)sendEvent:(RNGestureHandlerStateChange *)event withActionType:(RNGestureHandlerActionType)actionType
+- (void)sendEvent:(RNGestureHandlerStateChange *)event
+ withActionType:(RNGestureHandlerActionType)actionType
+ forRecognizer:(UIGestureRecognizer *)recognizer
{
switch (actionType) {
+ case RNGestureHandlerActionTypeNativeDetector:
+ case RNGestureHandlerActionTypeNativeDetectorAnimatedEvent: {
+ RNGestureHandlerDetector *detector = (RNGestureHandlerDetector *)recognizer.view;
+ if ([event isKindOfClass:[RNGestureHandlerEvent class]]) {
+ if (actionType == RNGestureHandlerActionTypeNativeDetectorAnimatedEvent) {
+ [self sendEventForNativeAnimatedEvent:event];
+ }
+
+ RNGestureHandlerEvent *gestureEvent = (RNGestureHandlerEvent *)event;
+ auto nativeEvent = [gestureEvent getNativeEvent];
+ [detector dispatchGestureEvent:nativeEvent];
+ } else {
+ auto nativeEvent = [event getNativeEvent];
+ [detector dispatchStateChangeEvent:nativeEvent];
+ }
+ break;
+ }
+
case RNGestureHandlerActionTypeReanimatedWorklet:
[self sendEventForReanimated:event];
break;
@@ -375,6 +400,42 @@ - (void)sendEvent:(RNGestureHandlerStateChange *)event withActionType:(RNGesture
}
}
+- (void)sendNativeTouchEventForGestureHandler:(RNGestureHandler *)handler withPointerType:(NSInteger)pointerType
+{
+ 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],
+ });
+ }
+
+ 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],
+ });
+ }
+
+ RNGestureHandlerDetector *detector = (RNGestureHandlerDetector *)handler.recognizer.view;
+ [detector dispatchTouchEvent:nativeEvent];
+}
+
- (void)sendEventForReanimated:(RNGestureHandlerStateChange *)event
{
// Delivers the event to Reanimated.
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.h
index fc943b78e0..684e4df6e5 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.h
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.h
@@ -9,9 +9,13 @@
#import
#endif
+#import "RNGestureHandlerManager.h"
+
@interface RNGestureHandlerModule : RCTEventEmitter
#ifdef RCT_NEW_ARCH_ENABLED
+
++ (RNGestureHandlerManager *)handlerManagerForModuleId:(int)moduleId;
#else
#endif
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm
index 5d19446a44..16e227dd98 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm
@@ -20,7 +20,6 @@
#import "RNGestureHandler.h"
#import "RNGestureHandlerDirection.h"
-#import "RNGestureHandlerManager.h"
#import "RNGestureHandlerState.h"
#import "RNGestureHandlerButton.h"
@@ -30,9 +29,7 @@
using namespace gesturehandler;
using namespace facebook;
-#ifdef RCT_NEW_ARCH_ENABLED
using namespace react;
-#endif // RCT_NEW_ARCH_ENABLED
#ifdef RCT_NEW_ARCH_ENABLED
@interface RNGestureHandlerModule ()
@@ -47,12 +44,11 @@ @interface RNGestureHandlerModule ()
typedef void (^GestureHandlerOperation)(RNGestureHandlerManager *manager);
@implementation RNGestureHandlerModule {
- RNGestureHandlerManager *_manager;
-
// Oparations called after views have been updated.
NSMutableArray *_operations;
jsi::Runtime *_rnRuntime;
+ int _moduleId;
bool _checkedIfReanimatedIsAvailable;
bool _isReanimatedAvailable;
@@ -61,6 +57,14 @@ @implementation RNGestureHandlerModule {
#ifdef RCT_NEW_ARCH_ENABLED
@synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED;
+
+static std::unordered_map _managers;
+
++ (RNGestureHandlerManager *)handlerManagerForModuleId:(int)moduleId
+{
+ return _managers[moduleId];
+}
+
#endif // RCT_NEW_ARCH_ENABLED
RCT_EXPORT_MODULE()
@@ -72,12 +76,12 @@ + (BOOL)requiresMainQueueSetup
- (void)invalidate
{
- RNGestureHandlerManager *handlerManager = _manager;
+ RNGestureHandlerManager *handlerManager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId];
dispatch_async(dispatch_get_main_queue(), ^{
[handlerManager dropAllGestureHandlers];
});
- _manager = nil;
+ _managers[_moduleId] = nullptr;
#ifndef RCT_NEW_ARCH_ENABLED
[self.bridge.uiManager.observerCoordinator removeObserver:self];
@@ -102,7 +106,7 @@ - (void)installJSIBindingsWithRuntime:(jsi::Runtime &)rnRuntime
_rnRuntime = &rnRuntime;
__weak RNGestureHandlerModule *weakSelf = self;
- RNGHRuntimeDecorator::installRNRuntimeBindings(rnRuntime, [weakSelf](int handlerTag, int state) {
+ RNGHRuntimeDecorator::installRNRuntimeBindings(rnRuntime, _moduleId, [weakSelf](int handlerTag, int state) {
RNGestureHandlerModule *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf setGestureState:state forHandler:handlerTag];
@@ -114,8 +118,10 @@ - (void)installJSIBindingsWithRuntime:(jsi::Runtime &)rnRuntime
#ifdef RCT_NEW_ARCH_ENABLED
- (void)initialize
{
- _manager = [[RNGestureHandlerManager alloc] initWithModuleRegistry:self.moduleRegistry
- viewRegistry:_viewRegistry_DEPRECATED];
+ static int nextModuleId = 0;
+ _moduleId = nextModuleId++;
+ _managers[_moduleId] = [[RNGestureHandlerManager alloc] initWithModuleRegistry:self.moduleRegistry
+ viewRegistry:_viewRegistry_DEPRECATED];
_operations = [NSMutableArray new];
}
#else
@@ -135,7 +141,7 @@ - (bool)installUIRuntimeBindings
{
__weak RNGestureHandlerModule *weakSelf = self;
- return RNGHRuntimeDecorator::installUIRuntimeBindings(*_rnRuntime, [weakSelf](int handlerTag, int state) {
+ return RNGHRuntimeDecorator::installUIRuntimeBindings(*_rnRuntime, _moduleId, [weakSelf](int handlerTag, int state) {
RNGestureHandlerModule *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf setGestureState:state forHandler:handlerTag];
@@ -206,12 +212,13 @@ - (void)flushOperations
return;
}
+ RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId];
NSArray *operations = _operations;
_operations = [NSMutableArray new];
[self.viewRegistry_DEPRECATED addUIBlock:^(RCTViewRegistry *viewRegistry) {
for (GestureHandlerOperation operation in operations) {
- operation(self->_manager);
+ operation(manager);
}
}];
#endif // RCT_NEW_ARCH_ENABLED
@@ -231,7 +238,8 @@ - (void)setGestureState:(int)state forHandler:(int)handlerTag
- (void)setGestureStateSync:(int)state forHandler:(int)handlerTag
{
RCTAssertMainQueue();
- RNGestureHandler *handler = [_manager handlerWithTag:@(handlerTag)];
+ RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId];
+ RNGestureHandler *handler = [manager handlerWithTag:@(handlerTag)];
if (handler != nil) {
if (state == 1) { // FAILED
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.h
new file mode 100644
index 0000000000..21543720a3
--- /dev/null
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.h
@@ -0,0 +1,15 @@
+#import
+
+#import "RNGestureHandlerEvents.h"
+
+@interface RNGestureHandlerEvent (NativeEvent)
+
+- (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerEvent)getNativeEvent;
+
+@end
+
+@interface RNGestureHandlerStateChange (NativeEvent)
+
+- (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerStateChange)getNativeEvent;
+
+@end
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.mm
new file mode 100644
index 0000000000..feea2e7785
--- /dev/null
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerNativeEventUtils.mm
@@ -0,0 +1,88 @@
+//
+// RNGestureHandlerNativeEventUtils.cpp
+// RNGestureHandler
+//
+// Created by Jakub Piasecki on 02/07/2025.
+//
+
+#include "RNGestureHandlerNativeEventUtils.h"
+
+static folly::dynamic rngh_dynamicFromId(id value);
+
+static folly::dynamic rngh_dynamicFromDictionary(NSDictionary *dictionary)
+{
+ folly::dynamic data = folly::dynamic::object;
+
+ for (NSString *key in dictionary) {
+ id value = dictionary[key];
+ std::string cppKey = [key UTF8String];
+ data[cppKey] = rngh_dynamicFromId(value);
+ }
+
+ return data;
+}
+
+folly::dynamic rngh_dynamicFromArray(NSArray *array)
+{
+ folly::dynamic result = folly::dynamic::array;
+
+ for (id value in array) {
+ result.push_back(rngh_dynamicFromId(value));
+ }
+
+ return result;
+}
+
+static folly::dynamic rngh_dynamicFromId(id value)
+{
+ if ([value isKindOfClass:[NSNumber class]]) {
+ return [(NSNumber *)value doubleValue];
+ }
+
+ if ([value isKindOfClass:[NSArray class]]) {
+ return rngh_dynamicFromArray((NSArray *)value);
+ }
+
+ if ([value isKindOfClass:[NSDictionary class]]) {
+ return rngh_dynamicFromDictionary((NSDictionary *)value);
+ }
+
+ @throw [NSException exceptionWithName:@"FailedToBuildEventException"
+ reason:@"Encountered a unknown value type"
+ userInfo:nil];
+}
+
+@implementation RNGestureHandlerEvent (NativeEvent)
+
+- (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerEvent)getNativeEvent
+{
+ folly::dynamic handlerData = rngh_dynamicFromId(self.extraData.data);
+
+ facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerEvent nativeEvent = {
+ .handlerTag = [self.handlerTag intValue],
+ .state = static_cast(self.state),
+ .handlerData = handlerData,
+ };
+
+ return nativeEvent;
+}
+
+@end
+
+@implementation RNGestureHandlerStateChange (NativeEvent)
+
+- (facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerStateChange)getNativeEvent
+{
+ folly::dynamic handlerData = rngh_dynamicFromId(self.extraData.data);
+
+ facebook::react::RNGestureHandlerDetectorEventEmitter::OnGestureHandlerStateChange nativeEvent = {
+ .handlerTag = [self.handlerTag intValue],
+ .state = static_cast(self.state),
+ .oldState = static_cast(self.previousState),
+ .handlerData = handlerData,
+ };
+
+ return nativeEvent;
+}
+
+@end
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerTracker.m b/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerTracker.mm
similarity index 96%
rename from packages/react-native-gesture-handler/apple/RNGestureHandlerPointerTracker.m
rename to packages/react-native-gesture-handler/apple/RNGestureHandlerPointerTracker.mm
index 2bef5e5d40..6008595388 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerTracker.m
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerPointerTracker.mm
@@ -1,5 +1,6 @@
#import "RNGestureHandlerPointerTracker.h"
#import "RNGestureHandler.h"
+#import "RNGestureHandlerDetector.h"
#import
@@ -239,7 +240,9 @@ - (void)sendEvent
// it may happen that the gesture recognizer is reset after it's been unbound from the view,
// it that recognizer tried to send event, the app would crash because the target of the event
// would be nil.
- if (!_gestureHandler.needsPointerData || _gestureHandler.recognizer.view.reactTag == nil) {
+ if (!_gestureHandler.needsPointerData ||
+ (_gestureHandler.recognizer.view.reactTag == nil &&
+ ![_gestureHandler.recognizer.view isKindOfClass:[RNGestureHandlerDetector class]])) {
return;
}
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.h
index 5e86c07973..dd73a1ce89 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.h
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.h
@@ -15,6 +15,7 @@
- (void)attachHandlerWithTag:(nonnull NSNumber *)handlerTag
toView:(nonnull RNGHUIView *)view
withActionType:(RNGestureHandlerActionType)actionType;
+- (void)detachHandlerWithTag:(nonnull NSNumber *)handlerTag;
- (void)dropHandlerWithTag:(nonnull NSNumber *)handlerTag;
- (void)dropAllHandlers;
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.m b/packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.m
index 197e0409d6..856c568e53 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.m
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerRegistry.m
@@ -43,6 +43,12 @@ - (void)attachHandlerWithTag:(NSNumber *)handlerTag
[handler bindToView:view];
}
+- (void)detachHandlerWithTag:(NSNumber *)handlerTag
+{
+ RNGestureHandler *handler = _handlers[handlerTag];
+ [handler unbindFromView];
+}
+
- (void)dropHandlerWithTag:(NSNumber *)handlerTag
{
RNGestureHandler *handler = _handlers[handlerTag];
diff --git a/packages/react-native-gesture-handler/package.json b/packages/react-native-gesture-handler/package.json
index 56ecd61260..be74ea7351 100644
--- a/packages/react-native-gesture-handler/package.json
+++ b/packages/react-native-gesture-handler/package.json
@@ -55,7 +55,8 @@
"ReanimatedDrawerLayout/",
"README.md",
"jestSetup.js",
- "RNGestureHandler.podspec"
+ "RNGestureHandler.podspec",
+ "react-native.config.js"
],
"repository": {
"type": "git",
@@ -135,7 +136,8 @@
},
"ios": {
"componentProvider": {
- "RNGestureHandlerButton": "RNGestureHandlerButtonComponentView"
+ "RNGestureHandlerButton": "RNGestureHandlerButtonComponentView",
+ "RNGestureHandlerDetector": "RNGestureHandlerDetector"
}
}
},
diff --git a/packages/react-native-gesture-handler/react-native.config.js b/packages/react-native-gesture-handler/react-native.config.js
new file mode 100644
index 0000000000..4ec41154cd
--- /dev/null
+++ b/packages/react-native-gesture-handler/react-native.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+ dependency: {
+ platforms: {
+ android: {
+ componentDescriptors: ['RNGestureHandlerDetectorComponentDescriptor'],
+ cmakeListsPath: './CMakeLists.txt',
+ },
+ },
+ },
+};
diff --git a/packages/react-native-gesture-handler/shared/RNGHRuntimeDecorator.cpp b/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.cpp
similarity index 92%
rename from packages/react-native-gesture-handler/shared/RNGHRuntimeDecorator.cpp
rename to packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.cpp
index 0406943186..3126317973 100644
--- a/packages/react-native-gesture-handler/shared/RNGHRuntimeDecorator.cpp
+++ b/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.cpp
@@ -14,6 +14,7 @@ using namespace facebook::react;
void RNGHRuntimeDecorator::installRNRuntimeBindings(
jsi::Runtime &rnRuntime,
+ int moduleId,
std::function &&setGestureState) {
const auto isViewFlatteningDisabled = jsi::Function::createFromHostFunction(
rnRuntime,
@@ -77,10 +78,15 @@ void RNGHRuntimeDecorator::installRNRuntimeBindings(
rnRuntime.global().setProperty(
rnRuntime, "_setGestureStateAsync", std::move(setGestureStateAsync));
+
+ auto moduleIdValue = jsi::Value(moduleId);
+ rnRuntime.global().setProperty(
+ rnRuntime, "_RNGH_MODULE_ID", std::move(moduleIdValue));
}
bool RNGHRuntimeDecorator::installUIRuntimeBindings(
jsi::Runtime &rnRuntime,
+ int moduleId,
std::function &&setGestureState) {
const auto runtimeHolder =
rnRuntime.global().getProperty(rnRuntime, "_WORKLET_RUNTIME");
@@ -118,6 +124,10 @@ bool RNGHRuntimeDecorator::installUIRuntimeBindings(
uiRuntime.global().setProperty(
uiRuntime, "_setGestureStateSync", std::move(setGestureStateSync));
+ auto moduleIdValue = jsi::Value(moduleId);
+ rnRuntime.global().setProperty(
+ rnRuntime, "_RNGH_MODULE_ID", std::move(moduleIdValue));
+
return true;
}
diff --git a/packages/react-native-gesture-handler/shared/RNGHRuntimeDecorator.h b/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.h
similarity index 91%
rename from packages/react-native-gesture-handler/shared/RNGHRuntimeDecorator.h
rename to packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.h
index 17be4fd990..146c403858 100644
--- a/packages/react-native-gesture-handler/shared/RNGHRuntimeDecorator.h
+++ b/packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.h
@@ -8,9 +8,11 @@ class RNGHRuntimeDecorator {
public:
static void installRNRuntimeBindings(
jsi::Runtime &rnRuntime,
+ int moduleId,
std::function &&setGestureState);
static bool installUIRuntimeBindings(
jsi::Runtime &rnRuntime,
+ int moduleId,
std::function &&setGestureState);
};
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h
new file mode 100644
index 0000000000..d9f3374c48
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h
@@ -0,0 +1,30 @@
+
+/**
+ * This code was generated by
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Do not edit this file as changes may cause incorrect behavior and will be
+ * lost once the code is regenerated.
+ *
+ * @generated by codegen project: GenerateComponentDescriptorH.js
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+namespace facebook::react {
+
+using RNGestureHandlerButtonComponentDescriptor =
+ ConcreteComponentDescriptor;
+using RNGestureHandlerRootViewComponentDescriptor =
+ ConcreteComponentDescriptor;
+
+void FBReactNativeSpec_registerComponentDescriptorsFromCodegen(
+ std::shared_ptr registry);
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorComponentDescriptor.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorComponentDescriptor.h
new file mode 100644
index 0000000000..7de207846b
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorComponentDescriptor.h
@@ -0,0 +1,31 @@
+
+/**
+ * This code was generated by
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Do not edit this file as changes may cause incorrect behavior and will be
+ * lost once the code is regenerated.
+ *
+ * @generated by codegen project: GenerateComponentDescriptorH.js
+ */
+
+#pragma once
+
+#include
+
+#include "RNGestureHandlerDetectorShadowNode.h"
+
+namespace facebook::react {
+
+class RNGestureHandlerDetectorComponentDescriptor final
+ : public ConcreteComponentDescriptor {
+ using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
+ void adopt(ShadowNode &shadowNode) const override {
+ react_native_assert(
+ dynamic_cast(&shadowNode));
+
+ ConcreteComponentDescriptor::adopt(shadowNode);
+ }
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorShadowNode.cpp b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorShadowNode.cpp
new file mode 100644
index 0000000000..7fef34cefd
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorShadowNode.cpp
@@ -0,0 +1,58 @@
+
+/**
+ * This code was generated by
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Do not edit this file as changes may cause incorrect behavior and will be
+ * lost once the code is regenerated.
+ *
+ * @generated by codegen project: GenerateShadowNodeCpp.js
+ */
+
+#include "RNGestureHandlerDetectorShadowNode.h"
+
+namespace facebook::react {
+
+extern const char RNGestureHandlerDetectorComponentName[] =
+ "RNGestureHandlerDetector";
+
+void RNGestureHandlerDetectorShadowNode::initialize() {
+ // Disable forcing view flattening
+ ShadowNode::traits_.unset(ShadowNodeTraits::ForceFlattenView);
+
+ // When the detector is cloned and has a child node, the child node should be
+ // cloned as well to ensure it is mutable.
+ const auto &children = getChildren();
+ if (!children.empty()) {
+ react_native_assert(
+ children.size() == 1 &&
+ "RNGestureHandlerDetector received more than one child");
+
+ const auto clonedChild = children[0]->clone({});
+ replaceChild(*children[0], clonedChild);
+ }
+}
+
+void RNGestureHandlerDetectorShadowNode::layout(LayoutContext layoutContext) {
+ YogaLayoutableShadowNode::layout(layoutContext);
+ // TODO: consider allowing more than one child and doing bounding box
+ react_native_assert(getChildren().size() == 1);
+
+ auto child = std::static_pointer_cast(
+ getChildren()[0]);
+
+ child->ensureUnsealed();
+ auto mutableChild = std::const_pointer_cast(child);
+
+ // TODO: figure out the correct way to setup metrics between detector and
+ // the child
+ auto metrics = child->getLayoutMetrics();
+ metrics.frame = child->getLayoutMetrics().frame;
+ setLayoutMetrics(metrics);
+
+ auto childmetrics = child->getLayoutMetrics();
+ childmetrics.frame.origin = Point{};
+ mutableChild->setLayoutMetrics(childmetrics);
+}
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorShadowNode.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorShadowNode.h
new file mode 100644
index 0000000000..a078cb2cb9
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorShadowNode.h
@@ -0,0 +1,57 @@
+
+/**
+ * This code was generated by
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Do not edit this file as changes may cause incorrect behavior and will be
+ * lost once the code is regenerated.
+ *
+ * @generated by codegen project: GenerateShadowNodeH.js
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include "RNGestureHandlerDetectorState.h"
+
+namespace facebook::react {
+
+JSI_EXPORT extern const char RNGestureHandlerDetectorComponentName[];
+
+/*
+ * `ShadowNode` for component.
+ */
+class RNGestureHandlerDetectorShadowNode final
+ : public ConcreteViewShadowNode<
+ RNGestureHandlerDetectorComponentName,
+ RNGestureHandlerDetectorProps,
+ RNGestureHandlerDetectorEventEmitter,
+ RNGestureHandlerDetectorState> {
+ public:
+ RNGestureHandlerDetectorShadowNode(
+ const ShadowNodeFragment &fragment,
+ const ShadowNodeFamily::Shared &family,
+ ShadowNodeTraits traits)
+ : ConcreteViewShadowNode(fragment, family, traits) {
+ initialize();
+ }
+
+ RNGestureHandlerDetectorShadowNode(
+ const ShadowNode &sourceShadowNode,
+ const ShadowNodeFragment &fragment)
+ : ConcreteViewShadowNode(sourceShadowNode, fragment) {
+ initialize();
+ }
+
+ void layout(LayoutContext layoutContext) override;
+
+ private:
+ void initialize();
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorState.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorState.h
new file mode 100644
index 0000000000..e40ddef5f1
--- /dev/null
+++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerDetectorState.h
@@ -0,0 +1,32 @@
+/**
+ * This code was generated by
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Do not edit this file as changes may cause incorrect behavior and will be
+ * lost once the code is regenerated.
+ *
+ * @generated by codegen project: GenerateStateH.js
+ */
+#pragma once
+
+#ifdef ANDROID
+#include
+#endif
+
+namespace facebook::react {
+
+class RNGestureHandlerDetectorState {
+ public:
+ RNGestureHandlerDetectorState() = default;
+
+#ifdef ANDROID
+ RNGestureHandlerDetectorState(
+ RNGestureHandlerDetectorState const &previousState,
+ folly::dynamic data){};
+ folly::dynamic getDynamic() const {
+ return {};
+ };
+#endif
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native-gesture-handler/src/NativeDetector.tsx b/packages/react-native-gesture-handler/src/NativeDetector.tsx
new file mode 100644
index 0000000000..667cb654a3
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/NativeDetector.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import RNGestureHandlerDetectorNativeComponent from './specs/RNGestureHandlerDetectorNativeComponent';
+import { Animated, StyleSheet } from 'react-native';
+import { NativeGesture } from './useGesture';
+
+export interface NativeDetectorProps {
+ children?: React.ReactNode;
+ gesture: NativeGesture;
+}
+
+const AnimatedDetector = Animated.createAnimatedComponent(
+ RNGestureHandlerDetectorNativeComponent
+);
+
+export function NativeDetector({ gesture, children }: NativeDetectorProps) {
+ 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/components/GestureHandlerRootView.android.tsx b/packages/react-native-gesture-handler/src/components/GestureHandlerRootView.android.tsx
index 8e019b60dc..7eede77a66 100644
--- a/packages/react-native-gesture-handler/src/components/GestureHandlerRootView.android.tsx
+++ b/packages/react-native-gesture-handler/src/components/GestureHandlerRootView.android.tsx
@@ -12,12 +12,13 @@ export default function GestureHandlerRootView({
...rest
}: GestureHandlerRootViewProps) {
return (
-
+
-
+
);
}
diff --git a/packages/react-native-gesture-handler/src/globals.ts b/packages/react-native-gesture-handler/src/globals.ts
new file mode 100644
index 0000000000..7e0383e3da
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/globals.ts
@@ -0,0 +1,8 @@
+export {};
+
+declare global {
+ // Module ID for the native Gesture Handler module, injected via JSI.
+ // @internal
+ // eslint-disable-next-line no-var
+ var _RNGH_MODULE_ID: number;
+}
diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts
index 7ad400a7a7..c7f6504399 100644
--- a/packages/react-native-gesture-handler/src/index.ts
+++ b/packages/react-native-gesture-handler/src/index.ts
@@ -1,3 +1,5 @@
+import './globals';
+
import { initialize } from './init';
export { Directions } from './Directions';
@@ -59,7 +61,6 @@ export type { LongPressGestureType as LongPressGesture } from './handlers/gestur
export type { PinchGestureType as PinchGesture } from './handlers/gestures/pinchGesture';
export type { RotationGestureType as RotationGesture } from './handlers/gestures/rotationGesture';
export type { ForceTouchGestureType as ForceTouchGesture } from './handlers/gestures/forceTouchGesture';
-export type { NativeGestureType as NativeGesture } from './handlers/gestures/nativeGesture';
export type { ManualGestureType as ManualGesture } from './handlers/gestures/manualGesture';
export type { HoverGestureType as HoverGesture } from './handlers/gestures/hoverGesture';
export type {
@@ -161,4 +162,9 @@ export type {
} from './components/DrawerLayout';
export { default as DrawerLayout } from './components/DrawerLayout';
+export type { NativeDetectorProps } from './NativeDetector';
+export { NativeDetector } from './NativeDetector';
+
+export * from './useGesture';
+
initialize();
diff --git a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerDetectorNativeComponent.ts b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerDetectorNativeComponent.ts
new file mode 100644
index 0000000000..25af84632d
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerDetectorNativeComponent.ts
@@ -0,0 +1,58 @@
+import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
+import type {
+ Int32,
+ DirectEventHandler,
+ UnsafeMixed,
+ Double,
+} from 'react-native/Libraries/Types/CodegenTypes';
+import type { ViewProps } from 'react-native';
+
+type GestureHandlerEvent = Readonly<{
+ handlerTag: Int32;
+ state: Int32;
+ handlerData: UnsafeMixed;
+}>;
+
+type GestureHandlerStateChangeEvent = Readonly<{
+ handlerTag: Int32;
+ state: Int32;
+ oldState: Int32;
+ handlerData: UnsafeMixed;
+}>;
+
+type GestureHandlerTouchEvent = Readonly<{
+ handlerTag: Int32;
+ numberOfTouches: Int32;
+ state: Int32;
+ eventType: Int32;
+ allTouches: {
+ id: Int32;
+ x: Double;
+ y: Double;
+ absoluteX: Double;
+ absoluteY: Double;
+ }[];
+ changedTouches: {
+ id: Int32;
+ x: Double;
+ y: Double;
+ absoluteX: Double;
+ absoluteY: Double;
+ }[];
+ pointerType: Int32;
+}>;
+
+export interface NativeProps extends ViewProps {
+ onGestureHandlerEvent?: DirectEventHandler;
+ onGestureHandlerAnimatedEvent?: DirectEventHandler;
+ onGestureHandlerStateChange?: DirectEventHandler;
+ onGestureHandlerTouchEvent?: DirectEventHandler;
+
+ handlerTags: Int32[];
+ dispatchesAnimatedEvents: boolean;
+ moduleId: Int32;
+}
+
+export default codegenNativeComponent('RNGestureHandlerDetector', {
+ interfaceOnly: true,
+});
diff --git a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerRootViewNativeComponent.ts b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerRootViewNativeComponent.ts
index e92061f1f3..798857f541 100644
--- a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerRootViewNativeComponent.ts
+++ b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerRootViewNativeComponent.ts
@@ -1,6 +1,9 @@
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
+import type { Int32 } from 'react-native/Libraries/Types/CodegenTypes';
import type { ViewProps } from 'react-native';
-interface NativeProps extends ViewProps {}
+interface NativeProps extends ViewProps {
+ moduleId: Int32;
+}
export default codegenNativeComponent('RNGestureHandlerRootView');
diff --git a/packages/react-native-gesture-handler/src/useGesture.ts b/packages/react-native-gesture-handler/src/useGesture.ts
new file mode 100644
index 0000000000..8dfe6b2260
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/useGesture.ts
@@ -0,0 +1,67 @@
+import { useEffect, useMemo } from 'react';
+import { getNextHandlerTag } from './handlers/getNextHandlerTag';
+import RNGestureHandlerModule from './RNGestureHandlerModule';
+
+type GestureType =
+ | 'TapGestureHandler'
+ | 'LongPressGestureHandler'
+ | 'PanGestureHandler'
+ | 'PinchGestureHandler'
+ | 'RotationGestureHandler'
+ | 'FlingGestureHandler'
+ | 'ForceTouchGestureHandler'
+ | 'ManualGestureHandler'
+ | 'NativeViewGestureHandler';
+
+export interface NativeGesture {
+ tag: number;
+ name: GestureType;
+ config: Record;
+ dispatchesAnimatedEvents: boolean;
+}
+
+export function useGesture(
+ type: GestureType,
+ fullConfig: Record
+): NativeGesture {
+ const tag = useMemo(() => getNextHandlerTag(), []);
+
+ const {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGestureHandlerStateChange,
+ onGestureHandlerAnimatedEvent,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGestureHandlerEvent,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ onGestureHandlerTouchEvent,
+ ...config
+ } = fullConfig;
+
+ useMemo(() => {
+ RNGestureHandlerModule.createGestureHandler(type, tag, {});
+ RNGestureHandlerModule.flushOperations();
+ }, [type, tag]);
+
+ useEffect(() => {
+ return () => {
+ RNGestureHandlerModule.dropGestureHandler(tag);
+ RNGestureHandlerModule.flushOperations();
+ };
+ }, [type, tag]);
+
+ useEffect(() => {
+ // TODO: filter changes - passing functions (and possibly other types)
+ // causes a native crash
+ RNGestureHandlerModule.updateGestureHandler(tag, config);
+ RNGestureHandlerModule.flushOperations();
+ }, [config, tag]);
+
+ return {
+ tag: tag,
+ name: type,
+ config: fullConfig,
+ dispatchesAnimatedEvents:
+ !!onGestureHandlerAnimatedEvent &&
+ '__isNative' in (onGestureHandlerAnimatedEvent as any),
+ };
+}