Skip to content

Commit f48d46c

Browse files
authored
[Web] Reset web-exclusive properties when handler is disabled. (#3041)
## Description As mentioned in [this comment](#3010 (comment)), it may be confusing that Gesture Handler blocks default events even if it is explicitly disabled. This PR adds styles reset when `enabled` property changes. Currently it changes 3 things: 1. Changes `touch-action` to default value 2. Changes `user-select` to default value 3. Re-enables context menu usage. I'm not sure if I missed something. If so, please mention it in a comment. ## Test plan Tested on newly added example.
1 parent 78de2c3 commit f48d46c

File tree

6 files changed

+171
-10
lines changed

6 files changed

+171
-10
lines changed

example/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import HorizontalDrawer from './src/basic/horizontalDrawer';
5252
import PagerAndDrawer from './src/basic/pagerAndDrawer';
5353
import ForceTouch from './src/basic/forcetouch';
5454
import Fling from './src/basic/fling';
55+
import WebStylesResetExample from './src/release_tests/webStylesReset';
5556

5657
import ReanimatedSimple from './src/new_api/reanimated';
5758
import Camera from './src/new_api/camera';
@@ -151,6 +152,7 @@ const EXAMPLES: ExamplesSection[] = [
151152
{ name: 'Swipeable Reanimation', component: SwipeableReanimation },
152153
{ name: 'RectButton (borders)', component: RectButtonBorders },
153154
{ name: 'Gesturized pressable', component: GesturizedPressable },
155+
{ name: 'Web styles reset', component: WebStylesResetExample },
154156
],
155157
},
156158
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { useState } from 'react';
2+
import { StyleSheet, Text, View } from 'react-native';
3+
import {
4+
Gesture,
5+
GestureDetector,
6+
Pressable,
7+
} from 'react-native-gesture-handler';
8+
import Animated, {
9+
interpolateColor,
10+
useAnimatedStyle,
11+
useSharedValue,
12+
withTiming,
13+
} from 'react-native-reanimated';
14+
15+
const Colors = {
16+
enabled: '#32a852',
17+
disabled: '#b02525',
18+
};
19+
20+
const AnimationDuration = 250;
21+
22+
export default function WebStylesResetExample() {
23+
const [enabled, setEnabled] = useState(true);
24+
const [x, setX] = useState(0);
25+
const [y, setY] = useState(0);
26+
27+
const colorProgress = useSharedValue(0);
28+
29+
const animatedStyles = useAnimatedStyle(() => {
30+
const backgroundColor = interpolateColor(
31+
colorProgress.value,
32+
[0, 1],
33+
[Colors.enabled, Colors.disabled]
34+
);
35+
36+
return { backgroundColor };
37+
});
38+
39+
const g = Gesture.Pan()
40+
.onUpdate((e) => {
41+
setX(e.x);
42+
setY(e.y);
43+
})
44+
.enabled(enabled);
45+
46+
return (
47+
<View style={[styles.container, styles.centered]}>
48+
<GestureDetector gesture={g} enableContextMenu={false}>
49+
<Animated.View style={[styles.box, styles.centered, animatedStyles]}>
50+
<Text style={{ fontSize: 32 }}> Lorem Ipsum </Text>
51+
</Animated.View>
52+
</GestureDetector>
53+
54+
<Pressable
55+
style={[styles.button, styles.centered]}
56+
onPress={() => {
57+
setEnabled((prev) => !prev);
58+
59+
colorProgress.value = withTiming(enabled ? 1 : 0, {
60+
duration: AnimationDuration,
61+
});
62+
}}>
63+
<Text style={{ fontSize: 16 }}>{enabled ? 'Disable' : 'Enable'}</Text>
64+
</Pressable>
65+
66+
<Text style={{ fontSize: 16 }}>
67+
{' '}
68+
x: {x}, y: {y}{' '}
69+
</Text>
70+
</View>
71+
);
72+
}
73+
74+
const styles = StyleSheet.create({
75+
centered: {
76+
display: 'flex',
77+
justifyContent: 'center',
78+
alignItems: 'center',
79+
},
80+
81+
container: {
82+
flex: 1,
83+
backgroundColor: '#F5FCFF',
84+
},
85+
86+
button: {
87+
width: 250,
88+
height: 35,
89+
backgroundColor: 'plum',
90+
borderRadius: 10,
91+
margin: 25,
92+
},
93+
94+
box: {
95+
width: 250,
96+
height: 250,
97+
borderRadius: 25,
98+
},
99+
});

src/web/handlers/GestureHandler.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import EventManager from '../tools/EventManager';
1414
import GestureHandlerOrchestrator from '../tools/GestureHandlerOrchestrator';
1515
import InteractionManager from '../tools/InteractionManager';
1616
import PointerTracker, { TrackerElement } from '../tools/PointerTracker';
17-
import { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate';
1817
import IGestureHandler from './IGestureHandler';
1918
import { MouseButton } from '../../handlers/gestureHandlerCommon';
2019
import { PointerType } from '../../PointerType';
20+
import { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate';
2121

2222
export default abstract class GestureHandler implements IGestureHandler {
2323
private lastSentState: State | null = null;
@@ -589,6 +589,8 @@ export default abstract class GestureHandler implements IGestureHandler {
589589
this.config = { enabled: enabled, ...props };
590590
this.enabled = enabled;
591591

592+
this.delegate.onEnabledChange(enabled);
593+
592594
if (this.config.shouldCancelWhenOutside !== undefined) {
593595
this.setShouldCancelWhenOutside(this.config.shouldCancelWhenOutside);
594596
}

src/web/interfaces.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type ConfigArgs =
3636
| undefined;
3737

3838
export interface Config extends Record<string, ConfigArgs> {
39-
enabled?: boolean;
39+
enabled: boolean;
4040
simultaneousHandlers?: Handler[] | null;
4141
waitFor?: Handler[] | null;
4242
blocksHandlers?: Handler[] | null;

src/web/tools/GestureHandlerDelegate.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface GestureHandlerDelegate<TComponent, THandler> {
2020
onEnd(): void;
2121
onCancel(): void;
2222
onFail(): void;
23+
onEnabledChange(enabled: boolean): void;
2324

2425
destroy(config: Config): void;
2526
}

src/web/tools/GestureHandlerWebDelegate.ts

+65-8
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,22 @@ import { Config } from '../interfaces';
1313
import { MouseButton } from '../../handlers/gestureHandlerCommon';
1414
import KeyboardEventManager from './KeyboardEventManager';
1515

16+
interface DefaultViewStyles {
17+
userSelect: string;
18+
touchAction: string;
19+
}
20+
1621
export class GestureHandlerWebDelegate
1722
implements GestureHandlerDelegate<HTMLElement, IGestureHandler>
1823
{
24+
private isInitialized = false;
1925
private view!: HTMLElement;
2026
private gestureHandler!: IGestureHandler;
2127
private eventManagers: EventManager<unknown>[] = [];
28+
private defaultViewStyles: DefaultViewStyles = {
29+
userSelect: '',
30+
touchAction: '',
31+
};
2232

2333
getView(): HTMLElement {
2434
return this.view;
@@ -31,19 +41,21 @@ export class GestureHandlerWebDelegate
3141
);
3242
}
3343

44+
this.isInitialized = true;
45+
3446
this.gestureHandler = handler;
3547
this.view = findNodeHandle(viewRef) as unknown as HTMLElement;
3648

37-
const config = handler.getConfig();
49+
this.defaultViewStyles = {
50+
userSelect: this.view.style.userSelect,
51+
touchAction: this.view.style.touchAction,
52+
};
3853

39-
this.addContextMenuListeners(config);
54+
const config = handler.getConfig();
4055

41-
this.view.style['userSelect'] = config.userSelect ?? 'none';
42-
this.view.style['webkitUserSelect'] = config.userSelect ?? 'none';
43-
44-
this.view.style['touchAction'] = config.touchAction ?? 'none';
45-
// @ts-ignore This one disables default events on Safari
46-
this.view.style['WebkitTouchCallout'] = 'none';
56+
this.setUserSelect(config.enabled);
57+
this.setTouchAction(config.enabled);
58+
this.setContextMenu(config.enabled);
4759

4860
this.eventManagers.push(new PointerEventManager(this.view));
4961
this.eventManagers.push(new TouchEventManager(this.view));
@@ -119,6 +131,51 @@ export class GestureHandlerWebDelegate
119131
e.stopPropagation();
120132
}
121133

134+
private setUserSelect(isHandlerEnabled: boolean) {
135+
const { userSelect } = this.gestureHandler.getConfig();
136+
137+
this.view.style['userSelect'] = isHandlerEnabled
138+
? userSelect ?? 'none'
139+
: this.defaultViewStyles.userSelect;
140+
141+
this.view.style['webkitUserSelect'] = isHandlerEnabled
142+
? userSelect ?? 'none'
143+
: this.defaultViewStyles.userSelect;
144+
}
145+
146+
private setTouchAction(isHandlerEnabled: boolean) {
147+
const { touchAction } = this.gestureHandler.getConfig();
148+
149+
this.view.style['touchAction'] = isHandlerEnabled
150+
? touchAction ?? 'none'
151+
: this.defaultViewStyles.touchAction;
152+
153+
// @ts-ignore This one disables default events on Safari
154+
this.view.style['WebkitTouchCallout'] = isHandlerEnabled
155+
? touchAction ?? 'none'
156+
: this.defaultViewStyles.touchAction;
157+
}
158+
159+
private setContextMenu(isHandlerEnabled: boolean) {
160+
const config = this.gestureHandler.getConfig();
161+
162+
if (isHandlerEnabled) {
163+
this.addContextMenuListeners(config);
164+
} else {
165+
this.removeContextMenuListeners(config);
166+
}
167+
}
168+
169+
onEnabledChange(enabled: boolean): void {
170+
if (!this.isInitialized) {
171+
return;
172+
}
173+
174+
this.setUserSelect(enabled);
175+
this.setTouchAction(enabled);
176+
this.setContextMenu(enabled);
177+
}
178+
122179
onBegin(): void {
123180
// no-op for now
124181
}

0 commit comments

Comments
 (0)