Skip to content

Commit

Permalink
[Web] Reset web-exclusive properties when handler is disabled. (#3041)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
m-bert authored Aug 12, 2024
1 parent 78de2c3 commit f48d46c
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 10 deletions.
2 changes: 2 additions & 0 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import HorizontalDrawer from './src/basic/horizontalDrawer';
import PagerAndDrawer from './src/basic/pagerAndDrawer';
import ForceTouch from './src/basic/forcetouch';
import Fling from './src/basic/fling';
import WebStylesResetExample from './src/release_tests/webStylesReset';

import ReanimatedSimple from './src/new_api/reanimated';
import Camera from './src/new_api/camera';
Expand Down Expand Up @@ -151,6 +152,7 @@ const EXAMPLES: ExamplesSection[] = [
{ name: 'Swipeable Reanimation', component: SwipeableReanimation },
{ name: 'RectButton (borders)', component: RectButtonBorders },
{ name: 'Gesturized pressable', component: GesturizedPressable },
{ name: 'Web styles reset', component: WebStylesResetExample },
],
},
{
Expand Down
99 changes: 99 additions & 0 deletions example/src/release_tests/webStylesReset/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
Gesture,
GestureDetector,
Pressable,
} 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 [x, setX] = useState(0);
const [y, setY] = useState(0);

const colorProgress = useSharedValue(0);

const animatedStyles = useAnimatedStyle(() => {
const backgroundColor = interpolateColor(
colorProgress.value,
[0, 1],
[Colors.enabled, Colors.disabled]
);

return { backgroundColor };
});

const g = Gesture.Pan()
.onUpdate((e) => {
setX(e.x);
setY(e.y);
})
.enabled(enabled);

return (
<View style={[styles.container, styles.centered]}>
<GestureDetector gesture={g} enableContextMenu={false}>
<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>

<Text style={{ fontSize: 16 }}>
{' '}
x: {x}, y: {y}{' '}
</Text>
</View>
);
}

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,
},
});
4 changes: 3 additions & 1 deletion src/web/handlers/GestureHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import EventManager from '../tools/EventManager';
import GestureHandlerOrchestrator from '../tools/GestureHandlerOrchestrator';
import InteractionManager from '../tools/InteractionManager';
import PointerTracker, { TrackerElement } from '../tools/PointerTracker';
import { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate';
import IGestureHandler from './IGestureHandler';
import { MouseButton } from '../../handlers/gestureHandlerCommon';
import { PointerType } from '../../PointerType';
import { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate';

export default abstract class GestureHandler implements IGestureHandler {
private lastSentState: State | null = null;
Expand Down Expand Up @@ -589,6 +589,8 @@ export default abstract class GestureHandler implements IGestureHandler {
this.config = { enabled: enabled, ...props };
this.enabled = enabled;

this.delegate.onEnabledChange(enabled);

if (this.config.shouldCancelWhenOutside !== undefined) {
this.setShouldCancelWhenOutside(this.config.shouldCancelWhenOutside);
}
Expand Down
2 changes: 1 addition & 1 deletion src/web/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type ConfigArgs =
| undefined;

export interface Config extends Record<string, ConfigArgs> {
enabled?: boolean;
enabled: boolean;
simultaneousHandlers?: Handler[] | null;
waitFor?: Handler[] | null;
blocksHandlers?: Handler[] | null;
Expand Down
1 change: 1 addition & 0 deletions src/web/tools/GestureHandlerDelegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface GestureHandlerDelegate<TComponent, THandler> {
onEnd(): void;
onCancel(): void;
onFail(): void;
onEnabledChange(enabled: boolean): void;

destroy(config: Config): void;
}
73 changes: 65 additions & 8 deletions src/web/tools/GestureHandlerWebDelegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,22 @@ import { Config } from '../interfaces';
import { MouseButton } from '../../handlers/gestureHandlerCommon';
import KeyboardEventManager from './KeyboardEventManager';

interface DefaultViewStyles {
userSelect: string;
touchAction: string;
}

export class GestureHandlerWebDelegate
implements GestureHandlerDelegate<HTMLElement, IGestureHandler>
{
private isInitialized = false;
private view!: HTMLElement;
private gestureHandler!: IGestureHandler;
private eventManagers: EventManager<unknown>[] = [];
private defaultViewStyles: DefaultViewStyles = {
userSelect: '',
touchAction: '',
};

getView(): HTMLElement {
return this.view;
Expand All @@ -31,19 +41,21 @@ export class GestureHandlerWebDelegate
);
}

this.isInitialized = true;

this.gestureHandler = handler;
this.view = findNodeHandle(viewRef) as unknown as HTMLElement;

const config = handler.getConfig();
this.defaultViewStyles = {
userSelect: this.view.style.userSelect,
touchAction: this.view.style.touchAction,
};

this.addContextMenuListeners(config);
const config = handler.getConfig();

this.view.style['userSelect'] = config.userSelect ?? 'none';
this.view.style['webkitUserSelect'] = config.userSelect ?? 'none';

this.view.style['touchAction'] = config.touchAction ?? 'none';
// @ts-ignore This one disables default events on Safari
this.view.style['WebkitTouchCallout'] = 'none';
this.setUserSelect(config.enabled);
this.setTouchAction(config.enabled);
this.setContextMenu(config.enabled);

this.eventManagers.push(new PointerEventManager(this.view));
this.eventManagers.push(new TouchEventManager(this.view));
Expand Down Expand Up @@ -119,6 +131,51 @@ export class GestureHandlerWebDelegate
e.stopPropagation();
}

private setUserSelect(isHandlerEnabled: boolean) {
const { userSelect } = this.gestureHandler.getConfig();

this.view.style['userSelect'] = isHandlerEnabled
? userSelect ?? 'none'
: this.defaultViewStyles.userSelect;

this.view.style['webkitUserSelect'] = isHandlerEnabled
? userSelect ?? 'none'
: this.defaultViewStyles.userSelect;
}

private setTouchAction(isHandlerEnabled: boolean) {
const { touchAction } = this.gestureHandler.getConfig();

this.view.style['touchAction'] = isHandlerEnabled
? touchAction ?? 'none'
: this.defaultViewStyles.touchAction;

// @ts-ignore This one disables default events on Safari
this.view.style['WebkitTouchCallout'] = isHandlerEnabled
? touchAction ?? 'none'
: this.defaultViewStyles.touchAction;
}

private setContextMenu(isHandlerEnabled: boolean) {
const config = this.gestureHandler.getConfig();

if (isHandlerEnabled) {
this.addContextMenuListeners(config);
} else {
this.removeContextMenuListeners(config);
}
}

onEnabledChange(enabled: boolean): void {
if (!this.isInitialized) {
return;
}

this.setUserSelect(enabled);
this.setTouchAction(enabled);
this.setContextMenu(enabled);
}

onBegin(): void {
// no-op for now
}
Expand Down

0 comments on commit f48d46c

Please sign in to comment.