151 lines
4.2 KiB
JavaScript
151 lines
4.2 KiB
JavaScript
/**
|
|
* TT-Core Infinite Scroll Composable (Vue 3)
|
|
* Provides infinite scrolling functionality
|
|
*/
|
|
|
|
/**
|
|
* Create an infinite scroll composable
|
|
* @param {Ref} items - Reactive reference to items array
|
|
* @param {Object} options - Scroll options
|
|
* @returns {Object} - Composable with visible items and methods
|
|
*/
|
|
export function useInfiniteScroll(items, options = {}) {
|
|
const { ref, computed, onMounted, onBeforeUnmount, onUpdated } = Vue;
|
|
|
|
const visibleCount = ref(options.initialCount || 50);
|
|
const incrementBy = options.incrementBy || 50;
|
|
const sentinelRef = ref(null);
|
|
let scrollObserver = null;
|
|
|
|
const visibleItems = computed(() => {
|
|
return items.value.slice(0, visibleCount.value);
|
|
});
|
|
|
|
const hasMore = computed(() => {
|
|
return visibleCount.value < items.value.length;
|
|
});
|
|
|
|
const loadMore = () => {
|
|
if (hasMore.value) {
|
|
visibleCount.value += incrementBy;
|
|
}
|
|
};
|
|
|
|
const resetVisibleCount = () => {
|
|
visibleCount.value = options.initialCount || 50;
|
|
};
|
|
|
|
const setupScrollObserver = () => {
|
|
scrollObserver = new IntersectionObserver(
|
|
([entry]) => {
|
|
if (entry && entry.isIntersecting) {
|
|
loadMore();
|
|
}
|
|
},
|
|
{
|
|
root: options.root || null,
|
|
threshold: 0.1
|
|
}
|
|
);
|
|
|
|
if (sentinelRef.value) {
|
|
scrollObserver.observe(sentinelRef.value);
|
|
}
|
|
};
|
|
|
|
onMounted(() => {
|
|
setupScrollObserver();
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
if (scrollObserver) {
|
|
scrollObserver.disconnect();
|
|
scrollObserver = null;
|
|
}
|
|
});
|
|
|
|
onUpdated(() => {
|
|
// Reconnect observer when DOM updates
|
|
if (scrollObserver && sentinelRef.value) {
|
|
scrollObserver.disconnect();
|
|
scrollObserver.observe(sentinelRef.value);
|
|
}
|
|
});
|
|
|
|
return {
|
|
sentinelRef,
|
|
visibleItems,
|
|
visibleCount,
|
|
hasMore,
|
|
loadMore,
|
|
resetVisibleCount
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an infinite scroll mixin (backward compatibility)
|
|
* @param {Object} options - Scroll options
|
|
* @returns {Object} - Vue mixin
|
|
*/
|
|
export function createInfiniteScrollMixin(options = {}) {
|
|
return {
|
|
data() {
|
|
return {
|
|
visibleCount: options.initialCount || 50,
|
|
incrementBy: options.incrementBy || 50,
|
|
scrollObserver: null
|
|
};
|
|
},
|
|
computed: {
|
|
visibleItems() {
|
|
const items = this[options.itemsKey || 'items'] || [];
|
|
return items.slice(0, this.visibleCount);
|
|
}
|
|
},
|
|
mounted() {
|
|
this.setupScrollObserver();
|
|
},
|
|
beforeUnmount() {
|
|
if (this.scrollObserver) {
|
|
this.scrollObserver.disconnect();
|
|
this.scrollObserver = null;
|
|
}
|
|
},
|
|
updated() {
|
|
// Reconnect observer when DOM updates
|
|
if (this.scrollObserver && this.$refs.sentinel) {
|
|
this.scrollObserver.disconnect();
|
|
this.scrollObserver.observe(this.$refs.sentinel);
|
|
}
|
|
},
|
|
methods: {
|
|
setupScrollObserver() {
|
|
this.scrollObserver = new IntersectionObserver(
|
|
([entry]) => {
|
|
if (entry && entry.isIntersecting) {
|
|
this.loadMore();
|
|
}
|
|
},
|
|
{
|
|
root: this.$refs.tableWrap || null,
|
|
threshold: 0.1
|
|
}
|
|
);
|
|
|
|
if (this.$refs.sentinel) {
|
|
this.scrollObserver.observe(this.$refs.sentinel);
|
|
}
|
|
},
|
|
loadMore() {
|
|
const items = this[options.itemsKey || 'items'] || [];
|
|
if (this.visibleCount < items.length) {
|
|
this.visibleCount += this.incrementBy;
|
|
}
|
|
},
|
|
resetVisibleCount() {
|
|
this.visibleCount = options.initialCount || 50;
|
|
}
|
|
}
|
|
};
|
|
}
|