Skip to content

Commit dff741a

Browse files
authored
Stop react-native-pager-view from entering the web bundle of the example app (#4604)
## Description Since #4589 the web build of `expo-example` fails with `Unable to resolve module ../../src/private/devsupport/rndevtools/ReactDevToolsSettingsManager from react-native/Libraries/Core/setUpReactDevTools.js`. The new `Native intercept cancellation` test screen imports `react-native-pager-view` statically. The screen is registered with `unsupportedPlatforms` for web, but that only hides it from the list, Metro still resolves the import for every platform. On web `PagerViewNativeComponent` pulls in `codegenNativeCommands`, which reaches `InitializeCore` through `RendererProxy`, and `InitializeCore` requires a module that only ships `.ios.js` and `.android.js` variants. The screen is now `index.android.tsx`, with an `index.tsx` stub for the other platforms, the same pattern the legacy `pagerAndDrawer` example uses. The import never enters the web graph. ## Test plan - `expo start` web, Android and iOS bundles all build. The web bundle contains the stub and no `react-native-pager-view` module, the Android bundle contains the real screen. - `yarn ts-check` and eslint pass.
1 parent 404c091 commit dff741a

2 files changed

Lines changed: 200 additions & 194 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import React, { useState } from 'react';
2+
import {
3+
DrawerLayoutAndroid,
4+
Pressable,
5+
StyleSheet,
6+
Text,
7+
View,
8+
} from 'react-native';
9+
import { GestureDetector, useTapGesture } from 'react-native-gesture-handler';
10+
import PagerView from 'react-native-pager-view';
11+
import Animated, {
12+
interpolateColor,
13+
useAnimatedStyle,
14+
useSharedValue,
15+
withTiming,
16+
} from 'react-native-reanimated';
17+
import { scheduleOnRN } from 'react-native-worklets';
18+
19+
import { COLORS, useIndexedLogger } from '../../../common';
20+
21+
// A native view takes over the touch without the root ever seeing a
22+
// disallow-intercept request. The framework still sends ACTION_CANCEL to the
23+
// tap's detector, which is what cancels the tap below.
24+
// - Pager: swipe the inner pager starting on the box. The inner pager host
25+
// asks its ancestors not to intercept on DOWN, so the pager's own request on
26+
// MOVE stops at the already flagged page view and never reaches the root.
27+
// - Drawer: swipe the drawer open from the right edge starting on the box.
28+
// DrawerLayout intercepts on MOVE and never requests at all. With gesture
29+
// navigation the system back swipe takes the edge, use 3-button navigation.
30+
// In both cases the pager / drawer moves and the tap count stays the same.
31+
32+
type Mode = 'pager' | 'drawer';
33+
34+
type TapBoxProps = {
35+
count: number;
36+
onTap: () => void;
37+
log: (message: string) => void;
38+
};
39+
40+
function TapBox({ count, onTap, log }: TapBoxProps) {
41+
const colorProgress = useSharedValue(0);
42+
43+
const animatedStyle = useAnimatedStyle(() => ({
44+
backgroundColor: interpolateColor(
45+
colorProgress.value,
46+
[0, 1],
47+
[COLORS.NAVY, COLORS.KINDA_BLUE]
48+
),
49+
}));
50+
51+
const tap = useTapGesture({
52+
onBegin: () => {
53+
log('onBegin');
54+
colorProgress.value = withTiming(1, { duration: 100 });
55+
},
56+
onActivate: () => {
57+
log('onActivate');
58+
scheduleOnRN(onTap);
59+
},
60+
onDeactivate: () => {
61+
log('onDeactivate');
62+
},
63+
onFinalize: () => {
64+
log('onFinalize');
65+
colorProgress.value = withTiming(0, { duration: 100 });
66+
},
67+
});
68+
69+
return (
70+
<GestureDetector gesture={tap}>
71+
<Animated.View testID="tap-box" style={[styles.box, animatedStyle]}>
72+
<Text style={styles.boxText}>Tap count: {count}</Text>
73+
</Animated.View>
74+
</GestureDetector>
75+
);
76+
}
77+
78+
function PagerMode(props: TapBoxProps) {
79+
return (
80+
<PagerView style={styles.flex} initialPage={0}>
81+
<View key="outer-1" style={styles.flex}>
82+
<PagerView style={styles.flex} initialPage={0}>
83+
<View key="inner-1" style={styles.flex}>
84+
<TapBox {...props} />
85+
</View>
86+
<View key="inner-2" style={[styles.flex, styles.center]}>
87+
<Text testID="inner-page-2">Inner page 2</Text>
88+
</View>
89+
</PagerView>
90+
</View>
91+
<View key="outer-2" style={[styles.flex, styles.center]}>
92+
<Text>Outer page 2</Text>
93+
</View>
94+
</PagerView>
95+
);
96+
}
97+
98+
function DrawerMode(props: TapBoxProps) {
99+
return (
100+
<DrawerLayoutAndroid
101+
style={styles.flex}
102+
drawerWidth={300}
103+
drawerPosition="right"
104+
renderNavigationView={() => (
105+
<View testID="drawer-panel" style={[styles.flex, styles.center]}>
106+
<Text>Drawer</Text>
107+
</View>
108+
)}>
109+
<TapBox {...props} />
110+
</DrawerLayoutAndroid>
111+
);
112+
}
113+
114+
export default function NativeInterceptCancellationExample() {
115+
const [mode, setMode] = useState<Mode>('pager');
116+
const [count, setCount] = useState(0);
117+
const log = useIndexedLogger();
118+
119+
const boxProps = {
120+
count,
121+
log,
122+
onTap: () => setCount((c) => c + 1),
123+
};
124+
125+
return (
126+
<View style={styles.flex}>
127+
<View style={styles.header}>
128+
<Text style={styles.hint}>
129+
Swipe the pager or drag the drawer open from the right edge, starting
130+
on the box. The count must not change.
131+
</Text>
132+
<View style={styles.buttons}>
133+
<ModeButton mode="pager" current={mode} onSelect={setMode} />
134+
<ModeButton mode="drawer" current={mode} onSelect={setMode} />
135+
</View>
136+
</View>
137+
{mode === 'pager' ? (
138+
<PagerMode {...boxProps} />
139+
) : (
140+
<DrawerMode {...boxProps} />
141+
)}
142+
</View>
143+
);
144+
}
145+
146+
type ModeButtonProps = {
147+
mode: Mode;
148+
current: Mode;
149+
onSelect: (mode: Mode) => void;
150+
};
151+
152+
function ModeButton({ mode, current, onSelect }: ModeButtonProps) {
153+
return (
154+
<Pressable
155+
testID={`mode-${mode}`}
156+
style={[styles.button, mode === current && styles.buttonActive]}
157+
onPress={() => onSelect(mode)}>
158+
<Text style={styles.buttonText}>
159+
{mode === 'pager' ? 'Pager' : 'Drawer'}
160+
</Text>
161+
</Pressable>
162+
);
163+
}
164+
165+
const styles = StyleSheet.create({
166+
flex: { flex: 1 },
167+
center: { alignItems: 'center', justifyContent: 'center' },
168+
header: {
169+
padding: 12,
170+
gap: 8,
171+
backgroundColor: COLORS.offWhite,
172+
},
173+
hint: {
174+
textAlign: 'center',
175+
opacity: 0.6,
176+
fontSize: 14,
177+
},
178+
buttons: { flexDirection: 'row', justifyContent: 'center', gap: 8 },
179+
button: {
180+
paddingVertical: 8,
181+
paddingHorizontal: 14,
182+
borderRadius: 6,
183+
backgroundColor: COLORS.KINDA_YELLOW,
184+
},
185+
buttonActive: { backgroundColor: COLORS.YELLOW },
186+
buttonText: { fontWeight: '600', color: COLORS.NAVY },
187+
box: {
188+
flex: 1,
189+
alignItems: 'center',
190+
justifyContent: 'center',
191+
},
192+
boxText: {
193+
fontSize: 20,
194+
fontWeight: '700',
195+
color: COLORS.offWhite,
196+
},
197+
});
Lines changed: 3 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,197 +1,6 @@
1-
import React, { useState } from 'react';
2-
import {
3-
DrawerLayoutAndroid,
4-
Pressable,
5-
StyleSheet,
6-
Text,
7-
View,
8-
} from 'react-native';
9-
import { GestureDetector, useTapGesture } from 'react-native-gesture-handler';
10-
import PagerView from 'react-native-pager-view';
11-
import Animated, {
12-
interpolateColor,
13-
useAnimatedStyle,
14-
useSharedValue,
15-
withTiming,
16-
} from 'react-native-reanimated';
17-
import { scheduleOnRN } from 'react-native-worklets';
18-
19-
import { COLORS, useIndexedLogger } from '../../../common';
20-
21-
// A native view takes over the touch without the root ever seeing a
22-
// disallow-intercept request. The framework still sends ACTION_CANCEL to the
23-
// tap's detector, which is what cancels the tap below.
24-
// - Pager: swipe the inner pager starting on the box. The inner pager host
25-
// asks its ancestors not to intercept on DOWN, so the pager's own request on
26-
// MOVE stops at the already flagged page view and never reaches the root.
27-
// - Drawer: swipe the drawer open from the right edge starting on the box.
28-
// DrawerLayout intercepts on MOVE and never requests at all. With gesture
29-
// navigation the system back swipe takes the edge, use 3-button navigation.
30-
// In both cases the pager / drawer moves and the tap count stays the same.
31-
32-
type Mode = 'pager' | 'drawer';
33-
34-
type TapBoxProps = {
35-
count: number;
36-
onTap: () => void;
37-
log: (message: string) => void;
38-
};
39-
40-
function TapBox({ count, onTap, log }: TapBoxProps) {
41-
const colorProgress = useSharedValue(0);
42-
43-
const animatedStyle = useAnimatedStyle(() => ({
44-
backgroundColor: interpolateColor(
45-
colorProgress.value,
46-
[0, 1],
47-
[COLORS.NAVY, COLORS.KINDA_BLUE]
48-
),
49-
}));
50-
51-
const tap = useTapGesture({
52-
onBegin: () => {
53-
log('onBegin');
54-
colorProgress.value = withTiming(1, { duration: 100 });
55-
},
56-
onActivate: () => {
57-
log('onActivate');
58-
scheduleOnRN(onTap);
59-
},
60-
onDeactivate: () => {
61-
log('onDeactivate');
62-
},
63-
onFinalize: () => {
64-
log('onFinalize');
65-
colorProgress.value = withTiming(0, { duration: 100 });
66-
},
67-
});
68-
69-
return (
70-
<GestureDetector gesture={tap}>
71-
<Animated.View testID="tap-box" style={[styles.box, animatedStyle]}>
72-
<Text style={styles.boxText}>Tap count: {count}</Text>
73-
</Animated.View>
74-
</GestureDetector>
75-
);
76-
}
77-
78-
function PagerMode(props: TapBoxProps) {
79-
return (
80-
<PagerView style={styles.flex} initialPage={0}>
81-
<View key="outer-1" style={styles.flex}>
82-
<PagerView style={styles.flex} initialPage={0}>
83-
<View key="inner-1" style={styles.flex}>
84-
<TapBox {...props} />
85-
</View>
86-
<View key="inner-2" style={[styles.flex, styles.center]}>
87-
<Text testID="inner-page-2">Inner page 2</Text>
88-
</View>
89-
</PagerView>
90-
</View>
91-
<View key="outer-2" style={[styles.flex, styles.center]}>
92-
<Text>Outer page 2</Text>
93-
</View>
94-
</PagerView>
95-
);
96-
}
97-
98-
function DrawerMode(props: TapBoxProps) {
99-
return (
100-
<DrawerLayoutAndroid
101-
style={styles.flex}
102-
drawerWidth={300}
103-
drawerPosition="right"
104-
renderNavigationView={() => (
105-
<View testID="drawer-panel" style={[styles.flex, styles.center]}>
106-
<Text>Drawer</Text>
107-
</View>
108-
)}>
109-
<TapBox {...props} />
110-
</DrawerLayoutAndroid>
111-
);
112-
}
1+
import React from 'react';
2+
import { Text } from 'react-native';
1133

1144
export default function NativeInterceptCancellationExample() {
115-
const [mode, setMode] = useState<Mode>('pager');
116-
const [count, setCount] = useState(0);
117-
const log = useIndexedLogger();
118-
119-
const boxProps = {
120-
count,
121-
log,
122-
onTap: () => setCount((c) => c + 1),
123-
};
124-
125-
return (
126-
<View style={styles.flex}>
127-
<View style={styles.header}>
128-
<Text style={styles.hint}>
129-
Swipe the pager or drag the drawer open from the right edge, starting
130-
on the box. The count must not change.
131-
</Text>
132-
<View style={styles.buttons}>
133-
<ModeButton mode="pager" current={mode} onSelect={setMode} />
134-
<ModeButton mode="drawer" current={mode} onSelect={setMode} />
135-
</View>
136-
</View>
137-
{mode === 'pager' ? (
138-
<PagerMode {...boxProps} />
139-
) : (
140-
<DrawerMode {...boxProps} />
141-
)}
142-
</View>
143-
);
5+
return <Text>Sorry, this is a demo of Android-only native components</Text>;
1446
}
145-
146-
type ModeButtonProps = {
147-
mode: Mode;
148-
current: Mode;
149-
onSelect: (mode: Mode) => void;
150-
};
151-
152-
function ModeButton({ mode, current, onSelect }: ModeButtonProps) {
153-
return (
154-
<Pressable
155-
testID={`mode-${mode}`}
156-
style={[styles.button, mode === current && styles.buttonActive]}
157-
onPress={() => onSelect(mode)}>
158-
<Text style={styles.buttonText}>
159-
{mode === 'pager' ? 'Pager' : 'Drawer'}
160-
</Text>
161-
</Pressable>
162-
);
163-
}
164-
165-
const styles = StyleSheet.create({
166-
flex: { flex: 1 },
167-
center: { alignItems: 'center', justifyContent: 'center' },
168-
header: {
169-
padding: 12,
170-
gap: 8,
171-
backgroundColor: COLORS.offWhite,
172-
},
173-
hint: {
174-
textAlign: 'center',
175-
opacity: 0.6,
176-
fontSize: 14,
177-
},
178-
buttons: { flexDirection: 'row', justifyContent: 'center', gap: 8 },
179-
button: {
180-
paddingVertical: 8,
181-
paddingHorizontal: 14,
182-
borderRadius: 6,
183-
backgroundColor: COLORS.KINDA_YELLOW,
184-
},
185-
buttonActive: { backgroundColor: COLORS.YELLOW },
186-
buttonText: { fontWeight: '600', color: COLORS.NAVY },
187-
box: {
188-
flex: 1,
189-
alignItems: 'center',
190-
justifyContent: 'center',
191-
},
192-
boxText: {
193-
fontSize: 20,
194-
fontWeight: '700',
195-
color: COLORS.offWhite,
196-
},
197-
});

0 commit comments

Comments
 (0)