UNPKG

7.12 kBJavaScriptView Raw
1/**
2* This source code is quoted from rc-tabs.
3* homepage: https://github.com/react-component/tabs
4*/
5import React from 'react';
6import PropTypes from 'prop-types';
7import classnames from 'classnames';
8import Hammer from 'react-hammerjs';
9import ReactDOM from 'react-dom';
10import {
11 isVertical,
12 getStyle,
13 setPxStyle,
14} from './utils';
15
16export default class SwipeableTabBarNode extends React.Component {
17 constructor(props) {
18 super(props);
19
20 const { hasPrevPage, hasNextPage } = this.checkPaginationByKey(this.props.activeKey);
21 this.state = {
22 hasPrevPage,
23 hasNextPage,
24 };
25 }
26
27 componentDidMount() {
28 const swipe = this.props.getRef('swipe');
29 const nav = this.props.getRef('nav');
30 const { activeKey } = this.props;
31 this.swipeNode = ReactDOM.findDOMNode(swipe); // dom which scroll (9999px)
32 this.realNode = ReactDOM.findDOMNode(nav); // dom which visiable in screen (viewport)
33 this.setCache();
34 this.setSwipePositionByKey(activeKey);
35 }
36
37 componentDidUpdate(prevProps) {
38 this.setCache();
39 if ((this.props.activeKey && this.props.activeKey !== prevProps.activeKey)
40 || this.props.panels.length !== prevProps.panels.length
41 || this.props.pageSize !== prevProps.pageSize
42 ) {
43 this.setSwipePositionByKey(this.props.activeKey);
44 }
45 }
46
47 onPan = (e) => {
48 const { vertical, totalAvaliableDelta, totalDelta } = this.cache;
49 const { speed } = this.props;
50 // calculate touch distance
51 let nowDelta = vertical ? e.deltaY : e.deltaX;
52 nowDelta *= (speed / 10);
53
54 // calculate distance dom need transform
55 let _nextDelta = nowDelta + totalDelta;
56
57 if (this.isRtl()) {
58 // calculate distance from right when direction is right-to-left
59 if (_nextDelta <= 0) {
60 _nextDelta = 0;
61 } else if (_nextDelta >= totalAvaliableDelta) {
62 _nextDelta = totalAvaliableDelta;
63 }
64 }
65 // calculate distance from left when direction is left-to-right
66 else if (_nextDelta >= 0) {
67 _nextDelta = 0;
68 } else if (_nextDelta <= -totalAvaliableDelta) {
69 _nextDelta = -totalAvaliableDelta;
70 }
71
72 this.cache.totalDelta = _nextDelta;
73 this.setSwipePosition();
74
75 // calculate pagination display
76 const { hasPrevPage, hasNextPage } = this.checkPaginationByDelta(this.cache.totalDelta);
77 if (hasPrevPage !== this.state.hasPrevPage || hasNextPage !== this.state.hasNextPage) {
78 this.setState({
79 hasPrevPage,
80 hasNextPage,
81 });
82 }
83 }
84
85 setCache() {
86 const { tabBarPosition, pageSize, panels } = this.props;
87 const _isVertical = isVertical(tabBarPosition);
88 const _viewSize = getStyle(this.realNode, _isVertical ? 'height' : 'width');
89 const _tabWidth = _viewSize / pageSize;
90 this.cache = {
91 ...this.cache,
92 vertical: _isVertical,
93 totalAvaliableDelta: _tabWidth * panels.length - _viewSize,
94 tabWidth: _tabWidth,
95 };
96 }
97
98 /**
99 * used for props.activeKey setting, not for swipe callback
100 */
101 getDeltaByKey(activeKey) {
102 const { pageSize } = this.props;
103 const index = this.getIndexByKey(activeKey);
104 const centerTabCount = Math.floor(pageSize / 2);
105 const { tabWidth } = this.cache;
106 let delta = (index - centerTabCount) * tabWidth;
107 // in rtl direction tabs are ordered from right to left, so delta should be positive in order to
108 // push swiped element to righ side (start of view)
109 if (!this.isRtl()) {
110 delta *= -1;
111 }
112 return delta;
113 }
114
115 getIndexByKey(activeKey) {
116 const { panels } = this.props;
117 const length = panels.length;
118 for (let i = 0; i < length; i++) {
119 if (panels[i].key === activeKey) {
120 return i;
121 }
122 }
123 return -1;
124 }
125
126 setSwipePositionByKey(activeKey) {
127 const { hasPrevPage, hasNextPage } = this.checkPaginationByKey(activeKey);
128 const { totalAvaliableDelta } = this.cache;
129 this.setState({
130 hasPrevPage,
131 hasNextPage,
132 });
133 let delta;
134 if (!hasPrevPage) {
135 // the first page
136 delta = 0;
137 } else if (!hasNextPage) {
138 // the last page
139 delta = this.isRtl() ? totalAvaliableDelta : -totalAvaliableDelta;
140 } else if (hasNextPage) {
141 // the middle page
142 delta = this.getDeltaByKey(activeKey);
143 }
144 this.cache.totalDelta = delta;
145 this.setSwipePosition();
146 }
147
148 setSwipePosition() {
149 const { totalDelta, vertical } = this.cache;
150 setPxStyle(this.swipeNode, totalDelta, vertical);
151 }
152
153 checkPaginationByKey(activeKey) {
154 const { panels, pageSize } = this.props;
155 const index = this.getIndexByKey(activeKey);
156 const centerTabCount = Math.floor(pageSize / 2);
157 // the basic rule is to make activeTab be shown in the center of TabBar viewport
158 return {
159 hasPrevPage: index - centerTabCount > 0,
160 hasNextPage: index + centerTabCount < panels.length,
161 };
162 }
163
164 checkPaginationByDelta(delta) {
165 const { totalAvaliableDelta } = this.cache;
166 return {
167 hasPrevPage: delta < 0,
168 hasNextPage: -delta < totalAvaliableDelta,
169 };
170 }
171 isRtl() {
172 return this.props.direction === 'rtl';
173 }
174 render() {
175 const { clsPrefix, hammerOptions, tabBarPosition } = this.props;
176 const { hasPrevPage, hasNextPage } = this.state;
177 const navClassName = `${clsPrefix}-nav`;
178 const navClasses = classnames({
179 [navClassName]: true,
180 });
181 const events = {
182 onPan: this.onPan,
183 };
184 return (
185 <div
186 className={classnames({
187 [`${clsPrefix}-nav-container`]: 1,
188 [`${clsPrefix}-nav-swipe-container`]: 1,
189 // page classname can be used to render special style when there has a prev/next page
190 [`${clsPrefix}-prevpage`]: hasPrevPage,
191 [`${clsPrefix}-nextpage`]: hasNextPage,
192 })}
193 key="container"
194 ref={this.props.saveRef('container')}
195 >
196 <div className={`${clsPrefix}-nav-wrap`} ref={this.props.saveRef('navWrap')}>
197 <Hammer
198 {...events}
199 direction={isVertical(tabBarPosition) ? 'DIRECTION_ALL' : 'DIRECTION_HORIZONTAL'}
200 options={hammerOptions}
201 >
202 <div className={`${clsPrefix}-nav-swipe`} ref={this.props.saveRef('swipe')}>
203 <div className={navClasses} ref={this.props.saveRef('nav')}>
204 {this.props.children}
205 </div>
206 </div>
207 </Hammer>
208 </div>
209 </div>
210 );
211 }
212}
213
214SwipeableTabBarNode.propTypes = {
215 activeKey: PropTypes.string,
216 panels: PropTypes.node,
217 pageSize: PropTypes.number,
218 tabBarPosition: PropTypes.oneOf(['left', 'right', 'top', 'bottom']),
219 clsPrefix: PropTypes.string,
220 children: PropTypes.node,
221 hammerOptions: PropTypes.object,
222 speed: PropTypes.number,
223 saveRef: PropTypes.func,
224 getRef: PropTypes.func,
225 direction: PropTypes.string,
226};
227
228SwipeableTabBarNode.defaultProps = {
229 panels: null,
230 tabBarPosition: 'top',
231 clsPrefix: '',
232 children: null,
233 hammerOptions: {},
234 pageSize: 5, // per page show how many tabs
235 speed: 7, // swipe speed, 1 to 10, more bigger more faster
236 saveRef: () => { },
237 getRef: () => { },
238};
\No newline at end of file