diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts index a9767d6c7d..9044a8a3df 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -29,6 +29,19 @@ export type ReanimatedHandler = { context: ReanimatedContext; }; +export type NativeEventsManager = new (component: { + props: Record; + _componentRef: React.Ref; + // Removed in https://github.com/software-mansion/react-native-reanimated/pull/6736 + // but we likely want to keep it for compatibility with older Reanimated versions + _componentViewTag: number; + getComponentViewTag: () => number; +}) => { + attachEvents: () => void; + detachEvents: () => void; + updateEvents: (prevProps: Record) => void; +}; + let Reanimated: | { default: { @@ -38,6 +51,7 @@ let Reanimated: options?: unknown ): ComponentClass

; }; + NativeEventsManager: NativeEventsManager; useHandler: ( handlers: GestureCallbacks ) => ReanimatedHandler; diff --git a/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.tsx index 5b91d456e2..961acece25 100644 --- a/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.tsx @@ -1,3 +1,4 @@ import RNGestureHandlerDetectorNativeComponent from '../../specs/RNGestureHandlerDetectorNativeComponent'; +export type { NativeProps as RNGestureHandlerDetectorNativeComponentProps } from '../../specs/RNGestureHandlerDetectorNativeComponent'; const HostGestureDetector = RNGestureHandlerDetectorNativeComponent; export default HostGestureDetector; diff --git a/packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx index bae409b602..1fdb907f5e 100644 --- a/packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx @@ -6,8 +6,8 @@ import { AnimatedNativeDetector, NativeDetectorProps, nativeDetectorStyles, - ReanimatedNativeDetector, } from './common'; +import { ReanimatedNativeDetector } from './ReanimatedNativeDetector'; export function NativeDetector({ gesture, diff --git a/packages/react-native-gesture-handler/src/v3/detectors/ReanimatedNativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/ReanimatedNativeDetector.tsx new file mode 100644 index 0000000000..ec84030cf4 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/detectors/ReanimatedNativeDetector.tsx @@ -0,0 +1,95 @@ +import { useEffect, useMemo, useRef } from 'react'; +import { + NativeEventsManager, + Reanimated, +} from '../../handlers/gestures/reanimatedWrapper'; +import HostGestureDetector, { + type RNGestureHandlerDetectorNativeComponentProps, +} from './HostGestureDetector'; +import { findNodeHandle } from 'react-native'; + +let NativeEventsManagerImpl = Reanimated?.NativeEventsManager; + +if (!NativeEventsManagerImpl) { + // When Reanimated.NativeEventsManager is undefined, it may be that an older + // Reanimated version is used which doesn't export NativeEventsManager, or + // Reanimated is not installed at all. For the older versions, try to import + // NativeEventsManager by a subpath. Otherwise, it will stay undefined. + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + NativeEventsManagerImpl = + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access + require('react-native-reanimated/src/createAnimatedComponent/NativeEventsManager').NativeEventsManager; + } catch { + // fail silently + } +} + +type WorkletProps = Pick< + RNGestureHandlerDetectorNativeComponentProps, + | 'onGestureHandlerReanimatedStateChange' + | 'onGestureHandlerReanimatedEvent' + | 'onGestureHandlerReanimatedTouchEvent' +>; + +function LeanReanimatedNativeDetector( + props: RNGestureHandlerDetectorNativeComponentProps +) { + const prevProps = useRef(null); + const eventManager = useRef | null>(null); + const viewRef = useRef(null); + + const { + onGestureHandlerReanimatedStateChange, + onGestureHandlerReanimatedEvent, + onGestureHandlerReanimatedTouchEvent, + ...restProps + } = props; + + const reaProps: WorkletProps = useMemo( + () => ({ + onGestureHandlerReanimatedStateChange, + // @ts-ignore This is a type mismatch between RNGH types and RN Codegen types + onGestureHandlerReanimatedEvent, + // @ts-ignore This is a type mismatch between RNGH types and RN Codegen types + onGestureHandlerReanimatedTouchEvent, + }), + [ + onGestureHandlerReanimatedEvent, + onGestureHandlerReanimatedStateChange, + onGestureHandlerReanimatedTouchEvent, + ] + ); + + useEffect(() => { + const nativeTag = findNodeHandle(viewRef.current) ?? -1; + // @ts-expect-error Reanimated expects __nativeTag to be present on the ref + viewRef.__nativeTag = nativeTag; + // @ts-expect-error NativeEventsManager should be defined here, if it isn't, we should + // go the fallback way and use Reanimated's createAnimatedComponent + eventManager.current = new NativeEventsManagerImpl({ + props: reaProps, + _componentRef: viewRef, + _componentViewTag: nativeTag, + getComponentViewTag: () => nativeTag, + }); + eventManager.current.attachEvents(); + + return () => { + eventManager.current?.detachEvents(); + }; + }, []); + + useEffect(() => { + if (prevProps.current) { + eventManager.current?.updateEvents(prevProps.current); + } + prevProps.current = reaProps; + }, [reaProps]); + + return ; +} + +export const ReanimatedNativeDetector = NativeEventsManagerImpl + ? LeanReanimatedNativeDetector + : Reanimated?.default.createAnimatedComponent(HostGestureDetector); diff --git a/packages/react-native-gesture-handler/src/v3/detectors/ReanimatedNativeDetector.web.tsx b/packages/react-native-gesture-handler/src/v3/detectors/ReanimatedNativeDetector.web.tsx new file mode 100644 index 0000000000..2036bce696 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/detectors/ReanimatedNativeDetector.web.tsx @@ -0,0 +1,5 @@ +import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; +import HostGestureDetector from './HostGestureDetector'; + +export const ReanimatedNativeDetector = + Reanimated?.default.createAnimatedComponent(HostGestureDetector); diff --git a/packages/react-native-gesture-handler/src/v3/detectors/VirtualDetector/InterceptingGestureDetector.tsx b/packages/react-native-gesture-handler/src/v3/detectors/VirtualDetector/InterceptingGestureDetector.tsx index 94d16ac136..8b4fe6ad25 100644 --- a/packages/react-native-gesture-handler/src/v3/detectors/VirtualDetector/InterceptingGestureDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/detectors/VirtualDetector/InterceptingGestureDetector.tsx @@ -17,10 +17,10 @@ import { AnimatedNativeDetector, InterceptingGestureDetectorProps, nativeDetectorStyles, - ReanimatedNativeDetector, } from '../common'; import { tagMessage } from '../../../utils'; import { useEnsureGestureHandlerRootView } from '../useEnsureGestureHandlerRootView'; +import { ReanimatedNativeDetector } from '../ReanimatedNativeDetector'; interface VirtualChildrenForNative { viewTag: number; diff --git a/packages/react-native-gesture-handler/src/v3/detectors/common.ts b/packages/react-native-gesture-handler/src/v3/detectors/common.ts index ae1560381e..a31d5b5ba0 100644 --- a/packages/react-native-gesture-handler/src/v3/detectors/common.ts +++ b/packages/react-native-gesture-handler/src/v3/detectors/common.ts @@ -1,6 +1,5 @@ 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'; @@ -23,9 +22,6 @@ export type GestureDetectorProps = export const AnimatedNativeDetector = Animated.createAnimatedComponent(HostGestureDetector); -export const ReanimatedNativeDetector = - Reanimated?.default.createAnimatedComponent(HostGestureDetector); - export const nativeDetectorStyles = StyleSheet.create({ detector: { display: 'contents',