UNPKG

17.4 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
3import _typeof from "@babel/runtime/helpers/esm/typeof";
4import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
5import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
6import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
7var _excluded = ["prefixCls", "className", "height", "itemHeight", "fullHeight", "style", "data", "children", "itemKey", "virtual", "direction", "scrollWidth", "component", "onScroll", "onVirtualScroll", "onVisibleChange", "innerProps", "extraRender"];
8import * as React from 'react';
9import { useRef, useState } from 'react';
10import { flushSync } from 'react-dom';
11import classNames from 'classnames';
12import ResizeObserver from 'rc-resize-observer';
13import Filler from './Filler';
14import ScrollBar from './ScrollBar';
15import useChildren from './hooks/useChildren';
16import useHeights from './hooks/useHeights';
17import useScrollTo from './hooks/useScrollTo';
18import useDiffItem from './hooks/useDiffItem';
19import useFrameWheel from './hooks/useFrameWheel';
20import useMobileTouchMove from './hooks/useMobileTouchMove';
21import useOriginScroll from './hooks/useOriginScroll';
22import useLayoutEffect from "rc-util/es/hooks/useLayoutEffect";
23import { getSpinSize } from './utils/scrollbarUtil';
24import { useEvent } from 'rc-util';
25var EMPTY_DATA = [];
26var ScrollStyle = {
27 overflowY: 'auto',
28 overflowAnchor: 'none'
29};
30export function RawList(props, ref) {
31 var _props$prefixCls = props.prefixCls,
32 prefixCls = _props$prefixCls === void 0 ? 'rc-virtual-list' : _props$prefixCls,
33 className = props.className,
34 height = props.height,
35 itemHeight = props.itemHeight,
36 _props$fullHeight = props.fullHeight,
37 fullHeight = _props$fullHeight === void 0 ? true : _props$fullHeight,
38 style = props.style,
39 data = props.data,
40 children = props.children,
41 itemKey = props.itemKey,
42 virtual = props.virtual,
43 direction = props.direction,
44 scrollWidth = props.scrollWidth,
45 _props$component = props.component,
46 Component = _props$component === void 0 ? 'div' : _props$component,
47 onScroll = props.onScroll,
48 onVirtualScroll = props.onVirtualScroll,
49 onVisibleChange = props.onVisibleChange,
50 innerProps = props.innerProps,
51 extraRender = props.extraRender,
52 restProps = _objectWithoutProperties(props, _excluded);
53 // ================================= MISC =================================
54 var useVirtual = !!(virtual !== false && height && itemHeight);
55 var inVirtual = useVirtual && data && itemHeight * data.length > height;
56 var isRTL = direction === 'rtl';
57 var mergedClassName = classNames(prefixCls, _defineProperty({}, "".concat(prefixCls, "-rtl"), isRTL), className);
58 var mergedData = data || EMPTY_DATA;
59 var componentRef = useRef();
60 var fillerInnerRef = useRef();
61 // =============================== Item Key ===============================
62 var _useState = useState(0),
63 _useState2 = _slicedToArray(_useState, 2),
64 offsetTop = _useState2[0],
65 setOffsetTop = _useState2[1];
66 var _useState3 = useState(0),
67 _useState4 = _slicedToArray(_useState3, 2),
68 offsetLeft = _useState4[0],
69 setOffsetLeft = _useState4[1];
70 var _useState5 = useState(false),
71 _useState6 = _slicedToArray(_useState5, 2),
72 scrollMoving = _useState6[0],
73 setScrollMoving = _useState6[1];
74 var onScrollbarStartMove = function onScrollbarStartMove() {
75 setScrollMoving(true);
76 };
77 var onScrollbarStopMove = function onScrollbarStopMove() {
78 setScrollMoving(false);
79 };
80 // =============================== Item Key ===============================
81 var getKey = React.useCallback(function (item) {
82 if (typeof itemKey === 'function') {
83 return itemKey(item);
84 }
85 return item === null || item === void 0 ? void 0 : item[itemKey];
86 }, [itemKey]);
87 var sharedConfig = {
88 getKey: getKey
89 };
90 // ================================ Scroll ================================
91 function syncScrollTop(newTop) {
92 setOffsetTop(function (origin) {
93 var value;
94 if (typeof newTop === 'function') {
95 value = newTop(origin);
96 } else {
97 value = newTop;
98 }
99 var alignedTop = keepInRange(value);
100 componentRef.current.scrollTop = alignedTop;
101 return alignedTop;
102 });
103 }
104 // ================================ Legacy ================================
105 // Put ref here since the range is generate by follow
106 var rangeRef = useRef({
107 start: 0,
108 end: mergedData.length
109 });
110 var diffItemRef = useRef();
111 var _useDiffItem = useDiffItem(mergedData, getKey),
112 _useDiffItem2 = _slicedToArray(_useDiffItem, 1),
113 diffItem = _useDiffItem2[0];
114 diffItemRef.current = diffItem;
115 // ================================ Height ================================
116 var _useHeights = useHeights(getKey, null, null),
117 _useHeights2 = _slicedToArray(_useHeights, 4),
118 setInstanceRef = _useHeights2[0],
119 collectHeight = _useHeights2[1],
120 heights = _useHeights2[2],
121 heightUpdatedMark = _useHeights2[3];
122 // ========================== Visible Calculation =========================
123 var _React$useMemo = React.useMemo(function () {
124 if (!useVirtual) {
125 return {
126 scrollHeight: undefined,
127 start: 0,
128 end: mergedData.length - 1,
129 offset: undefined
130 };
131 }
132 // Always use virtual scroll bar in avoid shaking
133 if (!inVirtual) {
134 var _fillerInnerRef$curre;
135 return {
136 scrollHeight: ((_fillerInnerRef$curre = fillerInnerRef.current) === null || _fillerInnerRef$curre === void 0 ? void 0 : _fillerInnerRef$curre.offsetHeight) || 0,
137 start: 0,
138 end: mergedData.length - 1,
139 offset: undefined
140 };
141 }
142 var itemTop = 0;
143 var startIndex;
144 var startOffset;
145 var endIndex;
146 var dataLen = mergedData.length;
147 for (var i = 0; i < dataLen; i += 1) {
148 var item = mergedData[i];
149 var key = getKey(item);
150 var cacheHeight = heights.get(key);
151 var currentItemBottom = itemTop + (cacheHeight === undefined ? itemHeight : cacheHeight);
152 // Check item top in the range
153 if (currentItemBottom >= offsetTop && startIndex === undefined) {
154 startIndex = i;
155 startOffset = itemTop;
156 }
157 // Check item bottom in the range. We will render additional one item for motion usage
158 if (currentItemBottom > offsetTop + height && endIndex === undefined) {
159 endIndex = i;
160 }
161 itemTop = currentItemBottom;
162 }
163 // When scrollTop at the end but data cut to small count will reach this
164 if (startIndex === undefined) {
165 startIndex = 0;
166 startOffset = 0;
167 endIndex = Math.ceil(height / itemHeight);
168 }
169 if (endIndex === undefined) {
170 endIndex = mergedData.length - 1;
171 }
172 // Give cache to improve scroll experience
173 endIndex = Math.min(endIndex + 1, mergedData.length);
174 return {
175 scrollHeight: itemTop,
176 start: startIndex,
177 end: endIndex,
178 offset: startOffset
179 };
180 }, [inVirtual, useVirtual, offsetTop, mergedData, heightUpdatedMark, height]),
181 scrollHeight = _React$useMemo.scrollHeight,
182 start = _React$useMemo.start,
183 end = _React$useMemo.end,
184 fillerOffset = _React$useMemo.offset;
185 rangeRef.current.start = start;
186 rangeRef.current.end = end;
187 // ================================= Size =================================
188 var _React$useState = React.useState({
189 width: 0,
190 height: height
191 }),
192 _React$useState2 = _slicedToArray(_React$useState, 2),
193 size = _React$useState2[0],
194 setSize = _React$useState2[1];
195 var onHolderResize = function onHolderResize(sizeInfo) {
196 setSize(sizeInfo);
197 };
198 // Hack on scrollbar to enable flash call
199 var verticalScrollBarRef = useRef();
200 var horizontalScrollBarRef = useRef();
201 var horizontalScrollBarSpinSize = React.useMemo(function () {
202 return getSpinSize(size.width, scrollWidth);
203 }, [size.width, scrollWidth]);
204 var verticalScrollBarSpinSize = React.useMemo(function () {
205 return getSpinSize(size.height, scrollHeight);
206 }, [size.height, scrollHeight]);
207 // =============================== In Range ===============================
208 var maxScrollHeight = scrollHeight - height;
209 var maxScrollHeightRef = useRef(maxScrollHeight);
210 maxScrollHeightRef.current = maxScrollHeight;
211 function keepInRange(newScrollTop) {
212 var newTop = newScrollTop;
213 if (!Number.isNaN(maxScrollHeightRef.current)) {
214 newTop = Math.min(newTop, maxScrollHeightRef.current);
215 }
216 newTop = Math.max(newTop, 0);
217 return newTop;
218 }
219 var isScrollAtTop = offsetTop <= 0;
220 var isScrollAtBottom = offsetTop >= maxScrollHeight;
221 var originScroll = useOriginScroll(isScrollAtTop, isScrollAtBottom);
222 // ================================ Scroll ================================
223 var getVirtualScrollInfo = function getVirtualScrollInfo() {
224 return {
225 x: isRTL ? -offsetLeft : offsetLeft,
226 y: offsetTop
227 };
228 };
229 var lastVirtualScrollInfoRef = useRef(getVirtualScrollInfo());
230 var triggerScroll = useEvent(function () {
231 if (onVirtualScroll) {
232 var nextInfo = getVirtualScrollInfo();
233 // Trigger when offset changed
234 if (lastVirtualScrollInfoRef.current.x !== nextInfo.x || lastVirtualScrollInfoRef.current.y !== nextInfo.y) {
235 onVirtualScroll(nextInfo);
236 lastVirtualScrollInfoRef.current = nextInfo;
237 }
238 }
239 });
240 function onScrollBar(newScrollOffset, horizontal) {
241 var newOffset = newScrollOffset;
242 if (horizontal) {
243 flushSync(function () {
244 setOffsetLeft(newOffset);
245 });
246 triggerScroll();
247 } else {
248 syncScrollTop(newOffset);
249 }
250 }
251 // When data size reduce. It may trigger native scroll event back to fit scroll position
252 function onFallbackScroll(e) {
253 var newScrollTop = e.currentTarget.scrollTop;
254 if (newScrollTop !== offsetTop) {
255 syncScrollTop(newScrollTop);
256 }
257 // Trigger origin onScroll
258 onScroll === null || onScroll === void 0 ? void 0 : onScroll(e);
259 triggerScroll();
260 }
261 var keepInHorizontalRange = function keepInHorizontalRange(nextOffsetLeft) {
262 var tmpOffsetLeft = nextOffsetLeft;
263 var max = scrollWidth - size.width;
264 tmpOffsetLeft = Math.max(tmpOffsetLeft, 0);
265 tmpOffsetLeft = Math.min(tmpOffsetLeft, max);
266 return tmpOffsetLeft;
267 };
268 var onWheelDelta = useEvent(function (offsetXY, fromHorizontal) {
269 if (fromHorizontal) {
270 // Horizontal scroll no need sync virtual position
271 flushSync(function () {
272 setOffsetLeft(function (left) {
273 var nextOffsetLeft = left + (isRTL ? -offsetXY : offsetXY);
274 return keepInHorizontalRange(nextOffsetLeft);
275 });
276 });
277 triggerScroll();
278 } else {
279 syncScrollTop(function (top) {
280 var newTop = top + offsetXY;
281 return newTop;
282 });
283 }
284 });
285 // Since this added in global,should use ref to keep update
286 var _useFrameWheel = useFrameWheel(useVirtual, isScrollAtTop, isScrollAtBottom, !!scrollWidth, onWheelDelta),
287 _useFrameWheel2 = _slicedToArray(_useFrameWheel, 2),
288 onRawWheel = _useFrameWheel2[0],
289 onFireFoxScroll = _useFrameWheel2[1];
290 // Mobile touch move
291 useMobileTouchMove(useVirtual, componentRef, function (deltaY, smoothOffset) {
292 if (originScroll(deltaY, smoothOffset)) {
293 return false;
294 }
295 onRawWheel({
296 preventDefault: function preventDefault() {},
297 deltaY: deltaY
298 });
299 return true;
300 });
301 useLayoutEffect(function () {
302 // Firefox only
303 function onMozMousePixelScroll(e) {
304 if (useVirtual) {
305 e.preventDefault();
306 }
307 }
308 var componentEle = componentRef.current;
309 componentEle.addEventListener('wheel', onRawWheel);
310 componentEle.addEventListener('DOMMouseScroll', onFireFoxScroll);
311 componentEle.addEventListener('MozMousePixelScroll', onMozMousePixelScroll);
312 return function () {
313 componentEle.removeEventListener('wheel', onRawWheel);
314 componentEle.removeEventListener('DOMMouseScroll', onFireFoxScroll);
315 componentEle.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll);
316 };
317 }, [useVirtual]);
318 // ================================= Ref ==================================
319 var delayHideScrollBar = function delayHideScrollBar() {
320 var _verticalScrollBarRef, _horizontalScrollBarR;
321 (_verticalScrollBarRef = verticalScrollBarRef.current) === null || _verticalScrollBarRef === void 0 ? void 0 : _verticalScrollBarRef.delayHidden();
322 (_horizontalScrollBarR = horizontalScrollBarRef.current) === null || _horizontalScrollBarR === void 0 ? void 0 : _horizontalScrollBarR.delayHidden();
323 };
324 var _scrollTo = useScrollTo(componentRef, mergedData, heights, itemHeight, getKey, collectHeight, syncScrollTop, delayHideScrollBar);
325 React.useImperativeHandle(ref, function () {
326 return {
327 getScrollInfo: getVirtualScrollInfo,
328 scrollTo: function scrollTo(config) {
329 function isPosScroll(arg) {
330 return arg && _typeof(arg) === 'object' && ('left' in arg || 'top' in arg);
331 }
332 if (isPosScroll(config)) {
333 // Scroll X
334 if (config.left !== undefined) {
335 setOffsetLeft(keepInHorizontalRange(config.left));
336 }
337 // Scroll Y
338 _scrollTo(config.top);
339 } else {
340 _scrollTo(config);
341 }
342 }
343 };
344 });
345 // ================================ Effect ================================
346 /** We need told outside that some list not rendered */
347 useLayoutEffect(function () {
348 if (onVisibleChange) {
349 var renderList = mergedData.slice(start, end + 1);
350 onVisibleChange(renderList, mergedData);
351 }
352 }, [start, end, mergedData]);
353 // ================================ Extra =================================
354 var getSize = function getSize(startKey) {
355 var endKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : startKey;
356 var top = 0;
357 var bottom = 0;
358 var total = 0;
359 var dataLen = mergedData.length;
360 for (var i = 0; i < dataLen; i += 1) {
361 var _heights$get;
362 var item = mergedData[i];
363 var key = getKey(item);
364 var cacheHeight = (_heights$get = heights.get(key)) !== null && _heights$get !== void 0 ? _heights$get : itemHeight;
365 bottom = total + cacheHeight;
366 if (key === startKey) {
367 top = total;
368 }
369 if (key === endKey) {
370 break;
371 }
372 total = bottom;
373 }
374 return {
375 top: top,
376 bottom: bottom
377 };
378 };
379 var extraContent = extraRender === null || extraRender === void 0 ? void 0 : extraRender({
380 start: start,
381 end: end,
382 virtual: inVirtual,
383 offsetX: offsetLeft,
384 offsetY: fillerOffset,
385 rtl: isRTL,
386 getSize: getSize
387 });
388 // ================================ Render ================================
389 var listChildren = useChildren(mergedData, start, end, scrollWidth, setInstanceRef, children, sharedConfig);
390 var componentStyle = null;
391 if (height) {
392 componentStyle = _objectSpread(_defineProperty({}, fullHeight ? 'height' : 'maxHeight', height), ScrollStyle);
393 if (useVirtual) {
394 componentStyle.overflowY = 'hidden';
395 if (scrollWidth) {
396 componentStyle.overflowX = 'hidden';
397 }
398 if (scrollMoving) {
399 componentStyle.pointerEvents = 'none';
400 }
401 }
402 }
403 var containerProps = {};
404 if (isRTL) {
405 containerProps.dir = 'rtl';
406 }
407 return /*#__PURE__*/React.createElement("div", _extends({
408 style: _objectSpread(_objectSpread({}, style), {}, {
409 position: 'relative'
410 }),
411 className: mergedClassName
412 }, containerProps, restProps), /*#__PURE__*/React.createElement(ResizeObserver, {
413 onResize: onHolderResize
414 }, /*#__PURE__*/React.createElement(Component, {
415 className: "".concat(prefixCls, "-holder"),
416 style: componentStyle,
417 ref: componentRef,
418 onScroll: onFallbackScroll,
419 onMouseEnter: delayHideScrollBar
420 }, /*#__PURE__*/React.createElement(Filler, {
421 prefixCls: prefixCls,
422 height: scrollHeight,
423 offsetX: offsetLeft,
424 offsetY: fillerOffset,
425 scrollWidth: scrollWidth,
426 onInnerResize: collectHeight,
427 ref: fillerInnerRef,
428 innerProps: innerProps,
429 rtl: isRTL,
430 extra: extraContent
431 }, listChildren))), inVirtual && scrollHeight > height && /*#__PURE__*/React.createElement(ScrollBar, {
432 ref: verticalScrollBarRef,
433 prefixCls: prefixCls,
434 scrollOffset: offsetTop,
435 scrollRange: scrollHeight,
436 rtl: isRTL,
437 onScroll: onScrollBar,
438 onStartMove: onScrollbarStartMove,
439 onStopMove: onScrollbarStopMove,
440 spinSize: verticalScrollBarSpinSize,
441 containerSize: size.height
442 }), inVirtual && scrollWidth && /*#__PURE__*/React.createElement(ScrollBar, {
443 ref: horizontalScrollBarRef,
444 prefixCls: prefixCls,
445 scrollOffset: offsetLeft,
446 scrollRange: scrollWidth,
447 rtl: isRTL,
448 onScroll: onScrollBar,
449 onStartMove: onScrollbarStartMove,
450 onStopMove: onScrollbarStopMove,
451 spinSize: horizontalScrollBarSpinSize,
452 containerSize: size.width,
453 horizontal: true
454 }));
455}
456var List = /*#__PURE__*/React.forwardRef(RawList);
457List.displayName = 'List';
458export default List;
\No newline at end of file