b0e824642a394ef7aeb6e1b0e960fb8ce945bedc04c2b584e8e726409913f23d4e7e38fe86bd1ac807ce6bc3744097fcec2fff7f465f21ef76dc907f2dcca0 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. import { defineComponent, getCurrentInstance, ref, computed, unref, onMounted, onUpdated, onActivated, resolveDynamicComponent, h, Fragment, nextTick } from 'vue';
  2. import { useEventListener, isClient } from '@vueuse/core';
  3. import { useCache } from '../hooks/use-cache.mjs';
  4. import useWheel from '../hooks/use-wheel.mjs';
  5. import ScrollBar from '../components/scrollbar.mjs';
  6. import { isHorizontal, getRTLOffsetType, getScrollDir } from '../utils.mjs';
  7. import { virtualizedListProps } from '../props.mjs';
  8. import { ITEM_RENDER_EVT, SCROLL_EVT, HORIZONTAL, RTL, RTL_OFFSET_POS_ASC, RTL_OFFSET_NAG, BACKWARD, FORWARD, AUTO_ALIGNMENT, 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 { isString, hasOwn } from '@vue/shared';
  12. const createList = ({
  13. name,
  14. getOffset,
  15. getItemSize,
  16. getItemOffset,
  17. getEstimatedTotalSize,
  18. getStartIndexForOffset,
  19. getStopIndexForStartIndex,
  20. initCache,
  21. clearCache,
  22. validateProps
  23. }) => {
  24. return defineComponent({
  25. name: name != null ? name : "ElVirtualList",
  26. props: virtualizedListProps,
  27. emits: [ITEM_RENDER_EVT, SCROLL_EVT],
  28. setup(props, { emit, expose }) {
  29. validateProps(props);
  30. const instance = getCurrentInstance();
  31. const ns = useNamespace("vl");
  32. const dynamicSizeCache = ref(initCache(props, instance));
  33. const getItemStyleCache = useCache();
  34. const windowRef = ref();
  35. const innerRef = ref();
  36. const scrollbarRef = ref();
  37. const states = ref({
  38. isScrolling: false,
  39. scrollDir: "forward",
  40. scrollOffset: isNumber(props.initScrollOffset) ? props.initScrollOffset : 0,
  41. updateRequested: false,
  42. isScrollbarDragging: false,
  43. scrollbarAlwaysOn: props.scrollbarAlwaysOn
  44. });
  45. const itemsToRender = computed(() => {
  46. const { total, cache } = props;
  47. const { isScrolling, scrollDir, scrollOffset } = unref(states);
  48. if (total === 0) {
  49. return [0, 0, 0, 0];
  50. }
  51. const startIndex = getStartIndexForOffset(props, scrollOffset, unref(dynamicSizeCache));
  52. const stopIndex = getStopIndexForStartIndex(props, startIndex, scrollOffset, unref(dynamicSizeCache));
  53. const cacheBackward = !isScrolling || scrollDir === BACKWARD ? Math.max(1, cache) : 1;
  54. const cacheForward = !isScrolling || scrollDir === FORWARD ? Math.max(1, cache) : 1;
  55. return [
  56. Math.max(0, startIndex - cacheBackward),
  57. Math.max(0, Math.min(total - 1, stopIndex + cacheForward)),
  58. startIndex,
  59. stopIndex
  60. ];
  61. });
  62. const estimatedTotalSize = computed(() => getEstimatedTotalSize(props, unref(dynamicSizeCache)));
  63. const _isHorizontal = computed(() => isHorizontal(props.layout));
  64. const windowStyle = computed(() => [
  65. {
  66. position: "relative",
  67. [`overflow-${_isHorizontal.value ? "x" : "y"}`]: "scroll",
  68. WebkitOverflowScrolling: "touch",
  69. willChange: "transform"
  70. },
  71. {
  72. direction: props.direction,
  73. height: isNumber(props.height) ? `${props.height}px` : props.height,
  74. width: isNumber(props.width) ? `${props.width}px` : props.width
  75. },
  76. props.style
  77. ]);
  78. const innerStyle = computed(() => {
  79. const size = unref(estimatedTotalSize);
  80. const horizontal = unref(_isHorizontal);
  81. return {
  82. height: horizontal ? "100%" : `${size}px`,
  83. pointerEvents: unref(states).isScrolling ? "none" : void 0,
  84. width: horizontal ? `${size}px` : "100%"
  85. };
  86. });
  87. const clientSize = computed(() => _isHorizontal.value ? props.width : props.height);
  88. const { onWheel } = useWheel({
  89. atStartEdge: computed(() => states.value.scrollOffset <= 0),
  90. atEndEdge: computed(() => states.value.scrollOffset >= estimatedTotalSize.value),
  91. layout: computed(() => props.layout)
  92. }, (offset) => {
  93. var _a, _b;
  94. (_b = (_a = scrollbarRef.value).onMouseUp) == null ? void 0 : _b.call(_a);
  95. scrollTo(Math.min(states.value.scrollOffset + offset, estimatedTotalSize.value - clientSize.value));
  96. });
  97. useEventListener(windowRef, "wheel", onWheel, {
  98. passive: false
  99. });
  100. const emitEvents = () => {
  101. const { total } = props;
  102. if (total > 0) {
  103. const [cacheStart, cacheEnd, visibleStart, visibleEnd] = unref(itemsToRender);
  104. emit(ITEM_RENDER_EVT, cacheStart, cacheEnd, visibleStart, visibleEnd);
  105. }
  106. const { scrollDir, scrollOffset, updateRequested } = unref(states);
  107. emit(SCROLL_EVT, scrollDir, scrollOffset, updateRequested);
  108. };
  109. const scrollVertically = (e) => {
  110. const { clientHeight, scrollHeight, scrollTop } = e.currentTarget;
  111. const _states = unref(states);
  112. if (_states.scrollOffset === scrollTop) {
  113. return;
  114. }
  115. const scrollOffset = Math.max(0, Math.min(scrollTop, scrollHeight - clientHeight));
  116. states.value = {
  117. ..._states,
  118. isScrolling: true,
  119. scrollDir: getScrollDir(_states.scrollOffset, scrollOffset),
  120. scrollOffset,
  121. updateRequested: false
  122. };
  123. nextTick(resetIsScrolling);
  124. };
  125. const scrollHorizontally = (e) => {
  126. const { clientWidth, scrollLeft, scrollWidth } = e.currentTarget;
  127. const _states = unref(states);
  128. if (_states.scrollOffset === scrollLeft) {
  129. return;
  130. }
  131. const { direction } = props;
  132. let scrollOffset = scrollLeft;
  133. if (direction === RTL) {
  134. switch (getRTLOffsetType()) {
  135. case RTL_OFFSET_NAG: {
  136. scrollOffset = -scrollLeft;
  137. break;
  138. }
  139. case RTL_OFFSET_POS_DESC: {
  140. scrollOffset = scrollWidth - clientWidth - scrollLeft;
  141. break;
  142. }
  143. }
  144. }
  145. scrollOffset = Math.max(0, Math.min(scrollOffset, scrollWidth - clientWidth));
  146. states.value = {
  147. ..._states,
  148. isScrolling: true,
  149. scrollDir: getScrollDir(_states.scrollOffset, scrollOffset),
  150. scrollOffset,
  151. updateRequested: false
  152. };
  153. nextTick(resetIsScrolling);
  154. };
  155. const onScroll = (e) => {
  156. unref(_isHorizontal) ? scrollHorizontally(e) : scrollVertically(e);
  157. emitEvents();
  158. };
  159. const onScrollbarScroll = (distanceToGo, totalSteps) => {
  160. const offset = (estimatedTotalSize.value - clientSize.value) / totalSteps * distanceToGo;
  161. scrollTo(Math.min(estimatedTotalSize.value - clientSize.value, offset));
  162. };
  163. const scrollTo = (offset) => {
  164. offset = Math.max(offset, 0);
  165. if (offset === unref(states).scrollOffset) {
  166. return;
  167. }
  168. states.value = {
  169. ...unref(states),
  170. scrollOffset: offset,
  171. scrollDir: getScrollDir(unref(states).scrollOffset, offset),
  172. updateRequested: true
  173. };
  174. nextTick(resetIsScrolling);
  175. };
  176. const scrollToItem = (idx, alignment = AUTO_ALIGNMENT) => {
  177. const { scrollOffset } = unref(states);
  178. idx = Math.max(0, Math.min(idx, props.total - 1));
  179. scrollTo(getOffset(props, idx, alignment, scrollOffset, unref(dynamicSizeCache)));
  180. };
  181. const getItemStyle = (idx) => {
  182. const { direction, itemSize, layout } = props;
  183. const itemStyleCache = getItemStyleCache.value(clearCache && itemSize, clearCache && layout, clearCache && direction);
  184. let style;
  185. if (hasOwn(itemStyleCache, String(idx))) {
  186. style = itemStyleCache[idx];
  187. } else {
  188. const offset = getItemOffset(props, idx, unref(dynamicSizeCache));
  189. const size = getItemSize(props, idx, unref(dynamicSizeCache));
  190. const horizontal = unref(_isHorizontal);
  191. const isRtl = direction === RTL;
  192. const offsetHorizontal = horizontal ? offset : 0;
  193. itemStyleCache[idx] = style = {
  194. position: "absolute",
  195. left: isRtl ? void 0 : `${offsetHorizontal}px`,
  196. right: isRtl ? `${offsetHorizontal}px` : void 0,
  197. top: !horizontal ? `${offset}px` : 0,
  198. height: !horizontal ? `${size}px` : "100%",
  199. width: horizontal ? `${size}px` : "100%"
  200. };
  201. }
  202. return style;
  203. };
  204. const resetIsScrolling = () => {
  205. states.value.isScrolling = false;
  206. nextTick(() => {
  207. getItemStyleCache.value(-1, null, null);
  208. });
  209. };
  210. const resetScrollTop = () => {
  211. const window = windowRef.value;
  212. if (window) {
  213. window.scrollTop = 0;
  214. }
  215. };
  216. onMounted(() => {
  217. if (!isClient)
  218. return;
  219. const { initScrollOffset } = props;
  220. const windowElement = unref(windowRef);
  221. if (isNumber(initScrollOffset) && windowElement) {
  222. if (unref(_isHorizontal)) {
  223. windowElement.scrollLeft = initScrollOffset;
  224. } else {
  225. windowElement.scrollTop = initScrollOffset;
  226. }
  227. }
  228. emitEvents();
  229. });
  230. onUpdated(() => {
  231. const { direction, layout } = props;
  232. const { scrollOffset, updateRequested } = unref(states);
  233. const windowElement = unref(windowRef);
  234. if (updateRequested && windowElement) {
  235. if (layout === HORIZONTAL) {
  236. if (direction === RTL) {
  237. switch (getRTLOffsetType()) {
  238. case RTL_OFFSET_NAG: {
  239. windowElement.scrollLeft = -scrollOffset;
  240. break;
  241. }
  242. case RTL_OFFSET_POS_ASC: {
  243. windowElement.scrollLeft = scrollOffset;
  244. break;
  245. }
  246. default: {
  247. const { clientWidth, scrollWidth } = windowElement;
  248. windowElement.scrollLeft = scrollWidth - clientWidth - scrollOffset;
  249. break;
  250. }
  251. }
  252. } else {
  253. windowElement.scrollLeft = scrollOffset;
  254. }
  255. } else {
  256. windowElement.scrollTop = scrollOffset;
  257. }
  258. }
  259. });
  260. onActivated(() => {
  261. unref(windowRef).scrollTop = unref(states).scrollOffset;
  262. });
  263. const api = {
  264. ns,
  265. clientSize,
  266. estimatedTotalSize,
  267. windowStyle,
  268. windowRef,
  269. innerRef,
  270. innerStyle,
  271. itemsToRender,
  272. scrollbarRef,
  273. states,
  274. getItemStyle,
  275. onScroll,
  276. onScrollbarScroll,
  277. onWheel,
  278. scrollTo,
  279. scrollToItem,
  280. resetScrollTop
  281. };
  282. expose({
  283. windowRef,
  284. innerRef,
  285. getItemStyleCache,
  286. scrollTo,
  287. scrollToItem,
  288. resetScrollTop,
  289. states
  290. });
  291. return api;
  292. },
  293. render(ctx) {
  294. var _a;
  295. const {
  296. $slots,
  297. className,
  298. clientSize,
  299. containerElement,
  300. data,
  301. getItemStyle,
  302. innerElement,
  303. itemsToRender,
  304. innerStyle,
  305. layout,
  306. total,
  307. onScroll,
  308. onScrollbarScroll,
  309. states,
  310. useIsScrolling,
  311. windowStyle,
  312. ns
  313. } = ctx;
  314. const [start, end] = itemsToRender;
  315. const Container = resolveDynamicComponent(containerElement);
  316. const Inner = resolveDynamicComponent(innerElement);
  317. const children = [];
  318. if (total > 0) {
  319. for (let i = start; i <= end; i++) {
  320. children.push(h(Fragment, { key: i }, (_a = $slots.default) == null ? void 0 : _a.call($slots, {
  321. data,
  322. index: i,
  323. isScrolling: useIsScrolling ? states.isScrolling : void 0,
  324. style: getItemStyle(i)
  325. })));
  326. }
  327. }
  328. const InnerNode = [
  329. h(Inner, {
  330. style: innerStyle,
  331. ref: "innerRef"
  332. }, !isString(Inner) ? {
  333. default: () => children
  334. } : children)
  335. ];
  336. const scrollbar = h(ScrollBar, {
  337. ref: "scrollbarRef",
  338. clientSize,
  339. layout,
  340. onScroll: onScrollbarScroll,
  341. ratio: clientSize * 100 / this.estimatedTotalSize,
  342. scrollFrom: states.scrollOffset / (this.estimatedTotalSize - clientSize),
  343. total,
  344. alwaysOn: states.scrollbarAlwaysOn
  345. });
  346. const listContainer = h(Container, {
  347. class: [ns.e("window"), className],
  348. style: windowStyle,
  349. onScroll,
  350. ref: "windowRef",
  351. key: 0
  352. }, !isString(Container) ? { default: () => [InnerNode] } : [InnerNode]);
  353. return h("div", {
  354. key: 0,
  355. class: [ns.e("wrapper"), states.scrollbarAlwaysOn ? "always-on" : ""]
  356. }, [listContainer, scrollbar]);
  357. }
  358. });
  359. };
  360. export { createList as default };
  361. //# sourceMappingURL=build-list.mjs.map