@zoverdoser/react-native-tab-view 1.3.1 → 1.3.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 (41) hide show
  1. package/lib/module/PagerViewAdapter.js +29 -12
  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 +20 -2
  7. package/lib/module/TabBar.js.map +1 -1
  8. package/lib/module/TabBarItem.js +63 -32
  9. package/lib/module/TabBarItem.js.map +1 -1
  10. package/lib/module/TabView.js +2 -0
  11. package/lib/module/TabView.js.map +1 -1
  12. package/lib/typescript/src/PagerViewAdapter.d.ts +2 -0
  13. package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -1
  14. package/lib/typescript/src/PanResponderAdapter.d.ts +2 -0
  15. package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
  16. package/lib/typescript/src/PlatformPressable.d.ts +1 -1
  17. package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
  18. package/lib/typescript/src/SceneMap.d.ts +1 -1
  19. package/lib/typescript/src/SceneMap.d.ts.map +1 -1
  20. package/lib/typescript/src/SceneView.d.ts +1 -1
  21. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  22. package/lib/typescript/src/TabBar.d.ts +1 -1
  23. package/lib/typescript/src/TabBar.d.ts.map +1 -1
  24. package/lib/typescript/src/TabBarIndicator.d.ts +1 -1
  25. package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
  26. package/lib/typescript/src/TabBarItem.d.ts +4 -1
  27. package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
  28. package/lib/typescript/src/TabBarItemLabel.d.ts +1 -1
  29. package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -1
  30. package/lib/typescript/src/TabView.d.ts +1 -1
  31. package/lib/typescript/src/TabView.d.ts.map +1 -1
  32. package/lib/typescript/src/types.d.ts +2 -1
  33. package/lib/typescript/src/types.d.ts.map +1 -1
  34. package/package.json +7 -6
  35. package/src/PagerViewAdapter.tsx +68 -16
  36. package/src/PanResponderAdapter.tsx +2 -0
  37. package/src/PlatformPressable.tsx +1 -1
  38. package/src/TabBar.tsx +39 -2
  39. package/src/TabBarItem.tsx +111 -47
  40. package/src/TabView.tsx +2 -1
  41. package/src/types.tsx +2 -1
@@ -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
- }, [route, customIcon, 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
  }
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