Skip to content

Commit ecb3e5b

Browse files
committed
fix(android): respect pointer events on gesture root views
1 parent 736318e commit ecb3e5b

4 files changed

Lines changed: 118 additions & 0 deletions

File tree

‎apps/basic-example/src/App.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler';
55
import ContentsButton from './ContentsButton';
66
import NativeDetector from './NativeDetector';
77
import Navigator from './Navigator';
8+
import PointerEventsRepro from './PointerEventsRepro';
89
import RuntimeDecoration from './RuntimeDecoration';
910
import Text from './Text';
1011

@@ -25,6 +26,10 @@ const EXAMPLES = [
2526
name: 'Contents Button',
2627
component: ContentsButton,
2728
},
29+
{
30+
name: 'Root Pointer Events',
31+
component: PointerEventsRepro,
32+
},
2833
];
2934

3035
const Stack = Navigator.create();
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import * as React from 'react';
2+
import {
3+
Button,
4+
Pressable,
5+
StyleSheet,
6+
Text,
7+
View,
8+
type ViewProps,
9+
} from 'react-native';
10+
import {
11+
GestureDetector,
12+
GestureHandlerRootView,
13+
useTapGesture,
14+
} from 'react-native-gesture-handler';
15+
16+
export default function PointerEventsRepro() {
17+
const [mode, setMode] =
18+
React.useState<ViewProps['pointerEvents']>('box-none');
19+
const [plain, setPlain] = React.useState(false);
20+
const [background, setBackground] = React.useState(0);
21+
const [child, setChild] = React.useState(0);
22+
const [gesture, setGesture] = React.useState(0);
23+
const tap = useTapGesture({
24+
runOnJS: true,
25+
onActivate: () => setGesture((n) => n + 1),
26+
});
27+
const Wrapper = plain ? View : GestureHandlerRootView;
28+
return (
29+
<GestureHandlerRootView style={styles.screen}>
30+
<Text>Root pointerEvents regression</Text>
31+
<Text testID="status">
32+
{plain ? 'View' : 'Root'} / {mode ?? 'unset'} / background {background}{' '}
33+
/ child {child} / gesture {gesture}
34+
</Text>
35+
<Button
36+
title={plain ? 'Use Root' : 'Use View'}
37+
onPress={() => setPlain(!plain)}
38+
/>
39+
<View style={styles.controls}>
40+
{(['auto', 'none', 'box-only', 'box-none', undefined] as const).map(
41+
(value) => (
42+
<Button
43+
key={value ?? 'unset'}
44+
title={value ?? 'unset'}
45+
onPress={() => setMode(value)}
46+
/>
47+
)
48+
)}
49+
</View>
50+
<Button
51+
title="Reset counts"
52+
onPress={() => {
53+
setBackground(0);
54+
setChild(0);
55+
setGesture(0);
56+
}}
57+
/>
58+
<View style={styles.area}>
59+
<Pressable
60+
accessibilityLabel="Background"
61+
style={StyleSheet.absoluteFill}
62+
onPress={() => setBackground((n) => n + 1)}>
63+
<Text style={{ marginTop: 250 }}>Background</Text>
64+
</Pressable>
65+
<Wrapper pointerEvents={mode} style={styles.overlay}>
66+
<View pointerEvents="none" style={styles.side}>
67+
<Text>Empty side</Text>
68+
</View>
69+
<Pressable
70+
accessibilityLabel="Child press"
71+
style={styles.child}
72+
onPress={() => setChild((n) => n + 1)}>
73+
<Text>Child press</Text>
74+
</Pressable>
75+
<GestureDetector gesture={tap}>
76+
<View
77+
accessible
78+
accessibilityLabel="Child gesture"
79+
style={styles.child}>
80+
<Text>Child gesture</Text>
81+
</View>
82+
</GestureDetector>
83+
</Wrapper>
84+
</View>
85+
</GestureHandlerRootView>
86+
);
87+
}
88+
const styles = StyleSheet.create({
89+
screen: { flex: 1, paddingTop: 48, backgroundColor: 'white' },
90+
controls: { flexDirection: 'row', flexWrap: 'wrap' },
91+
area: { height: 350, margin: 16, backgroundColor: '#ddf' },
92+
overlay: { height: 230, backgroundColor: '#fdd8', alignItems: 'center' },
93+
side: { position: 'absolute', left: 4, top: 90, width: 70 },
94+
child: {
95+
width: 160,
96+
height: 75,
97+
margin: 12,
98+
alignItems: 'center',
99+
justifyContent: 'center',
100+
backgroundColor: '#9d9',
101+
},
102+
});

‎packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootViewManager.kt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.swmansion.gesturehandler.react
22

33
import com.facebook.react.module.annotations.ReactModule
4+
import com.facebook.react.uimanager.PointerEvents.Companion.parsePointerEvents
45
import com.facebook.react.uimanager.ThemedReactContext
56
import com.facebook.react.uimanager.ViewGroupManager
67
import com.facebook.react.uimanager.ViewManagerDelegate
@@ -39,6 +40,11 @@ class RNGestureHandlerRootViewManager :
3940
view.setModuleId(value)
4041
}
4142

43+
@ReactProp(name = "pointerEvents")
44+
override fun setPointerEvents(view: RNGestureHandlerRootView, pointerEventsStr: String?) {
45+
view.pointerEvents = parsePointerEvents(pointerEventsStr)
46+
}
47+
4248
@ReactProp(name = "unstable_forceActive")
4349
override fun setUnstable_forceActive(view: RNGestureHandlerRootView, active: Boolean) {
4450
view.setUnstableForceActive(active)

‎packages/react-native-gesture-handler/src/specs/RNGestureHandlerRootViewNativeComponent.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ export interface RootViewNativeProps extends ViewProps {
66
unstable_forceActive?: boolean;
77
}
88

9+
// @ts-expect-error WithDefault adds null for codegen, unlike ViewProps.pointerEvents.
910
interface NativeProps extends ViewProps {
11+
pointerEvents?: CodegenTypes.WithDefault<
12+
'box-none' | 'none' | 'box-only' | 'auto',
13+
'auto'
14+
>;
1015
moduleId?: CodegenTypes.WithDefault<CodegenTypes.Int32, -1>;
1116
unstable_forceActive?: boolean;
1217
}

0 commit comments

Comments
 (0)