List.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
  2. import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
  3. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  4. import _extends from "@babel/runtime/helpers/esm/extends";
  5. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  6. var _excluded = ["prefixCls", "height", "itemHeight", "fullHeight", "data", "itemKey", "virtual", "component", "onScroll", "children", "style", "class"];
  7. import { resolveDirective as _resolveDirective, createVNode as _createVNode } from "vue";
  8. import { shallowRef, toRaw, onMounted, onUpdated, ref, defineComponent, watchEffect, computed, nextTick, onBeforeUnmount, reactive, watch } from 'vue';
  9. import Filler from './Filler';
  10. import Item from './Item';
  11. import ScrollBar from './ScrollBar';
  12. import useHeights from './hooks/useHeights';
  13. import useScrollTo from './hooks/useScrollTo';
  14. import useFrameWheel from './hooks/useFrameWheel';
  15. import useMobileTouchMove from './hooks/useMobileTouchMove';
  16. import useOriginScroll from './hooks/useOriginScroll';
  17. import PropTypes from '../_util/vue-types';
  18. import classNames from '../_util/classNames';
  19. import supportsPassive from '../_util/supportsPassive';
  20. var EMPTY_DATA = [];
  21. var ScrollStyle = {
  22. overflowY: 'auto',
  23. overflowAnchor: 'none'
  24. };
  25. function renderChildren(list, startIndex, endIndex, setNodeRef, renderFunc, _ref) {
  26. var getKey = _ref.getKey;
  27. return list.slice(startIndex, endIndex + 1).map(function (item, index) {
  28. var eleIndex = startIndex + index;
  29. var node = renderFunc(item, eleIndex, {
  30. // style: status === 'MEASURE_START' ? { visibility: 'hidden' } : {},
  31. });
  32. var key = getKey(item);
  33. return _createVNode(Item, {
  34. "key": key,
  35. "setRef": function setRef(ele) {
  36. return setNodeRef(item, ele);
  37. }
  38. }, {
  39. default: function _default() {
  40. return [node];
  41. }
  42. });
  43. });
  44. }
  45. var List = defineComponent({
  46. compatConfig: {
  47. MODE: 3
  48. },
  49. name: 'List',
  50. inheritAttrs: false,
  51. props: {
  52. prefixCls: String,
  53. data: PropTypes.array,
  54. height: Number,
  55. itemHeight: Number,
  56. /** If not match virtual scroll condition, Set List still use height of container. */
  57. fullHeight: {
  58. type: Boolean,
  59. default: undefined
  60. },
  61. itemKey: {
  62. type: [String, Number, Function],
  63. required: true
  64. },
  65. component: {
  66. type: [String, Object]
  67. },
  68. /** Set `false` will always use real scroll instead of virtual one */
  69. virtual: {
  70. type: Boolean,
  71. default: undefined
  72. },
  73. children: Function,
  74. onScroll: Function,
  75. onMousedown: Function,
  76. onMouseenter: Function,
  77. onVisibleChange: Function
  78. },
  79. setup: function setup(props, _ref2) {
  80. var expose = _ref2.expose;
  81. // ================================= MISC =================================
  82. var useVirtual = computed(function () {
  83. var height = props.height,
  84. itemHeight = props.itemHeight,
  85. virtual = props.virtual;
  86. return !!(virtual !== false && height && itemHeight);
  87. });
  88. var inVirtual = computed(function () {
  89. var height = props.height,
  90. itemHeight = props.itemHeight,
  91. data = props.data;
  92. return useVirtual.value && data && itemHeight * data.length > height;
  93. });
  94. var state = reactive({
  95. scrollTop: 0,
  96. scrollMoving: false
  97. });
  98. var data = computed(function () {
  99. return props.data || EMPTY_DATA;
  100. });
  101. var mergedData = shallowRef([]);
  102. watch(data, function () {
  103. mergedData.value = toRaw(data.value).slice();
  104. }, {
  105. immediate: true
  106. });
  107. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  108. var itemKey = shallowRef(function (_item) {
  109. return undefined;
  110. });
  111. watch(function () {
  112. return props.itemKey;
  113. }, function (val) {
  114. if (typeof val === 'function') {
  115. itemKey.value = val;
  116. } else {
  117. itemKey.value = function (item) {
  118. return item === null || item === void 0 ? void 0 : item[val];
  119. };
  120. }
  121. }, {
  122. immediate: true
  123. });
  124. var componentRef = ref();
  125. var fillerInnerRef = ref();
  126. var scrollBarRef = ref(); // Hack on scrollbar to enable flash call
  127. // =============================== Item Key ===============================
  128. var getKey = function getKey(item) {
  129. return itemKey.value(item);
  130. };
  131. var sharedConfig = {
  132. getKey: getKey
  133. };
  134. // ================================ Scroll ================================
  135. function syncScrollTop(newTop) {
  136. var value;
  137. if (typeof newTop === 'function') {
  138. value = newTop(state.scrollTop);
  139. } else {
  140. value = newTop;
  141. }
  142. var alignedTop = keepInRange(value);
  143. if (componentRef.value) {
  144. componentRef.value.scrollTop = alignedTop;
  145. }
  146. state.scrollTop = alignedTop;
  147. }
  148. // ================================ Height ================================
  149. var _useHeights = useHeights(mergedData, getKey, null, null),
  150. _useHeights2 = _slicedToArray(_useHeights, 4),
  151. setInstance = _useHeights2[0],
  152. collectHeight = _useHeights2[1],
  153. heights = _useHeights2[2],
  154. updatedMark = _useHeights2[3];
  155. var calRes = reactive({
  156. scrollHeight: undefined,
  157. start: 0,
  158. end: 0,
  159. offset: undefined
  160. });
  161. var offsetHeight = ref(0);
  162. onMounted(function () {
  163. nextTick(function () {
  164. var _fillerInnerRef$value;
  165. offsetHeight.value = ((_fillerInnerRef$value = fillerInnerRef.value) === null || _fillerInnerRef$value === void 0 ? void 0 : _fillerInnerRef$value.offsetHeight) || 0;
  166. });
  167. });
  168. onUpdated(function () {
  169. nextTick(function () {
  170. var _fillerInnerRef$value2;
  171. offsetHeight.value = ((_fillerInnerRef$value2 = fillerInnerRef.value) === null || _fillerInnerRef$value2 === void 0 ? void 0 : _fillerInnerRef$value2.offsetHeight) || 0;
  172. });
  173. });
  174. watch([useVirtual, mergedData], function () {
  175. if (!useVirtual.value) {
  176. _extends(calRes, {
  177. scrollHeight: undefined,
  178. start: 0,
  179. end: mergedData.value.length - 1,
  180. offset: undefined
  181. });
  182. }
  183. }, {
  184. immediate: true
  185. });
  186. watch([useVirtual, mergedData, offsetHeight, inVirtual], function () {
  187. // Always use virtual scroll bar in avoid shaking
  188. if (useVirtual.value && !inVirtual.value) {
  189. _extends(calRes, {
  190. scrollHeight: offsetHeight.value,
  191. start: 0,
  192. end: mergedData.value.length - 1,
  193. offset: undefined
  194. });
  195. }
  196. if (componentRef.value) {
  197. state.scrollTop = componentRef.value.scrollTop;
  198. }
  199. }, {
  200. immediate: true
  201. });
  202. watch([inVirtual, useVirtual, function () {
  203. return state.scrollTop;
  204. }, mergedData, updatedMark, function () {
  205. return props.height;
  206. }, offsetHeight], function () {
  207. if (!useVirtual.value || !inVirtual.value) {
  208. return;
  209. }
  210. var itemTop = 0;
  211. var startIndex;
  212. var startOffset;
  213. var endIndex;
  214. var dataLen = mergedData.value.length;
  215. var data = mergedData.value;
  216. var scrollTop = state.scrollTop;
  217. var itemHeight = props.itemHeight,
  218. height = props.height;
  219. var scrollTopHeight = scrollTop + height;
  220. for (var i = 0; i < dataLen; i += 1) {
  221. var item = data[i];
  222. var key = getKey(item);
  223. var cacheHeight = heights.get(key);
  224. if (cacheHeight === undefined) {
  225. cacheHeight = itemHeight;
  226. }
  227. var currentItemBottom = itemTop + cacheHeight;
  228. if (startIndex === undefined && currentItemBottom >= scrollTop) {
  229. startIndex = i;
  230. startOffset = itemTop;
  231. }
  232. // Check item bottom in the range. We will render additional one item for motion usage
  233. if (endIndex === undefined && currentItemBottom > scrollTopHeight) {
  234. endIndex = i;
  235. }
  236. itemTop = currentItemBottom;
  237. }
  238. // When scrollTop at the end but data cut to small count will reach this
  239. if (startIndex === undefined) {
  240. startIndex = 0;
  241. startOffset = 0;
  242. endIndex = Math.ceil(height / itemHeight);
  243. }
  244. if (endIndex === undefined) {
  245. endIndex = dataLen - 1;
  246. }
  247. // Give cache to improve scroll experience
  248. endIndex = Math.min(endIndex + 1, dataLen);
  249. _extends(calRes, {
  250. scrollHeight: itemTop,
  251. start: startIndex,
  252. end: endIndex,
  253. offset: startOffset
  254. });
  255. }, {
  256. immediate: true
  257. });
  258. // =============================== In Range ===============================
  259. var maxScrollHeight = computed(function () {
  260. return calRes.scrollHeight - props.height;
  261. });
  262. function keepInRange(newScrollTop) {
  263. var newTop = newScrollTop;
  264. if (!Number.isNaN(maxScrollHeight.value)) {
  265. newTop = Math.min(newTop, maxScrollHeight.value);
  266. }
  267. newTop = Math.max(newTop, 0);
  268. return newTop;
  269. }
  270. var isScrollAtTop = computed(function () {
  271. return state.scrollTop <= 0;
  272. });
  273. var isScrollAtBottom = computed(function () {
  274. return state.scrollTop >= maxScrollHeight.value;
  275. });
  276. var originScroll = useOriginScroll(isScrollAtTop, isScrollAtBottom);
  277. // ================================ Scroll ================================
  278. function onScrollBar(newScrollTop) {
  279. var newTop = newScrollTop;
  280. syncScrollTop(newTop);
  281. }
  282. // When data size reduce. It may trigger native scroll event back to fit scroll position
  283. function onFallbackScroll(e) {
  284. var _props$onScroll;
  285. var newScrollTop = e.currentTarget.scrollTop;
  286. if (newScrollTop !== state.scrollTop) {
  287. syncScrollTop(newScrollTop);
  288. }
  289. // Trigger origin onScroll
  290. (_props$onScroll = props.onScroll) === null || _props$onScroll === void 0 ? void 0 : _props$onScroll.call(props, e);
  291. }
  292. // Since this added in global,should use ref to keep update
  293. var _useFrameWheel = useFrameWheel(useVirtual, isScrollAtTop, isScrollAtBottom, function (offsetY) {
  294. syncScrollTop(function (top) {
  295. var newTop = top + offsetY;
  296. return newTop;
  297. });
  298. }),
  299. _useFrameWheel2 = _slicedToArray(_useFrameWheel, 2),
  300. onRawWheel = _useFrameWheel2[0],
  301. onFireFoxScroll = _useFrameWheel2[1];
  302. // Mobile touch move
  303. useMobileTouchMove(useVirtual, componentRef, function (deltaY, smoothOffset) {
  304. if (originScroll(deltaY, smoothOffset)) {
  305. return false;
  306. }
  307. onRawWheel({
  308. preventDefault: function preventDefault() {},
  309. deltaY: deltaY
  310. });
  311. return true;
  312. });
  313. // Firefox only
  314. function onMozMousePixelScroll(e) {
  315. if (useVirtual.value) {
  316. e.preventDefault();
  317. }
  318. }
  319. var removeEventListener = function removeEventListener() {
  320. if (componentRef.value) {
  321. componentRef.value.removeEventListener('wheel', onRawWheel, supportsPassive ? {
  322. passive: false
  323. } : false);
  324. componentRef.value.removeEventListener('DOMMouseScroll', onFireFoxScroll);
  325. componentRef.value.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll);
  326. }
  327. };
  328. watchEffect(function () {
  329. nextTick(function () {
  330. if (componentRef.value) {
  331. removeEventListener();
  332. componentRef.value.addEventListener('wheel', onRawWheel, supportsPassive ? {
  333. passive: false
  334. } : false);
  335. componentRef.value.addEventListener('DOMMouseScroll', onFireFoxScroll);
  336. componentRef.value.addEventListener('MozMousePixelScroll', onMozMousePixelScroll);
  337. }
  338. });
  339. });
  340. onBeforeUnmount(function () {
  341. removeEventListener();
  342. });
  343. // ================================= Ref ==================================
  344. var scrollTo = useScrollTo(componentRef, mergedData, heights, props, getKey, collectHeight, syncScrollTop, function () {
  345. var _scrollBarRef$value;
  346. (_scrollBarRef$value = scrollBarRef.value) === null || _scrollBarRef$value === void 0 ? void 0 : _scrollBarRef$value.delayHidden();
  347. });
  348. expose({
  349. scrollTo: scrollTo
  350. });
  351. var componentStyle = computed(function () {
  352. var cs = null;
  353. if (props.height) {
  354. cs = _objectSpread(_defineProperty({}, props.fullHeight ? 'height' : 'maxHeight', props.height + 'px'), ScrollStyle);
  355. if (useVirtual.value) {
  356. cs.overflowY = 'hidden';
  357. if (state.scrollMoving) {
  358. cs.pointerEvents = 'none';
  359. }
  360. }
  361. }
  362. return cs;
  363. });
  364. // ================================ Effect ================================
  365. /** We need told outside that some list not rendered */
  366. watch([function () {
  367. return calRes.start;
  368. }, function () {
  369. return calRes.end;
  370. }, mergedData], function () {
  371. if (props.onVisibleChange) {
  372. var renderList = mergedData.value.slice(calRes.start, calRes.end + 1);
  373. props.onVisibleChange(renderList, mergedData.value);
  374. }
  375. }, {
  376. flush: 'post'
  377. });
  378. return {
  379. state: state,
  380. mergedData: mergedData,
  381. componentStyle: componentStyle,
  382. onFallbackScroll: onFallbackScroll,
  383. onScrollBar: onScrollBar,
  384. componentRef: componentRef,
  385. useVirtual: useVirtual,
  386. calRes: calRes,
  387. collectHeight: collectHeight,
  388. setInstance: setInstance,
  389. sharedConfig: sharedConfig,
  390. scrollBarRef: scrollBarRef,
  391. fillerInnerRef: fillerInnerRef
  392. };
  393. },
  394. render: function render() {
  395. var _this = this;
  396. var _this$$props$this$$at = _objectSpread(_objectSpread({}, this.$props), this.$attrs),
  397. _this$$props$this$$at2 = _this$$props$this$$at.prefixCls,
  398. prefixCls = _this$$props$this$$at2 === void 0 ? 'rc-virtual-list' : _this$$props$this$$at2,
  399. height = _this$$props$this$$at.height,
  400. itemHeight = _this$$props$this$$at.itemHeight,
  401. fullHeight = _this$$props$this$$at.fullHeight,
  402. data = _this$$props$this$$at.data,
  403. itemKey = _this$$props$this$$at.itemKey,
  404. virtual = _this$$props$this$$at.virtual,
  405. _this$$props$this$$at3 = _this$$props$this$$at.component,
  406. Component = _this$$props$this$$at3 === void 0 ? 'div' : _this$$props$this$$at3,
  407. onScroll = _this$$props$this$$at.onScroll,
  408. _this$$props$this$$at4 = _this$$props$this$$at.children,
  409. children = _this$$props$this$$at4 === void 0 ? this.$slots.default : _this$$props$this$$at4,
  410. style = _this$$props$this$$at.style,
  411. className = _this$$props$this$$at.class,
  412. restProps = _objectWithoutProperties(_this$$props$this$$at, _excluded);
  413. var mergedClassName = classNames(prefixCls, className);
  414. var scrollTop = this.state.scrollTop;
  415. var _this$calRes = this.calRes,
  416. scrollHeight = _this$calRes.scrollHeight,
  417. offset = _this$calRes.offset,
  418. start = _this$calRes.start,
  419. end = _this$calRes.end;
  420. var componentStyle = this.componentStyle,
  421. onFallbackScroll = this.onFallbackScroll,
  422. onScrollBar = this.onScrollBar,
  423. useVirtual = this.useVirtual,
  424. collectHeight = this.collectHeight,
  425. sharedConfig = this.sharedConfig,
  426. setInstance = this.setInstance,
  427. mergedData = this.mergedData;
  428. return _createVNode("div", _objectSpread({
  429. "style": _objectSpread(_objectSpread({}, style), {}, {
  430. position: 'relative'
  431. }),
  432. "class": mergedClassName
  433. }, restProps), [_createVNode(Component, {
  434. "class": "".concat(prefixCls, "-holder"),
  435. "style": componentStyle,
  436. "ref": "componentRef",
  437. "onScroll": onFallbackScroll
  438. }, {
  439. default: function _default() {
  440. return [_createVNode(Filler, {
  441. "prefixCls": prefixCls,
  442. "height": scrollHeight,
  443. "offset": offset,
  444. "onInnerResize": collectHeight,
  445. "ref": "fillerInnerRef"
  446. }, {
  447. default: function _default() {
  448. return renderChildren(mergedData, start, end, setInstance, children, sharedConfig);
  449. }
  450. })];
  451. }
  452. }), useVirtual && _createVNode(ScrollBar, {
  453. "ref": "scrollBarRef",
  454. "prefixCls": prefixCls,
  455. "scrollTop": scrollTop,
  456. "height": height,
  457. "scrollHeight": scrollHeight,
  458. "count": mergedData.length,
  459. "onScroll": onScrollBar,
  460. "onStartMove": function onStartMove() {
  461. _this.state.scrollMoving = true;
  462. },
  463. "onStopMove": function onStopMove() {
  464. _this.state.scrollMoving = false;
  465. }
  466. }, null)]);
  467. }
  468. });
  469. export default List;