@react-navigation/bottom-tabs 8.0.0-alpha.32 → 8.0.0-alpha.34

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 (54) hide show
  1. package/lib/module/views/BottomTabBar.js +20 -14
  2. package/lib/module/views/BottomTabBar.js.map +1 -1
  3. package/lib/module/views/BottomTabViewCustom.js +39 -18
  4. package/lib/module/views/BottomTabViewCustom.js.map +1 -1
  5. package/lib/module/views/BottomTabViewNativeImpl.js +42 -26
  6. package/lib/module/views/BottomTabViewNativeImpl.js.map +1 -1
  7. package/lib/module/views/Deferred.js +41 -0
  8. package/lib/module/views/Deferred.js.map +1 -0
  9. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts +1 -1
  10. package/lib/typescript/src/TransitionConfigs/SceneStyleInterpolators.d.ts.map +1 -1
  11. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +1 -1
  12. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  13. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +1 -1
  14. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
  15. package/lib/typescript/src/index.d.ts +11 -11
  16. package/lib/typescript/src/index.d.ts.map +1 -1
  17. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts +2 -2
  18. package/lib/typescript/src/navigators/createBottomTabNavigator.d.ts.map +1 -1
  19. package/lib/typescript/src/types.d.ts +8 -2
  20. package/lib/typescript/src/types.d.ts.map +1 -1
  21. package/lib/typescript/src/utils/BottomTabAnimationContext.d.ts +1 -1
  22. package/lib/typescript/src/utils/BottomTabAnimationContext.d.ts.map +1 -1
  23. package/lib/typescript/src/utils/useBottomTabAnimation.d.ts +1 -1
  24. package/lib/typescript/src/utils/useBottomTabAnimation.d.ts.map +1 -1
  25. package/lib/typescript/src/utils/useTabBarPosition.d.ts +1 -1
  26. package/lib/typescript/src/utils/useTabBarPosition.d.ts.map +1 -1
  27. package/lib/typescript/src/views/BottomTabBar.d.ts +3 -2
  28. package/lib/typescript/src/views/BottomTabBar.d.ts.map +1 -1
  29. package/lib/typescript/src/views/BottomTabItem.d.ts +3 -3
  30. package/lib/typescript/src/views/BottomTabItem.d.ts.map +1 -1
  31. package/lib/typescript/src/views/BottomTabView.d.ts +2 -2
  32. package/lib/typescript/src/views/BottomTabView.d.ts.map +1 -1
  33. package/lib/typescript/src/views/BottomTabViewCustom.d.ts +3 -2
  34. package/lib/typescript/src/views/BottomTabViewCustom.d.ts.map +1 -1
  35. package/lib/typescript/src/views/BottomTabViewNative.android.d.ts +1 -1
  36. package/lib/typescript/src/views/BottomTabViewNative.android.d.ts.map +1 -1
  37. package/lib/typescript/src/views/BottomTabViewNative.d.ts +1 -1
  38. package/lib/typescript/src/views/BottomTabViewNative.d.ts.map +1 -1
  39. package/lib/typescript/src/views/BottomTabViewNative.ios.d.ts +1 -1
  40. package/lib/typescript/src/views/BottomTabViewNative.ios.d.ts.map +1 -1
  41. package/lib/typescript/src/views/BottomTabViewNativeImpl.d.ts +3 -2
  42. package/lib/typescript/src/views/BottomTabViewNativeImpl.d.ts.map +1 -1
  43. package/lib/typescript/src/views/Deferred.d.ts +31 -0
  44. package/lib/typescript/src/views/Deferred.d.ts.map +1 -0
  45. package/lib/typescript/src/views/ScreenContent.d.ts +2 -2
  46. package/lib/typescript/src/views/ScreenContent.d.ts.map +1 -1
  47. package/lib/typescript/src/views/TabBarIcon.d.ts +1 -1
  48. package/lib/typescript/src/views/TabBarIcon.d.ts.map +1 -1
  49. package/package.json +13 -13
  50. package/src/types.tsx +8 -2
  51. package/src/views/BottomTabBar.tsx +38 -9
  52. package/src/views/BottomTabViewCustom.tsx +57 -24
  53. package/src/views/BottomTabViewNativeImpl.tsx +27 -9
  54. package/src/views/Deferred.tsx +57 -0
@@ -36,6 +36,7 @@ import { BottomTabBarHeightContext } from '../utils/BottomTabBarHeightContext';
36
36
  import { useAnimatedHashMap } from '../utils/useAnimatedHashMap';
37
37
  import { useTabBarPosition } from '../utils/useTabBarPosition';
38
38
  import { BottomTabBar, getTabBarHeight } from './BottomTabBar';
39
+ import { Deferred } from './Deferred';
39
40
  import { ScreenContent } from './ScreenContent';
40
41
 
41
42
  type Props = BottomTabNavigationConfig & {
@@ -79,7 +80,14 @@ export function BottomTabViewCustom({
79
80
  descriptors,
80
81
  }: Props) {
81
82
  const { routes } = state;
82
- 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;
83
91
 
84
92
  const [loaded, setLoaded] = React.useState([focusedRouteKey]);
85
93
 
@@ -141,7 +149,15 @@ export function BottomTabViewCustom({
141
149
 
142
150
  const animations = state.routes
143
151
  .map((route, index) => {
144
- const { options } = descriptors[route.key];
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;
145
161
  const {
146
162
  animation = 'none',
147
163
  transitionSpec = NAMED_TRANSITIONS_PRESETS[animation]
@@ -161,7 +177,15 @@ export function BottomTabViewCustom({
161
177
  const toValue =
162
178
  index === state.index ? 0 : index >= state.index ? 1 : -1;
163
179
 
164
- return Animated[spec.animation](tabAnims[route.key], {
180
+ const tabAnimation = tabAnims[route.key];
181
+
182
+ if (tabAnimation == null) {
183
+ throw new Error(
184
+ `Couldn't find an animation for route '${route.key}'.`
185
+ );
186
+ }
187
+
188
+ return Animated[spec.animation](tabAnimation, {
165
189
  ...spec.config,
166
190
  toValue,
167
191
  useNativeDriver,
@@ -218,7 +242,7 @@ export function BottomTabViewCustom({
218
242
  descriptors,
219
243
  dimensions: SafeAreaProviderCompat.initialMetrics.frame,
220
244
  insets: SafeAreaProviderCompat.initialMetrics.insets,
221
- style: descriptors[focusedRouteKey].options.tabBarStyle,
245
+ style: focusedOptions?.tabBarStyle,
222
246
  })
223
247
  );
224
248
 
@@ -235,9 +259,7 @@ export function BottomTabViewCustom({
235
259
  </BottomTabBarHeightCallbackContext.Provider>
236
260
  );
237
261
 
238
- const tabBarPosition = useTabBarPosition(
239
- descriptors[focusedRouteKey].options
240
- );
262
+ const tabBarPosition = useTabBarPosition(focusedOptions ?? {});
241
263
 
242
264
  return (
243
265
  <SafeAreaProviderCompat
@@ -255,6 +277,12 @@ export function BottomTabViewCustom({
255
277
  {routes.map((route, index) => {
256
278
  const descriptor = descriptors[route.key];
257
279
 
280
+ if (descriptor == null) {
281
+ throw new Error(
282
+ `Couldn't find a descriptor for route '${route.key}'.`
283
+ );
284
+ }
285
+
258
286
  const { navigation, options, render } = descriptor;
259
287
 
260
288
  const {
@@ -269,22 +297,19 @@ export function BottomTabViewCustom({
269
297
  const isFocused = state.index === index;
270
298
  const isPreloaded = state.preloadedRouteKeys.includes(route.key);
271
299
 
272
- if (
273
- lazy &&
274
- !loaded.includes(route.key) &&
275
- !isFocused &&
276
- !isPreloaded
277
- ) {
278
- // Don't render a lazy screen if we've never navigated to it or it wasn't preloaded
279
- return null;
280
- }
281
-
282
300
  const animationEnabled = hasAnimation(descriptor.options);
301
+ const tabAnimation = tabAnims[route.key];
302
+
303
+ if (tabAnimation == null) {
304
+ throw new Error(
305
+ `Couldn't find an animation for route '${route.key}'.`
306
+ );
307
+ }
283
308
 
284
309
  const content = (
285
310
  <AnimatedScreenContent
286
311
  key={route.key}
287
- progress={tabAnims[route.key]}
312
+ progress={tabAnimation}
288
313
  animationEnabled={animationEnabled}
289
314
  sceneStyleInterpolator={sceneStyleInterpolator}
290
315
  style={[StyleSheet.absoluteFill, customSceneStyle]}
@@ -318,14 +343,22 @@ export function BottomTabViewCustom({
318
343
  (lazy === false && !loaded.includes(route.key));
319
344
 
320
345
  return (
321
- <ActivityView
346
+ <Deferred
322
347
  key={route.key}
323
- mode={isFocused ? 'normal' : isActive ? 'inert' : 'paused'}
324
- visible={isFocused || isAnimatingRoute}
325
- style={{ ...StyleSheet.absoluteFill, zIndex: isFocused ? 0 : -1 }}
348
+ lazy={lazy}
349
+ visible={isFocused || isPreloaded || loaded.includes(route.key)}
326
350
  >
327
- {content}
328
- </ActivityView>
351
+ <ActivityView
352
+ mode={isFocused ? 'normal' : isActive ? 'inert' : 'paused'}
353
+ visible={isFocused || isAnimatingRoute}
354
+ style={{
355
+ ...StyleSheet.absoluteFill,
356
+ zIndex: isFocused ? 0 : -1,
357
+ }}
358
+ >
359
+ {content}
360
+ </ActivityView>
361
+ </Deferred>
329
362
  );
330
363
  })}
331
364
  </Container>
@@ -39,10 +39,12 @@ import type {
39
39
  BottomTabDescriptorMap,
40
40
  BottomTabNavigationConfig,
41
41
  BottomTabNavigationHelpers,
42
+ BottomTabNavigationOptions,
42
43
  } from '../types';
43
44
  import { BottomTabAnimationContext } from '../utils/BottomTabAnimationContext';
44
45
  import { BottomTabBarHeightContext } from '../utils/BottomTabBarHeightContext';
45
46
  import { useTabBarPosition } from '../utils/useTabBarPosition';
47
+ import { Deferred } from './Deferred';
46
48
  import { ScreenContent } from './ScreenContent';
47
49
 
48
50
  type Props = BottomTabNavigationConfig & {
@@ -125,7 +127,13 @@ export function BottomTabViewNative({
125
127
  }: Props) {
126
128
  const { dark, colors, fonts } = useTheme();
127
129
 
128
- const focusedRouteKey = state.routes[state.index].key;
130
+ const focusedRoute = state.routes[state.index];
131
+
132
+ if (focusedRoute == null) {
133
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
134
+ }
135
+
136
+ const focusedRouteKey = focusedRoute.key;
129
137
 
130
138
  const [loaded, setLoaded] = React.useState([focusedRouteKey]);
131
139
 
@@ -302,7 +310,8 @@ export function BottomTabViewNative({
302
310
  });
303
311
  };
304
312
 
305
- const currentOptions = descriptors[state.routes[state.index].key]?.options;
313
+ const currentOptions: BottomTabNavigationOptions =
314
+ descriptors[focusedRouteKey]?.options ?? {};
306
315
 
307
316
  const {
308
317
  fontFamily = Platform.select({
@@ -438,6 +447,7 @@ export function BottomTabViewNative({
438
447
  onTabSelectionRejected={onTabSelectionRejected}
439
448
  onTabSelectionPrevented={onTabSelectionPrevented}
440
449
  tabBarHidden={hasCustomTabBar || shouldHideTabBar}
450
+ nativeContainerStyle={{ backgroundColor }}
441
451
  colorScheme={dark ? 'dark' : 'light'}
442
452
  ios={{
443
453
  bottomAccessory: bottomAccessory
@@ -450,13 +460,21 @@ export function BottomTabViewNative({
450
460
  }}
451
461
  >
452
462
  {state.routes.map((route, index) => {
453
- const { options, render, navigation } = descriptors[route.key];
463
+ const descriptor = descriptors[route.key];
464
+
465
+ if (descriptor == null) {
466
+ throw new Error(
467
+ `Couldn't find a descriptor for route '${route.key}'.`
468
+ );
469
+ }
470
+
471
+ const { options, render, navigation } = descriptor;
454
472
  const isFocused = state.index === index;
455
473
  const isPreloaded = state.preloadedRouteKeys.includes(route.key);
456
474
 
457
475
  const {
458
476
  title,
459
- lazy = true,
477
+ lazy = false,
460
478
  inactiveBehavior = 'pause',
461
479
  tabBarLabel,
462
480
  tabBarSelectionEnabled,
@@ -630,10 +648,10 @@ export function BottomTabViewNative({
630
648
  overrideScrollViewContentInsetAdjustmentBehavior,
631
649
  }}
632
650
  >
633
- {lazy &&
634
- !loaded.includes(route.key) &&
635
- !isFocused &&
636
- !isPreloaded ? null : (
651
+ <Deferred
652
+ lazy={lazy}
653
+ visible={isFocused || isPreloaded || loaded.includes(route.key)}
654
+ >
637
655
  <ActivityView
638
656
  key={route.key}
639
657
  mode={isFocused ? 'normal' : isActive ? 'inert' : 'paused'}
@@ -661,7 +679,7 @@ export function BottomTabViewNative({
661
679
  </AnimatedScreenContent>
662
680
  </ScreenContent>
663
681
  </ActivityView>
664
- )}
682
+ </Deferred>
665
683
  </Tabs.Screen>
666
684
  );
667
685
  })}
@@ -0,0 +1,57 @@
1
+ import * as React from 'react';
2
+
3
+ type Props = {
4
+ /**
5
+ * Whether lazy rendering is enabled.
6
+ */
7
+ lazy: boolean;
8
+ /**
9
+ * Whether the component is visible.
10
+ */
11
+ visible: boolean;
12
+ /**
13
+ * Content to render.
14
+ */
15
+ children: React.ReactElement;
16
+ };
17
+
18
+ /**
19
+ * Render content lazily based on visibility.
20
+ *
21
+ * When lazy is enabled:
22
+ * - If content is visible, it will render immediately
23
+ * - If content is not visible, it won't render until it becomes visible
24
+ *
25
+ * Otherwise:
26
+ * - If content is visible, it will render immediately
27
+ * - If content is not visible, it will defer rendering
28
+ *
29
+ * Once rendered, the content remains rendered.
30
+ */
31
+ export function Deferred({ lazy, visible, children }: Props) {
32
+ const [rendered, setRendered] = React.useState(lazy ? visible : false);
33
+
34
+ const shouldRenderDeferred = !(lazy || visible || rendered);
35
+
36
+ React.useEffect(() => {
37
+ if (shouldRenderDeferred === false) {
38
+ return;
39
+ }
40
+
41
+ React.startTransition(() => {
42
+ setRendered(true);
43
+ });
44
+ }, [shouldRenderDeferred]);
45
+
46
+ if (visible && rendered === false) {
47
+ setRendered(true);
48
+
49
+ return children;
50
+ }
51
+
52
+ if (rendered) {
53
+ return children;
54
+ }
55
+
56
+ return null;
57
+ }