553efa59b2850f39a195110bc1fde87a5413040caa98ac51094479992253fbc570f6d68444596e5f7aab8cb51a8dab58bfb9034ba387ee399c506008f4063d 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. import { defineComponent, getCurrentInstance, ref, computed, unref, onMounted, nextTick, resolveDynamicComponent, h, Fragment } from 'vue';
  2. import { useEventListener, isClient } from '@vueuse/core';
  3. import ScrollBar from '../components/scrollbar.mjs';
  4. import { useGridWheel } from '../hooks/use-grid-wheel.mjs';
  5. import { useCache } from '../hooks/use-cache.mjs';
  6. import { virtualizedGridProps } from '../props.mjs';
  7. import { getScrollDir, getRTLOffsetType, isRTL } from '../utils.mjs';
  8. import { ITEM_RENDER_EVT, SCROLL_EVT, FORWARD, BACKWARD, AUTO_ALIGNMENT, RTL, RTL_OFFSET_POS_ASC, RTL_OFFSET_NAG, RTL_OFFSET_POS_DESC } from '../defaults.mjs';
  9. import { useNamespace } from '../../../../hooks/use-namespace/index.mjs';
  10. import { isNumber } from '../../../../utils/types.mjs';
  11. import { getScrollBarWidth } from '../../../../utils/dom/scroll.mjs';
  12. import { isString, hasOwn } from '@vue/shared';
  13. const createGrid = ({
  14. name,
  15. clearCache,
  16. getColumnPosition,
  17. getColumnStartIndexForOffset,
  18. getColumnStopIndexForStartIndex,
  19. getEstimatedTotalHeight,
  20. getEstimatedTotalWidth,
  21. getColumnOffset,
  22. getRowOffset,
  23. getRowPosition,
  24. getRowStartIndexForOffset,
  25. getRowStopIndexForStartIndex,
  26. initCache,
  27. injectToInstance,
  28. validateProps
  29. }) => {
  30. return defineComponent({
  31. name: name != null ? name : "ElVirtualList",
  32. props: virtualizedGridProps,
  33. emits: [ITEM_RENDER_EVT, SCROLL_EVT],
  34. setup(props, { emit, expose, slots }) {
  35. const ns = useNamespace("vl");
  36. validateProps(props);
  37. const instance = getCurrentInstance();
  38. const cache = ref(initCache(props, instance));
  39. injectToInstance == null ? void 0 : injectToInstance(instance, cache);
  40. const windowRef = ref();
  41. const hScrollbar = ref();
  42. const vScrollbar = ref();
  43. const innerRef = ref(null);
  44. const states = ref({
  45. isScrolling: false,
  46. scrollLeft: isNumber(props.initScrollLeft) ? props.initScrollLeft : 0,
  47. scrollTop: isNumber(props.initScrollTop) ? props.initScrollTop : 0,
  48. updateRequested: false,
  49. xAxisScrollDir: FORWARD,
  50. yAxisScrollDir: FORWARD
  51. });
  52. const getItemStyleCache = useCache();
  53. const parsedHeight = computed(() => Number.parseInt(`${props.height}`, 10));
  54. const parsedWidth = computed(() => Number.parseInt(`${props.width}`, 10));
  55. const columnsToRender = computed(() => {
  56. const { totalColumn, totalRow, columnCache } = props;
  57. const { isScrolling, xAxisScrollDir, scrollLeft } = unref(states);
  58. if (totalColumn === 0 || totalRow === 0) {
  59. return [0, 0, 0, 0];
  60. }
  61. const startIndex = getColumnStartIndexForOffset(props, scrollLeft, unref(cache));
  62. const stopIndex = getColumnStopIndexForStartIndex(props, startIndex, scrollLeft, unref(cache));
  63. const cacheBackward = !isScrolling || xAxisScrollDir === BACKWARD ? Math.max(1, columnCache) : 1;
  64. const cacheForward = !isScrolling || xAxisScrollDir === FORWARD ? Math.max(1, columnCache) : 1;
  65. return [
  66. Math.max(0, startIndex - cacheBackward),
  67. Math.max(0, Math.min(totalColumn - 1, stopIndex + cacheForward)),
  68. startIndex,
  69. stopIndex
  70. ];
  71. });
  72. const rowsToRender = computed(() => {
  73. const { totalColumn, totalRow, rowCache } = props;
  74. const { isScrolling, yAxisScrollDir, scrollTop } = unref(states);
  75. if (totalColumn === 0 || totalRow === 0) {
  76. return [0, 0, 0, 0];
  77. }
  78. const startIndex = getRowStartIndexForOffset(props, scrollTop, unref(cache));
  79. const stopIndex = getRowStopIndexForStartIndex(props, startIndex, scrollTop, unref(cache));
  80. const cacheBackward = !isScrolling || yAxisScrollDir === BACKWARD ? Math.max(1, rowCache) : 1;
  81. const cacheForward = !isScrolling || yAxisScrollDir === FORWARD ? Math.max(1, rowCache) : 1;
  82. return [
  83. Math.max(0, startIndex - cacheBackward),
  84. Math.max(0, Math.min(totalRow - 1, stopIndex + cacheForward)),
  85. startIndex,
  86. stopIndex
  87. ];
  88. });
  89. const estimatedTotalHeight = computed(() => getEstimatedTotalHeight(props, unref(cache)));
  90. const estimatedTotalWidth = computed(() => getEstimatedTotalWidth(props, unref(cache)));
  91. const windowStyle = computed(() => {
  92. var _a;
  93. return [
  94. {
  95. position: "relative",
  96. overflow: "hidden",
  97. WebkitOverflowScrolling: "touch",
  98. willChange: "transform"
  99. },
  100. {
  101. direction: props.direction,
  102. height: isNumber(props.height) ? `${props.height}px` : props.height,
  103. width: isNumber(props.width) ? `${props.width}px` : props.width
  104. },
  105. (_a = props.style) != null ? _a : {}
  106. ];
  107. });
  108. const innerStyle = computed(() => {
  109. const width = `${unref(estimatedTotalWidth)}px`;
  110. const height = `${unref(estimatedTotalHeight)}px`;
  111. return {
  112. height,
  113. pointerEvents: unref(states).isScrolling ? "none" : void 0,
  114. width
  115. };
  116. });
  117. const emitEvents = () => {
  118. const { totalColumn, totalRow } = props;
  119. if (totalColumn > 0 && totalRow > 0) {
  120. const [
  121. columnCacheStart,
  122. columnCacheEnd,
  123. columnVisibleStart,
  124. columnVisibleEnd
  125. ] = unref(columnsToRender);
  126. const [rowCacheStart, rowCacheEnd, rowVisibleStart, rowVisibleEnd] = unref(rowsToRender);
  127. emit(ITEM_RENDER_EVT, {
  128. columnCacheStart,
  129. columnCacheEnd,
  130. rowCacheStart,
  131. rowCacheEnd,
  132. columnVisibleStart,
  133. columnVisibleEnd,
  134. rowVisibleStart,
  135. rowVisibleEnd
  136. });
  137. }
  138. const {
  139. scrollLeft,
  140. scrollTop,
  141. updateRequested,
  142. xAxisScrollDir,
  143. yAxisScrollDir
  144. } = unref(states);
  145. emit(SCROLL_EVT, {
  146. xAxisScrollDir,
  147. scrollLeft,
  148. yAxisScrollDir,
  149. scrollTop,
  150. updateRequested
  151. });
  152. };
  153. const onScroll = (e) => {
  154. const {
  155. clientHeight,
  156. clientWidth,
  157. scrollHeight,
  158. scrollLeft,
  159. scrollTop,
  160. scrollWidth
  161. } = e.currentTarget;
  162. const _states = unref(states);
  163. if (_states.scrollTop === scrollTop && _states.scrollLeft === scrollLeft) {
  164. return;
  165. }
  166. let _scrollLeft = scrollLeft;
  167. if (isRTL(props.direction)) {
  168. switch (getRTLOffsetType()) {
  169. case RTL_OFFSET_NAG:
  170. _scrollLeft = -scrollLeft;
  171. break;
  172. case RTL_OFFSET_POS_DESC:
  173. _scrollLeft = scrollWidth - clientWidth - scrollLeft;
  174. break;
  175. }
  176. }
  177. states.value = {
  178. ..._states,
  179. isScrolling: true,
  180. scrollLeft: _scrollLeft,
  181. scrollTop: Math.max(0, Math.min(scrollTop, scrollHeight - clientHeight)),
  182. updateRequested: true,
  183. xAxisScrollDir: getScrollDir(_states.scrollLeft, _scrollLeft),
  184. yAxisScrollDir: getScrollDir(_states.scrollTop, scrollTop)
  185. };
  186. nextTick(() => resetIsScrolling());
  187. onUpdated();
  188. emitEvents();
  189. };
  190. const onVerticalScroll = (distance, totalSteps) => {
  191. const height = unref(parsedHeight);
  192. const offset = (estimatedTotalHeight.value - height) / totalSteps * distance;
  193. scrollTo({
  194. scrollTop: Math.min(estimatedTotalHeight.value - height, offset)
  195. });
  196. };
  197. const onHorizontalScroll = (distance, totalSteps) => {
  198. const width = unref(parsedWidth);
  199. const offset = (estimatedTotalWidth.value - width) / totalSteps * distance;
  200. scrollTo({
  201. scrollLeft: Math.min(estimatedTotalWidth.value - width, offset)
  202. });
  203. };
  204. const { onWheel } = useGridWheel({
  205. atXStartEdge: computed(() => states.value.scrollLeft <= 0),
  206. atXEndEdge: computed(() => states.value.scrollLeft >= estimatedTotalWidth.value - unref(parsedWidth)),
  207. atYStartEdge: computed(() => states.value.scrollTop <= 0),
  208. atYEndEdge: computed(() => states.value.scrollTop >= estimatedTotalHeight.value - unref(parsedHeight))
  209. }, (x, y) => {
  210. var _a, _b, _c, _d;
  211. (_b = (_a = hScrollbar.value) == null ? void 0 : _a.onMouseUp) == null ? void 0 : _b.call(_a);
  212. (_d = (_c = vScrollbar.value) == null ? void 0 : _c.onMouseUp) == null ? void 0 : _d.call(_c);
  213. const width = unref(parsedWidth);
  214. const height = unref(parsedHeight);
  215. scrollTo({
  216. scrollLeft: Math.min(states.value.scrollLeft + x, estimatedTotalWidth.value - width),
  217. scrollTop: Math.min(states.value.scrollTop + y, estimatedTotalHeight.value - height)
  218. });
  219. });
  220. useEventListener(windowRef, "wheel", onWheel, {
  221. passive: false
  222. });
  223. const scrollTo = ({
  224. scrollLeft = states.value.scrollLeft,
  225. scrollTop = states.value.scrollTop
  226. }) => {
  227. scrollLeft = Math.max(scrollLeft, 0);
  228. scrollTop = Math.max(scrollTop, 0);
  229. const _states = unref(states);
  230. if (scrollTop === _states.scrollTop && scrollLeft === _states.scrollLeft) {
  231. return;
  232. }
  233. states.value = {
  234. ..._states,
  235. xAxisScrollDir: getScrollDir(_states.scrollLeft, scrollLeft),
  236. yAxisScrollDir: getScrollDir(_states.scrollTop, scrollTop),
  237. scrollLeft,
  238. scrollTop,
  239. updateRequested: true
  240. };
  241. nextTick(() => resetIsScrolling());
  242. onUpdated();
  243. emitEvents();
  244. };
  245. const scrollToItem = (rowIndex = 0, columnIdx = 0, alignment = AUTO_ALIGNMENT) => {
  246. const _states = unref(states);
  247. columnIdx = Math.max(0, Math.min(columnIdx, props.totalColumn - 1));
  248. rowIndex = Math.max(0, Math.min(rowIndex, props.totalRow - 1));
  249. const scrollBarWidth = getScrollBarWidth(ns.namespace.value);
  250. const _cache = unref(cache);
  251. const estimatedHeight = getEstimatedTotalHeight(props, _cache);
  252. const estimatedWidth = getEstimatedTotalWidth(props, _cache);
  253. scrollTo({
  254. scrollLeft: getColumnOffset(props, columnIdx, alignment, _states.scrollLeft, _cache, estimatedWidth > props.width ? scrollBarWidth : 0),
  255. scrollTop: getRowOffset(props, rowIndex, alignment, _states.scrollTop, _cache, estimatedHeight > props.height ? scrollBarWidth : 0)
  256. });
  257. };
  258. const getItemStyle = (rowIndex, columnIndex) => {
  259. const { columnWidth, direction, rowHeight } = props;
  260. const itemStyleCache = getItemStyleCache.value(clearCache && columnWidth, clearCache && rowHeight, clearCache && direction);
  261. const key = `${rowIndex},${columnIndex}`;
  262. if (hasOwn(itemStyleCache, key)) {
  263. return itemStyleCache[key];
  264. } else {
  265. const [, left] = getColumnPosition(props, columnIndex, unref(cache));
  266. const _cache = unref(cache);
  267. const rtl = isRTL(direction);
  268. const [height, top] = getRowPosition(props, rowIndex, _cache);
  269. const [width] = getColumnPosition(props, columnIndex, _cache);
  270. itemStyleCache[key] = {
  271. position: "absolute",
  272. left: rtl ? void 0 : `${left}px`,
  273. right: rtl ? `${left}px` : void 0,
  274. top: `${top}px`,
  275. height: `${height}px`,
  276. width: `${width}px`
  277. };
  278. return itemStyleCache[key];
  279. }
  280. };
  281. const resetIsScrolling = () => {
  282. states.value.isScrolling = false;
  283. nextTick(() => {
  284. getItemStyleCache.value(-1, null, null);
  285. });
  286. };
  287. onMounted(() => {
  288. if (!isClient)
  289. return;
  290. const { initScrollLeft, initScrollTop } = props;
  291. const windowElement = unref(windowRef);
  292. if (windowElement) {
  293. if (isNumber(initScrollLeft)) {
  294. windowElement.scrollLeft = initScrollLeft;
  295. }
  296. if (isNumber(initScrollTop)) {
  297. windowElement.scrollTop = initScrollTop;
  298. }
  299. }
  300. emitEvents();
  301. });
  302. const onUpdated = () => {
  303. const { direction } = props;
  304. const { scrollLeft, scrollTop, updateRequested } = unref(states);
  305. const windowElement = unref(windowRef);
  306. if (updateRequested && windowElement) {
  307. if (direction === RTL) {
  308. switch (getRTLOffsetType()) {
  309. case RTL_OFFSET_NAG: {
  310. windowElement.scrollLeft = -scrollLeft;
  311. break;
  312. }
  313. case RTL_OFFSET_POS_ASC: {
  314. windowElement.scrollLeft = scrollLeft;
  315. break;
  316. }
  317. default: {
  318. const { clientWidth, scrollWidth } = windowElement;
  319. windowElement.scrollLeft = scrollWidth - clientWidth - scrollLeft;
  320. break;
  321. }
  322. }
  323. } else {
  324. windowElement.scrollLeft = Math.max(0, scrollLeft);
  325. }
  326. windowElement.scrollTop = Math.max(0, scrollTop);
  327. }
  328. };
  329. const { resetAfterColumnIndex, resetAfterRowIndex, resetAfter } = instance.proxy;
  330. expose({
  331. windowRef,
  332. innerRef,
  333. getItemStyleCache,
  334. scrollTo,
  335. scrollToItem,
  336. states,
  337. resetAfterColumnIndex,
  338. resetAfterRowIndex,
  339. resetAfter
  340. });
  341. const renderScrollbars = () => {
  342. const {
  343. scrollbarAlwaysOn,
  344. scrollbarStartGap,
  345. scrollbarEndGap,
  346. totalColumn,
  347. totalRow
  348. } = props;
  349. const width = unref(parsedWidth);
  350. const height = unref(parsedHeight);
  351. const estimatedWidth = unref(estimatedTotalWidth);
  352. const estimatedHeight = unref(estimatedTotalHeight);
  353. const { scrollLeft, scrollTop } = unref(states);
  354. const horizontalScrollbar = h(ScrollBar, {
  355. ref: hScrollbar,
  356. alwaysOn: scrollbarAlwaysOn,
  357. startGap: scrollbarStartGap,
  358. endGap: scrollbarEndGap,
  359. class: ns.e("horizontal"),
  360. clientSize: width,
  361. layout: "horizontal",
  362. onScroll: onHorizontalScroll,
  363. ratio: width * 100 / estimatedWidth,
  364. scrollFrom: scrollLeft / (estimatedWidth - width),
  365. total: totalRow,
  366. visible: true
  367. });
  368. const verticalScrollbar = h(ScrollBar, {
  369. ref: vScrollbar,
  370. alwaysOn: scrollbarAlwaysOn,
  371. startGap: scrollbarStartGap,
  372. endGap: scrollbarEndGap,
  373. class: ns.e("vertical"),
  374. clientSize: height,
  375. layout: "vertical",
  376. onScroll: onVerticalScroll,
  377. ratio: height * 100 / estimatedHeight,
  378. scrollFrom: scrollTop / (estimatedHeight - height),
  379. total: totalColumn,
  380. visible: true
  381. });
  382. return {
  383. horizontalScrollbar,
  384. verticalScrollbar
  385. };
  386. };
  387. const renderItems = () => {
  388. var _a;
  389. const [columnStart, columnEnd] = unref(columnsToRender);
  390. const [rowStart, rowEnd] = unref(rowsToRender);
  391. const { data, totalColumn, totalRow, useIsScrolling, itemKey } = props;
  392. const children = [];
  393. if (totalRow > 0 && totalColumn > 0) {
  394. for (let row = rowStart; row <= rowEnd; row++) {
  395. for (let column = columnStart; column <= columnEnd; column++) {
  396. const key = itemKey({ columnIndex: column, data, rowIndex: row });
  397. children.push(h(Fragment, { key }, (_a = slots.default) == null ? void 0 : _a.call(slots, {
  398. columnIndex: column,
  399. data,
  400. isScrolling: useIsScrolling ? unref(states).isScrolling : void 0,
  401. style: getItemStyle(row, column),
  402. rowIndex: row
  403. })));
  404. }
  405. }
  406. }
  407. return children;
  408. };
  409. const renderInner = () => {
  410. const Inner = resolveDynamicComponent(props.innerElement);
  411. const children = renderItems();
  412. return [
  413. h(Inner, {
  414. style: unref(innerStyle),
  415. ref: innerRef
  416. }, !isString(Inner) ? {
  417. default: () => children
  418. } : children)
  419. ];
  420. };
  421. const renderWindow = () => {
  422. const Container = resolveDynamicComponent(props.containerElement);
  423. const { horizontalScrollbar, verticalScrollbar } = renderScrollbars();
  424. const Inner = renderInner();
  425. return h("div", {
  426. key: 0,
  427. class: ns.e("wrapper"),
  428. role: props.role
  429. }, [
  430. h(Container, {
  431. class: props.className,
  432. style: unref(windowStyle),
  433. onScroll,
  434. ref: windowRef
  435. }, !isString(Container) ? { default: () => Inner } : Inner),
  436. horizontalScrollbar,
  437. verticalScrollbar
  438. ]);
  439. };
  440. return renderWindow;
  441. }
  442. });
  443. };
  444. export { createGrid as default };
  445. //# sourceMappingURL=build-grid.mjs.map