diff --git a/src/handlers/createHandler.tsx b/src/handlers/createHandler.tsx index 11c397479f..371d632514 100644 --- a/src/handlers/createHandler.tsx +++ b/src/handlers/createHandler.tsx @@ -24,6 +24,7 @@ import { ActionType } from '../ActionType'; import { PressabilityDebugView } from './PressabilityDebugView'; import GestureHandlerRootViewContext from '../GestureHandlerRootViewContext'; import { ghQueueMicrotask } from '../ghQueueMicrotask'; +import { MountRegistry } from '../mountRegistry'; const UIManagerAny = UIManager as any; @@ -262,6 +263,8 @@ export default function createHandler< // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete handlerIDToTag[handlerID]; } + + MountRegistry.gestureHandlerWillUnmount(this); } private onGestureHandlerEvent = (event: GestureEvent) => { @@ -373,6 +376,10 @@ export default function createHandler< } scheduleFlushOperations(); + + ghQueueMicrotask(() => { + MountRegistry.gestureHandlerWillMount(this); + }); }; private updateGestureHandler = ( diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts index cd43d2a5dd..4af2b57031 100644 --- a/src/handlers/gestures/GestureDetector/attachHandlers.ts +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -14,6 +14,7 @@ import { checkGestureCallbacksForWorklets, ALLOWED_PROPS, } from './utils'; +import { MountRegistry } from '../../../mountRegistry'; interface AttachHandlersConfig { preparedGesture: AttachedGestureState; @@ -93,6 +94,8 @@ export function attachHandlers({ actionType ); } + + MountRegistry.gestureWillMount(gesture); } preparedGesture.attachedGestures = gesturesToAttach; diff --git a/src/handlers/gestures/GestureDetector/dropHandlers.ts b/src/handlers/gestures/GestureDetector/dropHandlers.ts index 2f8510c5c2..3159d74a5f 100644 --- a/src/handlers/gestures/GestureDetector/dropHandlers.ts +++ b/src/handlers/gestures/GestureDetector/dropHandlers.ts @@ -2,12 +2,15 @@ import { unregisterHandler } from '../../handlersRegistry'; import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; import { scheduleFlushOperations } from '../../utils'; import { AttachedGestureState } from './types'; +import { MountRegistry } from '../../../mountRegistry'; export function dropHandlers(preparedGesture: AttachedGestureState) { for (const handler of preparedGesture.attachedGestures) { RNGestureHandlerModule.dropGestureHandler(handler.handlerTag); unregisterHandler(handler.handlerTag, handler.config.testId); + + MountRegistry.gestureWillUnmount(handler); } scheduleFlushOperations(); diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 222569ceb6..dac3e49495 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -23,6 +23,7 @@ import { useWebEventHandlers } from './utils'; import { Wrap, AnimatedWrap } from './Wrap'; import { useDetectorUpdater } from './useDetectorUpdater'; import { useViewRefHandler } from './useViewRefHandler'; +import { useMountReactions } from './useMountReactions'; function propagateDetectorConfig( props: GestureDetectorProps, @@ -174,6 +175,8 @@ export const GestureDetector = (props: GestureDetectorProps) => { } }, [props]); + useMountReactions(updateAttachedGestures, preparedGesture); + if (shouldUseReanimated) { return ( void, + state: AttachedGestureState +) { + useEffect(() => { + return MountRegistry.addMountListener((gesture) => { + // At this point the ref in the gesture config should be updated, so we can check if one of the gestures + // set in a relation with the gesture got mounted. If so, we need to update the detector to propagate + // the changes to the native side. + for (const attachedGesture of state.attachedGestures) { + const blocksHandlers = attachedGesture.config.blocksHandlers; + const requireToFail = attachedGesture.config.requireToFail; + const simultaneousWith = attachedGesture.config.simultaneousWith; + + if ( + shouldUpdateDetector(blocksHandlers, gesture) || + shouldUpdateDetector(requireToFail, gesture) || + shouldUpdateDetector(simultaneousWith, gesture) + ) { + updateDetector(); + + // We can safely return here, if any other gestures should be updated, they will be by the above call + return; + } + } + }); + }, [updateDetector, state]); +} diff --git a/src/handlers/utils.ts b/src/handlers/utils.ts index 13fdc488ab..02e200dedd 100644 --- a/src/handlers/utils.ts +++ b/src/handlers/utils.ts @@ -36,7 +36,8 @@ export function filterConfig( } return filteredConfig; } -function transformIntoHandlerTags(handlerIDs: any) { + +export function transformIntoHandlerTags(handlerIDs: any) { handlerIDs = toArray(handlerIDs); if (Platform.OS === 'web') { diff --git a/src/mountRegistry.ts b/src/mountRegistry.ts new file mode 100644 index 0000000000..e61ba9dd07 --- /dev/null +++ b/src/mountRegistry.ts @@ -0,0 +1,51 @@ +import { GestureType } from './handlers/gestures/gesture'; + +interface ReactComponentWithHandlerTag extends React.Component { + handlerTag: number; +} + +export type GestureMountListener = ( + gesture: GestureType | ReactComponentWithHandlerTag +) => void; + +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class MountRegistry { + private static mountListeners = new Set(); + private static unmountListeners = new Set(); + + static addMountListener(listener: GestureMountListener): () => void { + this.mountListeners.add(listener); + + return () => { + this.mountListeners.delete(listener); + }; + } + + static addUnmountListener(listener: GestureMountListener): () => void { + this.unmountListeners.add(listener); + + return () => { + this.unmountListeners.delete(listener); + }; + } + + static gestureHandlerWillMount(handler: React.Component) { + this.mountListeners.forEach((listener) => + listener(handler as ReactComponentWithHandlerTag) + ); + } + + static gestureHandlerWillUnmount(handler: React.Component) { + this.unmountListeners.forEach((listener) => + listener(handler as ReactComponentWithHandlerTag) + ); + } + + static gestureWillMount(gesture: GestureType) { + this.mountListeners.forEach((listener) => listener(gesture)); + } + + static gestureWillUnmount(gesture: GestureType) { + this.unmountListeners.forEach((listener) => listener(gesture)); + } +}