List.js 15 KB

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