diff --git a/apps/basic-example/src/NativeDetector.tsx b/apps/basic-example/src/NativeDetector.tsx
index da305734c9..51c30594dc 100644
--- a/apps/basic-example/src/NativeDetector.tsx
+++ b/apps/basic-example/src/NativeDetector.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { Animated, Button, useAnimatedValue } from 'react-native';
import {
GestureHandlerRootView,
- NativeDetector,
+ GestureDetector,
usePan,
} from 'react-native-gesture-handler';
@@ -32,7 +32,7 @@ export default function App() {
/>
{visible && (
-
+
-
+
)}
);
diff --git a/apps/basic-example/src/Text.tsx b/apps/basic-example/src/Text.tsx
index 210fe4fcff..526dbd7033 100644
--- a/apps/basic-example/src/Text.tsx
+++ b/apps/basic-example/src/Text.tsx
@@ -3,8 +3,7 @@ import { StyleSheet, Text, View } from 'react-native';
import {
Gesture,
GestureDetector,
- LogicDetector,
- NativeDetector,
+ InterceptingGestureDetector,
useTap,
} from 'react-native-gesture-handler';
@@ -37,24 +36,24 @@ function NativeDetectorExample() {
Native Detector example - this one should work
-
+
Some text example running with RNGH
-
+
{' '}
try tapping on this part
-
-
+
+
{' '}
or on this part
-
+
this part is not special :(
-
+
);
}
diff --git a/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx b/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx
index fb8128dbdd..dbb7001b15 100644
--- a/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx
+++ b/packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx
@@ -1,7 +1,7 @@
import { tagMessage } from '../utils';
import { useExclusive, useRace, useSimultaneous } from '../v3/hooks/relations';
import { useGesture } from '../v3/hooks/useGesture';
-import { configureRelations } from '../v3/NativeDetector/utils';
+import { configureRelations } from '../v3/detectors/utils';
import { SingleGesture, SingleGestureName } from '../v3/types';
import { renderHook } from '@testing-library/react-native';
diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/index.tsx b/packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/index.tsx
index 0a2c5f7e61..12ff60d1cb 100644
--- a/packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/index.tsx
+++ b/packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/index.tsx
@@ -42,7 +42,7 @@ function propagateDetectorConfig(
}
}
-interface GestureDetectorProps {
+export interface GestureDetectorProps {
children?: React.ReactNode;
/**
* A gesture object containing the configuration and callbacks.
diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts
index c508c06faa..f1dfcea542 100644
--- a/packages/react-native-gesture-handler/src/index.ts
+++ b/packages/react-native-gesture-handler/src/index.ts
@@ -51,7 +51,7 @@ export { RotationGestureHandler } from './handlers/RotationGestureHandler';
export { FlingGestureHandler } from './handlers/FlingGestureHandler';
export { default as createNativeWrapper } from './handlers/createNativeWrapper';
export type { NativeViewGestureHandlerProps } from './handlers/NativeViewGestureHandler';
-export { GestureDetector } from './handlers/gestures/GestureDetector';
+export { GestureDetector as LegacyGestureDetector } from './handlers/gestures/GestureDetector';
export { GestureObjects as Gesture } from './handlers/gestures/gestureObjects';
export type { TapGestureType as LegacyTapGesture } from './handlers/gestures/tapGesture';
export type { PanGestureType as LegacyPanGesture } from './handlers/gestures/panGesture';
@@ -149,10 +149,12 @@ export type {
} from './components/Pressable';
export { default as Pressable } from './components/Pressable';
-export type { NativeDetectorProps } from './v3/NativeDetector/NativeDetector';
-export { NativeDetector } from './v3/NativeDetector/NativeDetector';
+export {
+ GestureDetector,
+ InterceptingGestureDetector,
+ GestureDetectorProps,
+} from './v3/detectors';
-export { LogicDetector } from './v3/LogicDetector';
export * from './v3/hooks/useGesture';
export * from './v3/hooks/relations';
diff --git a/packages/react-native-gesture-handler/src/v3/detectors/GestureDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/GestureDetector.tsx
new file mode 100644
index 0000000000..ba4d468a11
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/v3/detectors/GestureDetector.tsx
@@ -0,0 +1,31 @@
+import { NativeDetectorProps } from './common';
+import { BaseGesture } from '../../handlers/gestures/gesture';
+import { NativeDetector } from './NativeDetector';
+import { ComposedGesture } from '../../handlers/gestures/gestureComposition';
+import {
+ GestureDetectorProps as LegacyGestureDetectorProps,
+ GestureDetector as LegacyGestureDetector,
+} from '../../handlers/gestures/GestureDetector';
+import { DetectorContext } from './LogicDetector/useDetectorContext';
+import { LogicDetector } from './LogicDetector/LogicDetector';
+import { use } from 'react';
+
+export function GestureDetector(
+ props: NativeDetectorProps | LegacyGestureDetectorProps
+) {
+ if (
+ props.gesture instanceof ComposedGesture ||
+ props.gesture instanceof BaseGesture
+ ) {
+ return ;
+ }
+
+ const context = use(DetectorContext);
+ return context ? (
+ )} />
+ ) : (
+ )}
+ />
+ );
+}
diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.tsx
similarity index 100%
rename from packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx
rename to packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.tsx
diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx b/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.web.tsx
similarity index 93%
rename from packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx
rename to packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.web.tsx
index 0ab4d714ae..00115f2e87 100644
--- a/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx
+++ b/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.web.tsx
@@ -110,6 +110,11 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
);
props.logicChildren?.forEach((child) => {
+ if (child.viewRef.current == null) {
+ // We must check whether viewRef is not null as otherwise we get an error when intercepting gesture detector
+ // switches its component based on whether animated/reanimated events should run.
+ return;
+ }
if (!attachedLogicHandlers.current.has(child.viewTag)) {
attachedLogicHandlers.current.set(child.viewTag, new Set());
}
diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/InterceptingGestureDetector.tsx
similarity index 62%
rename from packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx
rename to packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/InterceptingGestureDetector.tsx
index da35851e1e..4f71860b57 100644
--- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx
+++ b/packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/InterceptingGestureDetector.tsx
@@ -1,46 +1,55 @@
import React, { RefObject, useCallback, useRef, useState } from 'react';
-import { Animated, StyleSheet } from 'react-native';
-import HostGestureDetector from './HostGestureDetector';
-import { tagMessage } from '../../utils';
+import HostGestureDetector from '../HostGestureDetector';
import {
LogicChildren,
- Gesture,
- DetectorCallbacks,
GestureHandlerEvent,
-} from '../types';
+ DetectorCallbacks,
+} from '../../types';
import { DetectorContext } from './useDetectorContext';
-import { Reanimated } from '../../handlers/gestures/reanimatedWrapper';
-import { configureRelations } from './utils';
-import { isComposedGesture } from '../hooks/utils/relationUtils';
-
-export interface NativeDetectorProps {
- children?: React.ReactNode;
- gesture: Gesture;
-}
-
-const AnimatedNativeDetector =
- Animated.createAnimatedComponent(HostGestureDetector);
-
-const ReanimatedNativeDetector =
- Reanimated?.default.createAnimatedComponent(HostGestureDetector);
-
-export function NativeDetector({
+import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
+import { configureRelations, ensureNativeDetectorComponent } from '../utils';
+import { isComposedGesture } from '../../hooks/utils/relationUtils';
+import {
+ AnimatedNativeDetector,
+ InterceptingGestureDetectorProps,
+ nativeDetectorStyles,
+ ReanimatedNativeDetector,
+} from '../common';
+import { tagMessage } from '../../../utils';
+
+export function InterceptingGestureDetector({
gesture,
children,
-}: NativeDetectorProps) {
+}: InterceptingGestureDetectorProps) {
const [logicChildren, setLogicChildren] = useState([]);
+
const logicMethods = useRef<
Map>>
>(new Map());
- const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents
+ const [shouldUseReanimated, setShouldUseReanimated] = useState(
+ gesture ? gesture.config.shouldUseReanimatedDetector : false
+ );
+ const [dispatchesAnimatedEvents, setDispatchesAnimatedEvents] = useState(
+ gesture ? gesture.config.dispatchesAnimatedEvents : false
+ );
+
+ const NativeDetectorComponent = dispatchesAnimatedEvents
? AnimatedNativeDetector
- : gesture.config.shouldUseReanimatedDetector
+ : shouldUseReanimated
? ReanimatedNativeDetector
: HostGestureDetector;
const register = useCallback(
- (child: LogicChildren, methods: RefObject>) => {
+ (
+ child: LogicChildren,
+ methods: RefObject>,
+ forReanimated: boolean | undefined,
+ forAnimated: boolean | undefined
+ ) => {
+ setShouldUseReanimated(!!forReanimated);
+ setDispatchesAnimatedEvents(!!forAnimated);
+
setLogicChildren((prev) => {
const index = prev.findIndex((c) => c.viewTag === child.viewTag);
if (index !== -1) {
@@ -76,11 +85,9 @@ export function NativeDetector({
);
}
- configureRelations(gesture);
-
const handleGestureEvent = (key: keyof DetectorCallbacks) => {
return (e: GestureHandlerEvent) => {
- if (gesture.detectorCallbacks[key]) {
+ if (gesture?.detectorCallbacks[key]) {
gesture.detectorCallbacks[key](e);
}
@@ -97,7 +104,7 @@ export function NativeDetector({
(key: keyof DetectorCallbacks) => {
const handlers: ((e: GestureHandlerEvent) => void)[] = [];
- if (gesture.detectorCallbacks[key]) {
+ if (gesture?.detectorCallbacks[key]) {
handlers.push(
gesture.detectorCallbacks[key] as (
e: GestureHandlerEvent
@@ -116,21 +123,27 @@ export function NativeDetector({
return handlers;
},
- [logicChildren, gesture.detectorCallbacks]
+ [logicChildren, gesture?.detectorCallbacks]
);
const reanimatedEventHandler = Reanimated?.useComposedEventHandler(
getHandlers('onReanimatedUpdateEvent')
);
- const reanimedStateChangeHandler = Reanimated?.useComposedEventHandler(
+ const reanimatedStateChangeHandler = Reanimated?.useComposedEventHandler(
getHandlers('onReanimatedStateChange')
);
const reanimatedTouchEventHandler = Reanimated?.useComposedEventHandler(
getHandlers('onReanimatedTouchEvent')
);
+ ensureNativeDetectorComponent(NativeDetectorComponent);
+
+ if (gesture) {
+ configureRelations(gesture);
+ }
+
return (
-
+
({
onGestureHandlerEvent={handleGestureEvent('onGestureHandlerEvent')}
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
onGestureHandlerAnimatedEvent={
- gesture.detectorCallbacks.onGestureHandlerAnimatedEvent
+ gesture?.detectorCallbacks.onGestureHandlerAnimatedEvent
}
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
onGestureHandlerTouchEvent={handleGestureEvent(
'onGestureHandlerTouchEvent'
)}
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
- onGestureHandlerReanimatedStateChange={reanimatedEventHandler}
+ onGestureHandlerReanimatedStateChange={
+ shouldUseReanimated ? reanimatedStateChangeHandler : undefined
+ }
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
- onGestureHandlerReanimatedEvent={reanimedStateChangeHandler}
+ onGestureHandlerReanimatedEvent={
+ shouldUseReanimated ? reanimatedEventHandler : undefined
+ }
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
- onGestureHandlerReanimatedTouchEvent={reanimatedTouchEventHandler}
+ onGestureHandlerReanimatedTouchEvent={
+ shouldUseReanimated ? reanimatedTouchEventHandler : undefined
+ }
moduleId={globalThis._RNGH_MODULE_ID}
- handlerTags={isComposedGesture(gesture) ? gesture.tags : [gesture.tag]}
- style={styles.detector}
+ handlerTags={
+ gesture
+ ? isComposedGesture(gesture)
+ ? gesture.tags
+ : [gesture.tag]
+ : []
+ }
+ style={nativeDetectorStyles.detector}
logicChildren={logicChildren}>
{children}
-
+
);
}
-
-const styles = StyleSheet.create({
- detector: {
- display: 'contents',
- // TODO: remove, debug info only
- backgroundColor: 'red',
- },
-});
diff --git a/packages/react-native-gesture-handler/src/v3/LogicDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/LogicDetector.tsx
similarity index 60%
rename from packages/react-native-gesture-handler/src/v3/LogicDetector.tsx
rename to packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/LogicDetector.tsx
index d3b1c640d6..798769c2ee 100644
--- a/packages/react-native-gesture-handler/src/v3/LogicDetector.tsx
+++ b/packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/LogicDetector.tsx
@@ -1,19 +1,29 @@
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
-import { Wrap } from '../handlers/gestures/GestureDetector/Wrap';
+import { Wrap } from '../../../handlers/gestures/GestureDetector/Wrap';
import { findNodeHandle, Platform } from 'react-native';
-import { useDetectorContext } from './NativeDetector/useDetectorContext';
-import { NativeDetectorProps } from './NativeDetector/NativeDetector';
-import { isComposedGesture } from './hooks/utils/relationUtils';
-import { DetectorCallbacks } from './types';
+import { useDetectorContext } from './useDetectorContext';
+import { isComposedGesture } from '../../hooks/utils/relationUtils';
+import { NativeDetectorProps } from '../common';
+import { configureRelations } from '../utils';
+import { tagMessage } from '../../../utils';
+import { DetectorCallbacks } from '../../types';
export function LogicDetector(
props: NativeDetectorProps
) {
- const { register, unregister } = useDetectorContext();
+ const context = useDetectorContext();
+ if (!context) {
+ throw new Error(
+ tagMessage(
+ 'Logic detector must be a descendant of an InterceptingGestureDecector'
+ )
+ );
+ }
+ const { register, unregister } = context;
+
const viewRef = useRef(null);
const [viewTag, setViewTag] = useState(-1);
const logicMethods = useRef(props.gesture.detectorCallbacks);
-
const handleRef = useCallback((node: any) => {
viewRef.current = node;
if (!node) {
@@ -52,12 +62,19 @@ export function LogicDetector(
Object.assign(logicProps, { viewRef });
}
- register(logicProps, logicMethods as RefObject>);
+ register(
+ logicProps,
+ logicMethods as RefObject>,
+ props.gesture.config.shouldUseReanimatedDetector,
+ props.gesture.config.dispatchesAnimatedEvents
+ );
return () => {
unregister(viewTag, handlerTags);
};
}, [viewTag, props.gesture, register, unregister]);
+ configureRelations(props.gesture);
+
return {props.children};
}
diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/useDetectorContext.ts b/packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/useDetectorContext.ts
similarity index 65%
rename from packages/react-native-gesture-handler/src/v3/NativeDetector/useDetectorContext.ts
rename to packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/useDetectorContext.ts
index 1b9d5a2b29..016f36cd84 100644
--- a/packages/react-native-gesture-handler/src/v3/NativeDetector/useDetectorContext.ts
+++ b/packages/react-native-gesture-handler/src/v3/detectors/LogicDetector/useDetectorContext.ts
@@ -1,10 +1,12 @@
import { createContext, RefObject, use } from 'react';
-import { DetectorCallbacks, LogicChildren } from '../types';
+import { DetectorCallbacks, LogicChildren } from '../../types';
type DetectorContextType = {
register: (
child: LogicChildren,
- methods: RefObject>
+ methods: RefObject>,
+ forReanimated: boolean | undefined,
+ forAnimated: boolean | undefined
) => void;
unregister: (child: number, handlerTags: number[]) => void;
};
@@ -14,7 +16,7 @@ export const DetectorContext = createContext(null);
export function useDetectorContext() {
const ctx = use(DetectorContext);
if (!ctx) {
- throw new Error('Logic detector must be a descendant of a Native Detector');
+ return null;
}
return ctx;
}
diff --git a/packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx
new file mode 100644
index 0000000000..5131ef6356
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import HostGestureDetector from './HostGestureDetector';
+import { configureRelations, ensureNativeDetectorComponent } from './utils';
+import { isComposedGesture } from '../hooks/utils/relationUtils';
+import {
+ AnimatedNativeDetector,
+ NativeDetectorProps,
+ nativeDetectorStyles,
+ ReanimatedNativeDetector,
+} from './common';
+
+export function NativeDetector({
+ gesture,
+ children,
+}: NativeDetectorProps) {
+ const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents
+ ? AnimatedNativeDetector
+ : gesture.config.shouldUseReanimatedDetector
+ ? ReanimatedNativeDetector
+ : HostGestureDetector;
+
+ ensureNativeDetectorComponent(NativeDetectorComponent);
+ configureRelations(gesture);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/react-native-gesture-handler/src/v3/detectors/common.ts b/packages/react-native-gesture-handler/src/v3/detectors/common.ts
new file mode 100644
index 0000000000..e42e261d2b
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/v3/detectors/common.ts
@@ -0,0 +1,35 @@
+import React from 'react';
+import { Gesture } from '../types';
+import { Reanimated } from '../../handlers/gestures/reanimatedWrapper';
+import { Animated, StyleSheet } from 'react-native';
+import HostGestureDetector from './HostGestureDetector';
+import { GestureDetectorProps as LegacyDetectorProps } from '../../handlers/gestures/GestureDetector';
+
+export interface NativeDetectorProps {
+ children?: React.ReactNode;
+ gesture: Gesture;
+}
+
+export interface InterceptingGestureDetectorProps {
+ children?: React.ReactNode;
+ gesture?: Gesture;
+}
+
+export type GestureDetectorProps =
+ | NativeDetectorProps
+ | InterceptingGestureDetectorProps
+ | LegacyDetectorProps;
+
+export const AnimatedNativeDetector =
+ Animated.createAnimatedComponent(HostGestureDetector);
+
+export const ReanimatedNativeDetector =
+ Reanimated?.default.createAnimatedComponent(HostGestureDetector);
+
+export const nativeDetectorStyles = StyleSheet.create({
+ detector: {
+ display: 'contents',
+ // TODO: remove, debug info only
+ backgroundColor: 'red',
+ },
+});
diff --git a/packages/react-native-gesture-handler/src/v3/detectors/index.ts b/packages/react-native-gesture-handler/src/v3/detectors/index.ts
new file mode 100644
index 0000000000..c2fc0375e5
--- /dev/null
+++ b/packages/react-native-gesture-handler/src/v3/detectors/index.ts
@@ -0,0 +1,3 @@
+export type { GestureDetectorProps } from './common';
+export { GestureDetector } from './GestureDetector';
+export { InterceptingGestureDetector } from './LogicDetector/InterceptingGestureDetector';
diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/detectors/utils.ts
similarity index 94%
rename from packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts
rename to packages/react-native-gesture-handler/src/v3/detectors/utils.ts
index 1051c3b809..cdf442e592 100644
--- a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts
+++ b/packages/react-native-gesture-handler/src/v3/detectors/utils.ts
@@ -5,6 +5,7 @@
// For `simultaneousHandlers` we use Set as the order doesn't matter.
import RNGestureHandlerModule from '../../RNGestureHandlerModule';
+import { tagMessage } from '../../utils';
import {
isComposedGesture,
prepareRelations,
@@ -149,3 +150,17 @@ export function configureRelations(
RNGestureHandlerModule.flushOperations();
}
+
+export function ensureNativeDetectorComponent(
+ NativeDetectorComponent: unknown
+): asserts NativeDetectorComponent {
+ if (!NativeDetectorComponent) {
+ throw new Error(
+ tagMessage(
+ 'Gesture expects to run on the UI thread, but failed to create the Reanimated NativeDetector.'
+ )
+ );
+ }
+}
+
+export const EMPTY_SET = new Set();