123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- // Composables
- import { useDisplay } from "./display.mjs";
- import { useResizeObserver } from "./resizeObserver.mjs"; // Utilities
- import { computed, ref, shallowRef, watch, watchEffect } from 'vue';
- import { clamp, createRange, propsFactory } from "../util/index.mjs"; // Types
- const UP = -1;
- const DOWN = 1;
- export const makeVirtualProps = propsFactory({
- itemHeight: {
- type: [Number, String],
- default: 48
- }
- }, 'virtual');
- export function useVirtual(props, items, offset) {
- const first = shallowRef(0);
- const baseItemHeight = shallowRef(props.itemHeight);
- const itemHeight = computed({
- get: () => parseInt(baseItemHeight.value ?? 0, 10),
- set(val) {
- baseItemHeight.value = val;
- }
- });
- const containerRef = ref();
- const {
- resizeRef,
- contentRect
- } = useResizeObserver();
- watchEffect(() => {
- resizeRef.value = containerRef.value;
- });
- const display = useDisplay();
- const sizeMap = new Map();
- let sizes = Array.from({
- length: items.value.length
- });
- const visibleItems = computed(() => {
- const height = (!contentRect.value || containerRef.value === document.documentElement ? display.height.value : contentRect.value.height) - (offset?.value ?? 0);
- return Math.ceil(height / itemHeight.value * 1.7 + 1);
- });
- function handleItemResize(index, height) {
- itemHeight.value = Math.max(itemHeight.value, height);
- sizes[index] = height;
- sizeMap.set(items.value[index], height);
- }
- function calculateOffset(index) {
- return sizes.slice(0, index).reduce((acc, val) => acc + (val || itemHeight.value), 0);
- }
- function calculateMidPointIndex(scrollTop) {
- const end = items.value.length;
- let middle = 0;
- let middleOffset = 0;
- while (middleOffset < scrollTop && middle < end) {
- middleOffset += sizes[middle++] || itemHeight.value;
- }
- return middle - 1;
- }
- let lastScrollTop = 0;
- function handleScroll() {
- if (!containerRef.value || !contentRect.value) return;
- const height = contentRect.value.height - 56;
- const scrollTop = containerRef.value.scrollTop;
- const direction = scrollTop < lastScrollTop ? UP : DOWN;
- const midPointIndex = calculateMidPointIndex(scrollTop + height / 2);
- const buffer = Math.round(visibleItems.value / 3);
- const firstIndex = midPointIndex - buffer;
- const lastIndex = first.value + buffer * 2 - 1;
- if (direction === UP && midPointIndex <= lastIndex) {
- first.value = clamp(firstIndex, 0, items.value.length);
- } else if (direction === DOWN && midPointIndex >= lastIndex) {
- first.value = clamp(firstIndex, 0, items.value.length - visibleItems.value);
- }
- lastScrollTop = scrollTop;
- }
- function scrollToIndex(index) {
- if (!containerRef.value) return;
- const offset = calculateOffset(index);
- containerRef.value.scrollTop = offset;
- }
- const last = computed(() => Math.min(items.value.length, first.value + visibleItems.value));
- const computedItems = computed(() => {
- return items.value.slice(first.value, last.value).map((item, index) => ({
- raw: item,
- index: index + first.value
- }));
- });
- const paddingTop = computed(() => calculateOffset(first.value));
- const paddingBottom = computed(() => calculateOffset(items.value.length) - calculateOffset(last.value));
- watch(() => items.value.length, () => {
- sizes = createRange(items.value.length).map(() => itemHeight.value);
- sizeMap.forEach((height, item) => {
- const index = items.value.indexOf(item);
- if (index === -1) {
- sizeMap.delete(item);
- } else {
- sizes[index] = height;
- }
- });
- });
- return {
- containerRef,
- computedItems,
- itemHeight,
- paddingTop,
- paddingBottom,
- scrollToIndex,
- handleScroll,
- handleItemResize
- };
- }
- //# sourceMappingURL=virtual.mjs.map
|