List.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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. }, {
  197. immediate: true
  198. });
  199. watch([inVirtual, useVirtual, function () {
  200. return state.scrollTop;
  201. }, mergedData, updatedMark, function () {
  202. return props.height;
  203. }, offsetHeight], function () {
  204. if (!useVirtual.value || !inVirtual.value) {
  205. return;
  206. }
  207. var itemTop = 0;
  208. var startIndex;
  209. var startOffset;
  210. var endIndex;
  211. var dataLen = mergedData.value.length;
  212. var data = mergedData.value;
  213. var scrollTop = state.scrollTop;
  214. var itemHeight = props.itemHeight,
  215. height = props.height;
  216. var scrollTopHeight = scrollTop + height;
  217. for (var i = 0; i < dataLen; i += 1) {
  218. var item = data[i];
  219. var key = getKey(item);
  220. var cacheHeight = heights.get(key);
  221. if (cacheHeight === undefined) {
  222. cacheHeight = itemHeight;
  223. }
  224. var currentItemBottom = itemTop + cacheHeight;
  225. if (startIndex === undefined && currentItemBottom >= scrollTop) {
  226. startIndex = i;
  227. startOffset = itemTop;
  228. }
  229. // Check item bottom in the range. We will render additional one item for motion usage
  230. if (endIndex === undefined && currentItemBottom > scrollTopHeight) {
  231. endIndex = i;
  232. }
  233. itemTop = currentItemBottom;
  234. }
  235. // Fallback to normal if not match. This code should never reach
  236. /* istanbul ignore next */
  237. if (startIndex === undefined) {
  238. startIndex = 0;
  239. startOffset = 0;
  240. }
  241. if (endIndex === undefined) {
  242. endIndex = dataLen - 1;
  243. }
  244. // Give cache to improve scroll experience
  245. endIndex = Math.min(endIndex + 1, dataLen);
  246. _extends(calRes, {
  247. scrollHeight: itemTop,
  248. start: startIndex,
  249. end: endIndex,
  250. offset: startOffset
  251. });
  252. }, {
  253. immediate: true
  254. });
  255. // =============================== In Range ===============================
  256. var maxScrollHeight = computed(function () {
  257. return calRes.scrollHeight - props.height;
  258. });
  259. function keepInRange(newScrollTop) {
  260. var newTop = newScrollTop;
  261. if (!Number.isNaN(maxScrollHeight.value)) {
  262. newTop = Math.min(newTop, maxScrollHeight.value);
  263. }
  264. newTop = Math.max(newTop, 0);
  265. return newTop;
  266. }
  267. var isScrollAtTop = computed(function () {
  268. return state.scrollTop <= 0;
  269. });
  270. var isScrollAtBottom = computed(function () {
  271. return state.scrollTop >= maxScrollHeight.value;
  272. });
  273. var originScroll = useOriginScroll(isScrollAtTop, isScrollAtBottom);
  274. // ================================ Scroll ================================
  275. function onScrollBar(newScrollTop) {
  276. var newTop = newScrollTop;
  277. syncScrollTop(newTop);
  278. }
  279. // When data size reduce. It may trigger native scroll event back to fit scroll position
  280. function onFallbackScroll(e) {
  281. var _props$onScroll;
  282. var newScrollTop = e.currentTarget.scrollTop;
  283. if (Math.abs(newScrollTop - state.scrollTop) >= 1) {
  284. syncScrollTop(newScrollTop);
  285. }
  286. // Trigger origin onScroll
  287. (_props$onScroll = props.onScroll) === null || _props$onScroll === void 0 ? void 0 : _props$onScroll.call(props, e);
  288. }
  289. // Since this added in global,should use ref to keep update
  290. var _useFrameWheel = useFrameWheel(useVirtual, isScrollAtTop, isScrollAtBottom, function (offsetY) {
  291. syncScrollTop(function (top) {
  292. var newTop = top + offsetY;
  293. return newTop;
  294. });
  295. }),
  296. _useFrameWheel2 = _slicedToArray(_useFrameWheel, 2),
  297. onRawWheel = _useFrameWheel2[0],
  298. onFireFoxScroll = _useFrameWheel2[1];
  299. // Mobile touch move
  300. useMobileTouchMove(useVirtual, componentRef, function (deltaY, smoothOffset) {
  301. if (originScroll(deltaY, smoothOffset)) {
  302. return false;
  303. }
  304. onRawWheel({
  305. preventDefault: function preventDefault() {},
  306. deltaY: deltaY
  307. });
  308. return true;
  309. });
  310. // Firefox only
  311. function onMozMousePixelScroll(e) {
  312. if (useVirtual.value) {
  313. e.preventDefault();
  314. }
  315. }
  316. var removeEventListener = function removeEventListener() {
  317. if (componentRef.value) {
  318. componentRef.value.removeEventListener('wheel', onRawWheel, supportsPassive ? {
  319. passive: false
  320. } : false);
  321. componentRef.value.removeEventListener('DOMMouseScroll', onFireFoxScroll);
  322. componentRef.value.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll);
  323. }
  324. };
  325. watchEffect(function () {
  326. nextTick(function () {
  327. if (componentRef.value) {
  328. removeEventListener();
  329. componentRef.value.addEventListener('wheel', onRawWheel, supportsPassive ? {
  330. passive: false
  331. } : false);
  332. componentRef.value.addEventListener('DOMMouseScroll', onFireFoxScroll);
  333. componentRef.value.addEventListener('MozMousePixelScroll', onMozMousePixelScroll);
  334. }
  335. });
  336. });
  337. onBeforeUnmount(function () {
  338. removeEventListener();
  339. });
  340. // ================================= Ref ==================================
  341. var scrollTo = useScrollTo(componentRef, mergedData, heights, props, getKey, collectHeight, syncScrollTop, function () {
  342. var _scrollBarRef$value;
  343. (_scrollBarRef$value = scrollBarRef.value) === null || _scrollBarRef$value === void 0 ? void 0 : _scrollBarRef$value.delayHidden();
  344. });
  345. expose({
  346. scrollTo: scrollTo
  347. });
  348. var componentStyle = computed(function () {
  349. var cs = null;
  350. if (props.height) {
  351. cs = _objectSpread(_defineProperty({}, props.fullHeight ? 'height' : 'maxHeight', props.height + 'px'), ScrollStyle);
  352. if (useVirtual.value) {
  353. cs.overflowY = 'hidden';
  354. if (state.scrollMoving) {
  355. cs.pointerEvents = 'none';
  356. }
  357. }
  358. }
  359. return cs;
  360. });
  361. // ================================ Effect ================================
  362. /** We need told outside that some list not rendered */
  363. watch([function () {
  364. return calRes.start;
  365. }, function () {
  366. return calRes.end;
  367. }, mergedData], function () {
  368. if (props.onVisibleChange) {
  369. var renderList = mergedData.value.slice(calRes.start, calRes.end + 1);
  370. props.onVisibleChange(renderList, mergedData.value);
  371. }
  372. }, {
  373. flush: 'post'
  374. });
  375. return {
  376. state: state,
  377. mergedData: mergedData,
  378. componentStyle: componentStyle,
  379. onFallbackScroll: onFallbackScroll,
  380. onScrollBar: onScrollBar,
  381. componentRef: componentRef,
  382. useVirtual: useVirtual,
  383. calRes: calRes,
  384. collectHeight: collectHeight,
  385. setInstance: setInstance,
  386. sharedConfig: sharedConfig,
  387. scrollBarRef: scrollBarRef,
  388. fillerInnerRef: fillerInnerRef
  389. };
  390. },
  391. render: function render() {
  392. var _this = this;
  393. var _this$$props$this$$at = _objectSpread(_objectSpread({}, this.$props), this.$attrs),
  394. _this$$props$this$$at2 = _this$$props$this$$at.prefixCls,
  395. prefixCls = _this$$props$this$$at2 === void 0 ? 'rc-virtual-list' : _this$$props$this$$at2,
  396. height = _this$$props$this$$at.height,
  397. itemHeight = _this$$props$this$$at.itemHeight,
  398. fullHeight = _this$$props$this$$at.fullHeight,
  399. data = _this$$props$this$$at.data,
  400. itemKey = _this$$props$this$$at.itemKey,
  401. virtual = _this$$props$this$$at.virtual,
  402. _this$$props$this$$at3 = _this$$props$this$$at.component,
  403. Component = _this$$props$this$$at3 === void 0 ? 'div' : _this$$props$this$$at3,
  404. onScroll = _this$$props$this$$at.onScroll,
  405. _this$$props$this$$at4 = _this$$props$this$$at.children,
  406. children = _this$$props$this$$at4 === void 0 ? this.$slots.default : _this$$props$this$$at4,
  407. style = _this$$props$this$$at.style,
  408. className = _this$$props$this$$at.class,
  409. restProps = _objectWithoutProperties(_this$$props$this$$at, _excluded);
  410. var mergedClassName = classNames(prefixCls, className);
  411. var scrollTop = this.state.scrollTop;
  412. var _this$calRes = this.calRes,
  413. scrollHeight = _this$calRes.scrollHeight,
  414. offset = _this$calRes.offset,
  415. start = _this$calRes.start,
  416. end = _this$calRes.end;
  417. var componentStyle = this.componentStyle,
  418. onFallbackScroll = this.onFallbackScroll,
  419. onScrollBar = this.onScrollBar,
  420. useVirtual = this.useVirtual,
  421. collectHeight = this.collectHeight,
  422. sharedConfig = this.sharedConfig,
  423. setInstance = this.setInstance,
  424. mergedData = this.mergedData;
  425. return _createVNode("div", _objectSpread({
  426. "style": _objectSpread(_objectSpread({}, style), {}, {
  427. position: 'relative'
  428. }),
  429. "class": mergedClassName
  430. }, restProps), [_createVNode(Component, {
  431. "class": "".concat(prefixCls, "-holder"),
  432. "style": componentStyle,
  433. "ref": "componentRef",
  434. "onScroll": onFallbackScroll
  435. }, {
  436. default: function _default() {
  437. return [_createVNode(Filler, {
  438. "prefixCls": prefixCls,
  439. "height": scrollHeight,
  440. "offset": offset,
  441. "onInnerResize": collectHeight,
  442. "ref": "fillerInnerRef"
  443. }, {
  444. default: function _default() {
  445. return renderChildren(mergedData, start, end, setInstance, children, sharedConfig);
  446. }
  447. })];
  448. }
  449. }), useVirtual && _createVNode(ScrollBar, {
  450. "ref": "scrollBarRef",
  451. "prefixCls": prefixCls,
  452. "scrollTop": scrollTop,
  453. "height": height,
  454. "scrollHeight": scrollHeight,
  455. "count": mergedData.length,
  456. "onScroll": onScrollBar,
  457. "onStartMove": function onStartMove() {
  458. _this.state.scrollMoving = true;
  459. },
  460. "onStopMove": function onStopMove() {
  461. _this.state.scrollMoving = false;
  462. }
  463. }, null)]);
  464. }
  465. });
  466. export default List;