@react-navigation/bottom-tabs 7.0.0-alpha.2 → 7.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js +47 -0
- package/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js.map +1 -0
- package/lib/commonjs/TransitionConfigs/TransitionPresets.js +19 -0
- package/lib/commonjs/TransitionConfigs/TransitionPresets.js.map +1 -0
- package/lib/commonjs/TransitionConfigs/TransitionSpecs.js +24 -0
- package/lib/commonjs/TransitionConfigs/TransitionSpecs.js.map +1 -0
- package/lib/commonjs/index.js +9 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/useAnimatedHashMap.js +31 -0
- package/lib/commonjs/utils/useAnimatedHashMap.js.map +1 -0
- package/lib/commonjs/views/BottomTabView.js +74 -4
- package/lib/commonjs/views/BottomTabView.js.map +1 -1
- package/lib/commonjs/views/ScreenFallback.js +6 -8
- package/lib/commonjs/views/ScreenFallback.js.map +1 -1
- package/lib/module/TransitionConfigs/SceneStyleInterpolators.js +40 -0
- package/lib/module/TransitionConfigs/SceneStyleInterpolators.js.map +1 -0
- package/lib/module/TransitionConfigs/TransitionPresets.js +11 -0
- package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -0
- package/lib/module/TransitionConfigs/TransitionSpecs.js +16 -0
- package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -0
- package/lib/module/index.js +9 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/useAnimatedHashMap.js +23 -0
- package/lib/module/utils/useAnimatedHashMap.js.map +1 -0
- package/lib/module/views/BottomTabView.js +75 -5
- package/lib/module/views/BottomTabView.js.map +1 -1
- package/lib/module/views/ScreenFallback.js +6 -8
- package/lib/module/views/ScreenFallback.js.map +1 -1
- package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts +10 -0
- package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts.map +1 -0
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +4 -0
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -0
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +4 -0
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +50 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/useAnimatedHashMap.d.ts +4 -0
- package/lib/typescript/src/utils/useAnimatedHashMap.d.ts.map +1 -0
- package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -1
- package/lib/typescript/src/views/ScreenFallback.d.ts +4 -4
- package/lib/typescript/src/views/ScreenFallback.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/TransitionConfigs/SceneStyleInterpolators.tsx +44 -0
- package/src/TransitionConfigs/TransitionPresets.tsx +13 -0
- package/src/TransitionConfigs/TransitionSpecs.tsx +19 -0
- package/src/index.tsx +9 -0
- package/src/types.tsx +70 -0
- package/src/utils/useAnimatedHashMap.tsx +25 -0
- package/src/views/BottomTabView.tsx +93 -6
- package/src/views/ScreenFallback.tsx +6 -13
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
TabNavigationState,
|
|
10
10
|
} from '@react-navigation/native';
|
|
11
11
|
import * as React from 'react';
|
|
12
|
-
import { Platform, StyleSheet } from 'react-native';
|
|
12
|
+
import { Animated, Platform, StyleSheet } from 'react-native';
|
|
13
13
|
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
|
14
14
|
|
|
15
15
|
import type {
|
|
@@ -18,12 +18,15 @@ import type {
|
|
|
18
18
|
BottomTabHeaderProps,
|
|
19
19
|
BottomTabNavigationConfig,
|
|
20
20
|
BottomTabNavigationHelpers,
|
|
21
|
+
BottomTabNavigationOptions,
|
|
21
22
|
BottomTabNavigationProp,
|
|
22
23
|
} from '../types';
|
|
23
24
|
import { BottomTabBarHeightCallbackContext } from '../utils/BottomTabBarHeightCallbackContext';
|
|
24
25
|
import { BottomTabBarHeightContext } from '../utils/BottomTabBarHeightContext';
|
|
26
|
+
import { useAnimatedHashMap } from '../utils/useAnimatedHashMap';
|
|
25
27
|
import { BottomTabBar, getTabBarHeight } from './BottomTabBar';
|
|
26
28
|
import { MaybeScreen, MaybeScreenContainer } from './ScreenFallback';
|
|
29
|
+
import CompositeAnimation = Animated.CompositeAnimation;
|
|
27
30
|
|
|
28
31
|
type Props = BottomTabNavigationConfig & {
|
|
29
32
|
state: TabNavigationState<ParamListBase>;
|
|
@@ -31,6 +34,21 @@ type Props = BottomTabNavigationConfig & {
|
|
|
31
34
|
descriptors: BottomTabDescriptorMap;
|
|
32
35
|
};
|
|
33
36
|
|
|
37
|
+
const EPSILON = 1e-5;
|
|
38
|
+
const STATE_INACTIVE = 0;
|
|
39
|
+
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
|
|
40
|
+
const STATE_ON_TOP = 2;
|
|
41
|
+
|
|
42
|
+
const hasAnimation = (options: BottomTabNavigationOptions) => {
|
|
43
|
+
const { animationEnabled, transitionSpec } = options;
|
|
44
|
+
|
|
45
|
+
if (animationEnabled === false || !transitionSpec) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
|
|
34
52
|
export function BottomTabView(props: Props) {
|
|
35
53
|
const {
|
|
36
54
|
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
|
|
@@ -43,14 +61,54 @@ export function BottomTabView(props: Props) {
|
|
|
43
61
|
Platform.OS === 'ios',
|
|
44
62
|
sceneContainerStyle,
|
|
45
63
|
} = props;
|
|
46
|
-
|
|
47
64
|
const focusedRouteKey = state.routes[state.index].key;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List of loaded tabs, tabs will be loaded when navigated to.
|
|
68
|
+
*/
|
|
48
69
|
const [loaded, setLoaded] = React.useState([focusedRouteKey]);
|
|
49
70
|
|
|
50
71
|
if (!loaded.includes(focusedRouteKey)) {
|
|
72
|
+
// Set the current tab to be loaded if it was not loaded before
|
|
51
73
|
setLoaded([...loaded, focusedRouteKey]);
|
|
52
74
|
}
|
|
53
75
|
|
|
76
|
+
const tabAnims = useAnimatedHashMap(state);
|
|
77
|
+
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
const animateToIndex = () => {
|
|
80
|
+
Animated.parallel(
|
|
81
|
+
state.routes
|
|
82
|
+
.map((route, index) => {
|
|
83
|
+
const { options } = descriptors[route.key];
|
|
84
|
+
const { transitionSpec } = options;
|
|
85
|
+
|
|
86
|
+
const animationEnabled = hasAnimation(options);
|
|
87
|
+
|
|
88
|
+
const toValue =
|
|
89
|
+
index === state.index ? 0 : index >= state.index ? 1 : -1;
|
|
90
|
+
|
|
91
|
+
if (!animationEnabled || !transitionSpec) {
|
|
92
|
+
return Animated.timing(tabAnims[route.key], {
|
|
93
|
+
toValue,
|
|
94
|
+
duration: 0,
|
|
95
|
+
useNativeDriver: true,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Animated[transitionSpec.animation](tabAnims[route.key], {
|
|
100
|
+
...transitionSpec.config,
|
|
101
|
+
toValue,
|
|
102
|
+
useNativeDriver: true,
|
|
103
|
+
});
|
|
104
|
+
})
|
|
105
|
+
.filter(Boolean) as CompositeAnimation[]
|
|
106
|
+
).start();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
animateToIndex();
|
|
110
|
+
}, [descriptors, state.index, state.routes, tabAnims]);
|
|
111
|
+
|
|
54
112
|
const dimensions = SafeAreaProviderCompat.initialMetrics.frame;
|
|
55
113
|
const [tabBarHeight, setTabBarHeight] = React.useState(() =>
|
|
56
114
|
getTabBarHeight({
|
|
@@ -88,16 +146,25 @@ export function BottomTabView(props: Props) {
|
|
|
88
146
|
|
|
89
147
|
const { routes } = state;
|
|
90
148
|
|
|
149
|
+
// If there is no animation, we only have 2 states: visible and invisible
|
|
150
|
+
const hasTwoStates = !routes.some((route) =>
|
|
151
|
+
hasAnimation(descriptors[route.key].options)
|
|
152
|
+
);
|
|
153
|
+
|
|
91
154
|
return (
|
|
92
155
|
<SafeAreaProviderCompat>
|
|
93
156
|
<MaybeScreenContainer
|
|
94
157
|
enabled={detachInactiveScreens}
|
|
95
|
-
hasTwoStates
|
|
158
|
+
hasTwoStates={hasTwoStates}
|
|
96
159
|
style={styles.container}
|
|
97
160
|
>
|
|
98
161
|
{routes.map((route, index) => {
|
|
99
162
|
const descriptor = descriptors[route.key];
|
|
100
|
-
const {
|
|
163
|
+
const {
|
|
164
|
+
lazy = true,
|
|
165
|
+
unmountOnBlur,
|
|
166
|
+
sceneStyleInterpolator,
|
|
167
|
+
} = descriptor.options;
|
|
101
168
|
const isFocused = state.index === index;
|
|
102
169
|
|
|
103
170
|
if (unmountOnBlur && !isFocused) {
|
|
@@ -123,11 +190,31 @@ export function BottomTabView(props: Props) {
|
|
|
123
190
|
headerTransparent,
|
|
124
191
|
} = descriptor.options;
|
|
125
192
|
|
|
193
|
+
const { sceneStyle } =
|
|
194
|
+
sceneStyleInterpolator?.({
|
|
195
|
+
current: tabAnims[route.key],
|
|
196
|
+
}) ?? {};
|
|
197
|
+
|
|
198
|
+
const animationEnabled = hasAnimation(descriptor.options);
|
|
199
|
+
const activityState = isFocused
|
|
200
|
+
? STATE_ON_TOP // the screen is on top after the transition
|
|
201
|
+
: animationEnabled // is animation is not enabled, immediately move to inactive state
|
|
202
|
+
? tabAnims[route.key].interpolate({
|
|
203
|
+
inputRange: [0, 1 - EPSILON, 1],
|
|
204
|
+
outputRange: [
|
|
205
|
+
STATE_TRANSITIONING_OR_BELOW_TOP, // screen visible during transition
|
|
206
|
+
STATE_TRANSITIONING_OR_BELOW_TOP,
|
|
207
|
+
STATE_INACTIVE, // the screen is detached after transition
|
|
208
|
+
],
|
|
209
|
+
extrapolate: 'extend',
|
|
210
|
+
})
|
|
211
|
+
: STATE_INACTIVE;
|
|
212
|
+
|
|
126
213
|
return (
|
|
127
214
|
<MaybeScreen
|
|
128
215
|
key={route.key}
|
|
129
216
|
style={[StyleSheet.absoluteFill, { zIndex: isFocused ? 0 : -1 }]}
|
|
130
|
-
|
|
217
|
+
active={activityState}
|
|
131
218
|
enabled={detachInactiveScreens}
|
|
132
219
|
freezeOnBlur={freezeOnBlur}
|
|
133
220
|
>
|
|
@@ -146,7 +233,7 @@ export function BottomTabView(props: Props) {
|
|
|
146
233
|
descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
|
|
147
234
|
options: descriptor.options,
|
|
148
235
|
})}
|
|
149
|
-
style={sceneContainerStyle}
|
|
236
|
+
style={[sceneContainerStyle, animationEnabled && sceneStyle]}
|
|
150
237
|
>
|
|
151
238
|
{descriptor.render()}
|
|
152
239
|
</Screen>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { ResourceSavingView } from '@react-navigation/elements';
|
|
2
1
|
import * as React from 'react';
|
|
3
|
-
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
|
|
2
|
+
import { Animated, StyleProp, View, ViewProps, ViewStyle } from 'react-native';
|
|
4
3
|
|
|
5
4
|
type Props = {
|
|
6
|
-
visible: boolean;
|
|
7
|
-
children: React.ReactNode;
|
|
8
5
|
enabled: boolean;
|
|
6
|
+
active: 0 | 1 | 2 | Animated.AnimatedInterpolation<0 | 1>;
|
|
7
|
+
children: React.ReactNode;
|
|
9
8
|
freezeOnBlur?: boolean;
|
|
10
9
|
style?: StyleProp<ViewStyle>;
|
|
11
10
|
};
|
|
@@ -33,18 +32,12 @@ export const MaybeScreenContainer = ({
|
|
|
33
32
|
return <View {...rest} />;
|
|
34
33
|
};
|
|
35
34
|
|
|
36
|
-
export function MaybeScreen({
|
|
35
|
+
export function MaybeScreen({ enabled, active, ...rest }: ViewProps & Props) {
|
|
37
36
|
if (Screens?.screensEnabled?.()) {
|
|
38
37
|
return (
|
|
39
|
-
<Screens.Screen activityState={
|
|
40
|
-
{children}
|
|
41
|
-
</Screens.Screen>
|
|
38
|
+
<Screens.Screen enabled={enabled} activityState={active} {...rest} />
|
|
42
39
|
);
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
return
|
|
46
|
-
<ResourceSavingView visible={visible} {...rest}>
|
|
47
|
-
{children}
|
|
48
|
-
</ResourceSavingView>
|
|
49
|
-
);
|
|
42
|
+
return <View {...rest} />;
|
|
50
43
|
}
|