@react-navigation/bottom-tabs 8.0.0-alpha.4 → 8.0.0-alpha.41

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 (82) hide show
  1. package/lib/module/index.js +1 -1
  2. package/lib/module/index.js.map +1 -1
  3. package/lib/module/navigators/createBottomTabNavigator.js +4 -25
  4. package/lib/module/navigators/createBottomTabNavigator.js.map +1 -1
  5. package/lib/module/utils/useBottomTabAnimation.js +1 -1
  6. package/lib/module/utils/useBottomTabAnimation.js.map +1 -1
  7. package/lib/module/utils/useBottomTabBarHeight.js +1 -1
  8. package/lib/module/utils/useBottomTabBarHeight.js.map +1 -1
  9. package/lib/module/views/BottomTabBar.js +40 -29
  10. package/lib/module/views/BottomTabBar.js.map +1 -1
  11. package/lib/module/views/BottomTabItem.js +2 -2
  12. package/lib/module/views/BottomTabItem.js.map +1 -1
  13. package/lib/module/views/{BottomTabViewCommon.js → BottomTabView.js} +1 -1
  14. package/lib/module/views/BottomTabView.js.map +1 -0
  15. package/lib/module/views/BottomTabViewCustom.js +90 -71
  16. package/lib/module/views/BottomTabViewCustom.js.map +1 -1
  17. package/lib/module/views/BottomTabViewNativeImpl.js +333 -115
  18. package/lib/module/views/BottomTabViewNativeImpl.js.map +1 -1
  19. package/lib/module/views/Deferred.js +41 -0
  20. package/lib/module/views/Deferred.js.map +1 -0
  21. package/lib/module/views/ScreenContent.js.map +1 -1
  22. package/lib/module/views/TabBarIcon.js +11 -48
  23. package/lib/module/views/TabBarIcon.js.map +1 -1
  24. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts +1 -1
  25. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts.map +1 -1
  26. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +1 -1
  27. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  28. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +1 -1
  29. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
  30. package/lib/typescript/src/index.d.ts +11 -11
  31. package/lib/typescript/src/index.d.ts.map +1 -1
  32. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts +9 -15
  33. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts.map +1 -1
  34. package/lib/typescript/src/types.d.ts +64 -102
  35. package/lib/typescript/src/types.d.ts.map +1 -1
  36. package/lib/typescript/src/utils/BottomTabAnimationContext.d.ts +1 -1
  37. package/lib/typescript/src/utils/BottomTabAnimationContext.d.ts.map +1 -1
  38. package/lib/typescript/src/utils/useAnimatedHashMap.d.ts +1 -1
  39. package/lib/typescript/src/utils/useAnimatedHashMap.d.ts.map +1 -1
  40. package/lib/typescript/src/utils/useBottomTabAnimation.d.ts +1 -1
  41. package/lib/typescript/src/utils/useBottomTabAnimation.d.ts.map +1 -1
  42. package/lib/typescript/src/utils/useTabBarPosition.d.ts +1 -1
  43. package/lib/typescript/src/utils/useTabBarPosition.d.ts.map +1 -1
  44. package/lib/typescript/src/views/BottomTabBar.d.ts +3 -2
  45. package/lib/typescript/src/views/BottomTabBar.d.ts.map +1 -1
  46. package/lib/typescript/src/views/BottomTabItem.d.ts +20 -20
  47. package/lib/typescript/src/views/BottomTabItem.d.ts.map +1 -1
  48. package/lib/typescript/src/views/{BottomTabViewCommon.d.ts → BottomTabView.d.ts} +3 -3
  49. package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -0
  50. package/lib/typescript/src/views/BottomTabViewCustom.d.ts +3 -2
  51. package/lib/typescript/src/views/BottomTabViewCustom.d.ts.map +1 -1
  52. package/lib/typescript/src/views/BottomTabViewNative.android.d.ts +1 -1
  53. package/lib/typescript/src/views/BottomTabViewNative.android.d.ts.map +1 -1
  54. package/lib/typescript/src/views/BottomTabViewNative.d.ts +1 -1
  55. package/lib/typescript/src/views/BottomTabViewNative.d.ts.map +1 -1
  56. package/lib/typescript/src/views/BottomTabViewNative.ios.d.ts +1 -1
  57. package/lib/typescript/src/views/BottomTabViewNative.ios.d.ts.map +1 -1
  58. package/lib/typescript/src/views/BottomTabViewNativeImpl.d.ts +3 -2
  59. package/lib/typescript/src/views/BottomTabViewNativeImpl.d.ts.map +1 -1
  60. package/lib/typescript/src/views/Deferred.d.ts +31 -0
  61. package/lib/typescript/src/views/Deferred.d.ts.map +1 -0
  62. package/lib/typescript/src/views/ScreenContent.d.ts +4 -4
  63. package/lib/typescript/src/views/ScreenContent.d.ts.map +1 -1
  64. package/lib/typescript/src/views/TabBarIcon.d.ts +8 -8
  65. package/lib/typescript/src/views/TabBarIcon.d.ts.map +1 -1
  66. package/package.json +19 -22
  67. package/src/index.tsx +2 -2
  68. package/src/navigators/createBottomTabNavigator.tsx +10 -75
  69. package/src/types.tsx +64 -110
  70. package/src/utils/useAnimatedHashMap.tsx +1 -1
  71. package/src/utils/useBottomTabAnimation.tsx +1 -1
  72. package/src/utils/useBottomTabBarHeight.tsx +1 -1
  73. package/src/views/BottomTabBar.tsx +68 -25
  74. package/src/views/BottomTabItem.tsx +18 -20
  75. package/src/views/BottomTabViewCustom.tsx +155 -125
  76. package/src/views/BottomTabViewNativeImpl.tsx +470 -155
  77. package/src/views/Deferred.tsx +57 -0
  78. package/src/views/ScreenContent.tsx +1 -2
  79. package/src/views/TabBarIcon.tsx +13 -56
  80. package/lib/module/views/BottomTabViewCommon.js.map +0 -1
  81. package/lib/typescript/src/views/BottomTabViewCommon.d.ts.map +0 -1
  82. /package/src/views/{BottomTabViewCommon.tsx → BottomTabView.tsx} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
+ ActivityView,
2
3
  Container,
3
- Lazy,
4
4
  SafeAreaProviderCompat,
5
5
  } from '@react-navigation/elements/internal';
6
6
  import {
@@ -17,7 +17,6 @@ import {
17
17
  StyleSheet,
18
18
  type ViewStyle,
19
19
  } from 'react-native';
20
- import { Screen, ScreenContainer } from 'react-native-screens';
21
20
 
22
21
  import {
23
22
  FadeTransition,
@@ -37,6 +36,7 @@ import { BottomTabBarHeightContext } from '../utils/BottomTabBarHeightContext';
37
36
  import { useAnimatedHashMap } from '../utils/useAnimatedHashMap';
38
37
  import { useTabBarPosition } from '../utils/useTabBarPosition';
39
38
  import { BottomTabBar, getTabBarHeight } from './BottomTabBar';
39
+ import { Deferred } from './Deferred';
40
40
  import { ScreenContent } from './ScreenContent';
41
41
 
42
42
  type Props = BottomTabNavigationConfig & {
@@ -45,11 +45,6 @@ type Props = BottomTabNavigationConfig & {
45
45
  descriptors: BottomTabDescriptorMap;
46
46
  };
47
47
 
48
- const EPSILON = 1e-5;
49
- const STATE_INACTIVE = 0;
50
- const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
51
- const STATE_ON_TOP = 2;
52
-
53
48
  const NAMED_TRANSITIONS_PRESETS = {
54
49
  fade: FadeTransition,
55
50
  shift: ShiftTransition,
@@ -83,22 +78,51 @@ export function BottomTabViewCustom({
83
78
  state,
84
79
  navigation,
85
80
  descriptors,
86
- detachInactiveScreens = Platform.OS === 'web' ||
87
- Platform.OS === 'android' ||
88
- Platform.OS === 'ios',
89
81
  }: Props) {
90
82
  const { routes } = state;
91
- const focusedRouteKey = routes[state.index].key;
83
+ const focusedRoute = routes[state.index];
84
+
85
+ if (focusedRoute == null) {
86
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
87
+ }
88
+
89
+ const focusedRouteKey = focusedRoute.key;
90
+ const focusedOptions = descriptors[focusedRouteKey]?.options;
91
+
92
+ const [loaded, setLoaded] = React.useState([focusedRouteKey]);
93
+
94
+ if (!loaded.includes(focusedRouteKey)) {
95
+ setLoaded([...loaded, focusedRouteKey]);
96
+ }
97
+
98
+ const [lastUpdate, setLastUpdate] = React.useState<{
99
+ current: string;
100
+ previous?: string;
101
+ animating: boolean;
102
+ }>({
103
+ current: focusedRouteKey,
104
+ animating: false,
105
+ });
106
+
107
+ if (lastUpdate.current !== focusedRouteKey) {
108
+ setLastUpdate({
109
+ current: focusedRouteKey,
110
+ previous: lastUpdate.current,
111
+ animating: true,
112
+ });
113
+ }
92
114
 
93
- const previousRouteKeyRef = React.useRef(focusedRouteKey);
94
115
  const tabAnims = useAnimatedHashMap(state);
95
116
 
117
+ const previousRouteKeyRef = React.useRef(focusedRouteKey);
118
+
96
119
  React.useEffect(() => {
97
120
  const previousRouteKey = previousRouteKeyRef.current;
98
121
 
99
122
  let popToTopAction: NavigationAction | undefined;
100
123
 
101
124
  if (
125
+ previousRouteKey &&
102
126
  previousRouteKey !== focusedRouteKey &&
103
127
  descriptors[previousRouteKey]?.options.popToTopOnBlur
104
128
  ) {
@@ -114,6 +138,8 @@ export function BottomTabViewCustom({
114
138
  }
115
139
  }
116
140
 
141
+ let timer: ReturnType<typeof setTimeout> | undefined;
142
+
117
143
  const animateToIndex = () => {
118
144
  if (previousRouteKey !== focusedRouteKey) {
119
145
  navigation.emit({
@@ -122,41 +148,51 @@ export function BottomTabViewCustom({
122
148
  });
123
149
  }
124
150
 
125
- Animated.parallel(
126
- state.routes
127
- .map((route, index) => {
128
- const { options } = descriptors[route.key];
129
- const {
130
- animation = 'none',
131
- transitionSpec = NAMED_TRANSITIONS_PRESETS[animation]
132
- .transitionSpec,
133
- } = options;
134
-
135
- let spec = transitionSpec;
136
-
137
- if (
138
- route.key !== previousRouteKey &&
139
- route.key !== focusedRouteKey
140
- ) {
141
- // Don't animate if the screen is not previous one or new one
142
- // This will avoid flicker for screens not involved in the transition
143
- spec = NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
144
- }
145
-
146
- spec = spec ?? NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
147
-
148
- const toValue =
149
- index === state.index ? 0 : index >= state.index ? 1 : -1;
150
-
151
- return Animated[spec.animation](tabAnims[route.key], {
152
- ...spec.config,
153
- toValue,
154
- useNativeDriver,
155
- });
156
- })
157
- .filter(Boolean) as Animated.CompositeAnimation[]
158
- ).start(({ finished }) => {
159
- if (finished && popToTopAction) {
151
+ const animations = state.routes.map((route, index) => {
152
+ const descriptor = descriptors[route.key];
153
+
154
+ if (descriptor == null) {
155
+ throw new Error(
156
+ `Couldn't find a descriptor for route '${route.key}'.`
157
+ );
158
+ }
159
+
160
+ const { options } = descriptor;
161
+ const {
162
+ animation = 'none',
163
+ transitionSpec = NAMED_TRANSITIONS_PRESETS[animation].transitionSpec,
164
+ } = options;
165
+
166
+ let spec = transitionSpec;
167
+
168
+ if (route.key !== previousRouteKey && route.key !== focusedRouteKey) {
169
+ // Don't animate if the screen is not previous one or new one
170
+ // This will avoid flicker for screens not involved in the transition
171
+ spec = NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
172
+ }
173
+
174
+ spec = spec ?? NAMED_TRANSITIONS_PRESETS.none.transitionSpec;
175
+
176
+ const toValue =
177
+ index === state.index ? 0 : index >= state.index ? 1 : -1;
178
+
179
+ const tabAnimation = tabAnims[route.key];
180
+
181
+ if (tabAnimation == null) {
182
+ throw new Error(
183
+ `Couldn't find an animation for route '${route.key}'.`
184
+ );
185
+ }
186
+
187
+ return Animated[spec.animation](tabAnimation, {
188
+ ...spec.config,
189
+ toValue,
190
+ useNativeDriver,
191
+ });
192
+ });
193
+
194
+ Animated.parallel(animations).start(({ finished }) => {
195
+ if (popToTopAction) {
160
196
  navigation.dispatch(popToTopAction);
161
197
  }
162
198
 
@@ -166,12 +202,26 @@ export function BottomTabViewCustom({
166
202
  target: focusedRouteKey,
167
203
  });
168
204
  }
205
+
206
+ if (finished && animations.length) {
207
+ // Delay clearing `animating` state
208
+ // This will give time for `popToAction` to get handled before pause
209
+ timer = setTimeout(() => {
210
+ setLastUpdate((update) =>
211
+ update.animating ? { ...update, animating: false } : update
212
+ );
213
+ }, 32);
214
+ }
169
215
  });
170
216
  };
171
217
 
172
218
  animateToIndex();
173
219
 
174
220
  previousRouteKeyRef.current = focusedRouteKey;
221
+
222
+ return () => {
223
+ clearTimeout(timer);
224
+ };
175
225
  }, [
176
226
  descriptors,
177
227
  focusedRouteKey,
@@ -187,7 +237,7 @@ export function BottomTabViewCustom({
187
237
  descriptors,
188
238
  dimensions: SafeAreaProviderCompat.initialMetrics.frame,
189
239
  insets: SafeAreaProviderCompat.initialMetrics.insets,
190
- style: descriptors[focusedRouteKey].options.tabBarStyle,
240
+ style: focusedOptions?.tabBarStyle,
191
241
  })
192
242
  );
193
243
 
@@ -204,14 +254,7 @@ export function BottomTabViewCustom({
204
254
  </BottomTabBarHeightCallbackContext.Provider>
205
255
  );
206
256
 
207
- // If there is no animation, we only have 2 states: visible and invisible
208
- const hasTwoStates = !routes.some((route) =>
209
- hasAnimation(descriptors[route.key].options)
210
- );
211
-
212
- const tabBarPosition = useTabBarPosition(
213
- descriptors[focusedRouteKey].options
214
- );
257
+ const tabBarPosition = useTabBarPosition(focusedOptions ?? {});
215
258
 
216
259
  return (
217
260
  <SafeAreaProviderCompat
@@ -225,19 +268,21 @@ export function BottomTabViewCustom({
225
268
  {tabBarPosition === 'top' || tabBarPosition === 'left'
226
269
  ? tabBarElement
227
270
  : null}
228
- <ScreenContainer
229
- key="screens"
230
- enabled={detachInactiveScreens}
231
- hasTwoStates={hasTwoStates}
232
- style={styles.screens}
233
- >
271
+ <Container key="screens" style={styles.screens}>
234
272
  {routes.map((route, index) => {
235
273
  const descriptor = descriptors[route.key];
236
274
 
275
+ if (descriptor == null) {
276
+ throw new Error(
277
+ `Couldn't find a descriptor for route '${route.key}'.`
278
+ );
279
+ }
280
+
237
281
  const { navigation, options, render } = descriptor;
238
282
 
239
283
  const {
240
284
  lazy = true,
285
+ inactiveBehavior = 'pause',
241
286
  animation = 'none',
242
287
  sceneStyleInterpolator = NAMED_TRANSITIONS_PRESETS[animation]
243
288
  .sceneStyleInterpolator,
@@ -248,85 +293,70 @@ export function BottomTabViewCustom({
248
293
  const isPreloaded = state.preloadedRouteKeys.includes(route.key);
249
294
 
250
295
  const animationEnabled = hasAnimation(descriptor.options);
296
+ const tabAnimation = tabAnims[route.key];
297
+
298
+ if (tabAnimation == null) {
299
+ throw new Error(
300
+ `Couldn't find an animation for route '${route.key}'.`
301
+ );
302
+ }
251
303
 
252
304
  const content = (
253
305
  <AnimatedScreenContent
254
- progress={tabAnims[route.key]}
306
+ key={route.key}
307
+ progress={tabAnimation}
255
308
  animationEnabled={animationEnabled}
256
309
  sceneStyleInterpolator={sceneStyleInterpolator}
257
- style={customSceneStyle}
310
+ style={[StyleSheet.absoluteFill, customSceneStyle]}
258
311
  >
259
- <Lazy enabled={lazy} visible={isFocused || isPreloaded}>
260
- <ScreenContent
261
- isFocused={isFocused}
262
- route={route}
263
- navigation={navigation}
264
- options={options}
312
+ <ScreenContent
313
+ isFocused={isFocused}
314
+ route={route}
315
+ navigation={navigation}
316
+ options={options}
317
+ >
318
+ <BottomTabBarHeightContext.Provider
319
+ value={tabBarPosition === 'bottom' ? tabBarHeight : 0}
265
320
  >
266
- <BottomTabBarHeightContext.Provider
267
- value={tabBarPosition === 'bottom' ? tabBarHeight : 0}
268
- >
269
- {render()}
270
- </BottomTabBarHeightContext.Provider>
271
- </ScreenContent>
272
- </Lazy>
321
+ {render()}
322
+ </BottomTabBarHeightContext.Provider>
323
+ </ScreenContent>
273
324
  </AnimatedScreenContent>
274
325
  );
275
326
 
276
- if (Platform.OS === 'web') {
277
- /**
278
- * Don't use react-native-screens on web:
279
- * - It applies display: none as fallback, which triggers `onLayout` events
280
- * - We still need to hide the view when screens is not enabled
281
- * - We can use `inert` to handle a11y better for unfocused screens
282
- */
283
- return (
284
- <Container
285
- key={route.key}
286
- inert={!isFocused}
287
- style={{
288
- ...StyleSheet.absoluteFillObject,
289
- visibility: isFocused ? 'visible' : 'hidden',
290
- }}
291
- >
292
- {content}
293
- </Container>
294
- );
295
- }
327
+ const isAnimatingRoute =
328
+ lastUpdate.animating &&
329
+ (lastUpdate.previous === route.key ||
330
+ lastUpdate.current === route.key);
296
331
 
297
- const activityState = isFocused
298
- ? STATE_ON_TOP // the screen is on top after the transition
299
- : animationEnabled // is animation is not enabled, immediately move to inactive state
300
- ? tabAnims[route.key].interpolate({
301
- inputRange: [0, 1 - EPSILON, 1],
302
- outputRange: [
303
- STATE_TRANSITIONING_OR_BELOW_TOP, // screen visible during transition
304
- STATE_TRANSITIONING_OR_BELOW_TOP,
305
- STATE_INACTIVE, // the screen is detached after transition
306
- ],
307
- extrapolate: 'extend',
308
- })
309
- : STATE_INACTIVE;
332
+ // For preloaded screens and if lazy is false,
333
+ // Keep them active so that the effects can run
334
+ const isActive =
335
+ inactiveBehavior === 'none' ||
336
+ isAnimatingRoute ||
337
+ isPreloaded ||
338
+ (lazy === false && !loaded.includes(route.key));
310
339
 
311
340
  return (
312
- <Screen
341
+ <Deferred
313
342
  key={route.key}
314
- style={[
315
- StyleSheet.absoluteFill,
316
- {
317
- zIndex: isFocused ? 0 : -1,
318
- pointerEvents: isFocused ? 'auto' : 'none',
319
- },
320
- ]}
321
- activityState={activityState}
322
- enabled={detachInactiveScreens}
323
- shouldFreeze={activityState === STATE_INACTIVE && !isPreloaded}
343
+ lazy={lazy}
344
+ visible={isFocused || isPreloaded || loaded.includes(route.key)}
324
345
  >
325
- {content}
326
- </Screen>
346
+ <ActivityView
347
+ mode={isFocused ? 'normal' : isActive ? 'inert' : 'paused'}
348
+ visible={isFocused || isAnimatingRoute}
349
+ style={{
350
+ ...StyleSheet.absoluteFill,
351
+ zIndex: isFocused ? 0 : -1,
352
+ }}
353
+ >
354
+ {content}
355
+ </ActivityView>
356
+ </Deferred>
327
357
  );
328
358
  })}
329
- </ScreenContainer>
359
+ </Container>
330
360
  {tabBarPosition === 'bottom' || tabBarPosition === 'right'
331
361
  ? tabBarElement
332
362
  : null}
@@ -343,7 +373,7 @@ function AnimatedScreenContent({
343
373
  }: {
344
374
  progress: Animated.Value;
345
375
  animationEnabled: boolean;
346
- sceneStyleInterpolator?: BottomTabSceneStyleInterpolator;
376
+ sceneStyleInterpolator?: BottomTabSceneStyleInterpolator | undefined;
347
377
  children: React.ReactNode;
348
378
  style: StyleProp<ViewStyle>;
349
379
  }) {