Skip to content

[Web] Native detector component #3637

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 58 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
ea8bd73
basic component
akwasniewski Jul 25, 2025
32fea07
Merge branch 'next' into @akwasniewski/native-detector-web
akwasniewski Jul 25, 2025
968112c
better useffect triggers:
akwasniewski Jul 25, 2025
6a69cd7
added todos
akwasniewski Jul 25, 2025
2b404af
moved Gesture handler detectors
akwasniewski Jul 25, 2025
9c30c1a
path fix
akwasniewski Jul 25, 2025
d7dd057
extracted host gesture detector
akwasniewski Jul 25, 2025
56b93d3
not extending view props
akwasniewski Jul 25, 2025
ad8ef64
native detector action type
akwasniewski Jul 25, 2025
0304177
moved implementation of web handler
akwasniewski Jul 28, 2025
4aaf700
memoisation
akwasniewski Jul 28, 2025
eeddd24
detaching handlers
akwasniewski Jul 28, 2025
3242ffd
forgot to asign to oldHandlerTags
akwasniewski Jul 28, 2025
7147ab3
removed unnecessary types
akwasniewski Jul 28, 2025
cc78f74
native detector animated event action type
akwasniewski Jul 28, 2025
067becb
removed console log
akwasniewski Jul 28, 2025
d782c95
handling animated events in sendEvents
akwasniewski Jul 29, 2025
86046bd
first refactor
akwasniewski Jul 29, 2025
6c34d33
using native detector animated event
akwasniewski Jul 29, 2025
cd793b3
refactor 2
akwasniewski Jul 29, 2025
580494d
use callback in native detector
akwasniewski Jul 30, 2025
eb98bf4
tagMessage
akwasniewski Jul 30, 2025
af663c8
should prevent drop
akwasniewski Jul 30, 2025
42e84b4
old api compatibility
akwasniewski Jul 30, 2025
079fdb4
not throwing strings
akwasniewski Jul 30, 2025
cb72d7e
removed unnessary check
akwasniewski Jul 30, 2025
de98017
even more refactoring
akwasniewski Jul 30, 2025
e193ff4
throwing when propsref is null
akwasniewski Jul 30, 2025
2ba053a
helper action type functions
akwasniewski Jul 30, 2025
8ab666b
separate paths on touch events
akwasniewski Jul 30, 2025
c445262
removed unsafe memoisation
akwasniewski Jul 30, 2025
46a9570
props extending
akwasniewski Jul 30, 2025
37c4070
failing on detach
akwasniewski Jul 31, 2025
20cbc9e
one more refactor
akwasniewski Aug 1, 2025
58f8d60
display contents
akwasniewski Aug 1, 2025
532f35a
Merge branch 'next' into @akwasniewski/native-detector-web
akwasniewski Aug 1, 2025
7d205dc
removed action type native detector animated event
akwasniewski Aug 1, 2025
0c6f200
Update packages/react-native-gesture-handler/src/web/tools/GestureHan…
akwasniewski Aug 4, 2025
00738d6
minor fixes
akwasniewski Aug 4, 2025
9c17a96
type predicate
akwasniewski Aug 4, 2025
2353a84
readded memoisation
akwasniewski Aug 4, 2025
2443971
changed dependencies
akwasniewski Aug 5, 2025
f3198f5
moved forAnimated to config
akwasniewski Aug 5, 2025
686fadd
removed forAnimated from attachHandlers
akwasniewski Aug 5, 2025
b0d384d
type predicate using protected props ref
akwasniewski Aug 5, 2025
2ba5ae8
native gesture bugfix
akwasniewski Aug 5, 2025
38b240a
Merge branch 'next' into @akwasniewski/native-detector-web
akwasniewski Aug 5, 2025
7c00105
removed forAnimated
akwasniewski Aug 5, 2025
dceba3b
Merge branch 'next' into @akwasniewski/native-detector-web
akwasniewski Aug 5, 2025
b738254
removed usecallback
akwasniewski Aug 6, 2025
1f62d5c
private isInitialized
akwasniewski Aug 6, 2025
4d916f5
removed type predicate
akwasniewski Aug 6, 2025
18b1b77
safe detaching
akwasniewski Aug 6, 2025
5fdd932
minor fixes in GestureHandler
akwasniewski Aug 7, 2025
a5febe8
view getter
akwasniewski Aug 7, 2025
9ad8c0e
fix
akwasniewski Aug 7, 2025
10ce758
safer
akwasniewski Aug 7, 2025
1cd4bf1
forAnimated defaults to false
akwasniewski Aug 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-native-gesture-handler/src/ActionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const ActionType = {
NATIVE_ANIMATED_EVENT: 2,
JS_FUNCTION_OLD_API: 3,
JS_FUNCTION_NEW_API: 4,
NATIVE_DETECTOR: 5,
} as const;

// eslint-disable-next-line @typescript-eslint/no-redeclare -- backward compatibility; it can be used as a type and as a value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import type { ActionType } from './ActionType';
import { Gestures } from './web/Gestures';
import type { Config } from './web/interfaces';
import type { Config, PropsRef } from './web/interfaces';
import InteractionManager from './web/tools/InteractionManager';
import NodeManager from './web/tools/NodeManager';
import { GestureHandlerWebDelegate } from './web/tools/GestureHandlerWebDelegate';
Expand Down Expand Up @@ -47,8 +47,8 @@
handlerTag: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
newView: any,
_actionType: ActionType,
propsRef: React.RefObject<unknown>
actionType: ActionType,
propsRef: React.RefObject<PropsRef>
) {
if (!(newView instanceof Element || newView instanceof React.Component)) {
shouldPreventDrop = true;
Expand All @@ -63,7 +63,15 @@
}

// @ts-ignore Types should be HTMLElement or React.Component
NodeManager.getHandler(handlerTag).init(newView, propsRef);
NodeManager.getHandler(handlerTag).init(newView, propsRef, actionType);

Check warning on line 66 in packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts

View workflow job for this annotation

GitHub Actions / check

Unsafe call of an `any` typed value
},
detachGestureHandler(handlerTag: number) {
if (shouldPreventDrop) {
shouldPreventDrop = false;
return;
}

NodeManager.getHandler(handlerTag).detach();
},
updateGestureHandler(handlerTag: number, newConfig: Config) {
NodeManager.getHandler(handlerTag).updateGestureConfig(newConfig);
Expand All @@ -78,6 +86,7 @@
},
dropGestureHandler(handlerTag: number) {
if (shouldPreventDrop) {
shouldPreventDrop = false;
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@ import { ActionType } from '../../../ActionType';
import { Platform } from 'react-native';
import type RNGestureHandlerModuleWeb from '../../../RNGestureHandlerModule.web';
import { ghQueueMicrotask } from '../../../ghQueueMicrotask';
import { AttachedGestureState, WebEventHandler } from './types';
import { AttachedGestureState } from './types';
import {
extractGestureRelations,
checkGestureCallbacksForWorklets,
ALLOWED_PROPS,
} from './utils';
import { MountRegistry } from '../../../mountRegistry';
import { PropsRef } from '../../../web/interfaces';

interface AttachHandlersConfig {
preparedGesture: AttachedGestureState;
gestureConfig: ComposedGesture | GestureType;
gesturesToAttach: GestureType[];
viewTag: number;
webEventHandlersRef: React.RefObject<WebEventHandler>;
webEventHandlersRef: React.RefObject<PropsRef>;
}

export function attachHandlers({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { GestureType, HandlerCallbacks } from '../gesture';
import { SharedValue } from '../reanimatedWrapper';
import { HandlerStateChangeEvent } from '../../gestureHandlerCommon';

export interface AttachedGestureState {
// Array of gestures that should be attached to the view under that gesture detector
Expand All @@ -23,10 +22,3 @@ export interface GestureDetectorState {
previousViewTag: number;
forceRebuildReanimatedEvent: boolean;
}

export interface WebEventHandler {
onGestureHandlerEvent: (event: HandlerStateChangeEvent<unknown>) => void;
onGestureHandlerStateChange?: (
event: HandlerStateChangeEvent<unknown>
) => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ import React, { useCallback } from 'react';
import { GestureType } from '../gesture';
import { ComposedGesture } from '../gestureComposition';

import {
AttachedGestureState,
GestureDetectorState,
WebEventHandler,
} from './types';
import { AttachedGestureState, GestureDetectorState } from './types';
import { attachHandlers } from './attachHandlers';
import { updateHandlers } from './updateHandlers';
import { needsToReattach } from './needsToReattach';
import { dropHandlers } from './dropHandlers';
import { useForceRender, validateDetectorChildren } from './utils';
import findNodeHandle from '../../../findNodeHandle';
import { PropsRef } from '../../../web/interfaces';

// Returns a function that's responsible for updating the attached gestures
// If the view has changed, it will reattach the handlers to the new view
Expand All @@ -22,7 +19,7 @@ export function useDetectorUpdater(
preparedGesture: AttachedGestureState,
gesturesToAttach: GestureType[],
gestureConfig: ComposedGesture | GestureType,
webEventHandlersRef: React.RefObject<WebEventHandler>
webEventHandlersRef: React.RefObject<PropsRef>
) {
const forceRender = useForceRender();
const updateAttachedGestures = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { RNRenderer } from '../../../RNRenderer';
import { useCallback, useRef, useState } from 'react';
import { Reanimated } from '../reanimatedWrapper';
import { onGestureHandlerEvent } from '../eventReceiver';
import { WebEventHandler } from './types';
import { PropsRef } from '../../../web/interfaces';

export const ALLOWED_PROPS = [
...baseGestureHandlerWithDetectorProps,
Expand Down Expand Up @@ -167,7 +167,7 @@ export function useForceRender() {
}

export function useWebEventHandlers() {
return useRef<WebEventHandler>({
return useRef<PropsRef>({
onGestureHandlerEvent: (e: HandlerStateChangeEvent<unknown>) => {
onGestureHandlerEvent(e.nativeEvent);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import RNGestureHandlerDetectorNativeComponent from '../specs/RNGestureHandlerDetectorNativeComponent';
const HostGestureDetector = RNGestureHandlerDetectorNativeComponent;
export default HostGestureDetector;
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useEffect, useRef } from 'react';
import { View } from 'react-native';
import RNGestureHandlerModule from '../RNGestureHandlerModule.web';
import { ActionType } from '../ActionType';
import { PropsRef } from '../web/interfaces';

export interface GestureHandlerDetectorProps extends PropsRef {
handlerTags: number[];
moduleId: number;
children?: React.ReactNode;
}

const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
const { handlerTags, children } = props;

const viewRef = useRef(null);
const propsRef = useRef<PropsRef>(props);
const attachedHandlerTags = useRef<Set<number>>(new Set<number>());

const detachHandlers = (oldHandlerTags: Set<number>) => {
oldHandlerTags.forEach((tag) => {
RNGestureHandlerModule.detachGestureHandler(tag);
});
};

const attachHandlers = (currentHandlerTags: Set<number>) => {
const oldHandlerTags =
attachedHandlerTags.current.difference(currentHandlerTags);
const newHandlerTags = currentHandlerTags.difference(
attachedHandlerTags.current
);

detachHandlers(oldHandlerTags);

newHandlerTags.forEach((tag) => {
RNGestureHandlerModule.attachGestureHandler(
tag,
viewRef.current,
ActionType.NATIVE_DETECTOR,
propsRef
);
});
attachedHandlerTags.current = currentHandlerTags;
};

useEffect(() => {
attachHandlers(new Set(handlerTags));
}, [handlerTags]);

useEffect(() => {
return () => {
detachHandlers(attachedHandlerTags.current);
};
}, []);

return (
<View style={{ display: 'contents' }} ref={viewRef}>
{children}
</View>
);
};

export default HostGestureDetector;
14 changes: 6 additions & 8 deletions packages/react-native-gesture-handler/src/v3/NativeDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,27 @@ import { NativeGesture } from './hooks/useGesture';
import { Reanimated } from '../handlers/gestures/reanimatedWrapper';

import { Animated, StyleSheet } from 'react-native';
import RNGestureHandlerDetectorNativeComponent from '../specs/RNGestureHandlerDetectorNativeComponent';
import HostGestureDetector from './HostGestureDetector';
import { tagMessage } from '../utils';

export interface NativeDetectorProps {
children?: React.ReactNode;
gesture: NativeGesture;
}

const AnimatedNativeDetector = Animated.createAnimatedComponent(
RNGestureHandlerDetectorNativeComponent
);
const AnimatedNativeDetector =
Animated.createAnimatedComponent(HostGestureDetector);

const ReanimatedNativeDetector = Reanimated?.default.createAnimatedComponent(
RNGestureHandlerDetectorNativeComponent
);
const ReanimatedNativeDetector =
Reanimated?.default.createAnimatedComponent(HostGestureDetector);

export function NativeDetector({ gesture, children }: NativeDetectorProps) {
const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents
? AnimatedNativeDetector
: // TODO: Remove this cast when we properly type config
(gesture.config.shouldUseReanimated as boolean)
? ReanimatedNativeDetector
: RNGestureHandlerDetectorNativeComponent;
: HostGestureDetector;

// It might happen only with ReanimatedNativeDetector
if (!NativeDetectorComponent) {
Expand Down
Loading
Loading