1 | import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
|
2 | import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
|
3 | import _inherits from 'babel-runtime/helpers/inherits';
|
4 |
|
5 | var _class, _temp;
|
6 |
|
7 | import PropTypes from 'prop-types';
|
8 | import React, { Component } from 'react';
|
9 | import cx from 'classnames';
|
10 | import { polyfill } from 'react-lifecycles-compat';
|
11 | import { findDOMNode } from 'react-dom';
|
12 | import { events } from '../util';
|
13 |
|
14 | var NOOP = function NOOP() {};
|
15 | var MAX_SYNC_UPDATES = 40;
|
16 |
|
17 | var isEqualSubset = function isEqualSubset(a, b) {
|
18 | for (var key in b) {
|
19 | if (a[key] !== b[key]) {
|
20 | return false;
|
21 | }
|
22 | }
|
23 |
|
24 | return true;
|
25 | };
|
26 |
|
27 | var getOffset = function getOffset(el) {
|
28 | var offset = el.clientLeft || 0;
|
29 | do {
|
30 | offset += el.offsetTop || 0;
|
31 | el = el.offsetParent;
|
32 | } while (el);
|
33 | return offset;
|
34 | };
|
35 |
|
36 | var constrain = function constrain(from, size, _ref) {
|
37 | var children = _ref.children,
|
38 | minSize = _ref.minSize;
|
39 |
|
40 | var length = children && children.length;
|
41 | size = Math.max(size, minSize);
|
42 | if (size > length) {
|
43 | size = length;
|
44 | }
|
45 | from = from ? Math.max(Math.min(from, length - size), 0) : 0;
|
46 |
|
47 | return { from: from, size: size };
|
48 | };
|
49 |
|
50 | var VirtualList = (_temp = _class = function (_Component) {
|
51 | _inherits(VirtualList, _Component);
|
52 |
|
53 | function VirtualList(props) {
|
54 | _classCallCheck(this, VirtualList);
|
55 |
|
56 | var _this = _possibleConstructorReturn(this, _Component.call(this, props));
|
57 |
|
58 | var jumpIndex = props.jumpIndex;
|
59 |
|
60 | var _constrain = constrain(jumpIndex, 0, props),
|
61 | from = _constrain.from,
|
62 | size = _constrain.size;
|
63 |
|
64 | _this.state = { from: from, size: size };
|
65 | _this.cache = {};
|
66 | _this.cacheAdd = {};
|
67 | _this.scrollTo = _this.scrollTo.bind(_this);
|
68 | _this.cachedScroll = null;
|
69 | _this.unstable = false;
|
70 | _this.updateCounter = 0;
|
71 | return _this;
|
72 | }
|
73 |
|
74 | VirtualList.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
|
75 | var from = prevState.from,
|
76 | size = prevState.size;
|
77 |
|
78 |
|
79 | return constrain(from, size, nextProps);
|
80 | };
|
81 |
|
82 | VirtualList.prototype.componentDidMount = function componentDidMount() {
|
83 | var jumpIndex = this.props.jumpIndex;
|
84 |
|
85 |
|
86 | this.updateFrameAndClearCache = this.updateFrameAndClearCache.bind(this);
|
87 |
|
88 | events.on(window, 'resize', this.updateFrameAndClearCache);
|
89 |
|
90 | this.updateFrame(this.scrollTo.bind(this, jumpIndex));
|
91 | };
|
92 |
|
93 | VirtualList.prototype.componentDidUpdate = function componentDidUpdate(prevProps) {
|
94 | var _this2 = this;
|
95 |
|
96 | var oldIndex = prevProps.jumpIndex;
|
97 | var newIndex = this.props.jumpIndex;
|
98 |
|
99 | if (oldIndex !== newIndex) {
|
100 | this.updateFrame(this.scrollTo.bind(this, newIndex));
|
101 | }
|
102 |
|
103 |
|
104 | if (this.unstable) {
|
105 | return;
|
106 | }
|
107 |
|
108 | if (++this.updateCounter > MAX_SYNC_UPDATES) {
|
109 | this.unstable = true;
|
110 | }
|
111 |
|
112 | if (!this.updateCounterTimeoutId) {
|
113 | this.updateCounterTimeoutId = setTimeout(function () {
|
114 | _this2.updateCounter = 0;
|
115 | delete _this2.updateCounterTimeoutId;
|
116 | }, 0);
|
117 | }
|
118 |
|
119 | this.updateFrame();
|
120 | };
|
121 |
|
122 | VirtualList.prototype.componentWillUnmount = function componentWillUnmount() {
|
123 | events.off(window, 'resize', this.updateFrameAndClearCache);
|
124 |
|
125 | events.off(this.scrollParent, 'scroll', this.updateFrameAndClearCache);
|
126 | events.off(this.scrollParent, 'mousewheel', NOOP);
|
127 | };
|
128 |
|
129 | VirtualList.prototype.maybeSetState = function maybeSetState(b, cb) {
|
130 | if (isEqualSubset(this.state, b)) {
|
131 | return cb();
|
132 | }
|
133 |
|
134 | this.setState(b, cb);
|
135 | };
|
136 |
|
137 | VirtualList.prototype.getEl = function getEl() {
|
138 | return this.el || this.items || {};
|
139 | };
|
140 |
|
141 | VirtualList.prototype.getScrollParent = function getScrollParent() {
|
142 | var el = this.getEl();
|
143 | el = el.parentElement;
|
144 |
|
145 | switch (window.getComputedStyle(el).overflowY) {
|
146 | case 'auto':
|
147 | case 'scroll':
|
148 | case 'overlay':
|
149 | case 'visible':
|
150 | return el;
|
151 | }
|
152 |
|
153 | return window;
|
154 | };
|
155 |
|
156 | VirtualList.prototype.getScroll = function getScroll() {
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | var scrollParent = this.scrollParent;
|
162 |
|
163 | var scrollKey = 'scrollTop';
|
164 | var actual = scrollParent === window ?
|
165 |
|
166 |
|
167 | document.body[scrollKey] || document.documentElement[scrollKey] : scrollParent[scrollKey];
|
168 | var max = this.getScrollSize() - this.getViewportSize();
|
169 |
|
170 | var scroll = Math.max(0, Math.min(actual, max));
|
171 | var el = this.getEl();
|
172 | this.cachedScroll = getOffset(scrollParent) + scroll - getOffset(el);
|
173 |
|
174 | return this.cachedScroll;
|
175 | };
|
176 |
|
177 | VirtualList.prototype.setScroll = function setScroll(offset) {
|
178 | var scrollParent = this.scrollParent;
|
179 |
|
180 | offset += getOffset(this.getEl());
|
181 | if (scrollParent === window) {
|
182 | return window.scrollTo(0, offset);
|
183 | }
|
184 |
|
185 | offset -= getOffset(this.scrollParent);
|
186 | scrollParent.scrollTop = offset;
|
187 | };
|
188 |
|
189 | VirtualList.prototype.getViewportSize = function getViewportSize() {
|
190 | var scrollParent = this.scrollParent;
|
191 |
|
192 | return scrollParent === window ? window.innerHeight : scrollParent.clientHeight;
|
193 | };
|
194 |
|
195 | VirtualList.prototype.getScrollSize = function getScrollSize() {
|
196 | var scrollParent = this.scrollParent;
|
197 | var _document = document,
|
198 | body = _document.body,
|
199 | documentElement = _document.documentElement;
|
200 |
|
201 | var key = 'scrollHeight';
|
202 | return scrollParent === window ? Math.max(body[key], documentElement[key]) : scrollParent[key];
|
203 | };
|
204 |
|
205 | VirtualList.prototype.getStartAndEnd = function getStartAndEnd() {
|
206 | var threshold = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props.threshold;
|
207 |
|
208 | var scroll = this.getScroll();
|
209 |
|
210 | var trueScroll = scroll;
|
211 | var start = Math.max(0, trueScroll - threshold);
|
212 | var end = trueScroll + this.getViewportSize() + threshold;
|
213 |
|
214 | return { start: start, end: end };
|
215 | };
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | VirtualList.prototype.updateFrameAndClearCache = function updateFrameAndClearCache(cb) {
|
221 | this.cachedScroll = null;
|
222 | return this.updateFrame(cb);
|
223 | };
|
224 |
|
225 | VirtualList.prototype.updateFrame = function updateFrame(cb) {
|
226 | this.updateScrollParent();
|
227 |
|
228 | if (typeof cb !== 'function') {
|
229 | cb = NOOP;
|
230 | }
|
231 |
|
232 | return this.updateVariableFrame(cb);
|
233 | };
|
234 |
|
235 | VirtualList.prototype.updateScrollParent = function updateScrollParent() {
|
236 | var prev = this.scrollParent;
|
237 | this.scrollParent = this.getScrollParent();
|
238 |
|
239 | if (prev === this.scrollParent) {
|
240 | return;
|
241 | }
|
242 | if (prev) {
|
243 | events.off(prev, 'scroll', this.updateFrameAndClearCache);
|
244 | events.off(prev, 'mousewheel', NOOP);
|
245 | }
|
246 |
|
247 | events.on(this.scrollParent, 'scroll', this.updateFrameAndClearCache);
|
248 | events.on(this.scrollParent, 'mousewheel', NOOP);
|
249 |
|
250 |
|
251 |
|
252 | };
|
253 |
|
254 | VirtualList.prototype.updateVariableFrame = function updateVariableFrame(cb) {
|
255 | if (!this.props.itemSizeGetter) {
|
256 | this.cacheSizes();
|
257 | }
|
258 |
|
259 | var _getStartAndEnd = this.getStartAndEnd(),
|
260 | start = _getStartAndEnd.start,
|
261 | end = _getStartAndEnd.end;
|
262 |
|
263 | var _props = this.props,
|
264 | pageSize = _props.pageSize,
|
265 | children = _props.children;
|
266 |
|
267 | var length = children.length;
|
268 | var space = 0;
|
269 | var from = 0;
|
270 | var size = 0;
|
271 | var maxFrom = length - 1;
|
272 |
|
273 | while (from < maxFrom) {
|
274 | var itemSize = this.getSizeOf(from);
|
275 | if (itemSize === null || itemSize === undefined || space + itemSize > start) {
|
276 | break;
|
277 | }
|
278 | space += itemSize;
|
279 | ++from;
|
280 | }
|
281 |
|
282 | var maxSize = length - from;
|
283 |
|
284 | while (size < maxSize && space < end) {
|
285 | var _itemSize = this.getSizeOf(from + size);
|
286 | if (_itemSize === null || _itemSize === undefined) {
|
287 | size = Math.min(size + pageSize, maxSize);
|
288 | break;
|
289 | }
|
290 | space += _itemSize;
|
291 | ++size;
|
292 | }
|
293 |
|
294 | this.maybeSetState({ from: from, size: size }, cb);
|
295 | };
|
296 |
|
297 | VirtualList.prototype.getSpaceBefore = function getSpaceBefore(index) {
|
298 | var cache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
299 |
|
300 | if (!index) {
|
301 | return 0;
|
302 | }
|
303 | if (cache[index] !== null && cache[index] !== undefined) {
|
304 | return cache[index] || 0;
|
305 | }
|
306 |
|
307 |
|
308 | var from = index;
|
309 | while (from > 0 && (cache[from] === null || cache[from] === undefined)) {
|
310 | from--;
|
311 | }
|
312 |
|
313 |
|
314 | var space = cache[from] || 0;
|
315 | for (var i = from; i < index; ++i) {
|
316 | cache[i] = space;
|
317 | var itemSize = this.getSizeOf(i);
|
318 | if (itemSize === null || itemSize === undefined) {
|
319 | break;
|
320 | }
|
321 | space += itemSize;
|
322 | }
|
323 |
|
324 | cache[index] = space;
|
325 |
|
326 | return cache[index] || 0;
|
327 | };
|
328 |
|
329 | VirtualList.prototype.cacheSizes = function cacheSizes() {
|
330 | var cache = this.cache;
|
331 | var from = this.state.from;
|
332 | var _items = this.items,
|
333 | children = _items.children,
|
334 | _items$props = _items.props,
|
335 | props = _items$props === undefined ? {} : _items$props;
|
336 |
|
337 | var itemEls = children || props.children || [];
|
338 |
|
339 | try {
|
340 |
|
341 | for (var i = 0, l = itemEls.length; i < l; ++i) {
|
342 | var ulRef = findDOMNode(this.items);
|
343 | var height = ulRef.children[i].offsetHeight;
|
344 | if (height > 0) {
|
345 | cache[from + i] = height;
|
346 | }
|
347 | }
|
348 | } catch (error) {
|
349 |
|
350 | }
|
351 | };
|
352 |
|
353 | VirtualList.prototype.getSizeOf = function getSizeOf(index) {
|
354 | var cache = this.cache;
|
355 | var _props2 = this.props,
|
356 | itemSizeGetter = _props2.itemSizeGetter,
|
357 | jumpIndex = _props2.jumpIndex;
|
358 |
|
359 |
|
360 |
|
361 | if (index in cache) {
|
362 | return cache[index];
|
363 | }
|
364 | if (itemSizeGetter) {
|
365 | return itemSizeGetter(index);
|
366 | }
|
367 |
|
368 | if (!this.defaultItemHeight && jumpIndex > -1) {
|
369 | var keysList = Object.keys(this.cache);
|
370 | var len = keysList.length;
|
371 | var height = this.cache[len - 1];
|
372 | this.defaultItemHeight = height;
|
373 | }
|
374 |
|
375 | if (this.defaultItemHeight) {
|
376 | return this.defaultItemHeight;
|
377 | }
|
378 | };
|
379 |
|
380 | VirtualList.prototype.scrollTo = function scrollTo(index) {
|
381 | this.setScroll(this.getSpaceBefore(index, this.cacheAdd));
|
382 | };
|
383 |
|
384 | VirtualList.prototype.renderMenuItems = function renderMenuItems() {
|
385 | var _this3 = this;
|
386 |
|
387 | var _props3 = this.props,
|
388 | children = _props3.children,
|
389 | itemsRenderer = _props3.itemsRenderer;
|
390 | var _state = this.state,
|
391 | from = _state.from,
|
392 | size = _state.size;
|
393 |
|
394 | var items = [];
|
395 |
|
396 | for (var i = 0; i < size; ++i) {
|
397 | items.push(children[from + i]);
|
398 | }
|
399 |
|
400 | return itemsRenderer(items, function (c) {
|
401 | _this3.items = c;
|
402 | return _this3.items;
|
403 | });
|
404 | };
|
405 |
|
406 | VirtualList.prototype.render = function render() {
|
407 | var _cx,
|
408 | _this4 = this;
|
409 |
|
410 | var _props4 = this.props,
|
411 | _props4$children = _props4.children,
|
412 | children = _props4$children === undefined ? [] : _props4$children,
|
413 | prefix = _props4.prefix,
|
414 | className = _props4.className;
|
415 |
|
416 | var length = children.length;
|
417 | var from = this.state.from;
|
418 |
|
419 | var items = this.renderMenuItems();
|
420 |
|
421 | var style = { position: 'relative' };
|
422 |
|
423 | var size = this.getSpaceBefore(length, {});
|
424 |
|
425 | if (size) {
|
426 | style.height = size;
|
427 | }
|
428 | var offset = this.getSpaceBefore(from, this.cacheAdd);
|
429 | var transform = 'translate(0px, ' + offset + 'px)';
|
430 | var listStyle = {
|
431 | msTransform: transform,
|
432 | WebkitTransform: transform,
|
433 | transform: transform
|
434 | };
|
435 |
|
436 | var cls = cx((_cx = {}, _cx[prefix + 'virtual-list-wrapper'] = true, _cx[className] = !!className, _cx));
|
437 |
|
438 | return React.createElement(
|
439 | 'div',
|
440 | {
|
441 | className: cls,
|
442 | style: style,
|
443 | ref: function ref(c) {
|
444 | _this4.el = c;
|
445 | return _this4.el;
|
446 | }
|
447 | },
|
448 | React.createElement(
|
449 | 'div',
|
450 | { style: listStyle },
|
451 | items
|
452 | )
|
453 | );
|
454 | };
|
455 |
|
456 | return VirtualList;
|
457 | }(Component), _class.displayName = 'VirtualList', _class.propTypes = {
|
458 | prefix: PropTypes.string,
|
459 | |
460 |
|
461 |
|
462 | children: PropTypes.any,
|
463 | |
464 |
|
465 |
|
466 | minSize: PropTypes.number,
|
467 | |
468 |
|
469 |
|
470 | pageSize: PropTypes.number,
|
471 | |
472 |
|
473 |
|
474 | itemsRenderer: PropTypes.func,
|
475 | |
476 |
|
477 |
|
478 | threshold: PropTypes.number,
|
479 | |
480 |
|
481 |
|
482 | itemSizeGetter: PropTypes.func,
|
483 | |
484 |
|
485 |
|
486 | jumpIndex: PropTypes.number,
|
487 | className: PropTypes.string
|
488 | }, _class.defaultProps = {
|
489 | prefix: 'next-',
|
490 | itemsRenderer: function itemsRenderer(items, ref) {
|
491 | return React.createElement(
|
492 | 'ul',
|
493 | { ref: ref },
|
494 | items
|
495 | );
|
496 | },
|
497 | minSize: 1,
|
498 | pageSize: 10,
|
499 | jumpIndex: 0,
|
500 | threshold: 100
|
501 | }, _temp);
|
502 | VirtualList.displayName = 'VirtualList';
|
503 |
|
504 |
|
505 | export default polyfill(VirtualList); |
\ | No newline at end of file |