Add incremental loading to manager and client lists
This commit is contained in:
93
app/composables/useIncrementalList.ts
Normal file
93
app/composables/useIncrementalList.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, unref, watch, type ComputedRef, type Ref } from 'vue';
|
||||
|
||||
type ReactiveValue<T> = Ref<T> | ComputedRef<T>;
|
||||
|
||||
type UseIncrementalListOptions = {
|
||||
pageSize?: number;
|
||||
enabled?: ReactiveValue<boolean>;
|
||||
resetKeys?: Array<ReactiveValue<unknown> | unknown>;
|
||||
};
|
||||
|
||||
export function useIncrementalList<T>(
|
||||
items: ReactiveValue<T[]>,
|
||||
options: UseIncrementalListOptions = {},
|
||||
) {
|
||||
const pageSize = options.pageSize ?? 24;
|
||||
const enabled = computed(() => unref(options.enabled ?? true));
|
||||
const visibleCount = ref(pageSize);
|
||||
const loadMoreSentinel = ref<HTMLElement | null>(null);
|
||||
let observer: IntersectionObserver | null = null;
|
||||
|
||||
const visibleItems = computed(() => (
|
||||
enabled.value
|
||||
? items.value.slice(0, visibleCount.value)
|
||||
: items.value
|
||||
));
|
||||
|
||||
const canLoadMore = computed(() => (
|
||||
enabled.value && visibleCount.value < items.value.length
|
||||
));
|
||||
|
||||
const remainingCount = computed(() => Math.max(items.value.length - visibleCount.value, 0));
|
||||
|
||||
function loadMore() {
|
||||
visibleCount.value = Math.min(visibleCount.value + pageSize, items.value.length);
|
||||
}
|
||||
|
||||
function resetVisibleCount() {
|
||||
visibleCount.value = pageSize;
|
||||
}
|
||||
|
||||
function disconnectObserver() {
|
||||
observer?.disconnect();
|
||||
observer = null;
|
||||
}
|
||||
|
||||
function connectObserver() {
|
||||
disconnectObserver();
|
||||
|
||||
if (!canLoadMore.value || !loadMoreSentinel.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
if (!entries.some((entry) => entry.isIntersecting)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadMore();
|
||||
}, {
|
||||
rootMargin: '240px 0px',
|
||||
});
|
||||
|
||||
observer.observe(loadMoreSentinel.value);
|
||||
}
|
||||
|
||||
const resetSignature = computed(() => JSON.stringify([
|
||||
enabled.value,
|
||||
items.value.length,
|
||||
...((options.resetKeys ?? []).map((entry) => unref(entry as ReactiveValue<unknown>))),
|
||||
]));
|
||||
|
||||
watch(resetSignature, resetVisibleCount, { immediate: true });
|
||||
watch(canLoadMore, () => {
|
||||
if (!canLoadMore.value) {
|
||||
disconnectObserver();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
watch([loadMoreSentinel, canLoadMore], connectObserver, { immediate: true });
|
||||
});
|
||||
|
||||
onBeforeUnmount(disconnectObserver);
|
||||
|
||||
return {
|
||||
canLoadMore,
|
||||
loadMore,
|
||||
loadMoreSentinel,
|
||||
remainingCount,
|
||||
visibleCount,
|
||||
visibleItems,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user