1 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
2 |
|
3 | import * as React from 'react';
|
4 | import { Animated, StyleSheet, View, I18nManager, Platform } from 'react-native';
|
5 | import TabBarItem from './TabBarItem';
|
6 | import TabBarIndicator from './TabBarIndicator';
|
7 | export default class TabBar extends React.Component {
|
8 | constructor(...args) {
|
9 | super(...args);
|
10 |
|
11 | _defineProperty(this, "state", {
|
12 | layout: {
|
13 | width: 0,
|
14 | height: 0
|
15 | },
|
16 | tabWidths: {}
|
17 | });
|
18 |
|
19 | _defineProperty(this, "measuredTabWidths", {});
|
20 |
|
21 | _defineProperty(this, "scrollAmount", new Animated.Value(0));
|
22 |
|
23 | _defineProperty(this, "scrollViewRef", React.createRef());
|
24 |
|
25 | _defineProperty(this, "getFlattenedTabWidth", style => {
|
26 | const tabStyle = StyleSheet.flatten(style);
|
27 | return tabStyle ? tabStyle.width : undefined;
|
28 | });
|
29 |
|
30 | _defineProperty(this, "getComputedTabWidth", (index, layout, routes, scrollEnabled, tabWidths, flattenedWidth) => {
|
31 | if (flattenedWidth === 'auto') {
|
32 | return tabWidths[routes[index].key] || 0;
|
33 | }
|
34 |
|
35 | switch (typeof flattenedWidth) {
|
36 | case 'number':
|
37 | return flattenedWidth;
|
38 |
|
39 | case 'string':
|
40 | if (flattenedWidth.endsWith('%')) {
|
41 | const width = parseFloat(flattenedWidth);
|
42 |
|
43 | if (Number.isFinite(width)) {
|
44 | return layout.width * (width / 100);
|
45 | }
|
46 | }
|
47 |
|
48 | }
|
49 |
|
50 | if (scrollEnabled) {
|
51 | return layout.width / 5 * 2;
|
52 | }
|
53 |
|
54 | return layout.width / routes.length;
|
55 | });
|
56 |
|
57 | _defineProperty(this, "getMaxScrollDistance", (tabBarWidth, layoutWidth) => tabBarWidth - layoutWidth);
|
58 |
|
59 | _defineProperty(this, "getTabBarWidth", (props, state) => {
|
60 | const {
|
61 | layout,
|
62 | tabWidths
|
63 | } = state;
|
64 | const {
|
65 | scrollEnabled,
|
66 | tabStyle
|
67 | } = props;
|
68 | const {
|
69 | routes
|
70 | } = props.navigationState;
|
71 | return routes.reduce((acc, _, i) => acc + this.getComputedTabWidth(i, layout, routes, scrollEnabled, tabWidths, this.getFlattenedTabWidth(tabStyle)), 0);
|
72 | });
|
73 |
|
74 | _defineProperty(this, "normalizeScrollValue", (props, state, value) => {
|
75 | const {
|
76 | layout
|
77 | } = state;
|
78 | const tabBarWidth = this.getTabBarWidth(props, state);
|
79 | const maxDistance = this.getMaxScrollDistance(tabBarWidth, layout.width);
|
80 | const scrollValue = Math.max(Math.min(value, maxDistance), 0);
|
81 |
|
82 | if (Platform.OS === 'android' && I18nManager.isRTL) {
|
83 |
|
84 |
|
85 | return maxDistance - scrollValue;
|
86 | }
|
87 |
|
88 | return scrollValue;
|
89 | });
|
90 |
|
91 | _defineProperty(this, "getScrollAmount", (props, state, index) => {
|
92 | const {
|
93 | layout,
|
94 | tabWidths
|
95 | } = state;
|
96 | const {
|
97 | scrollEnabled,
|
98 | tabStyle
|
99 | } = props;
|
100 | const {
|
101 | routes
|
102 | } = props.navigationState;
|
103 | const centerDistance = Array.from({
|
104 | length: index + 1
|
105 | }).reduce((total, _, i) => {
|
106 | const tabWidth = this.getComputedTabWidth(i, layout, routes, scrollEnabled, tabWidths, this.getFlattenedTabWidth(tabStyle));
|
107 |
|
108 |
|
109 | return total + (index === i ? tabWidth / 2 : tabWidth);
|
110 | }, 0);
|
111 | const scrollAmount = centerDistance - layout.width / 2;
|
112 | return this.normalizeScrollValue(props, state, scrollAmount);
|
113 | });
|
114 |
|
115 | _defineProperty(this, "resetScroll", index => {
|
116 | if (this.props.scrollEnabled) {
|
117 | var _this$scrollViewRef$c;
|
118 |
|
119 | (_this$scrollViewRef$c = this.scrollViewRef.current) === null || _this$scrollViewRef$c === void 0 ? void 0 : _this$scrollViewRef$c.scrollTo({
|
120 | x: this.getScrollAmount(this.props, this.state, index),
|
121 | animated: true
|
122 | });
|
123 | }
|
124 | });
|
125 |
|
126 | _defineProperty(this, "handleLayout", e => {
|
127 | const {
|
128 | height,
|
129 | width
|
130 | } = e.nativeEvent.layout;
|
131 |
|
132 | if (this.state.layout.width === width && this.state.layout.height === height) {
|
133 | return;
|
134 | }
|
135 |
|
136 | this.setState({
|
137 | layout: {
|
138 | height,
|
139 | width
|
140 | }
|
141 | });
|
142 | });
|
143 |
|
144 | _defineProperty(this, "getTranslateX", (scrollAmount, maxScrollDistance) => Animated.multiply(Platform.OS === 'android' && I18nManager.isRTL ? Animated.add(maxScrollDistance, Animated.multiply(scrollAmount, -1)) : scrollAmount, I18nManager.isRTL ? 1 : -1));
|
145 | }
|
146 |
|
147 | componentDidUpdate(prevProps, prevState) {
|
148 | const {
|
149 | navigationState
|
150 | } = this.props;
|
151 | const {
|
152 | layout,
|
153 | tabWidths
|
154 | } = this.state;
|
155 |
|
156 | if (prevProps.navigationState.routes.length !== navigationState.routes.length || prevProps.navigationState.index !== navigationState.index || prevState.layout.width !== layout.width || prevState.tabWidths !== tabWidths) {
|
157 | if (this.getFlattenedTabWidth(this.props.tabStyle) === 'auto' && !(layout.width && navigationState.routes.every(r => typeof tabWidths[r.key] === 'number'))) {
|
158 |
|
159 | return;
|
160 | }
|
161 |
|
162 | this.resetScroll(navigationState.index);
|
163 | }
|
164 | }
|
165 |
|
166 |
|
167 |
|
168 | render() {
|
169 | const {
|
170 | position,
|
171 | navigationState,
|
172 | jumpTo,
|
173 | scrollEnabled,
|
174 | bounces,
|
175 | getAccessibilityLabel,
|
176 | getAccessible,
|
177 | getLabelText,
|
178 | getTestID,
|
179 | renderBadge,
|
180 | renderIcon,
|
181 | renderLabel,
|
182 | renderTabBarItem,
|
183 | activeColor,
|
184 | inactiveColor,
|
185 | pressColor,
|
186 | pressOpacity,
|
187 | onTabPress,
|
188 | onTabLongPress,
|
189 | tabStyle,
|
190 | labelStyle,
|
191 | indicatorStyle,
|
192 | contentContainerStyle,
|
193 | style,
|
194 | indicatorContainerStyle
|
195 | } = this.props;
|
196 | const {
|
197 | layout,
|
198 | tabWidths
|
199 | } = this.state;
|
200 | const {
|
201 | routes
|
202 | } = navigationState;
|
203 | const isWidthDynamic = this.getFlattenedTabWidth(tabStyle) === 'auto';
|
204 | const tabBarWidth = this.getTabBarWidth(this.props, this.state);
|
205 | const tabBarWidthPercent = `${routes.length * 40}%`;
|
206 | const translateX = this.getTranslateX(this.scrollAmount, this.getMaxScrollDistance(tabBarWidth, layout.width));
|
207 | return React.createElement(Animated.View, {
|
208 | onLayout: this.handleLayout,
|
209 | style: [styles.tabBar, style]
|
210 | }, React.createElement(Animated.View, {
|
211 | pointerEvents: "none",
|
212 | style: [styles.indicatorContainer, scrollEnabled ? {
|
213 | transform: [{
|
214 | translateX
|
215 | }]
|
216 | } : null, tabBarWidth ? {
|
217 | width: tabBarWidth
|
218 | } : scrollEnabled ? {
|
219 | width: tabBarWidthPercent
|
220 | } : null, indicatorContainerStyle]
|
221 | }, this.props.renderIndicator({
|
222 | position,
|
223 | layout,
|
224 | navigationState,
|
225 | jumpTo,
|
226 | width: isWidthDynamic ? 'auto' : `${100 / routes.length}%`,
|
227 | style: indicatorStyle,
|
228 | getTabWidth: i => this.getComputedTabWidth(i, layout, routes, scrollEnabled, tabWidths, this.getFlattenedTabWidth(tabStyle))
|
229 | })), React.createElement(View, {
|
230 | style: styles.scroll
|
231 | }, React.createElement(Animated.ScrollView, {
|
232 | horizontal: true,
|
233 | accessibilityRole: "tablist",
|
234 | keyboardShouldPersistTaps: "handled",
|
235 | scrollEnabled: scrollEnabled,
|
236 | bounces: bounces,
|
237 | alwaysBounceHorizontal: false,
|
238 | scrollsToTop: false,
|
239 | showsHorizontalScrollIndicator: false,
|
240 | automaticallyAdjustContentInsets: false,
|
241 | overScrollMode: "never",
|
242 | contentContainerStyle: [styles.tabContent, scrollEnabled ? {
|
243 | width: tabBarWidth || tabBarWidthPercent
|
244 | } : styles.container, contentContainerStyle],
|
245 | scrollEventThrottle: 16,
|
246 | onScroll: Animated.event([{
|
247 | nativeEvent: {
|
248 | contentOffset: {
|
249 | x: this.scrollAmount
|
250 | }
|
251 | }
|
252 | }], {
|
253 | useNativeDriver: true
|
254 | }),
|
255 | ref: this.scrollViewRef
|
256 | }, routes.map(route => {
|
257 | const props = {
|
258 | key: route.key,
|
259 | position: position,
|
260 | route: route,
|
261 | navigationState: navigationState,
|
262 | getAccessibilityLabel: getAccessibilityLabel,
|
263 | getAccessible: getAccessible,
|
264 | getLabelText: getLabelText,
|
265 | getTestID: getTestID,
|
266 | renderBadge: renderBadge,
|
267 | renderIcon: renderIcon,
|
268 | renderLabel: renderLabel,
|
269 | activeColor: activeColor,
|
270 | inactiveColor: inactiveColor,
|
271 | pressColor: pressColor,
|
272 | pressOpacity: pressOpacity,
|
273 | onLayout: isWidthDynamic ? e => {
|
274 | this.measuredTabWidths[route.key] = e.nativeEvent.layout.width;
|
275 |
|
276 |
|
277 | if (routes.every(r => typeof this.measuredTabWidths[r.key] === 'number')) {
|
278 | this.setState({
|
279 | tabWidths: { ...this.measuredTabWidths
|
280 | }
|
281 | });
|
282 | }
|
283 | } : undefined,
|
284 | onPress: () => {
|
285 | const event = {
|
286 | route,
|
287 | defaultPrevented: false,
|
288 | preventDefault: () => {
|
289 | event.defaultPrevented = true;
|
290 | }
|
291 | };
|
292 | onTabPress === null || onTabPress === void 0 ? void 0 : onTabPress(event);
|
293 |
|
294 | if (event.defaultPrevented) {
|
295 | return;
|
296 | }
|
297 |
|
298 | this.props.jumpTo(route.key);
|
299 | },
|
300 | onLongPress: () => onTabLongPress === null || onTabLongPress === void 0 ? void 0 : onTabLongPress({
|
301 | route
|
302 | }),
|
303 | labelStyle: labelStyle,
|
304 | style: tabStyle
|
305 | };
|
306 | return renderTabBarItem ? renderTabBarItem(props) : React.createElement(TabBarItem, props);
|
307 | }))));
|
308 | }
|
309 |
|
310 | }
|
311 |
|
312 | _defineProperty(TabBar, "defaultProps", {
|
313 | getLabelText: ({
|
314 | route
|
315 | }) => route.title,
|
316 | getAccessible: ({
|
317 | route
|
318 | }) => typeof route.accessible !== 'undefined' ? route.accessible : true,
|
319 | getAccessibilityLabel: ({
|
320 | route
|
321 | }) => typeof route.accessibilityLabel === 'string' ? route.accessibilityLabel : typeof route.title === 'string' ? route.title : undefined,
|
322 | getTestID: ({
|
323 | route
|
324 | }) => route.testID,
|
325 | renderIndicator: props => React.createElement(TabBarIndicator, props)
|
326 | });
|
327 |
|
328 | const styles = StyleSheet.create({
|
329 | container: {
|
330 | flex: 1
|
331 | },
|
332 | scroll: {
|
333 | overflow: Platform.select({
|
334 | default: 'scroll',
|
335 | web: undefined
|
336 | })
|
337 | },
|
338 | tabBar: {
|
339 | backgroundColor: '#2196f3',
|
340 | elevation: 4,
|
341 | shadowColor: 'black',
|
342 | shadowOpacity: 0.1,
|
343 | shadowRadius: StyleSheet.hairlineWidth,
|
344 | shadowOffset: {
|
345 | height: StyleSheet.hairlineWidth,
|
346 | width: 0
|
347 | },
|
348 | zIndex: 1
|
349 | },
|
350 | tabContent: {
|
351 | flexDirection: 'row',
|
352 | flexWrap: 'nowrap'
|
353 | },
|
354 | indicatorContainer: {
|
355 | position: 'absolute',
|
356 | top: 0,
|
357 | left: 0,
|
358 | right: 0,
|
359 | bottom: 0
|
360 | }
|
361 | });
|
362 |
|
\ | No newline at end of file |