@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.
Files changed (52) hide show
  1. package/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js +47 -0
  2. package/lib/commonjs/TransitionConfigs/SceneStyleInterpolators.js.map +1 -0
  3. package/lib/commonjs/TransitionConfigs/TransitionPresets.js +19 -0
  4. package/lib/commonjs/TransitionConfigs/TransitionPresets.js.map +1 -0
  5. package/lib/commonjs/TransitionConfigs/TransitionSpecs.js +24 -0
  6. package/lib/commonjs/TransitionConfigs/TransitionSpecs.js.map +1 -0
  7. package/lib/commonjs/index.js +9 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/utils/useAnimatedHashMap.js +31 -0
  10. package/lib/commonjs/utils/useAnimatedHashMap.js.map +1 -0
  11. package/lib/commonjs/views/BottomTabView.js +74 -4
  12. package/lib/commonjs/views/BottomTabView.js.map +1 -1
  13. package/lib/commonjs/views/ScreenFallback.js +6 -8
  14. package/lib/commonjs/views/ScreenFallback.js.map +1 -1
  15. package/lib/module/TransitionConfigs/SceneStyleInterpolators.js +40 -0
  16. package/lib/module/TransitionConfigs/SceneStyleInterpolators.js.map +1 -0
  17. package/lib/module/TransitionConfigs/TransitionPresets.js +11 -0
  18. package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -0
  19. package/lib/module/TransitionConfigs/TransitionSpecs.js +16 -0
  20. package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -0
  21. package/lib/module/index.js +9 -0
  22. package/lib/module/index.js.map +1 -1
  23. package/lib/module/utils/useAnimatedHashMap.js +23 -0
  24. package/lib/module/utils/useAnimatedHashMap.js.map +1 -0
  25. package/lib/module/views/BottomTabView.js +75 -5
  26. package/lib/module/views/BottomTabView.js.map +1 -1
  27. package/lib/module/views/ScreenFallback.js +6 -8
  28. package/lib/module/views/ScreenFallback.js.map +1 -1
  29. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts +10 -0
  30. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts.map +1 -0
  31. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +4 -0
  32. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -0
  33. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +4 -0
  34. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -0
  35. package/lib/typescript/src/index.d.ts +7 -0
  36. package/lib/typescript/src/index.d.ts.map +1 -1
  37. package/lib/typescript/src/types.d.ts +50 -0
  38. package/lib/typescript/src/types.d.ts.map +1 -1
  39. package/lib/typescript/src/utils/useAnimatedHashMap.d.ts +4 -0
  40. package/lib/typescript/src/utils/useAnimatedHashMap.d.ts.map +1 -0
  41. package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -1
  42. package/lib/typescript/src/views/ScreenFallback.d.ts +4 -4
  43. package/lib/typescript/src/views/ScreenFallback.d.ts.map +1 -1
  44. package/package.json +5 -5
  45. package/src/TransitionConfigs/SceneStyleInterpolators.tsx +44 -0
  46. package/src/TransitionConfigs/TransitionPresets.tsx +13 -0
  47. package/src/TransitionConfigs/TransitionSpecs.tsx +19 -0
  48. package/src/index.tsx +9 -0
  49. package/src/types.tsx +70 -0
  50. package/src/utils/useAnimatedHashMap.tsx +25 -0
  51. package/src/views/BottomTabView.tsx +93 -6
  52. 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 { lazy = true, unmountOnBlur } = descriptor.options;
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
- visible={isFocused}
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({ visible, children, ...rest }: Props) {
35
+ export function MaybeScreen({ enabled, active, ...rest }: ViewProps & Props) {
37
36
  if (Screens?.screensEnabled?.()) {
38
37
  return (
39
- <Screens.Screen activityState={visible ? 2 : 0} {...rest}>
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
  }