diff --git a/example/App.tsx b/example/App.tsx index de46a28dfd..725fec0961 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -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'; @@ -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 }, ], }, { diff --git a/example/src/release_tests/webStylesReset/index.tsx b/example/src/release_tests/webStylesReset/index.tsx new file mode 100644 index 0000000000..b14bfac396 --- /dev/null +++ b/example/src/release_tests/webStylesReset/index.tsx @@ -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 ( + + + + Lorem Ipsum + + + + { + setEnabled((prev) => !prev); + + colorProgress.value = withTiming(enabled ? 1 : 0, { + duration: AnimationDuration, + }); + }}> + {enabled ? 'Disable' : 'Enable'} + + + + {' '} + x: {x}, y: {y}{' '} + + + ); +} + +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, + }, +}); diff --git a/src/web/handlers/GestureHandler.ts b/src/web/handlers/GestureHandler.ts index 9ff9a357ef..d8c643d4ea 100644 --- a/src/web/handlers/GestureHandler.ts +++ b/src/web/handlers/GestureHandler.ts @@ -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; @@ -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); } diff --git a/src/web/interfaces.ts b/src/web/interfaces.ts index b42961d933..8f4cd39a0b 100644 --- a/src/web/interfaces.ts +++ b/src/web/interfaces.ts @@ -36,7 +36,7 @@ type ConfigArgs = | undefined; export interface Config extends Record { - enabled?: boolean; + enabled: boolean; simultaneousHandlers?: Handler[] | null; waitFor?: Handler[] | null; blocksHandlers?: Handler[] | null; diff --git a/src/web/tools/GestureHandlerDelegate.ts b/src/web/tools/GestureHandlerDelegate.ts index 04e6f6f797..09dd1f1d47 100644 --- a/src/web/tools/GestureHandlerDelegate.ts +++ b/src/web/tools/GestureHandlerDelegate.ts @@ -20,6 +20,7 @@ export interface GestureHandlerDelegate { onEnd(): void; onCancel(): void; onFail(): void; + onEnabledChange(enabled: boolean): void; destroy(config: Config): void; } diff --git a/src/web/tools/GestureHandlerWebDelegate.ts b/src/web/tools/GestureHandlerWebDelegate.ts index bf3a9a67e5..5929e44f73 100644 --- a/src/web/tools/GestureHandlerWebDelegate.ts +++ b/src/web/tools/GestureHandlerWebDelegate.ts @@ -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 { + private isInitialized = false; private view!: HTMLElement; private gestureHandler!: IGestureHandler; private eventManagers: EventManager[] = []; + private defaultViewStyles: DefaultViewStyles = { + userSelect: '', + touchAction: '', + }; getView(): HTMLElement { return this.view; @@ -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)); @@ -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 }