Skip to content

Commit 02e1317

Browse files
authored
[Web] Native detector component (#3637)
## Description This PR adds a web version of `NativeDetector` component following its IOS/android implementations. ## Test plan * copy all react source files from basic-example to expo-example * add a console log, in the `NativeDetector` pan Gesture * check whether the gesture is properly recognised
1 parent d798b69 commit 02e1317

19 files changed

+255
-65
lines changed

packages/react-native-gesture-handler/src/ActionType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const ActionType = {
33
NATIVE_ANIMATED_EVENT: 2,
44
JS_FUNCTION_OLD_API: 3,
55
JS_FUNCTION_NEW_API: 4,
6+
NATIVE_DETECTOR: 5,
67
} as const;
78

89
// eslint-disable-next-line @typescript-eslint/no-redeclare -- backward compatibility; it can be used as a type and as a value

packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22

33
import type { ActionType } from './ActionType';
44
import { Gestures } from './web/Gestures';
5-
import type { Config } from './web/interfaces';
5+
import type { Config, PropsRef } from './web/interfaces';
66
import InteractionManager from './web/tools/InteractionManager';
77
import NodeManager from './web/tools/NodeManager';
88
import { GestureHandlerWebDelegate } from './web/tools/GestureHandlerWebDelegate';
@@ -47,8 +47,8 @@ export default {
4747
handlerTag: number,
4848
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4949
newView: any,
50-
_actionType: ActionType,
51-
propsRef: React.RefObject<unknown>
50+
actionType: ActionType,
51+
propsRef: React.RefObject<PropsRef>
5252
) {
5353
if (!(newView instanceof Element || newView instanceof React.Component)) {
5454
shouldPreventDrop = true;
@@ -63,7 +63,15 @@ export default {
6363
}
6464

6565
// @ts-ignore Types should be HTMLElement or React.Component
66-
NodeManager.getHandler(handlerTag).init(newView, propsRef);
66+
NodeManager.getHandler(handlerTag).init(newView, propsRef, actionType);
67+
},
68+
detachGestureHandler(handlerTag: number) {
69+
if (shouldPreventDrop) {
70+
shouldPreventDrop = false;
71+
return;
72+
}
73+
74+
NodeManager.detachGestureHandler(handlerTag);
6775
},
6876
updateGestureHandler(handlerTag: number, newConfig: Config) {
6977
NodeManager.getHandler(handlerTag).updateGestureConfig(newConfig);
@@ -78,6 +86,7 @@ export default {
7886
},
7987
dropGestureHandler(handlerTag: number) {
8088
if (shouldPreventDrop) {
89+
shouldPreventDrop = false;
8190
return;
8291
}
8392

packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/attachHandlers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@ import { ActionType } from '../../../ActionType';
88
import { Platform } from 'react-native';
99
import type RNGestureHandlerModuleWeb from '../../../RNGestureHandlerModule.web';
1010
import { ghQueueMicrotask } from '../../../ghQueueMicrotask';
11-
import { AttachedGestureState, WebEventHandler } from './types';
11+
import { AttachedGestureState } from './types';
1212
import {
1313
extractGestureRelations,
1414
checkGestureCallbacksForWorklets,
1515
ALLOWED_PROPS,
1616
} from './utils';
1717
import { MountRegistry } from '../../../mountRegistry';
18+
import { PropsRef } from '../../../web/interfaces';
1819

1920
interface AttachHandlersConfig {
2021
preparedGesture: AttachedGestureState;
2122
gestureConfig: ComposedGesture | GestureType;
2223
gesturesToAttach: GestureType[];
2324
viewTag: number;
24-
webEventHandlersRef: React.RefObject<WebEventHandler>;
25+
webEventHandlersRef: React.RefObject<PropsRef>;
2526
}
2627

2728
export function attachHandlers({

packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/types.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { GestureType, HandlerCallbacks } from '../gesture';
22
import { SharedValue } from '../reanimatedWrapper';
3-
import { HandlerStateChangeEvent } from '../../gestureHandlerCommon';
43

54
export interface AttachedGestureState {
65
// Array of gestures that should be attached to the view under that gesture detector
@@ -23,10 +22,3 @@ export interface GestureDetectorState {
2322
previousViewTag: number;
2423
forceRebuildReanimatedEvent: boolean;
2524
}
26-
27-
export interface WebEventHandler {
28-
onGestureHandlerEvent: (event: HandlerStateChangeEvent<unknown>) => void;
29-
onGestureHandlerStateChange?: (
30-
event: HandlerStateChangeEvent<unknown>
31-
) => void;
32-
}

packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/useDetectorUpdater.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@ import React, { useCallback } from 'react';
22
import { GestureType } from '../gesture';
33
import { ComposedGesture } from '../gestureComposition';
44

5-
import {
6-
AttachedGestureState,
7-
GestureDetectorState,
8-
WebEventHandler,
9-
} from './types';
5+
import { AttachedGestureState, GestureDetectorState } from './types';
106
import { attachHandlers } from './attachHandlers';
117
import { updateHandlers } from './updateHandlers';
128
import { needsToReattach } from './needsToReattach';
139
import { dropHandlers } from './dropHandlers';
1410
import { useForceRender, validateDetectorChildren } from './utils';
1511
import findNodeHandle from '../../../findNodeHandle';
12+
import { PropsRef } from '../../../web/interfaces';
1613

1714
// Returns a function that's responsible for updating the attached gestures
1815
// If the view has changed, it will reattach the handlers to the new view
@@ -22,7 +19,7 @@ export function useDetectorUpdater(
2219
preparedGesture: AttachedGestureState,
2320
gesturesToAttach: GestureType[],
2421
gestureConfig: ComposedGesture | GestureType,
25-
webEventHandlersRef: React.RefObject<WebEventHandler>
22+
webEventHandlersRef: React.RefObject<PropsRef>
2623
) {
2724
const forceRender = useForceRender();
2825
const updateAttachedGestures = useCallback(

packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { RNRenderer } from '../../../RNRenderer';
2121
import { useCallback, useRef, useState } from 'react';
2222
import { Reanimated } from '../reanimatedWrapper';
2323
import { onGestureHandlerEvent } from '../eventReceiver';
24-
import { WebEventHandler } from './types';
24+
import { PropsRef } from '../../../web/interfaces';
2525

2626
export const ALLOWED_PROPS = [
2727
...baseGestureHandlerWithDetectorProps,
@@ -167,7 +167,7 @@ export function useForceRender() {
167167
}
168168

169169
export function useWebEventHandlers() {
170-
return useRef<WebEventHandler>({
170+
return useRef<PropsRef>({
171171
onGestureHandlerEvent: (e: HandlerStateChangeEvent<unknown>) => {
172172
onGestureHandlerEvent(e.nativeEvent);
173173
},
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import RNGestureHandlerDetectorNativeComponent from '../specs/RNGestureHandlerDetectorNativeComponent';
2+
const HostGestureDetector = RNGestureHandlerDetectorNativeComponent;
3+
export default HostGestureDetector;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useEffect, useRef } from 'react';
2+
import { View } from 'react-native';
3+
import RNGestureHandlerModule from '../RNGestureHandlerModule.web';
4+
import { ActionType } from '../ActionType';
5+
import { PropsRef } from '../web/interfaces';
6+
7+
export interface GestureHandlerDetectorProps extends PropsRef {
8+
handlerTags: number[];
9+
moduleId: number;
10+
children?: React.ReactNode;
11+
}
12+
13+
const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
14+
const { handlerTags, children } = props;
15+
16+
const viewRef = useRef(null);
17+
const propsRef = useRef<PropsRef>(props);
18+
const attachedHandlerTags = useRef<Set<number>>(new Set<number>());
19+
20+
const detachHandlers = (oldHandlerTags: Set<number>) => {
21+
oldHandlerTags.forEach((tag) => {
22+
RNGestureHandlerModule.detachGestureHandler(tag);
23+
});
24+
};
25+
26+
const attachHandlers = (currentHandlerTags: Set<number>) => {
27+
const oldHandlerTags =
28+
attachedHandlerTags.current.difference(currentHandlerTags);
29+
const newHandlerTags = currentHandlerTags.difference(
30+
attachedHandlerTags.current
31+
);
32+
33+
detachHandlers(oldHandlerTags);
34+
35+
newHandlerTags.forEach((tag) => {
36+
RNGestureHandlerModule.attachGestureHandler(
37+
tag,
38+
viewRef.current,
39+
ActionType.NATIVE_DETECTOR,
40+
propsRef
41+
);
42+
});
43+
attachedHandlerTags.current = currentHandlerTags;
44+
};
45+
46+
useEffect(() => {
47+
attachHandlers(new Set(handlerTags));
48+
}, [handlerTags]);
49+
50+
useEffect(() => {
51+
return () => {
52+
detachHandlers(attachedHandlerTags.current);
53+
};
54+
}, []);
55+
56+
return (
57+
<View style={{ display: 'contents' }} ref={viewRef}>
58+
{children}
59+
</View>
60+
);
61+
};
62+
63+
export default HostGestureDetector;

packages/react-native-gesture-handler/src/v3/NativeDetector.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,27 @@ import { NativeGesture } from './hooks/useGesture';
33
import { Reanimated } from '../handlers/gestures/reanimatedWrapper';
44

55
import { Animated, StyleSheet } from 'react-native';
6-
import RNGestureHandlerDetectorNativeComponent from '../specs/RNGestureHandlerDetectorNativeComponent';
6+
import HostGestureDetector from './HostGestureDetector';
77
import { tagMessage } from '../utils';
88

99
export interface NativeDetectorProps {
1010
children?: React.ReactNode;
1111
gesture: NativeGesture;
1212
}
1313

14-
const AnimatedNativeDetector = Animated.createAnimatedComponent(
15-
RNGestureHandlerDetectorNativeComponent
16-
);
14+
const AnimatedNativeDetector =
15+
Animated.createAnimatedComponent(HostGestureDetector);
1716

18-
const ReanimatedNativeDetector = Reanimated?.default.createAnimatedComponent(
19-
RNGestureHandlerDetectorNativeComponent
20-
);
17+
const ReanimatedNativeDetector =
18+
Reanimated?.default.createAnimatedComponent(HostGestureDetector);
2119

2220
export function NativeDetector({ gesture, children }: NativeDetectorProps) {
2321
const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents
2422
? AnimatedNativeDetector
2523
: // TODO: Remove this cast when we properly type config
2624
(gesture.config.shouldUseReanimated as boolean)
2725
? ReanimatedNativeDetector
28-
: RNGestureHandlerDetectorNativeComponent;
26+
: HostGestureDetector;
2927

3028
// It might happen only with ReanimatedNativeDetector
3129
if (!NativeDetectorComponent) {

0 commit comments

Comments
 (0)