diff --git a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts index 4f76b33be6..a82cddc975 100644 --- a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts +++ b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts @@ -3,7 +3,7 @@ import React from 'react'; import type { ActionType } from './ActionType'; import type { GestureRelations } from './v3/types'; import { Gestures } from './web/Gestures'; -import type { Config, PropsRef } from './web/interfaces'; +import type { Config, HostDetector, PropsRef } from './web/interfaces'; import { GestureHandlerWebDelegate } from './web/tools/GestureHandlerWebDelegate'; import InteractionManager from './web/tools/InteractionManager'; import NodeManager from './web/tools/NodeManager'; @@ -39,7 +39,8 @@ export default { // eslint-disable-next-line @typescript-eslint/no-explicit-any newView: any, actionType: ActionType, - propsRef: React.RefObject + propsRef: React.RefObject, + hostDetector?: HostDetector | null ) { if (!(newView instanceof Element || newView instanceof React.Component)) { shouldPreventDrop = true; @@ -53,16 +54,21 @@ export default { ); } - // @ts-ignore Types should be HTMLElement or React.Component - NodeManager.getHandler(handlerTag).init(newView, propsRef, actionType); + NodeManager.getHandler(handlerTag).init( + // @ts-expect-error view ref type differs between web and native + newView, + propsRef, + actionType, + hostDetector ?? null + ); }, - detachGestureHandler(handlerTag: number) { + detachGestureHandler(handlerTag: number, hostDetector?: HostDetector | null) { if (shouldPreventDrop) { shouldPreventDrop = false; return; } - NodeManager.detachGestureHandler(handlerTag); + NodeManager.detachGestureHandler(handlerTag, hostDetector); }, setGestureHandlerConfig(handlerTag: number, newConfig: Config) { NodeManager.getHandler(handlerTag).setGestureConfig(newConfig); diff --git a/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.web.tsx b/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.web.tsx index 7ed195a7de..61fbadbed1 100644 --- a/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.web.tsx +++ b/packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.web.tsx @@ -36,7 +36,6 @@ export interface VirtualChildrenWeb { // one stable object keeps the helpers pure (no closures over component-render-scoped values), // which means the useEffects don't need them in their deps lists. type DetectorRefs = { - owner: object; viewRef: RefObject; propsRef: RefObject; // Tags observed for the detector view (top-level). @@ -94,7 +93,8 @@ function attachReadyHandler( tag, child.viewRef.current, actionType, - refs.propsRef + refs.propsRef, + refs.viewRef ); refs.attachedHandlers.add(tag); } @@ -116,7 +116,8 @@ function attachReadyHandler( tag, refs.viewRef.current, actionType, - refs.propsRef + refs.propsRef, + refs.viewRef ); refs.attachedHandlers.add(tag); } @@ -164,7 +165,8 @@ function tryAttachNativeHandlersToChildView(refs: DetectorRefs) { tag, target, ActionType.NATIVE_DETECTOR, - refs.propsRef + refs.propsRef, + refs.viewRef ); refs.attachedHandlers.add(tag); RNGestureHandlerModule.updateGestureHandlerConfig(tag, { @@ -191,16 +193,16 @@ function syncSubscriptions( if (subscribedSet.has(tag)) { continue; } - NodeManager.observeHandler(tag, refs.owner, () => { + NodeManager.observeHandler(tag, refs.viewRef, () => { attachReadyHandler(refs, tag, actionType, virtualViewTag); }); subscribedSet.add(tag); } for (const tag of toUnsubscribe) { - NodeManager.cancelObservation(tag, refs.owner); + NodeManager.cancelObservation(tag, refs.viewRef); if (refs.attachedHandlers.has(tag)) { - RNGestureHandlerModule.detachGestureHandler(tag); + RNGestureHandlerModule.detachGestureHandler(tag, refs.viewRef); refs.attachedHandlers.delete(tag); } subscribedSet.delete(tag); @@ -209,9 +211,9 @@ function syncSubscriptions( } function teardown(refs: DetectorRefs) { - NodeManager.cancelAllObservationsForOwner(refs.owner); + NodeManager.cancelAllObservationsForOwner(refs.viewRef); for (const tag of refs.attachedHandlers) { - RNGestureHandlerModule.detachGestureHandler(tag); + RNGestureHandlerModule.detachGestureHandler(tag, refs.viewRef); } refs.attachedHandlers.clear(); refs.subscribedHandlers.clear(); @@ -232,7 +234,6 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => { const refsRef = useRef(null); if (refsRef.current === null) { refsRef.current = { - owner: {}, viewRef, propsRef, subscribedHandlers: new Set(), diff --git a/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts index 9e5023a3e4..b7888aee9a 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts @@ -22,6 +22,7 @@ import type { Config, GestureHandlerNativeEvent, HitSlop, + HostDetector, PointerData, PropsRef, ResultEvent, @@ -45,6 +46,7 @@ export default abstract class GestureHandler implements IGestureHandler { private _enabled: boolean | null = null; private viewRef: number | null = null; + private _hostDetectorView: HostDetector | null = null; private propsRef: React.RefObject | null = null; protected actionType: ActionType | null = null; private forAnimated: boolean = false; @@ -84,15 +86,21 @@ export default abstract class GestureHandler implements IGestureHandler { // Initializing handler // - protected init( + public init( viewRef: number, propsRef: React.RefObject, - actionType: ActionType + actionType: ActionType, + hostDetector: HostDetector | null = null ) { + if (this.attached) { + this.detach(); + } + this.attached = true; this.propsRef = propsRef; this.viewRef = viewRef; this.actionType = actionType; + this.hostDetectorView = hostDetector; this.state = State.UNDETERMINED; this.delegate.init(viewRef, this); @@ -1061,6 +1069,13 @@ export default abstract class GestureHandler implements IGestureHandler { this._attached = value; } + public get hostDetectorView() { + return this._hostDetectorView; + } + protected set hostDetectorView(value) { + this._hostDetectorView = value; + } + public get activationIndex() { return this._activationIndex; } diff --git a/packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts index 47b3a0e93f..2e501cd8ad 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts @@ -1,3 +1,6 @@ +import type { RefObject } from 'react'; + +import type { ActionType } from '../../ActionType'; import type { ActiveCursor, MouseButton, @@ -7,13 +10,14 @@ import type { import type { PointerType } from '../../PointerType'; import type { State } from '../../State'; import type { SingleGestureName } from '../../v3/types'; -import type { Config } from '../interfaces'; +import type { Config, HostDetector, PropsRef } from '../interfaces'; import type EventManager from '../tools/EventManager'; import type { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate'; import type PointerTracker from '../tools/PointerTracker'; export default interface IGestureHandler { attached: boolean; + hostDetectorView: HostDetector | null; active: boolean; activationIndex: number; awaiting: boolean; @@ -49,6 +53,12 @@ export default interface IGestureHandler { fail: () => void; cancel: () => void; + init: ( + viewRef: number, + propsRef: RefObject, + actionType: ActionType, + hostDetector?: HostDetector | null + ) => void; reset: () => void; detach: () => void; diff --git a/packages/react-native-gesture-handler/src/web/handlers/LongPressGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/LongPressGestureHandler.ts index 5044318380..f061ac69e7 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/LongPressGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/LongPressGestureHandler.ts @@ -1,7 +1,12 @@ import type { ActionType } from '../../ActionType'; import { State } from '../../State'; import { SingleGestureName } from '../../v3/types'; -import type { AdaptedEvent, Config, PropsRef } from '../interfaces'; +import type { + AdaptedEvent, + Config, + HostDetector, + PropsRef, +} from '../interfaces'; import type { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate'; import GestureHandler from './GestureHandler'; import type IGestureHandler from './IGestureHandler'; @@ -35,13 +40,14 @@ export default class LongPressGestureHandler extends GestureHandler { public override init( ref: number, propsRef: React.RefObject, - actionType: ActionType + actionType: ActionType, + hostDetector: HostDetector | null = null ) { if (this.enableContextMenu === undefined) { this.enableContextMenu = false; } - super.init(ref, propsRef, actionType); + super.init(ref, propsRef, actionType, hostDetector); } protected override transformNativeEvent() { diff --git a/packages/react-native-gesture-handler/src/web/handlers/NativeViewGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/NativeViewGestureHandler.ts index ba507ade1b..9f72b5e3f8 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/NativeViewGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/NativeViewGestureHandler.ts @@ -10,7 +10,12 @@ import { DEFAULT_TOUCH_SLOP, NATIVE_GESTURE_ROLE_ATTRIBUTE, } from '../constants'; -import type { AdaptedEvent, Config, PropsRef } from '../interfaces'; +import type { + AdaptedEvent, + Config, + HostDetector, + PropsRef, +} from '../interfaces'; import { NativeGestureRole } from '../interfaces'; import type { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate'; import { @@ -45,9 +50,10 @@ export default class NativeViewGestureHandler extends GestureHandler { public override init( ref: number, propsRef: React.RefObject, - actionType: ActionType + actionType: ActionType, + hostDetector: HostDetector | null = null ): void { - super.init(ref, propsRef, actionType); + super.init(ref, propsRef, actionType, hostDetector); this.shouldCancelWhenOutside = true; diff --git a/packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts index 73df3006f1..83a7332fc7 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts @@ -4,7 +4,7 @@ import { SingleGestureName } from '../../v3/types'; import { DEFAULT_TOUCH_SLOP } from '../constants'; import type { ScaleGestureListener } from '../detectors/ScaleGestureDetector'; import ScaleGestureDetector from '../detectors/ScaleGestureDetector'; -import type { AdaptedEvent, PropsRef } from '../interfaces'; +import type { AdaptedEvent, HostDetector, PropsRef } from '../interfaces'; import type { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate'; import GestureHandler from './GestureHandler'; import type IGestureHandler from './IGestureHandler'; @@ -62,9 +62,10 @@ export default class PinchGestureHandler extends GestureHandler { public override init( ref: number, propsRef: React.RefObject, - actionType: ActionType + actionType: ActionType, + hostDetector: HostDetector | null = null ) { - super.init(ref, propsRef, actionType); + super.init(ref, propsRef, actionType, hostDetector); this.shouldCancelWhenOutside = false; } diff --git a/packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts index 16216b6312..fff2f869a5 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts @@ -3,7 +3,7 @@ import { State } from '../../State'; import { SingleGestureName } from '../../v3/types'; import type { RotationGestureListener } from '../detectors/RotationGestureDetector'; import RotationGestureDetector from '../detectors/RotationGestureDetector'; -import type { AdaptedEvent, PropsRef } from '../interfaces'; +import type { AdaptedEvent, HostDetector, PropsRef } from '../interfaces'; import type { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate'; import GestureHandler from './GestureHandler'; import type IGestureHandler from './IGestureHandler'; @@ -62,9 +62,10 @@ export default class RotationGestureHandler extends GestureHandler { public override init( ref: number, propsRef: React.RefObject, - actionType: ActionType + actionType: ActionType, + hostDetector: HostDetector | null = null ): void { - super.init(ref, propsRef, actionType); + super.init(ref, propsRef, actionType, hostDetector); this.shouldCancelWhenOutside = false; } diff --git a/packages/react-native-gesture-handler/src/web/interfaces.ts b/packages/react-native-gesture-handler/src/web/interfaces.ts index 6d1c330b02..069ba0b1f1 100644 --- a/packages/react-native-gesture-handler/src/web/interfaces.ts +++ b/packages/react-native-gesture-handler/src/web/interfaces.ts @@ -1,3 +1,5 @@ +import type { RefObject } from 'react'; + import type { Directions } from '../Directions'; import type { ActiveCursor, @@ -179,6 +181,8 @@ export type SVGRef = { elementRef: { current: SVGElement }; }; +export type HostDetector = RefObject; + export enum NativeGestureRole { Button = 'GestureHandlerButton', Switch = 'Switch', diff --git a/packages/react-native-gesture-handler/src/web/tools/NodeManager.ts b/packages/react-native-gesture-handler/src/web/tools/NodeManager.ts index 3fa04f4b7b..7877a8ec5f 100644 --- a/packages/react-native-gesture-handler/src/web/tools/NodeManager.ts +++ b/packages/react-native-gesture-handler/src/web/tools/NodeManager.ts @@ -1,6 +1,7 @@ import type { ValueOf } from '../../typeUtils'; import type { Gestures } from '../Gestures'; import type IGestureHandler from '../handlers/IGestureHandler'; +import type { HostDetector } from '../interfaces'; type HandlerReadyCallback = (handler: IGestureHandler) => void; @@ -60,12 +61,24 @@ export default abstract class NodeManager { delete this.gestures[handlerTag]; } - public static detachGestureHandler(handlerTag: number): void { + public static detachGestureHandler( + handlerTag: number, + hostDetector?: HostDetector | null + ): void { if (!(handlerTag in this.gestures)) { return; } - this.gestures[handlerTag].detach(); + const handler = this.gestures[handlerTag] as IGestureHandler; + + if ( + !handler.attached || + (hostDetector !== undefined && handler.hostDetectorView !== hostDetector) + ) { + return; + } + + handler.detach(); } // Invokes `callback` every time a handler with `tag` is created and, if the handler already exists,