import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { findNodeHandle, Platform } from 'react-native'; import { tagMessage } from '../../../utils'; import { isComposedGesture } from '../../hooks/utils/relationUtils'; import type { DetectorCallbacks, VirtualChild } from '../../types'; import type { VirtualDetectorProps } from '../common'; import { useDetectorAttachmentGuard } from '../useDetectorAttachmentGuard'; import { useGestureRelationsUpdater } from '../useGestureRelationsUpdater'; import { useNativeGestureRole } from '../useNativeGestureRole'; import { InterceptingDetectorMode, useInterceptingDetectorContext, } from './useInterceptingDetectorContext'; import { Wrap } from './Wrap'; function useRequiredInterceptingDetectorContext() { const context = useInterceptingDetectorContext(); if (!context) { throw new Error( tagMessage( 'VirtualGestureDetector must be a descendant of an InterceptingGestureDetector' ) ); } return context; } export function VirtualDetector< TConfig, THandlerData, TExtendedHandlerData extends THandlerData, >(props: VirtualDetectorProps) { // Don't memoize virtual detectors to be able to listen to changes in children // TODO: replace with MutationObserver when it rolls out in React Native 'use no memo'; const { register, unregister, setMode } = useRequiredInterceptingDetectorContext(); const viewRef = useRef(null); const [viewTag, setViewTag] = useState(-1); const handleRef = useCallback( (node: any) => { viewRef.current = node; if (node) { const tag: number = Platform.OS === 'web' ? node : findNodeHandle(node); setViewTag(tag ?? -1); } else { setViewTag(-1); } }, // Invalid dependency array to change the function when children change // eslint-disable-next-line react-hooks/exhaustive-deps [props.children] ); useNativeGestureRole(viewRef, props.children); const handlerTags = useMemo( () => isComposedGesture(props.gesture) ? props.gesture.handlerTags : [props.gesture.handlerTag], [props.gesture] ); useDetectorAttachmentGuard(handlerTags); useEffect(() => { if (viewTag === -1) { return; } if (props.gesture.config.dispatchesAnimatedEvents) { throw new Error( tagMessage( 'VirtualGestureDetector cannot handle Animated events with native driver when used inside InterceptingGestureDetector. Use Reanimated or Animated events without native driver instead.' ) ); } else if (props.gesture.config.shouldUseReanimatedDetector) { setMode(InterceptingDetectorMode.REANIMATED); } const virtualChild: VirtualChild = { viewTag, handlerTags, methods: props.gesture.detectorCallbacks as DetectorCallbacks< unknown, unknown >, // used by HostGestureDetector on web viewRef: Platform.OS === 'web' ? viewRef : undefined, userSelect: props.userSelect, touchAction: props.touchAction, enableContextMenu: props.enableContextMenu, }; register(virtualChild); return () => { unregister(virtualChild); }; }, [ viewTag, props.gesture, handlerTags, props.userSelect, props.touchAction, props.enableContextMenu, register, unregister, setMode, ]); useGestureRelationsUpdater(props.gesture); return {props.children}; }