@zoverdoser/react-native-tab-view 1.0.2 → 1.3.2

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 (57) hide show
  1. package/lib/module/PagerViewAdapter.js +28 -13
  2. package/lib/module/PagerViewAdapter.js.map +1 -1
  3. package/lib/module/PanResponderAdapter.js.map +1 -1
  4. package/lib/module/PlatformPressable.js +1 -1
  5. package/lib/module/PlatformPressable.js.map +1 -1
  6. package/lib/module/TabBar.js +27 -32
  7. package/lib/module/TabBar.js.map +1 -1
  8. package/lib/module/TabBarIndicator.js +110 -35
  9. package/lib/module/TabBarIndicator.js.map +1 -1
  10. package/lib/module/TabBarItem.js +63 -32
  11. package/lib/module/TabBarItem.js.map +1 -1
  12. package/lib/module/TabBarItemLabel.js.map +1 -1
  13. package/lib/module/TabView.js +2 -0
  14. package/lib/module/TabView.js.map +1 -1
  15. package/lib/module/useAnimatedValue.js.map +1 -1
  16. package/lib/typescript/src/Pager.android.d.ts +1 -1
  17. package/lib/typescript/src/Pager.android.d.ts.map +1 -1
  18. package/lib/typescript/src/Pager.d.ts +1 -1
  19. package/lib/typescript/src/Pager.d.ts.map +1 -1
  20. package/lib/typescript/src/Pager.ios.d.ts +1 -1
  21. package/lib/typescript/src/Pager.ios.d.ts.map +1 -1
  22. package/lib/typescript/src/PagerViewAdapter.d.ts +3 -1
  23. package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -1
  24. package/lib/typescript/src/PanResponderAdapter.d.ts +3 -1
  25. package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
  26. package/lib/typescript/src/PlatformPressable.d.ts +1 -1
  27. package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
  28. package/lib/typescript/src/SceneMap.d.ts +2 -2
  29. package/lib/typescript/src/SceneMap.d.ts.map +1 -1
  30. package/lib/typescript/src/SceneView.d.ts +2 -2
  31. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  32. package/lib/typescript/src/TabBar.d.ts +4 -4
  33. package/lib/typescript/src/TabBar.d.ts.map +1 -1
  34. package/lib/typescript/src/TabBarIndicator.d.ts +2 -2
  35. package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
  36. package/lib/typescript/src/TabBarItem.d.ts +5 -2
  37. package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
  38. package/lib/typescript/src/TabBarItemLabel.d.ts +1 -1
  39. package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -1
  40. package/lib/typescript/src/TabView.d.ts +2 -2
  41. package/lib/typescript/src/TabView.d.ts.map +1 -1
  42. package/lib/typescript/src/index.d.ts +10 -10
  43. package/lib/typescript/src/index.d.ts.map +1 -1
  44. package/lib/typescript/src/types.d.ts +2 -1
  45. package/lib/typescript/src/types.d.ts.map +1 -1
  46. package/lib/typescript/src/useAnimatedValue.d.ts.map +1 -1
  47. package/package.json +8 -7
  48. package/src/PagerViewAdapter.tsx +67 -17
  49. package/src/PanResponderAdapter.tsx +2 -0
  50. package/src/PlatformPressable.tsx +1 -1
  51. package/src/TabBar.tsx +50 -47
  52. package/src/TabBarIndicator.tsx +182 -41
  53. package/src/TabBarItem.tsx +111 -47
  54. package/src/TabBarItemLabel.tsx +1 -0
  55. package/src/TabView.tsx +2 -1
  56. package/src/types.tsx +2 -1
  57. package/src/useAnimatedValue.tsx +2 -2
@@ -30,35 +30,94 @@ export type Props<T extends Route> = SceneRendererProps & {
30
30
 
31
31
  const useNativeDriver = Platform.OS !== 'web';
32
32
 
33
+ const calculateSize = (
34
+ value: ViewStyle['width'] | undefined,
35
+ referenceWidth: number
36
+ ): number | undefined => {
37
+ if (typeof value === 'number') {
38
+ return value;
39
+ }
40
+
41
+ if (typeof value === 'string' && value.endsWith('%')) {
42
+ const parsed = parseFloat(value);
43
+
44
+ if (Number.isFinite(parsed)) {
45
+ return referenceWidth * (parsed / 100);
46
+ }
47
+ }
48
+
49
+ return undefined;
50
+ };
51
+
52
+ const getIndicatorWidthWithMargins = (
53
+ width: number,
54
+ style: ViewStyle | undefined,
55
+ direction: LocaleDirection
56
+ ) => {
57
+ const marginHorizontal = style?.marginHorizontal ?? style?.margin;
58
+
59
+ const leftMargin =
60
+ (direction === 'ltr' ? style?.marginStart : style?.marginEnd) ??
61
+ style?.marginLeft ??
62
+ marginHorizontal;
63
+
64
+ const rightMargin =
65
+ (direction === 'rtl' ? style?.marginStart : style?.marginEnd) ??
66
+ style?.marginRight ??
67
+ marginHorizontal;
68
+
69
+ return Math.max(
70
+ 0,
71
+ width -
72
+ (calculateSize(leftMargin, width) ?? 0) -
73
+ (calculateSize(rightMargin, width) ?? 0)
74
+ );
75
+ };
76
+
77
+ const getIndicatorWidth = (
78
+ tabWidth: number,
79
+ width: number | `${number}%`,
80
+ style: ViewStyle | undefined,
81
+ direction: LocaleDirection
82
+ ): number | `${number}%` => {
83
+ const customWidth = calculateSize(style?.width, tabWidth);
84
+
85
+ if (customWidth !== undefined) {
86
+ return customWidth;
87
+ }
88
+
89
+ if (typeof width === 'number') {
90
+ return getIndicatorWidthWithMargins(width, style, direction);
91
+ }
92
+
93
+ return width;
94
+ };
95
+
33
96
  const getTranslateX = (
34
97
  position: Animated.AnimatedInterpolation<number>,
35
98
  routes: Route[],
36
99
  getTabWidth: GetTabWidth,
37
100
  direction: LocaleDirection,
38
101
  gap?: number,
39
- width?: number | string
102
+ getWidth?: (index: number) => number | undefined
40
103
  ) => {
41
104
  const inputRange = routes.map((_, i) => i);
105
+ const outputRange = routes.map((_, i) => {
106
+ let sumTabWidth = 0;
42
107
 
43
- // every index contains widths at all previous indices
44
- const outputRange = routes.reduce<number[]>((acc, _, i) => {
45
- if (typeof width === 'number') {
46
- if (i === 0) return [getTabWidth(i) / 2 - width / 2];
108
+ for (let j = 0; j < i; j++) {
109
+ sumTabWidth += getTabWidth(j);
110
+ }
47
111
 
48
- let sumTabWidth = 0;
49
- for (let j = 0; j < i; j++) {
50
- sumTabWidth += getTabWidth(j);
51
- }
112
+ const indicatorWidth = getWidth?.(i);
113
+ const tabOffset = sumTabWidth + (gap ? gap * i : 0);
52
114
 
53
- return [
54
- ...acc,
55
- sumTabWidth + getTabWidth(i) / 2 + (gap ? gap * i : 0) - width / 2,
56
- ];
57
- } else {
58
- if (i === 0) return [0];
59
- return [...acc, acc[i - 1] + getTabWidth(i - 1) + (gap ?? 0)];
115
+ if (indicatorWidth === undefined) {
116
+ return tabOffset;
60
117
  }
61
- }, []);
118
+
119
+ return tabOffset + getTabWidth(i) / 2 - indicatorWidth / 2;
120
+ });
62
121
 
63
122
  const translateX = position.interpolate({
64
123
  inputRange,
@@ -83,31 +142,60 @@ export function TabBarIndicator<T extends Route>({
83
142
  const isIndicatorShown = React.useRef(false);
84
143
  const isWidthDynamic = width === 'auto';
85
144
 
145
+ const flattenedStyle = StyleSheet.flatten(style);
146
+
147
+ const hasCustomIndicatorWidth =
148
+ typeof flattenedStyle?.width === 'number' ||
149
+ (typeof flattenedStyle?.width === 'string' &&
150
+ flattenedStyle?.width.endsWith('%'));
151
+
152
+ const constantIndicatorWidth =
153
+ typeof flattenedStyle?.width === 'number'
154
+ ? flattenedStyle.width
155
+ : undefined;
156
+
157
+ const isCentered =
158
+ hasCustomIndicatorWidth &&
159
+ (flattenedStyle?.margin === 'auto' ||
160
+ flattenedStyle?.marginHorizontal === 'auto');
161
+
162
+ // If indicator has a custom width, we need to adjust calculations to account for it
163
+ // It should be centered relative to the tab if the margin is set to auto
164
+ const getCenteredIndicatorWidth = (tabWidth: number) => {
165
+ if (isCentered) {
166
+ return calculateSize(flattenedStyle?.width, tabWidth);
167
+ }
168
+
169
+ if (typeof width === 'number') {
170
+ return width;
171
+ }
172
+
173
+ return undefined;
174
+ };
175
+
86
176
  const opacity = useAnimatedValue(isWidthDynamic ? 0 : 1);
87
177
 
178
+ // We should fade-in the indicator when we have widths for all the tab items
88
179
  const indicatorVisible = isWidthDynamic
89
180
  ? layout.width &&
90
181
  navigationState.routes
91
- .slice(0, navigationState.index)
182
+ .slice(0, navigationState.index + 1)
92
183
  .every((_, r) => getTabWidth(r))
93
184
  : true;
94
185
 
95
186
  React.useEffect(() => {
96
187
  const fadeInIndicator = () => {
97
- if (
98
- !isIndicatorShown.current &&
99
- isWidthDynamic &&
100
- // We should fade-in the indicator when we have widths for all the tab items
101
- indicatorVisible
102
- ) {
103
- isIndicatorShown.current = true;
104
-
188
+ if (!isIndicatorShown.current && isWidthDynamic && indicatorVisible) {
105
189
  Animated.timing(opacity, {
106
190
  toValue: 1,
107
191
  duration: 150,
108
192
  easing: Easing.in(Easing.linear),
109
193
  useNativeDriver,
110
- }).start();
194
+ }).start(({ finished }) => {
195
+ if (finished) {
196
+ isIndicatorShown.current = true;
197
+ }
198
+ });
111
199
  }
112
200
  };
113
201
 
@@ -120,18 +208,25 @@ export function TabBarIndicator<T extends Route>({
120
208
 
121
209
  const transform = [];
122
210
 
123
- if (layout.width) {
124
- const translateX =
125
- routes.length > 1
126
- ? getTranslateX(position, routes, getTabWidth, direction, gap, width)
127
- : 0;
211
+ const translateX =
212
+ layout.width && routes.length > 1
213
+ ? getTranslateX(position, routes, getTabWidth, direction, gap, (index) =>
214
+ getCenteredIndicatorWidth(getTabWidth(index))
215
+ )
216
+ : 0;
128
217
 
129
- transform.push({ translateX });
130
- }
218
+ transform.push({ translateX });
131
219
 
132
- if (width === 'auto') {
220
+ if (width === 'auto' && constantIndicatorWidth == null) {
133
221
  const inputRange = routes.map((_, i) => i);
134
- const outputRange = inputRange.map(getTabWidth);
222
+ const outputRange = inputRange.map((i) => {
223
+ const tabW = getTabWidth(i);
224
+
225
+ return (
226
+ calculateSize(flattenedStyle?.width, tabW) ??
227
+ getIndicatorWidthWithMargins(tabW, flattenedStyle, direction)
228
+ );
229
+ });
135
230
 
136
231
  transform.push(
137
232
  {
@@ -150,27 +245,73 @@ export function TabBarIndicator<T extends Route>({
150
245
 
151
246
  const styleList: StyleProp<ViewStyle> = [];
152
247
 
153
- // scaleX doesn't work properly on chrome and opera for linux and android
154
- if (Platform.OS === 'web' && width === 'auto') {
248
+ // transform doesn't work properly on chrome and opera for linux and android
249
+ // so we need to use width and left/right instead of scaleX and translateX
250
+ // https://github.com/react-navigation/react-navigation/pull/11440
251
+ if (
252
+ Platform.OS === 'web' &&
253
+ width === 'auto' &&
254
+ constantIndicatorWidth == null
255
+ ) {
256
+ const start = flattenedStyle?.start;
257
+ const translate =
258
+ direction === 'rtl' ? Animated.multiply(translateX, -1) : translateX;
259
+ const offset =
260
+ typeof start === 'number' ? Animated.add(translate, start) : translate;
261
+
155
262
  styleList.push(
156
263
  { width: transform[1].scaleX },
157
- { left: transform[0].translateX }
264
+ direction === 'rtl' ? { right: offset } : { left: offset }
158
265
  );
159
266
  } else {
160
267
  styleList.push(
161
- { width: width === 'auto' ? 1 : width },
268
+ {
269
+ width:
270
+ width === 'auto'
271
+ ? // if the indicator has a constant width, use it as is
272
+ // we don't need to scale it to match tab width
273
+ (constantIndicatorWidth ?? 1)
274
+ : getIndicatorWidth(
275
+ getTabWidth(navigationState.index),
276
+ width,
277
+ flattenedStyle,
278
+ direction
279
+ ),
280
+ },
162
281
  { start: `${(100 / routes.length) * navigationState.index}%` },
163
282
  { transform }
164
283
  );
165
284
  }
166
285
 
286
+ let finalStyle;
287
+
288
+ if (hasCustomIndicatorWidth && style != null) {
289
+ const rest = { ...flattenedStyle };
290
+
291
+ delete rest.width;
292
+
293
+ if (isCentered) {
294
+ if (rest.margin === 'auto') {
295
+ delete rest.margin;
296
+ }
297
+
298
+ if (rest.marginHorizontal === 'auto') {
299
+ delete rest.marginHorizontal;
300
+ }
301
+ }
302
+
303
+ finalStyle = rest;
304
+ } else {
305
+ finalStyle = style;
306
+ }
307
+
167
308
  return (
168
309
  <Animated.View
169
310
  style={[
170
311
  styles.indicator,
171
312
  styleList,
172
313
  width === 'auto' ? { opacity: opacity } : null,
173
- style,
314
+ finalStyle,
174
315
  ]}
175
316
  >
176
317
  {children}
@@ -9,9 +9,11 @@ import {
9
9
  View,
10
10
  type ViewStyle,
11
11
  } from 'react-native';
12
+ import type { SharedValue } from 'react-native-reanimated';
12
13
  import {
13
14
  interpolateColor,
14
15
  useAnimatedStyle,
16
+ useDerivedValue,
15
17
  useSharedValue,
16
18
  } from 'react-native-reanimated';
17
19
  import useLatestCallback from 'use-latest-callback';
@@ -27,6 +29,7 @@ import type {
27
29
 
28
30
  export type Props<T extends Route> = TabDescriptor<T> & {
29
31
  position: Animated.AnimatedInterpolation<number>;
32
+ animatedPosition?: SharedValue<number>;
30
33
  route: T;
31
34
  navigationState: NavigationState<T>;
32
35
  pressColor?: string;
@@ -55,7 +58,6 @@ type TabBarItemInternalProps<T extends Route> = Omit<
55
58
  > & {
56
59
  isFocused: boolean;
57
60
  index: number;
58
- routesLength: number;
59
61
  } & TabDescriptor<T>;
60
62
 
61
63
  const ANDROID_RIPPLE_DEFAULT = { borderless: true };
@@ -69,6 +71,7 @@ const TabBarItemInternal = <T extends Route>({
69
71
  onPress,
70
72
  isFocused,
71
73
  position,
74
+ animatedPosition,
72
75
  style,
73
76
  labelStyle,
74
77
  onLayout,
@@ -80,18 +83,14 @@ const TabBarItemInternal = <T extends Route>({
80
83
  badge: customBadge,
81
84
  href,
82
85
  labelText,
83
- routesLength,
84
86
  android_ripple = ANDROID_RIPPLE_DEFAULT,
85
87
  labelAllowFontScaling,
86
88
  route,
87
89
  animatedStyles,
88
90
  }: TabBarItemInternalProps<T>) => {
89
91
  const inputRange = React.useMemo(
90
- () =>
91
- routesLength >= 2
92
- ? Array.from({ length: routesLength }, (_, i) => i)
93
- : [0, 1],
94
- [routesLength]
92
+ () => [tabIndex - 1, tabIndex, tabIndex + 1],
93
+ [tabIndex]
95
94
  );
96
95
  const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
97
96
 
@@ -106,70 +105,132 @@ const TabBarItemInternal = <T extends Route>({
106
105
  ? labelColorFromStyle
107
106
  : DEFAULT_INACTIVE_COLOR;
108
107
 
109
- const ReAnimatedProgress = useSharedValue(isFocused ? 1 : 0);
108
+ const fallbackProgress = useSharedValue(isFocused ? 1 : 0);
110
109
 
111
110
  React.useEffect(() => {
111
+ if (animatedPosition != null) {
112
+ return;
113
+ }
114
+
112
115
  const listenerId = position.addListener(({ value }) => {
113
116
  const progress =
114
117
  Math.abs(tabIndex - value) > 1 ? 0 : 1 - Math.abs(tabIndex - value);
115
- ReAnimatedProgress.value = progress;
118
+ fallbackProgress.value = progress;
116
119
  });
120
+
117
121
  return () => {
118
122
  position.removeListener(listenerId);
119
123
  };
120
- }, [position, ReAnimatedProgress, tabIndex, route.key]);
121
-
122
- const ReAnimatedStyleRef = React.useRef(
123
- useAnimatedStyle(() => {
124
- const color = interpolateColor(
125
- ReAnimatedProgress.value,
126
- [0, 1],
127
- [inactiveColor, activeColor]
128
- );
129
- return { color };
130
- }, [ReAnimatedProgress, activeColor, inactiveColor])
124
+ }, [animatedPosition, fallbackProgress, position, tabIndex]);
125
+
126
+ const reanimatedProgress = useDerivedValue(() => {
127
+ if (animatedPosition != null) {
128
+ return Math.max(0, 1 - Math.abs(tabIndex - animatedPosition.value));
129
+ }
130
+
131
+ return fallbackProgress.value;
132
+ }, [animatedPosition, fallbackProgress, tabIndex]);
133
+
134
+ const reanimatedStyle = useAnimatedStyle(() => {
135
+ const color = interpolateColor(
136
+ reanimatedProgress.value,
137
+ [0, 1],
138
+ [inactiveColor, activeColor]
139
+ );
140
+ return { color };
141
+ }, [reanimatedProgress, activeColor, inactiveColor]);
142
+
143
+ const inactiveItemOpacity = animatedStyles?.opacity?.[0];
144
+ const activeItemOpacity = animatedStyles?.opacity?.[1];
145
+ const inactiveScale = animatedStyles?.scale?.[0];
146
+ const activeScale = animatedStyles?.scale?.[1];
147
+
148
+ const itemOpacity = React.useMemo(
149
+ () =>
150
+ inactiveItemOpacity !== undefined && activeItemOpacity !== undefined
151
+ ? position.interpolate({
152
+ inputRange,
153
+ outputRange: [
154
+ inactiveItemOpacity,
155
+ activeItemOpacity,
156
+ inactiveItemOpacity,
157
+ ],
158
+ extrapolate: 'clamp',
159
+ })
160
+ : 1,
161
+ [activeItemOpacity, inactiveItemOpacity, inputRange, position]
131
162
  );
132
163
 
133
- const opacity = animatedStyles?.opacity
134
- ? position.interpolate({
164
+ const activeOpacity = React.useMemo(
165
+ () =>
166
+ position.interpolate({
135
167
  inputRange,
136
- outputRange: inputRange.map((i) =>
137
- i === tabIndex
138
- ? animatedStyles.opacity![1]
139
- : animatedStyles.opacity![0]
140
- ),
168
+ outputRange: [0, 1, 0],
141
169
  extrapolate: 'clamp',
142
- })
143
- : 1;
144
-
145
- const scale = animatedStyles?.scale
146
- ? position.interpolate({
170
+ }),
171
+ [inputRange, position]
172
+ );
173
+ const inactiveOpacity = React.useMemo(
174
+ () =>
175
+ position.interpolate({
147
176
  inputRange,
148
- outputRange: inputRange.map((i) =>
149
- i === tabIndex ? animatedStyles.scale![1] : animatedStyles.scale![0]
150
- ),
177
+ outputRange: [1, 0, 1],
151
178
  extrapolate: 'clamp',
152
- })
153
- : 1;
179
+ }),
180
+ [inputRange, position]
181
+ );
182
+
183
+ const scale = React.useMemo(
184
+ () =>
185
+ inactiveScale !== undefined && activeScale !== undefined
186
+ ? position.interpolate({
187
+ inputRange,
188
+ outputRange: [inactiveScale, activeScale, inactiveScale],
189
+ extrapolate: 'clamp',
190
+ })
191
+ : 1,
192
+ [activeScale, inactiveScale, inputRange, position]
193
+ );
154
194
 
155
195
  const icon = React.useMemo(() => {
156
196
  if (!customIcon) {
157
197
  return null;
158
198
  }
159
199
 
160
- const iconEle = customIcon({
161
- focused: isFocused,
162
- color: ReAnimatedStyleRef.current.color,
200
+ const inactiveIcon = customIcon({
201
+ focused: false,
202
+ color: inactiveColor,
203
+ size: ICON_SIZE,
204
+ route,
205
+ });
206
+
207
+ const activeIcon = customIcon({
208
+ focused: true,
209
+ color: activeColor,
163
210
  size: ICON_SIZE,
164
211
  route,
165
212
  });
166
213
 
167
214
  return (
168
215
  <View style={styles.icon}>
169
- <Animated.View style={{ opacity }}>{iconEle}</Animated.View>
216
+ <Animated.View style={{ opacity: inactiveOpacity }}>
217
+ {inactiveIcon}
218
+ </Animated.View>
219
+ <Animated.View
220
+ style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
221
+ >
222
+ {activeIcon}
223
+ </Animated.View>
170
224
  </View>
171
225
  );
172
- }, [customIcon, route, isFocused, ReAnimatedStyleRef, opacity]);
226
+ }, [
227
+ activeColor,
228
+ activeOpacity,
229
+ customIcon,
230
+ inactiveColor,
231
+ inactiveOpacity,
232
+ route,
233
+ ]);
173
234
 
174
235
  const renderLabel = React.useCallback(
175
236
  () =>
@@ -177,7 +238,7 @@ const TabBarItemInternal = <T extends Route>({
177
238
  customlabel({
178
239
  focused: isFocused,
179
240
  style: labelStyle,
180
- animatedStyles: ReAnimatedStyleRef.current,
241
+ animatedStyles: reanimatedStyle,
181
242
  labelText,
182
243
  allowFontScaling: labelAllowFontScaling,
183
244
  route,
@@ -187,7 +248,7 @@ const TabBarItemInternal = <T extends Route>({
187
248
  icon={icon}
188
249
  label={labelText}
189
250
  style={labelStyle}
190
- animatedStyles={ReAnimatedStyleRef.current}
251
+ animatedStyles={reanimatedStyle}
191
252
  labelAllowFontScaling={labelAllowFontScaling}
192
253
  />
193
254
  ),
@@ -199,7 +260,7 @@ const TabBarItemInternal = <T extends Route>({
199
260
  route,
200
261
  icon,
201
262
  isFocused,
202
- ReAnimatedStyleRef,
263
+ reanimatedStyle,
203
264
  ]
204
265
  );
205
266
 
@@ -232,7 +293,11 @@ const TabBarItemInternal = <T extends Route>({
232
293
  >
233
294
  <Animated.View
234
295
  pointerEvents="none"
235
- style={[styles.item, tabStyle, { opacity, transform: [{ scale }] }]}
296
+ style={[
297
+ styles.item,
298
+ tabStyle,
299
+ { opacity: itemOpacity, transform: [{ scale }] },
300
+ ]}
236
301
  >
237
302
  {icon}
238
303
  <View>{renderLabel()}</View>
@@ -267,7 +332,6 @@ export function TabBarItem<T extends Route>(props: Props<T>) {
267
332
  isFocused={navigationState.index === tabIndex}
268
333
  route={route}
269
334
  index={tabIndex}
270
- routesLength={navigationState.routes.length}
271
335
  />
272
336
  );
273
337
  }
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import type { StyleProp, TextStyle } from 'react-native';
3
3
  import { StyleSheet } from 'react-native';
4
4
  import Animated, { type AnimatedStyle } from 'react-native-reanimated';
5
+
5
6
  interface TabBarItemLabelProps {
6
7
  label?: string;
7
8
  style: StyleProp<TextStyle>;
package/src/TabView.tsx CHANGED
@@ -132,11 +132,12 @@ export function TabView<T extends Route>({
132
132
  style={pagerStyle}
133
133
  layoutDirection={direction}
134
134
  >
135
- {({ position, render, addEnterListener, jumpTo }) => {
135
+ {({ position, animatedPosition, render, addEnterListener, jumpTo }) => {
136
136
  // All the props here must not change between re-renders
137
137
  // This is crucial to optimizing the routes with PureComponent
138
138
  const sceneRendererProps = {
139
139
  position,
140
+ animatedPosition,
140
141
  layout,
141
142
  jumpTo,
142
143
  };
package/src/types.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Animated, StyleProp, TextStyle, ViewStyle } from 'react-native';
2
2
  import type { PagerViewProps } from 'react-native-pager-view';
3
- import type { AnimatedStyle } from 'react-native-reanimated';
3
+ import type { AnimatedStyle, SharedValue } from 'react-native-reanimated';
4
4
 
5
5
  export type TabDescriptor<T extends Route> = {
6
6
  accessibilityLabel?: string;
@@ -63,6 +63,7 @@ export type Listener = (value: number) => void;
63
63
  export type SceneRendererProps = {
64
64
  layout: Layout;
65
65
  position: Animated.AnimatedInterpolation<number>;
66
+ animatedPosition?: SharedValue<number>;
66
67
  jumpTo: (key: string) => void;
67
68
  };
68
69
 
@@ -1,12 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import { Animated } from 'react-native';
3
3
 
4
- export function useAnimatedValue(initialValue: number) {
4
+ export function useAnimatedValue(initialValue: number): Animated.Value {
5
5
  const lazyRef = React.useRef<Animated.Value>(undefined);
6
6
 
7
7
  if (lazyRef.current === undefined) {
8
8
  lazyRef.current = new Animated.Value(initialValue);
9
9
  }
10
10
 
11
- return lazyRef.current as Animated.Value;
11
+ return lazyRef.current;
12
12
  }