UNPKG

11.3 kBJavaScriptView Raw
1function _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
3import * as React from 'react';
4import { Animated, StyleSheet, View, I18nManager, Platform } from 'react-native';
5import TabBarItem from './TabBarItem';
6import TabBarIndicator from './TabBarIndicator';
7export 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", /*#__PURE__*/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 // On Android, scroll value is not applied in reverse in RTL
84 // so we need to manually adjust it to apply correct value
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)); // To get the current index centered we adjust scroll amount by width of indexes
107 // 0 through (i - 1) and add half the width of current index i
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 // When tab width is dynamic, only adjust the scroll once we have all tab widths and layout
159 return;
160 }
161
162 this.resetScroll(navigationState.index);
163 }
164 } // to store the layout.width of each tab
165 // when all onLayout's are fired, this would be set in state
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 /*#__PURE__*/React.createElement(Animated.View, {
208 onLayout: this.handleLayout,
209 style: [styles.tabBar, style]
210 }, /*#__PURE__*/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 })), /*#__PURE__*/React.createElement(View, {
230 style: styles.scroll
231 }, /*#__PURE__*/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; // When we have measured widths for all of the tabs, we should updates the state
275 // We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
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) : /*#__PURE__*/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 => /*#__PURE__*/React.createElement(TabBarIndicator, props)
326});
327
328const 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//# sourceMappingURL=TabBar.js.map
\No newline at end of file