Skip to content

Commit 08c46d8

Browse files
authored
[Web] Detector DOM props (#3877)
## Description This PR introduces missing web props to `NativeDetector`. ## Test plan <details> <summary>Tested on the following code:</summary> ```tsx import React, { useState } from 'react'; import { StyleSheet, Text, Pressable } from 'react-native'; import { GestureDetector, GestureHandlerRootView, usePanGesture, } from 'react-native-gesture-handler'; import Animated, { interpolateColor, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated'; const Colors = { enabled: '#32a852', disabled: '#b02525', }; const AnimationDuration = 250; export default function WebStylesResetExample() { const [enabled, setEnabled] = useState(true); const colorProgress = useSharedValue(0); const animatedStyles = useAnimatedStyle(() => { const backgroundColor = interpolateColor( colorProgress.value, [0, 1], [Colors.enabled, Colors.disabled] ); return { backgroundColor }; }); const g = usePanGesture({ onUpdate: (e) => { console.log(e.x, e.y); }, enabled: enabled, disableReanimated: true, }); return ( <GestureHandlerRootView style={[styles.container, styles.centered]}> <GestureDetector gesture={g} enableContextMenu={false} userSelect="auto"> <Animated.View style={[styles.box, styles.centered, animatedStyles]}> <Text style={{ fontSize: 32 }}> Lorem Ipsum </Text> </Animated.View> </GestureDetector> <Pressable style={[styles.button, styles.centered]} onPress={() => { setEnabled((prev) => !prev); colorProgress.value = withTiming(enabled ? 1 : 0, { duration: AnimationDuration, }); }}> <Text style={{ fontSize: 16 }}>{enabled ? 'Disable' : 'Enable'}</Text> </Pressable> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ centered: { display: 'flex', justifyContent: 'center', alignItems: 'center', }, container: { flex: 1, backgroundColor: '#F5FCFF', }, button: { width: 250, height: 35, backgroundColor: 'plum', borderRadius: 10, margin: 25, }, box: { width: 250, height: 250, borderRadius: 25, }, }); ``` </details>
1 parent f8f47af commit 08c46d8

10 files changed

Lines changed: 89 additions & 27 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export default {
6767
setGestureHandlerConfig(handlerTag: number, newConfig: Config) {
6868
NodeManager.getHandler(handlerTag).setGestureConfig(newConfig);
6969
},
70-
updateGestureHandlerConfig(handlerTag: number, newConfig: Config) {
70+
updateGestureHandlerConfig(handlerTag: number, newConfig: Partial<Config>) {
7171
NodeManager.getHandler(handlerTag).updateGestureConfig(newConfig);
7272
},
7373
getGestureHandlerNode(handlerTag: number) {

packages/react-native-gesture-handler/src/v3/detectors/HostGestureDetector.web.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import { ActionType } from '../../ActionType';
44
import { PropsRef } from '../../web/interfaces';
55
import { View } from 'react-native';
66
import { tagMessage } from '../../utils';
7+
import { TouchAction, UserSelect } from '../../handlers/gestureHandlerCommon';
78

89
export interface GestureHandlerDetectorProps extends PropsRef {
910
handlerTags: number[];
1011
moduleId: number;
1112
children?: React.ReactNode;
1213
virtualChildren?: Set<VirtualChildrenWeb>;
14+
userSelect?: UserSelect;
15+
touchAction?: TouchAction;
16+
enableContextMenu?: boolean;
1317
}
1418

1519
export interface VirtualChildrenWeb {
@@ -24,7 +28,7 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
2428
const { handlerTags, children } = props;
2529

2630
const viewRef = useRef<Element>(null);
27-
const propsRef = useRef<PropsRef>(props);
31+
const propsRef = useRef<GestureHandlerDetectorProps>(props);
2832
const attachedHandlers = useRef<Set<number>>(new Set<number>());
2933
const attachedNativeHandlers = useRef<Set<number>>(new Set<number>());
3034
const attachedVirtualHandlers = useRef<Map<number, Set<number>>>(new Map());
@@ -42,7 +46,7 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
4246

4347
const attachHandlers = (
4448
viewRef: RefObject<Element | null>,
45-
propsRef: RefObject<PropsRef>,
49+
propsRef: RefObject<GestureHandlerDetectorProps>,
4650
currentHandlerTags: Set<number>,
4751
attachedHandlerTags: Set<number>,
4852
actionType: ActionType
@@ -72,11 +76,32 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
7276
);
7377
}
7478
attachedHandlerTags.add(tag);
79+
80+
RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
81+
userSelect: props.userSelect,
82+
touchAction: props.touchAction,
83+
enableContextMenu: props.enableContextMenu,
84+
});
7585
});
7686
};
7787

7888
useEffect(() => {
89+
const shouldUpdateDOMProps =
90+
propsRef.current.userSelect !== props.userSelect ||
91+
propsRef.current.touchAction !== props.touchAction ||
92+
propsRef.current.enableContextMenu !== props.enableContextMenu;
93+
7994
propsRef.current = props;
95+
96+
if (shouldUpdateDOMProps) {
97+
for (const tag of attachedHandlers.current) {
98+
RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
99+
userSelect: props.userSelect,
100+
touchAction: props.touchAction,
101+
enableContextMenu: props.enableContextMenu,
102+
});
103+
}
104+
}
80105
}, [props]);
81106

82107
useEffect(() => {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { ReanimatedNativeDetector } from './ReanimatedNativeDetector';
1212
export function NativeDetector<THandlerData, TConfig>({
1313
gesture,
1414
children,
15+
touchAction,
16+
userSelect,
17+
enableContextMenu,
1518
}: NativeDetectorProps<THandlerData, TConfig>) {
1619
const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents
1720
? AnimatedNativeDetector
@@ -28,6 +31,9 @@ export function NativeDetector<THandlerData, TConfig>({
2831

2932
return (
3033
<NativeDetectorComponent
34+
touchAction={touchAction}
35+
userSelect={userSelect}
36+
enableContextMenu={enableContextMenu}
3137
pointerEvents={'box-none'}
3238
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
3339
onGestureHandlerStateChange={

packages/react-native-gesture-handler/src/v3/detectors/common.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Gesture } from '../types';
33
import { Animated, StyleSheet } from 'react-native';
44
import HostGestureDetector from './HostGestureDetector';
55
import { GestureDetectorProps as LegacyDetectorProps } from '../../handlers/gestures/GestureDetector';
6+
import { TouchAction, UserSelect } from '../../handlers/gestureHandlerCommon';
67

78
export enum GestureDetectorType {
89
Native,
@@ -13,6 +14,9 @@ export enum GestureDetectorType {
1314
export interface NativeDetectorProps<THandlerData, TConfig> {
1415
children?: React.ReactNode;
1516
gesture: Gesture<THandlerData, TConfig>;
17+
userSelect?: UserSelect;
18+
touchAction?: TouchAction;
19+
enableContextMenu?: boolean;
1620
}
1721

1822
export interface InterceptingGestureDetectorProps<THandlerData, TConfig> {

packages/react-native-gesture-handler/src/v3/hooks/utils/propsWhiteList.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,8 @@ const CommonConfig = new Set<keyof CommonGestureConfig>([
1919
'enabled',
2020
'shouldCancelWhenOutside',
2121
'hitSlop',
22-
'userSelect',
2322
'activeCursor',
2423
'mouseButton',
25-
'enableContextMenu',
26-
'touchAction',
2724
'testID',
2825
]);
2926

@@ -39,6 +36,9 @@ export const allowedNativeProps = new Set<
3936
...CommonConfig,
4037

4138
// InternalConfigProps
39+
'userSelect',
40+
'enableContextMenu',
41+
'touchAction',
4242
'dispatchesReanimatedEvents',
4343
'dispatchesAnimatedEvents',
4444
'needsPointerData',

packages/react-native-gesture-handler/src/v3/types/ConfigTypes.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ export type InternalConfigProps<THandlerData> = {
4747
dispatchesReanimatedEvents?: boolean;
4848
dispatchesAnimatedEvents?: boolean;
4949
needsPointerData?: boolean;
50+
userSelect?: UserSelect;
51+
touchAction?: TouchAction;
52+
enableContextMenu?: boolean;
5053
changeEventCalculator?: ChangeCalculatorType<THandlerData>;
5154
};
5255

@@ -60,14 +63,11 @@ export type CommonGestureConfig = {
6063
enabled?: boolean;
6164
shouldCancelWhenOutside?: boolean;
6265
hitSlop?: HitSlop;
63-
userSelect?: UserSelect;
6466
activeCursor?: ActiveCursor;
6567
mouseButton?: MouseButton;
66-
enableContextMenu?: boolean;
67-
touchAction?: TouchAction;
6868
cancelsTouchesInView?: boolean;
6969
},
70-
HitSlop | UserSelect | ActiveCursor | MouseButton | TouchAction
70+
HitSlop | ActiveCursor | MouseButton
7171
>;
7272

7373
export type ComposedGestureConfig = {

packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -712,27 +712,30 @@ export default abstract class GestureHandler implements IGestureHandler {
712712
//
713713

714714
// Helper function to correctly set enabled property
715-
private updateEnabled(enabled: boolean | undefined) {
715+
private maybeUpdateEnabled(enabled: boolean | undefined): boolean {
716716
if (enabled === undefined) {
717-
if (this._enabled) {
718-
return;
717+
if (this._enabled !== undefined) {
718+
return false;
719719
}
720720

721721
this._enabled = true;
722-
this.delegate.onEnabledChange();
723-
} else if (this._enabled !== enabled) {
724-
this._enabled = enabled;
725-
this.delegate.onEnabledChange();
722+
723+
return true;
726724
}
725+
726+
const prevEnabled = this._enabled;
727+
this._enabled = enabled;
728+
729+
return enabled !== prevEnabled;
727730
}
728731

729732
public setGestureConfig(config: Config) {
730733
this.resetConfig();
731734
this.updateGestureConfig(config);
732735
}
733736

734-
public updateGestureConfig(config: Config): void {
735-
this.updateEnabled(config.enabled);
737+
public updateGestureConfig(config: Partial<Config>): void {
738+
const enabledChanged = this.maybeUpdateEnabled(config.enabled);
736739

737740
if (config.hitSlop !== undefined) {
738741
this.hitSlop = config.hitSlop;
@@ -764,20 +767,31 @@ export default abstract class GestureHandler implements IGestureHandler {
764767
this.shouldCancelWhenOutside = config.shouldCancelWhenOutside;
765768
}
766769

767-
if (config.enableContextMenu !== undefined) {
768-
this.enableContextMenu = config.enableContextMenu;
769-
}
770-
771770
if (config.activeCursor !== undefined) {
772771
this._activeCursor = config.activeCursor;
773772
}
774773

774+
let shouldUpdateDOM = false;
775+
776+
if (config.enableContextMenu !== undefined) {
777+
this.enableContextMenu = config.enableContextMenu;
778+
shouldUpdateDOM = true;
779+
}
780+
775781
if (config.touchAction !== undefined) {
776782
this._touchAction = config.touchAction;
783+
shouldUpdateDOM = true;
777784
}
778785

779786
if (config.userSelect !== undefined) {
780787
this._userSelect = config.userSelect;
788+
shouldUpdateDOM = true;
789+
}
790+
791+
if (enabledChanged) {
792+
this.delegate.onEnabledChange();
793+
} else if (shouldUpdateDOM) {
794+
this.delegate.updateDOM();
781795
}
782796

783797
if (this.enabled) {

packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default interface IGestureHandler {
5656
sendEvent: (newState: State, oldState: State) => void;
5757

5858
setGestureConfig: (config: Config) => void;
59-
updateGestureConfig: (config: Config) => void;
59+
updateGestureConfig: (config: Partial<Config>) => void;
6060

6161
isButton?: () => boolean;
6262
}

packages/react-native-gesture-handler/src/web/tools/GestureHandlerDelegate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface GestureHandlerDelegate<TComponent, THandler> {
1010

1111
init(viewRef: number, handler: THandler): void;
1212
detach(): void;
13+
updateDOM(): void;
1314
isPointerInBounds({ x, y }: { x: number; y: number }): boolean;
1415
measureView(): MeasureResult;
1516
reset(): void;

packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,14 @@ export class GestureHandlerWebDelegate
6161
this.gestureHandler.attachEventManager(manager)
6262
);
6363

64-
this.configure();
64+
this.updateDOM();
6565

6666
this.isInitialized = true;
6767
}
6868

6969
detach(): void {
70+
this.restoreDefaultViewStyles();
71+
7072
this.defaultViewStyles = {
7173
userSelect: '',
7274
touchAction: '',
@@ -83,7 +85,17 @@ export class GestureHandlerWebDelegate
8385
this.isInitialized = false;
8486
}
8587

86-
configure(): void {
88+
restoreDefaultViewStyles(): void {
89+
this.ensureView(this.view);
90+
91+
this.view.style['userSelect'] = this.defaultViewStyles.userSelect;
92+
this.view.style['webkitUserSelect'] = this.defaultViewStyles.userSelect;
93+
this.view.style['touchAction'] = this.defaultViewStyles.touchAction;
94+
// @ts-ignore This one disables default events on Safari
95+
this.view.style['WebkitTouchCallout'] = this.defaultViewStyles.touchAction;
96+
}
97+
98+
updateDOM(): void {
8799
this.setUserSelect();
88100
this.setTouchAction();
89101
this.setContextMenu();
@@ -220,7 +232,7 @@ export class GestureHandlerWebDelegate
220232
return;
221233
}
222234

223-
this.configure();
235+
this.updateDOM();
224236

225237
this.eventManagers.forEach((manager) => {
226238
manager.setEnabled(this.gestureHandler.enabled);

0 commit comments

Comments
 (0)