Skip to content
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

[Web] Reset web-exclusive properties when handler is disabled. #3041

Merged
merged 8 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 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 @@
{ name: 'Swipeable Reanimation', component: SwipeableReanimation },
{ name: 'RectButton (borders)', component: RectButtonBorders },
{ name: 'Gesturized pressable', component: GesturizedPressable },
{ name: 'Web styles reset', component: WebStylesResetExample },
],
},
{
Expand Down Expand Up @@ -271,7 +273,7 @@
renderSectionHeader={({ section: { sectionTitle } }) => (
<Text style={styles.sectionTitle}>{sectionTitle}</Text>
)}
ItemSeparatorComponent={() => <View style={styles.separator} />}

Check warning on line 276 in example/App.tsx

View workflow job for this annotation

GitHub Actions / check (example)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “MainScreen” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
/>
</SafeAreaView>
);
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;
m-bert marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading