1 |
|
2 |
|
3 |
|
4 |
|
5 | import React from 'react';
|
6 | import PropTypes from 'prop-types';
|
7 | import classnames from 'classnames';
|
8 | import Hammer from 'react-hammerjs';
|
9 | import ReactDOM from 'react-dom';
|
10 | import {
|
11 | isVertical,
|
12 | getStyle,
|
13 | setPxStyle,
|
14 | } from './utils';
|
15 |
|
16 | export 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);
|
32 | this.realNode = ReactDOM.findDOMNode(nav);
|
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 |
|
51 | let nowDelta = vertical ? e.deltaY : e.deltaX;
|
52 | nowDelta *= (speed / 10);
|
53 |
|
54 |
|
55 | let _nextDelta = nowDelta + totalDelta;
|
56 |
|
57 | if (this.isRtl()) {
|
58 |
|
59 | if (_nextDelta <= 0) {
|
60 | _nextDelta = 0;
|
61 | } else if (_nextDelta >= totalAvaliableDelta) {
|
62 | _nextDelta = totalAvaliableDelta;
|
63 | }
|
64 | }
|
65 |
|
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 |
|
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 |
|
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 |
|
108 |
|
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 |
|
136 | delta = 0;
|
137 | } else if (!hasNextPage) {
|
138 |
|
139 | delta = this.isRtl() ? totalAvaliableDelta : -totalAvaliableDelta;
|
140 | } else if (hasNextPage) {
|
141 |
|
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 |
|
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 |
|
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 |
|
214 | SwipeableTabBarNode.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 |
|
228 | SwipeableTabBarNode.defaultProps = {
|
229 | panels: null,
|
230 | tabBarPosition: 'top',
|
231 | clsPrefix: '',
|
232 | children: null,
|
233 | hammerOptions: {},
|
234 | pageSize: 5,
|
235 | speed: 7,
|
236 | saveRef: () => { },
|
237 | getRef: () => { },
|
238 | }; |
\ | No newline at end of file |