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();