react-native-tab-view 3.2.1 → 3.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.
- package/{LICENSE.md → LICENSE} +0 -0
- package/README.md +5 -36
- package/lib/commonjs/PagerViewAdapter.js +2 -1
- package/lib/commonjs/PagerViewAdapter.js.map +1 -1
- package/lib/commonjs/PanResponderAdapter.js +2 -1
- package/lib/commonjs/PanResponderAdapter.js.map +1 -1
- package/lib/commonjs/SceneMap.js +9 -12
- package/lib/commonjs/SceneMap.js.map +1 -1
- package/lib/commonjs/SceneView.js +54 -101
- package/lib/commonjs/SceneView.js.map +1 -1
- package/lib/commonjs/TabBar.js +358 -325
- package/lib/commonjs/TabBar.js.map +1 -1
- package/lib/commonjs/TabBarIndicator.js +81 -99
- package/lib/commonjs/TabBarIndicator.js.map +1 -1
- package/lib/commonjs/TabBarItem.js +184 -161
- package/lib/commonjs/TabBarItem.js.map +1 -1
- package/lib/commonjs/TabView.js +2 -2
- package/lib/commonjs/TabView.js.map +1 -1
- package/lib/commonjs/index.js +3 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/PagerViewAdapter.js +2 -1
- package/lib/module/PagerViewAdapter.js.map +1 -1
- package/lib/module/PanResponderAdapter.js +3 -2
- package/lib/module/PanResponderAdapter.js.map +1 -1
- package/lib/module/SceneMap.js +9 -14
- package/lib/module/SceneMap.js.map +1 -1
- package/lib/module/SceneView.js +54 -99
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/TabBar.js +355 -324
- package/lib/module/TabBar.js.map +1 -1
- package/lib/module/TabBarIndicator.js +75 -93
- package/lib/module/TabBarIndicator.js.map +1 -1
- package/lib/module/TabBarItem.js +178 -154
- package/lib/module/TabBarItem.js.map +1 -1
- package/lib/module/TabView.js +2 -2
- package/lib/module/TabView.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/{Pager.android.d.ts → src/Pager.android.d.ts} +0 -0
- package/lib/typescript/{Pager.d.ts → src/Pager.d.ts} +0 -0
- package/lib/typescript/{Pager.ios.d.ts → src/Pager.ios.d.ts} +0 -0
- package/lib/typescript/{PagerViewAdapter.d.ts → src/PagerViewAdapter.d.ts} +1 -1
- package/lib/typescript/{PanResponderAdapter.d.ts → src/PanResponderAdapter.d.ts} +1 -1
- package/lib/typescript/{PlatformPressable.d.ts → src/PlatformPressable.d.ts} +0 -0
- package/lib/typescript/{SceneMap.d.ts → src/SceneMap.d.ts} +5 -3
- package/lib/typescript/src/SceneView.d.ts +15 -0
- package/lib/typescript/src/TabBar.d.ts +42 -0
- package/lib/typescript/src/TabBarIndicator.d.ts +12 -0
- package/lib/typescript/{TabBarItem.d.ts → src/TabBarItem.d.ts} +5 -7
- package/lib/typescript/{TabView.d.ts → src/TabView.d.ts} +1 -1
- package/lib/typescript/{index.d.ts → src/index.d.ts} +7 -7
- package/lib/typescript/{types.d.ts → src/types.d.ts} +0 -0
- package/lib/typescript/{useAnimatedValue.d.ts → src/useAnimatedValue.d.ts} +0 -0
- package/package.json +28 -58
- package/src/PagerViewAdapter.tsx +11 -5
- package/src/PanResponderAdapter.tsx +16 -12
- package/src/SceneMap.tsx +12 -7
- package/src/SceneView.tsx +73 -108
- package/src/TabBar.tsx +506 -401
- package/src/TabBarIndicator.tsx +114 -117
- package/src/TabBarItem.tsx +230 -200
- package/src/TabView.tsx +6 -5
- package/src/index.tsx +7 -12
- package/lib/typescript/SceneView.d.ts +0 -32
- package/lib/typescript/TabBar.d.ts +0 -72
- package/lib/typescript/TabBarIndicator.d.ts +0 -20
package/src/TabBar.tsx
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Animated,
|
|
4
|
+
FlatList,
|
|
5
|
+
I18nManager,
|
|
6
|
+
LayoutChangeEvent,
|
|
7
|
+
ListRenderItemInfo,
|
|
8
|
+
Platform,
|
|
9
|
+
StyleProp,
|
|
4
10
|
StyleSheet,
|
|
11
|
+
TextStyle,
|
|
5
12
|
View,
|
|
6
|
-
StyleProp,
|
|
7
13
|
ViewStyle,
|
|
8
|
-
|
|
9
|
-
LayoutChangeEvent,
|
|
10
|
-
I18nManager,
|
|
11
|
-
Platform,
|
|
12
|
-
FlatList,
|
|
13
|
-
ListRenderItemInfo,
|
|
14
|
+
ViewToken,
|
|
14
15
|
} from 'react-native';
|
|
15
|
-
import
|
|
16
|
+
import useLatestCallback from 'use-latest-callback';
|
|
17
|
+
|
|
16
18
|
import TabBarIndicator, { Props as IndicatorProps } from './TabBarIndicator';
|
|
19
|
+
import TabBarItem, { Props as TabBarItemProps } from './TabBarItem';
|
|
17
20
|
import type {
|
|
21
|
+
Event,
|
|
22
|
+
Layout,
|
|
23
|
+
NavigationState,
|
|
18
24
|
Route,
|
|
19
25
|
Scene,
|
|
20
26
|
SceneRendererProps,
|
|
21
|
-
NavigationState,
|
|
22
|
-
Layout,
|
|
23
|
-
Event,
|
|
24
27
|
} from './types';
|
|
28
|
+
import useAnimatedValue from './useAnimatedValue';
|
|
25
29
|
|
|
26
30
|
export type Props<T extends Route> = SceneRendererProps & {
|
|
27
31
|
navigationState: NavigationState<T>;
|
|
@@ -31,10 +35,10 @@ export type Props<T extends Route> = SceneRendererProps & {
|
|
|
31
35
|
inactiveColor?: string;
|
|
32
36
|
pressColor?: string;
|
|
33
37
|
pressOpacity?: number;
|
|
34
|
-
getLabelText
|
|
35
|
-
getAccessible
|
|
36
|
-
getAccessibilityLabel
|
|
37
|
-
getTestID
|
|
38
|
+
getLabelText?: (scene: Scene<T>) => string | undefined;
|
|
39
|
+
getAccessible?: (scene: Scene<T>) => boolean | undefined;
|
|
40
|
+
getAccessibilityLabel?: (scene: Scene<T>) => string | undefined;
|
|
41
|
+
getTestID?: (scene: Scene<T>) => string | undefined;
|
|
38
42
|
renderLabel?: (
|
|
39
43
|
scene: Scene<T> & {
|
|
40
44
|
focused: boolean;
|
|
@@ -48,7 +52,7 @@ export type Props<T extends Route> = SceneRendererProps & {
|
|
|
48
52
|
}
|
|
49
53
|
) => React.ReactNode;
|
|
50
54
|
renderBadge?: (scene: Scene<T>) => React.ReactNode;
|
|
51
|
-
renderIndicator
|
|
55
|
+
renderIndicator?: (props: IndicatorProps<T>) => React.ReactNode;
|
|
52
56
|
renderTabBarItem?: (
|
|
53
57
|
props: TabBarItemProps<T> & { key: string }
|
|
54
58
|
) => React.ReactElement;
|
|
@@ -61,437 +65,538 @@ export type Props<T extends Route> = SceneRendererProps & {
|
|
|
61
65
|
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
62
66
|
style?: StyleProp<ViewStyle>;
|
|
63
67
|
gap?: number;
|
|
68
|
+
testID?: string;
|
|
64
69
|
};
|
|
65
70
|
|
|
66
|
-
type
|
|
67
|
-
layout: Layout;
|
|
68
|
-
tabWidths: { [key: string]: number };
|
|
69
|
-
};
|
|
71
|
+
type FlattenedTabWidth = string | number | undefined;
|
|
70
72
|
|
|
71
73
|
const Separator = ({ width }: { width: number }) => {
|
|
72
74
|
return <View style={{ width }} />;
|
|
73
75
|
};
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
State
|
|
78
|
-
> {
|
|
79
|
-
static defaultProps = {
|
|
80
|
-
getLabelText: ({ route }: Scene<Route>) => route.title,
|
|
81
|
-
getAccessible: ({ route }: Scene<Route>) =>
|
|
82
|
-
typeof route.accessible !== 'undefined' ? route.accessible : true,
|
|
83
|
-
getAccessibilityLabel: ({ route }: Scene<Route>) =>
|
|
84
|
-
typeof route.accessibilityLabel === 'string'
|
|
85
|
-
? route.accessibilityLabel
|
|
86
|
-
: typeof route.title === 'string'
|
|
87
|
-
? route.title
|
|
88
|
-
: undefined,
|
|
89
|
-
getTestID: ({ route }: Scene<Route>) => route.testID,
|
|
90
|
-
renderIndicator: (props: IndicatorProps<Route>) => (
|
|
91
|
-
<TabBarIndicator {...props} />
|
|
92
|
-
),
|
|
93
|
-
gap: 0,
|
|
94
|
-
};
|
|
77
|
+
const getFlattenedTabWidth = (style: StyleProp<ViewStyle>) => {
|
|
78
|
+
const tabStyle = StyleSheet.flatten(style);
|
|
95
79
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
tabWidths: {},
|
|
99
|
-
};
|
|
80
|
+
return tabStyle?.width;
|
|
81
|
+
};
|
|
100
82
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return;
|
|
83
|
+
const getComputedTabWidth = (
|
|
84
|
+
index: number,
|
|
85
|
+
layout: Layout,
|
|
86
|
+
routes: Route[],
|
|
87
|
+
scrollEnabled: boolean | undefined,
|
|
88
|
+
tabWidths: { [key: string]: number },
|
|
89
|
+
flattenedWidth: FlattenedTabWidth
|
|
90
|
+
) => {
|
|
91
|
+
if (flattenedWidth === 'auto') {
|
|
92
|
+
return tabWidths[routes[index].key] || 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
switch (typeof flattenedWidth) {
|
|
96
|
+
case 'number':
|
|
97
|
+
return flattenedWidth;
|
|
98
|
+
case 'string':
|
|
99
|
+
if (flattenedWidth.endsWith('%')) {
|
|
100
|
+
const width = parseFloat(flattenedWidth);
|
|
101
|
+
if (Number.isFinite(width)) {
|
|
102
|
+
return layout.width * (width / 100);
|
|
103
|
+
}
|
|
123
104
|
}
|
|
105
|
+
}
|
|
124
106
|
|
|
125
|
-
|
|
126
|
-
|
|
107
|
+
if (scrollEnabled) {
|
|
108
|
+
return (layout.width / 5) * 2;
|
|
127
109
|
}
|
|
110
|
+
return layout.width / routes.length;
|
|
111
|
+
};
|
|
128
112
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
113
|
+
const getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
|
|
114
|
+
tabBarWidth - layoutWidth;
|
|
115
|
+
|
|
116
|
+
const getTranslateX = (
|
|
117
|
+
scrollAmount: Animated.Value,
|
|
118
|
+
maxScrollDistance: number
|
|
119
|
+
) =>
|
|
120
|
+
Animated.multiply(
|
|
121
|
+
Platform.OS === 'android' && I18nManager.isRTL
|
|
122
|
+
? Animated.add(maxScrollDistance, Animated.multiply(scrollAmount, -1))
|
|
123
|
+
: scrollAmount,
|
|
124
|
+
I18nManager.isRTL ? 1 : -1
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const getTabBarWidth = <T extends Route>({
|
|
128
|
+
navigationState,
|
|
129
|
+
layout,
|
|
130
|
+
gap,
|
|
131
|
+
scrollEnabled,
|
|
132
|
+
flattenedTabWidth,
|
|
133
|
+
tabWidths,
|
|
134
|
+
}: Pick<Props<T>, 'navigationState' | 'gap' | 'layout' | 'scrollEnabled'> & {
|
|
135
|
+
tabWidths: Record<string, number>;
|
|
136
|
+
flattenedTabWidth: FlattenedTabWidth;
|
|
137
|
+
}) => {
|
|
138
|
+
const { routes } = navigationState;
|
|
139
|
+
|
|
140
|
+
return routes.reduce<number>(
|
|
141
|
+
(acc, _, i) =>
|
|
142
|
+
acc +
|
|
143
|
+
(i > 0 ? gap ?? 0 : 0) +
|
|
144
|
+
getComputedTabWidth(
|
|
145
|
+
i,
|
|
146
|
+
layout,
|
|
147
|
+
routes,
|
|
148
|
+
scrollEnabled,
|
|
149
|
+
tabWidths,
|
|
150
|
+
flattenedTabWidth
|
|
151
|
+
),
|
|
152
|
+
0
|
|
153
|
+
);
|
|
154
|
+
};
|
|
132
155
|
|
|
133
|
-
|
|
156
|
+
const normalizeScrollValue = <T extends Route>({
|
|
157
|
+
layout,
|
|
158
|
+
navigationState,
|
|
159
|
+
gap,
|
|
160
|
+
scrollEnabled,
|
|
161
|
+
tabWidths,
|
|
162
|
+
value,
|
|
163
|
+
flattenedTabWidth,
|
|
164
|
+
}: Pick<Props<T>, 'layout' | 'navigationState' | 'gap' | 'scrollEnabled'> & {
|
|
165
|
+
tabWidths: Record<string, number>;
|
|
166
|
+
value: number;
|
|
167
|
+
flattenedTabWidth: FlattenedTabWidth;
|
|
168
|
+
}) => {
|
|
169
|
+
const tabBarWidth = getTabBarWidth({
|
|
170
|
+
layout,
|
|
171
|
+
navigationState,
|
|
172
|
+
tabWidths,
|
|
173
|
+
gap,
|
|
174
|
+
scrollEnabled,
|
|
175
|
+
flattenedTabWidth,
|
|
176
|
+
});
|
|
177
|
+
const maxDistance = getMaxScrollDistance(tabBarWidth, layout.width);
|
|
178
|
+
const scrollValue = Math.max(Math.min(value, maxDistance), 0);
|
|
179
|
+
|
|
180
|
+
if (Platform.OS === 'android' && I18nManager.isRTL) {
|
|
181
|
+
// On Android, scroll value is not applied in reverse in RTL
|
|
182
|
+
// so we need to manually adjust it to apply correct value
|
|
183
|
+
return maxDistance - scrollValue;
|
|
184
|
+
}
|
|
134
185
|
|
|
135
|
-
|
|
186
|
+
return scrollValue;
|
|
187
|
+
};
|
|
136
188
|
|
|
137
|
-
|
|
138
|
-
|
|
189
|
+
const getScrollAmount = <T extends Route>({
|
|
190
|
+
layout,
|
|
191
|
+
navigationState,
|
|
192
|
+
gap,
|
|
193
|
+
scrollEnabled,
|
|
194
|
+
flattenedTabWidth,
|
|
195
|
+
tabWidths,
|
|
196
|
+
}: Pick<Props<T>, 'layout' | 'navigationState' | 'scrollEnabled' | 'gap'> & {
|
|
197
|
+
tabWidths: Record<string, number>;
|
|
198
|
+
flattenedTabWidth: FlattenedTabWidth;
|
|
199
|
+
}) => {
|
|
200
|
+
const centerDistance = Array.from({
|
|
201
|
+
length: navigationState.index + 1,
|
|
202
|
+
}).reduce<number>((total, _, i) => {
|
|
203
|
+
const tabWidth = getComputedTabWidth(
|
|
204
|
+
i,
|
|
205
|
+
layout,
|
|
206
|
+
navigationState.routes,
|
|
207
|
+
scrollEnabled,
|
|
208
|
+
tabWidths,
|
|
209
|
+
flattenedTabWidth
|
|
210
|
+
);
|
|
139
211
|
|
|
140
|
-
|
|
141
|
-
|
|
212
|
+
// To get the current index centered we adjust scroll amount by width of indexes
|
|
213
|
+
// 0 through (i - 1) and add half the width of current index i
|
|
214
|
+
return (
|
|
215
|
+
total +
|
|
216
|
+
(navigationState.index === i
|
|
217
|
+
? (tabWidth + (gap ?? 0)) / 2
|
|
218
|
+
: tabWidth + (gap ?? 0))
|
|
219
|
+
);
|
|
220
|
+
}, 0);
|
|
221
|
+
|
|
222
|
+
const scrollAmount = centerDistance - layout.width / 2;
|
|
223
|
+
|
|
224
|
+
return normalizeScrollValue({
|
|
225
|
+
layout,
|
|
226
|
+
navigationState,
|
|
227
|
+
tabWidths,
|
|
228
|
+
value: scrollAmount,
|
|
229
|
+
gap,
|
|
230
|
+
scrollEnabled,
|
|
231
|
+
flattenedTabWidth,
|
|
232
|
+
});
|
|
233
|
+
};
|
|
142
234
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
235
|
+
const getLabelTextDefault = ({ route }: Scene<Route>) => route.title;
|
|
236
|
+
|
|
237
|
+
const getAccessibleDefault = ({ route }: Scene<Route>) =>
|
|
238
|
+
typeof route.accessible !== 'undefined' ? route.accessible : true;
|
|
239
|
+
|
|
240
|
+
const getAccessibilityLabelDefault = ({ route }: Scene<Route>) =>
|
|
241
|
+
typeof route.accessibilityLabel === 'string'
|
|
242
|
+
? route.accessibilityLabel
|
|
243
|
+
: typeof route.title === 'string'
|
|
244
|
+
? route.title
|
|
245
|
+
: undefined;
|
|
246
|
+
|
|
247
|
+
const renderIndicatorDefault = (props: IndicatorProps<Route>) => (
|
|
248
|
+
<TabBarIndicator {...props} />
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const getTestIdDefault = ({ route }: Scene<Route>) => route.testID;
|
|
252
|
+
|
|
253
|
+
// How many items measurements should we update per batch.
|
|
254
|
+
// Defaults to 10, since that's whats FlatList is using in initialNumToRender.
|
|
255
|
+
const MEASURE_PER_BATCH = 10;
|
|
256
|
+
|
|
257
|
+
export default function TabBar<T extends Route>({
|
|
258
|
+
getLabelText = getLabelTextDefault,
|
|
259
|
+
getAccessible = getAccessibleDefault,
|
|
260
|
+
getAccessibilityLabel = getAccessibilityLabelDefault,
|
|
261
|
+
getTestID = getTestIdDefault,
|
|
262
|
+
renderIndicator = renderIndicatorDefault,
|
|
263
|
+
gap = 0,
|
|
264
|
+
scrollEnabled,
|
|
265
|
+
jumpTo,
|
|
266
|
+
navigationState,
|
|
267
|
+
position,
|
|
268
|
+
activeColor,
|
|
269
|
+
bounces,
|
|
270
|
+
contentContainerStyle,
|
|
271
|
+
inactiveColor,
|
|
272
|
+
indicatorContainerStyle,
|
|
273
|
+
indicatorStyle,
|
|
274
|
+
labelStyle,
|
|
275
|
+
onTabLongPress,
|
|
276
|
+
onTabPress,
|
|
277
|
+
pressColor,
|
|
278
|
+
pressOpacity,
|
|
279
|
+
renderBadge,
|
|
280
|
+
renderIcon,
|
|
281
|
+
renderLabel,
|
|
282
|
+
renderTabBarItem,
|
|
283
|
+
style,
|
|
284
|
+
tabStyle,
|
|
285
|
+
testID,
|
|
286
|
+
}: Props<T>) {
|
|
287
|
+
const [layout, setLayout] = React.useState<Layout>({ width: 0, height: 0 });
|
|
288
|
+
const [tabWidths, setTabWidths] = React.useState<Record<string, number>>({});
|
|
289
|
+
const flatListRef = React.useRef<FlatList | null>(null);
|
|
290
|
+
const isFirst = React.useRef(true);
|
|
291
|
+
const scrollAmount = useAnimatedValue(0);
|
|
292
|
+
const measuredTabWidths = React.useRef<Record<string, number>>({});
|
|
293
|
+
|
|
294
|
+
const { routes } = navigationState;
|
|
295
|
+
const flattenedTabWidth = getFlattenedTabWidth(tabStyle);
|
|
296
|
+
const isWidthDynamic = flattenedTabWidth === 'auto';
|
|
297
|
+
const scrollOffset = getScrollAmount({
|
|
298
|
+
layout,
|
|
299
|
+
navigationState,
|
|
300
|
+
tabWidths,
|
|
301
|
+
gap,
|
|
302
|
+
scrollEnabled,
|
|
303
|
+
flattenedTabWidth,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const hasMeasuredTabWidths =
|
|
307
|
+
Boolean(layout.width) &&
|
|
308
|
+
routes
|
|
309
|
+
.slice(0, navigationState.index)
|
|
310
|
+
.every((r) => typeof tabWidths[r.key] === 'number');
|
|
311
|
+
|
|
312
|
+
React.useEffect(() => {
|
|
313
|
+
if (isFirst.current) {
|
|
314
|
+
isFirst.current = false;
|
|
315
|
+
return;
|
|
153
316
|
}
|
|
154
317
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return flattenedWidth;
|
|
158
|
-
case 'string':
|
|
159
|
-
if (flattenedWidth.endsWith('%')) {
|
|
160
|
-
const width = parseFloat(flattenedWidth);
|
|
161
|
-
if (Number.isFinite(width)) {
|
|
162
|
-
return layout.width * (width / 100);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
318
|
+
if (isWidthDynamic && !hasMeasuredTabWidths) {
|
|
319
|
+
return;
|
|
165
320
|
}
|
|
166
321
|
|
|
167
322
|
if (scrollEnabled) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return layout.width / routes.length;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
private getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
|
|
174
|
-
tabBarWidth - layoutWidth;
|
|
175
|
-
|
|
176
|
-
private getTabBarWidth = (props: Props<T>, state: State) => {
|
|
177
|
-
const { layout, tabWidths } = state;
|
|
178
|
-
const { scrollEnabled, tabStyle } = props;
|
|
179
|
-
const { routes } = props.navigationState;
|
|
180
|
-
|
|
181
|
-
return routes.reduce<number>(
|
|
182
|
-
(acc, _, i) =>
|
|
183
|
-
acc +
|
|
184
|
-
(i > 0 ? props.gap ?? 0 : 0) +
|
|
185
|
-
this.getComputedTabWidth(
|
|
186
|
-
i,
|
|
187
|
-
layout,
|
|
188
|
-
routes,
|
|
189
|
-
scrollEnabled,
|
|
190
|
-
tabWidths,
|
|
191
|
-
this.getFlattenedTabWidth(tabStyle)
|
|
192
|
-
),
|
|
193
|
-
0
|
|
194
|
-
);
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
private normalizeScrollValue = (
|
|
198
|
-
props: Props<T>,
|
|
199
|
-
state: State,
|
|
200
|
-
value: number
|
|
201
|
-
) => {
|
|
202
|
-
const { layout } = state;
|
|
203
|
-
const tabBarWidth = this.getTabBarWidth(props, state);
|
|
204
|
-
const maxDistance = this.getMaxScrollDistance(tabBarWidth, layout.width);
|
|
205
|
-
const scrollValue = Math.max(Math.min(value, maxDistance), 0);
|
|
206
|
-
|
|
207
|
-
if (Platform.OS === 'android' && I18nManager.isRTL) {
|
|
208
|
-
// On Android, scroll value is not applied in reverse in RTL
|
|
209
|
-
// so we need to manually adjust it to apply correct value
|
|
210
|
-
return maxDistance - scrollValue;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return scrollValue;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
private getScrollAmount = (props: Props<T>, state: State, index: number) => {
|
|
217
|
-
const { layout, tabWidths } = state;
|
|
218
|
-
const { scrollEnabled, tabStyle } = props;
|
|
219
|
-
const { routes } = props.navigationState;
|
|
220
|
-
|
|
221
|
-
const centerDistance = Array.from({ length: index + 1 }).reduce<number>(
|
|
222
|
-
(total, _, i) => {
|
|
223
|
-
const tabWidth = this.getComputedTabWidth(
|
|
224
|
-
i,
|
|
225
|
-
layout,
|
|
226
|
-
routes,
|
|
227
|
-
scrollEnabled,
|
|
228
|
-
tabWidths,
|
|
229
|
-
this.getFlattenedTabWidth(tabStyle)
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
// To get the current index centered we adjust scroll amount by width of indexes
|
|
233
|
-
// 0 through (i - 1) and add half the width of current index i
|
|
234
|
-
return (
|
|
235
|
-
total +
|
|
236
|
-
(index === i
|
|
237
|
-
? (tabWidth + (props.gap ?? 0)) / 2
|
|
238
|
-
: tabWidth + (props.gap ?? 0))
|
|
239
|
-
);
|
|
240
|
-
},
|
|
241
|
-
0
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
const scrollAmount = centerDistance - layout.width / 2;
|
|
245
|
-
|
|
246
|
-
return this.normalizeScrollValue(props, state, scrollAmount);
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
private resetScroll = (index: number) => {
|
|
250
|
-
if (this.props.scrollEnabled) {
|
|
251
|
-
this.flatListRef.current?.scrollToOffset({
|
|
252
|
-
offset: this.getScrollAmount(this.props, this.state, index),
|
|
323
|
+
flatListRef.current?.scrollToOffset({
|
|
324
|
+
offset: scrollOffset,
|
|
253
325
|
animated: true,
|
|
254
326
|
});
|
|
255
327
|
}
|
|
256
|
-
};
|
|
328
|
+
}, [hasMeasuredTabWidths, isWidthDynamic, scrollEnabled, scrollOffset]);
|
|
257
329
|
|
|
258
|
-
|
|
330
|
+
const handleLayout = (e: LayoutChangeEvent) => {
|
|
259
331
|
const { height, width } = e.nativeEvent.layout;
|
|
260
332
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
this.setState({
|
|
269
|
-
layout: {
|
|
270
|
-
height,
|
|
271
|
-
width,
|
|
272
|
-
},
|
|
273
|
-
});
|
|
333
|
+
setLayout((layout) =>
|
|
334
|
+
layout.width === width && layout.height === height
|
|
335
|
+
? layout
|
|
336
|
+
: { width, height }
|
|
337
|
+
);
|
|
274
338
|
};
|
|
275
339
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
340
|
+
const tabBarWidth = getTabBarWidth({
|
|
341
|
+
layout,
|
|
342
|
+
navigationState,
|
|
343
|
+
tabWidths,
|
|
344
|
+
gap,
|
|
345
|
+
scrollEnabled,
|
|
346
|
+
flattenedTabWidth,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const separatorsWidth = Math.max(0, routes.length - 1) * gap;
|
|
350
|
+
const separatorPercent = (separatorsWidth / tabBarWidth) * 100;
|
|
351
|
+
const tabBarWidthPercent = `${routes.length * 40}%`;
|
|
352
|
+
|
|
353
|
+
const translateX = React.useMemo(
|
|
354
|
+
() =>
|
|
355
|
+
getTranslateX(
|
|
356
|
+
scrollAmount,
|
|
357
|
+
getMaxScrollDistance(tabBarWidth, layout.width)
|
|
358
|
+
),
|
|
359
|
+
[layout.width, scrollAmount, tabBarWidth]
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const renderItem = React.useCallback(
|
|
363
|
+
({ item: route, index }: ListRenderItemInfo<T>) => {
|
|
364
|
+
const props: TabBarItemProps<T> & { key: string } = {
|
|
365
|
+
key: route.key,
|
|
366
|
+
position: position,
|
|
367
|
+
route: route,
|
|
368
|
+
navigationState: navigationState,
|
|
369
|
+
getAccessibilityLabel: getAccessibilityLabel,
|
|
370
|
+
getAccessible: getAccessible,
|
|
371
|
+
getLabelText: getLabelText,
|
|
372
|
+
getTestID: getTestID,
|
|
373
|
+
renderBadge: renderBadge,
|
|
374
|
+
renderIcon: renderIcon,
|
|
375
|
+
renderLabel: renderLabel,
|
|
376
|
+
activeColor: activeColor,
|
|
377
|
+
inactiveColor: inactiveColor,
|
|
378
|
+
pressColor: pressColor,
|
|
379
|
+
pressOpacity: pressOpacity,
|
|
380
|
+
onLayout: isWidthDynamic
|
|
381
|
+
? (e: LayoutChangeEvent) => {
|
|
382
|
+
measuredTabWidths.current[route.key] = e.nativeEvent.layout.width;
|
|
383
|
+
|
|
384
|
+
// When we have measured widths for all of the tabs, we should updates the state
|
|
385
|
+
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
|
|
386
|
+
// If we have more than 10 routes divide updating tabWidths into multiple batches. Here we update only first batch of 10 items.
|
|
387
|
+
if (
|
|
388
|
+
routes.length > MEASURE_PER_BATCH &&
|
|
389
|
+
index === MEASURE_PER_BATCH &&
|
|
390
|
+
routes
|
|
391
|
+
.slice(0, MEASURE_PER_BATCH)
|
|
392
|
+
.every(
|
|
393
|
+
(r) => typeof measuredTabWidths.current[r.key] === 'number'
|
|
394
|
+
)
|
|
395
|
+
) {
|
|
396
|
+
setTabWidths({ ...measuredTabWidths.current });
|
|
397
|
+
} else if (
|
|
398
|
+
routes.every(
|
|
399
|
+
(r) => typeof measuredTabWidths.current[r.key] === 'number'
|
|
400
|
+
)
|
|
401
|
+
) {
|
|
402
|
+
// When we have measured widths for all of the tabs, we should updates the state
|
|
403
|
+
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
|
|
404
|
+
setTabWidths({ ...measuredTabWidths.current });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
: undefined,
|
|
408
|
+
onPress: () => {
|
|
409
|
+
const event: Scene<T> & Event = {
|
|
410
|
+
route,
|
|
411
|
+
defaultPrevented: false,
|
|
412
|
+
preventDefault: () => {
|
|
413
|
+
event.defaultPrevented = true;
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
onTabPress?.(event);
|
|
418
|
+
|
|
419
|
+
if (event.defaultPrevented) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
286
422
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
423
|
+
jumpTo(route.key);
|
|
424
|
+
},
|
|
425
|
+
onLongPress: () => onTabLongPress?.({ route }),
|
|
426
|
+
labelStyle: labelStyle,
|
|
427
|
+
style: tabStyle,
|
|
428
|
+
// Calculate the deafult width for tab for FlatList to work
|
|
429
|
+
defaultTabWidth: !isWidthDynamic
|
|
430
|
+
? getComputedTabWidth(
|
|
431
|
+
index,
|
|
432
|
+
layout,
|
|
433
|
+
routes,
|
|
434
|
+
scrollEnabled,
|
|
435
|
+
tabWidths,
|
|
436
|
+
getFlattenedTabWidth(tabStyle)
|
|
437
|
+
)
|
|
438
|
+
: undefined,
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
<>
|
|
443
|
+
{gap > 0 && index > 0 ? <Separator width={gap} /> : null}
|
|
444
|
+
{renderTabBarItem ? (
|
|
445
|
+
renderTabBarItem(props)
|
|
446
|
+
) : (
|
|
447
|
+
<TabBarItem {...props} />
|
|
448
|
+
)}
|
|
449
|
+
</>
|
|
450
|
+
);
|
|
451
|
+
},
|
|
452
|
+
[
|
|
453
|
+
activeColor,
|
|
454
|
+
gap,
|
|
294
455
|
getAccessibilityLabel,
|
|
295
456
|
getAccessible,
|
|
296
457
|
getLabelText,
|
|
297
458
|
getTestID,
|
|
459
|
+
inactiveColor,
|
|
460
|
+
isWidthDynamic,
|
|
461
|
+
jumpTo,
|
|
462
|
+
labelStyle,
|
|
463
|
+
layout,
|
|
464
|
+
navigationState,
|
|
465
|
+
onTabLongPress,
|
|
466
|
+
onTabPress,
|
|
467
|
+
position,
|
|
468
|
+
pressColor,
|
|
469
|
+
pressOpacity,
|
|
298
470
|
renderBadge,
|
|
299
471
|
renderIcon,
|
|
300
472
|
renderLabel,
|
|
301
473
|
renderTabBarItem,
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
pressColor,
|
|
305
|
-
pressOpacity,
|
|
306
|
-
onTabPress,
|
|
307
|
-
onTabLongPress,
|
|
474
|
+
routes,
|
|
475
|
+
scrollEnabled,
|
|
308
476
|
tabStyle,
|
|
309
|
-
|
|
310
|
-
|
|
477
|
+
tabWidths,
|
|
478
|
+
]
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
const keyExtractor = React.useCallback((item: T) => item.key, []);
|
|
482
|
+
|
|
483
|
+
const contentContainerStyleMemoized = React.useMemo(
|
|
484
|
+
() => [
|
|
485
|
+
styles.tabContent,
|
|
486
|
+
scrollEnabled
|
|
487
|
+
? {
|
|
488
|
+
width:
|
|
489
|
+
tabBarWidth > separatorsWidth ? tabBarWidth : tabBarWidthPercent,
|
|
490
|
+
}
|
|
491
|
+
: styles.container,
|
|
311
492
|
contentContainerStyle,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
493
|
+
],
|
|
494
|
+
[
|
|
495
|
+
contentContainerStyle,
|
|
496
|
+
scrollEnabled,
|
|
497
|
+
separatorsWidth,
|
|
498
|
+
tabBarWidth,
|
|
499
|
+
tabBarWidthPercent,
|
|
500
|
+
]
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
const handleScroll = React.useMemo(
|
|
504
|
+
() =>
|
|
505
|
+
Animated.event(
|
|
506
|
+
[
|
|
507
|
+
{
|
|
508
|
+
nativeEvent: {
|
|
509
|
+
contentOffset: { x: scrollAmount },
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
{ useNativeDriver: true }
|
|
514
|
+
),
|
|
515
|
+
[scrollAmount]
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
const handleViewableItemsChanged = useLatestCallback(
|
|
519
|
+
({ changed }: { changed: ViewToken[] }) => {
|
|
520
|
+
if (routes.length <= MEASURE_PER_BATCH) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
// Get next vievable item
|
|
524
|
+
const item = changed[changed.length - 1];
|
|
525
|
+
const index = item?.index || 0;
|
|
526
|
+
if (
|
|
527
|
+
item.isViewable &&
|
|
528
|
+
(index % 10 === 0 ||
|
|
529
|
+
index === navigationState.index ||
|
|
530
|
+
index === routes.length - 1)
|
|
531
|
+
) {
|
|
532
|
+
setTabWidths({ ...measuredTabWidths.current });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
);
|
|
329
536
|
|
|
330
|
-
|
|
537
|
+
return (
|
|
538
|
+
<Animated.View onLayout={handleLayout} style={[styles.tabBar, style]}>
|
|
331
539
|
<Animated.View
|
|
332
|
-
|
|
333
|
-
style={[
|
|
540
|
+
pointerEvents="none"
|
|
541
|
+
style={[
|
|
542
|
+
styles.indicatorContainer,
|
|
543
|
+
scrollEnabled ? { transform: [{ translateX }] as any } : null,
|
|
544
|
+
tabBarWidth > separatorsWidth
|
|
545
|
+
? { width: tabBarWidth - separatorsWidth }
|
|
546
|
+
: scrollEnabled
|
|
547
|
+
? { width: tabBarWidthPercent }
|
|
548
|
+
: null,
|
|
549
|
+
indicatorContainerStyle,
|
|
550
|
+
]}
|
|
334
551
|
>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
: `${(100 - separatorPercent) / routes.length}%`,
|
|
356
|
-
style: indicatorStyle,
|
|
357
|
-
getTabWidth: (i: number) =>
|
|
358
|
-
this.getComputedTabWidth(
|
|
359
|
-
i,
|
|
360
|
-
layout,
|
|
361
|
-
routes,
|
|
362
|
-
scrollEnabled,
|
|
363
|
-
tabWidths,
|
|
364
|
-
this.getFlattenedTabWidth(tabStyle)
|
|
365
|
-
),
|
|
366
|
-
gap,
|
|
367
|
-
})}
|
|
368
|
-
</Animated.View>
|
|
369
|
-
<View style={styles.scroll}>
|
|
370
|
-
<Animated.FlatList
|
|
371
|
-
data={routes as Animated.WithAnimatedValue<T>[]}
|
|
372
|
-
keyExtractor={(item) => item.key}
|
|
373
|
-
horizontal
|
|
374
|
-
accessibilityRole="tablist"
|
|
375
|
-
keyboardShouldPersistTaps="handled"
|
|
376
|
-
scrollEnabled={scrollEnabled}
|
|
377
|
-
bounces={bounces}
|
|
378
|
-
alwaysBounceHorizontal={false}
|
|
379
|
-
scrollsToTop={false}
|
|
380
|
-
showsHorizontalScrollIndicator={false}
|
|
381
|
-
showsVerticalScrollIndicator={false}
|
|
382
|
-
automaticallyAdjustContentInsets={false}
|
|
383
|
-
overScrollMode="never"
|
|
384
|
-
contentContainerStyle={[
|
|
385
|
-
styles.tabContent,
|
|
386
|
-
scrollEnabled
|
|
387
|
-
? {
|
|
388
|
-
width:
|
|
389
|
-
tabBarWidth > separatorsWidth
|
|
390
|
-
? tabBarWidth
|
|
391
|
-
: tabBarWidthPercent,
|
|
392
|
-
}
|
|
393
|
-
: styles.container,
|
|
394
|
-
contentContainerStyle,
|
|
395
|
-
]}
|
|
396
|
-
scrollEventThrottle={16}
|
|
397
|
-
renderItem={({ item: route, index }: ListRenderItemInfo<T>) => {
|
|
398
|
-
const props: TabBarItemProps<T> & { key: string } = {
|
|
399
|
-
key: route.key,
|
|
400
|
-
position: position,
|
|
401
|
-
route: route,
|
|
402
|
-
navigationState: navigationState,
|
|
403
|
-
getAccessibilityLabel: getAccessibilityLabel,
|
|
404
|
-
getAccessible: getAccessible,
|
|
405
|
-
getLabelText: getLabelText,
|
|
406
|
-
getTestID: getTestID,
|
|
407
|
-
renderBadge: renderBadge,
|
|
408
|
-
renderIcon: renderIcon,
|
|
409
|
-
renderLabel: renderLabel,
|
|
410
|
-
activeColor: activeColor,
|
|
411
|
-
inactiveColor: inactiveColor,
|
|
412
|
-
pressColor: pressColor,
|
|
413
|
-
pressOpacity: pressOpacity,
|
|
414
|
-
onLayout: isWidthDynamic
|
|
415
|
-
? (e) => {
|
|
416
|
-
this.measuredTabWidths[route.key] =
|
|
417
|
-
e.nativeEvent.layout.width;
|
|
418
|
-
|
|
419
|
-
// When we have measured widths for all of the tabs, we should updates the state
|
|
420
|
-
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
|
|
421
|
-
if (
|
|
422
|
-
routes.every(
|
|
423
|
-
(r) =>
|
|
424
|
-
typeof this.measuredTabWidths[r.key] === 'number'
|
|
425
|
-
)
|
|
426
|
-
) {
|
|
427
|
-
this.setState({
|
|
428
|
-
tabWidths: { ...this.measuredTabWidths },
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
: undefined,
|
|
433
|
-
onPress: () => {
|
|
434
|
-
const event: Scene<T> & Event = {
|
|
435
|
-
route,
|
|
436
|
-
defaultPrevented: false,
|
|
437
|
-
preventDefault: () => {
|
|
438
|
-
event.defaultPrevented = true;
|
|
439
|
-
},
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
onTabPress?.(event);
|
|
443
|
-
|
|
444
|
-
if (event.defaultPrevented) {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
this.props.jumpTo(route.key);
|
|
449
|
-
},
|
|
450
|
-
onLongPress: () => onTabLongPress?.({ route }),
|
|
451
|
-
labelStyle: labelStyle,
|
|
452
|
-
style: [
|
|
453
|
-
tabStyle,
|
|
454
|
-
// Calculate the deafult width for tab for FlatList to work.
|
|
455
|
-
this.getFlattenedTabWidth(tabStyle) === undefined && {
|
|
456
|
-
width: this.getComputedTabWidth(
|
|
457
|
-
index,
|
|
458
|
-
layout,
|
|
459
|
-
routes,
|
|
460
|
-
scrollEnabled,
|
|
461
|
-
tabWidths,
|
|
462
|
-
this.getFlattenedTabWidth(tabStyle)
|
|
463
|
-
),
|
|
464
|
-
},
|
|
465
|
-
],
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
return (
|
|
469
|
-
<React.Fragment key={route.key}>
|
|
470
|
-
{gap > 0 && index > 0 ? <Separator width={gap} /> : null}
|
|
471
|
-
{renderTabBarItem ? (
|
|
472
|
-
renderTabBarItem(props)
|
|
473
|
-
) : (
|
|
474
|
-
<TabBarItem {...props} />
|
|
475
|
-
)}
|
|
476
|
-
</React.Fragment>
|
|
477
|
-
);
|
|
478
|
-
}}
|
|
479
|
-
onScroll={Animated.event(
|
|
480
|
-
[
|
|
481
|
-
{
|
|
482
|
-
nativeEvent: {
|
|
483
|
-
contentOffset: { x: this.scrollAmount },
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
],
|
|
487
|
-
{ useNativeDriver: true }
|
|
488
|
-
)}
|
|
489
|
-
ref={this.flatListRef}
|
|
490
|
-
/>
|
|
491
|
-
</View>
|
|
552
|
+
{renderIndicator({
|
|
553
|
+
position,
|
|
554
|
+
layout,
|
|
555
|
+
navigationState,
|
|
556
|
+
jumpTo,
|
|
557
|
+
width: isWidthDynamic
|
|
558
|
+
? 'auto'
|
|
559
|
+
: `${(100 - separatorPercent) / routes.length}%`,
|
|
560
|
+
style: indicatorStyle,
|
|
561
|
+
getTabWidth: (i: number) =>
|
|
562
|
+
getComputedTabWidth(
|
|
563
|
+
i,
|
|
564
|
+
layout,
|
|
565
|
+
routes,
|
|
566
|
+
scrollEnabled,
|
|
567
|
+
tabWidths,
|
|
568
|
+
flattenedTabWidth
|
|
569
|
+
),
|
|
570
|
+
gap,
|
|
571
|
+
})}
|
|
492
572
|
</Animated.View>
|
|
493
|
-
|
|
494
|
-
|
|
573
|
+
<View style={styles.scroll}>
|
|
574
|
+
<Animated.FlatList
|
|
575
|
+
data={routes as Animated.WithAnimatedValue<T>[]}
|
|
576
|
+
keyExtractor={keyExtractor}
|
|
577
|
+
horizontal
|
|
578
|
+
accessibilityRole="tablist"
|
|
579
|
+
keyboardShouldPersistTaps="handled"
|
|
580
|
+
scrollEnabled={scrollEnabled}
|
|
581
|
+
bounces={bounces}
|
|
582
|
+
initialNumToRender={MEASURE_PER_BATCH}
|
|
583
|
+
onViewableItemsChanged={handleViewableItemsChanged}
|
|
584
|
+
alwaysBounceHorizontal={false}
|
|
585
|
+
scrollsToTop={false}
|
|
586
|
+
showsHorizontalScrollIndicator={false}
|
|
587
|
+
showsVerticalScrollIndicator={false}
|
|
588
|
+
automaticallyAdjustContentInsets={false}
|
|
589
|
+
overScrollMode="never"
|
|
590
|
+
contentContainerStyle={contentContainerStyleMemoized}
|
|
591
|
+
scrollEventThrottle={16}
|
|
592
|
+
renderItem={renderItem}
|
|
593
|
+
onScroll={handleScroll}
|
|
594
|
+
ref={flatListRef}
|
|
595
|
+
testID={testID}
|
|
596
|
+
/>
|
|
597
|
+
</View>
|
|
598
|
+
</Animated.View>
|
|
599
|
+
);
|
|
495
600
|
}
|
|
496
601
|
|
|
497
602
|
const styles = StyleSheet.create({
|