UNPKG

@opensig/opendesign

Version:

<p align="center"><img src="../docs/public/opendesign-logo-light.png"/></p> <h1 align="center">opendesign</h1> <p align="center">一个 Vue 3 组件库</p> <p align="center"><b>皮肤可定制,使用 TypeScript</b></p>

387 lines (386 loc) 13 kB
import { defineComponent, computed, ref, watch, onMounted, createElementBlock, openBlock, withDirectives, createElementVNode, normalizeStyle, Fragment, renderList, renderSlot, unref, nextTick } from "vue"; import "../scrollbar/index.mjs"; import { virtualListProps } from "./types.mjs"; import { isUndefined } from "../_utils/is.mjs"; import { vOnResize } from "../directives/on-resize.mjs"; import { debounceRAF } from "../_utils/helper.mjs"; import { vScrollbar } from "../scrollbar/vScrollbar.mjs"; const _hoisted_1 = { class: "o-virtual-list" }; const _hoisted_2 = { key: 1, class: "o-virtual-render-item" }; const _sfc_main = /* @__PURE__ */ defineComponent({ __name: "OVirtualList", props: virtualListProps, emits: ["renderChange"], setup(__props, { expose: __expose, emit: __emit }) { const props = __props; const emits = __emit; const scrollbarProps = computed(() => { if (props.scrollbar === true) { return { showType: "always", size: "medium" }; } return props.scrollbar; }); const listData = ref([]); watch( () => props.list, (value) => { listData.value = value.map((item, index) => ({ id: item.id, data: item, index })); }, { immediate: true } ); const defaultStartIndex = computed(() => { if (isUndefined(props.defaultStartIndex)) { return 0; } return Math.max(Math.min(props.defaultStartIndex, props.list.length - 1), 0); }); const visibleStartIndex = ref(defaultStartIndex.value ?? 0); let visibleStartId; const renderCount = ref(1); const startIndex = computed(() => { return Math.max(visibleStartIndex.value - props.buffer, 0); }); const endIndex = computed(() => { return Math.min(visibleStartIndex.value + renderCount.value + props.buffer - 1, listData.value.length - 1); }); let lastVisibleStartIndex = visibleStartIndex.value; let lastRenderCount = renderCount.value; const emitRenderChange = () => { if (lastVisibleStartIndex !== visibleStartIndex.value || lastRenderCount !== renderCount.value) { emits("renderChange", { start: startIndex.value, end: endIndex.value, count: renderCount.value, visible: visibleStartIndex.value }); lastVisibleStartIndex = visibleStartIndex.value; lastRenderCount = renderCount.value; } }; const renderList$1 = computed(() => { return listData.value.slice(startIndex.value, endIndex.value + 1); }); watch(listData, (value) => { if (!isUndefined(visibleStartId) && wrapperRef.value) { const top = wrapperRef.value.scrollTop - listMetaData[visibleStartIndex.value].top; const index = value.findIndex((item) => item.id === visibleStartId); if (index >= 0) { visibleStartIndex.value = index; wrapperRef.value.scrollTop = listMetaData[index].top + top; } } }); const wrapperRef = ref(); const contentSize = ref((props.itemSize ? props.itemSize : props.defaultItemSize) * listData.value.length); const containerSize = ref({ height: 0, width: 0 }); const onContainerResize = () => { if (!wrapperRef.value) { return; } containerSize.value.height = wrapperRef.value.offsetHeight; containerSize.value.width = wrapperRef.value.offsetWidth; if (containerSize.value.height === 0) { offset.value = 0; } if (!initialScroll) { if (contentSize.value < containerSize.value.height) { visibleStartIndex.value = 0; } return; } const scrollTop = wrapperRef.value.scrollTop; for (let i = visibleStartIndex.value; i >= 0; i--) { const meta = listMetaData[i]; if (meta.top <= scrollTop) { visibleStartIndex.value = i; break; } } let count = renderCount.value; for (let i = endIndex.value; i < listMetaData.length; i++) { const meta = listMetaData[i]; if (meta.top < scrollTop + containerSize.value.height) { count++; } } renderCount.value = count; emitRenderChange(); }; const contentStyle = computed(() => ({ "--content-height": `${contentSize.value}px` })); const offset = ref(0); const renderListStyle = computed(() => { return { "--offsetY": `${offset.value}px` }; }); let initialScroll = props.itemSize ? true : false; const scrollToView = (index, align = "start", behavior = "instant") => { if (!wrapperRef.value) { return; } const toIndex = Math.max(Math.min(listMetaData.length - 1, index), 0); const item = listMetaData[toIndex]; const itemTop = item.top; const cSize = wrapperRef.value.offsetHeight; let _align = align; if (_align === "nearest") { const currScrollTop = wrapperRef.value.scrollTop; if (currScrollTop > itemTop) { _align = "start"; } else if (currScrollTop + cSize < itemTop) { _align = "end"; } else { return; } } let scrollTop = itemTop; if (_align !== "start") { const itemSize = listMetaData[toIndex].size; if (_align === "center") { scrollTop = itemTop - cSize / 2 + itemSize / 2; } else if (_align === "end") { scrollTop = itemTop - cSize + itemSize; } else if (typeof _align === "number") { scrollTop = itemTop - _align; } } wrapperRef.value.scrollTo({ top: scrollTop, behavior: props.itemSize ? behavior : "instant" }); }; let listMetaData = []; watch( [() => props.itemSize, () => listData.value], ([propSize, dataList]) => { const itemSize = propSize ? propSize : props.defaultItemSize; let lastTop = 0; listMetaData = dataList.map((item, index) => { if (!propSize) { const m = listMetaData.find((mItem) => mItem.id === item.id); if (m && m.measured) { lastTop = m.bottom; return m; } } const metaItem = { id: item.id, index, size: itemSize, top: lastTop, bottom: lastTop + itemSize, measured: propSize ? true : false, isScrolling: false }; lastTop += itemSize; return metaItem; }); contentSize.value = listMetaData[listMetaData.length - 1].bottom; }, { immediate: true } ); const updateMeta = (start = 0) => { for (let i = start + 1; i < listMetaData.length; i++) { const lastMeta = listMetaData[i - 1]; const meta = listMetaData[i]; meta.top = lastMeta.bottom; meta.bottom = meta.top + meta.size; } const last = listMetaData[listMetaData.length - 1]; last.bottom = last.top + last.size; contentSize.value = last.bottom; }; const updateVisibleCount = (scrollOffset) => { var _a; let scrollSize = scrollOffset; if (isUndefined(scrollSize)) { scrollSize = ((_a = wrapperRef.value) == null ? void 0 : _a.scrollTop) ?? 0; } const { height: containerHeight } = containerSize.value; if (!wrapperRef.value || !containerHeight) { return; } let render = 1; for (let i = visibleStartIndex.value + 1; i < listMetaData.length; i++) { const meta = listMetaData[i]; if (meta.top < scrollSize + containerHeight) { render++; } } renderCount.value = render; emitRenderChange(); }; const debounceUpdateVisibleCount = debounceRAF(updateVisibleCount); const getStartIndex = (scrollOffset) => { let start = 0; let end = listMetaData.length - 1; while (start < end) { const mid = Math.floor((start + end) / 2); const { top, bottom } = listMetaData[mid]; if (top <= scrollOffset && bottom > scrollOffset) { return mid; } else if (bottom === scrollOffset) { return mid + 1; } else if (bottom < scrollOffset) { start = mid; } else if (top > scrollOffset) { end = mid; } } return start; }; const onScroll = () => { var _a; const scrollOffset = ((_a = wrapperRef.value) == null ? void 0 : _a.scrollTop) ?? 0; if (props.itemSize) { visibleStartIndex.value = Math.floor(scrollOffset / props.itemSize); } else { visibleStartIndex.value = getStartIndex(scrollOffset); } offset.value = listMetaData[startIndex.value].top; visibleStartId = listMetaData[visibleStartIndex.value].id; debounceUpdateVisibleCount(scrollOffset); }; const onItemResize = (en, index) => { const el = en.target; const meta = listMetaData[index]; const size = el.offsetHeight; if (meta.measured && meta.size === size) { return; } if (meta.measured === false && wrapperRef.value && wrapperRef.value.scrollTop > meta.top) { wrapperRef.value.scrollTop += size - meta.size; } meta.size = size; meta.measured = true; meta.bottom = meta.top + meta.size; updateMeta(index); if (index === defaultStartIndex.value && !initialScroll) { nextTick(() => { scrollToView(defaultStartIndex.value); initialScroll = true; }); } debounceUpdateVisibleCount(); }; const init = () => { if (!wrapperRef.value) { return; } if (props.itemSize) { scrollToView(defaultStartIndex.value); } }; onMounted(() => { init(); }); __expose({ scrollToView }); return (_ctx, _cache) => { return openBlock(), createElementBlock("div", _hoisted_1, [ withDirectives((openBlock(), createElementBlock( "div", { class: "o-virtual-list-wrapper", ref_key: "wrapperRef", ref: wrapperRef, onScrollPassive: onScroll }, [ createElementVNode( "div", { class: "o-virtual-body", style: normalizeStyle(contentStyle.value) }, [ createElementVNode( "div", { class: "o-virtual-render-list", style: normalizeStyle(renderListStyle.value) }, [ (openBlock(true), createElementBlock( Fragment, null, renderList(renderList$1.value, (item) => { return openBlock(), createElementBlock( Fragment, { key: item.index }, [ props.itemSize ? (openBlock(), createElementBlock( "div", { key: 0, class: "o-virtual-render-item", style: normalizeStyle({ height: props.itemSize + "px" }) }, [ renderSlot(_ctx.$slots, "default", { item: item.data, index: item.index }) ], 4 /* STYLE */ )) : withDirectives((openBlock(), createElementBlock("div", _hoisted_2, [ renderSlot(_ctx.$slots, "default", { item: item.data, index: item.index }) ])), [ [unref(vOnResize), (en) => onItemResize(en, item.index)] ]) ], 64 /* STABLE_FRAGMENT */ ); }), 128 /* KEYED_FRAGMENT */ )) ], 4 /* STYLE */ ) ], 4 /* STYLE */ ) ], 32 /* NEED_HYDRATION */ )), [ [unref(vOnResize), onContainerResize], [unref(vScrollbar), scrollbarProps.value] ]) ]); }; } }); export { _sfc_main as default };