Teren-app/resources/js/Composables/useInfiniteList.js
2026-04-16 23:11:49 +02:00

98 lines
2.7 KiB
JavaScript

import { ref, onMounted, onUnmounted } from "vue";
import { router } from "@inertiajs/vue3";
/**
* Composable for infinite scroll with Inertia v2.
*
* @param {Function} getProp - () => the current paginator object from Inertia props
* @param {string} propName - the prop key name to reload
* @param {string} pageParam - query string parameter name for page number
* @param {Function} getRouteUrl - () => current URL to reload
*/
export function useInfiniteList(getProp, propName, pageParam, getRouteUrl) {
const items = ref([]);
const currentPage = ref(1);
const lastPage = ref(1);
const isLoadingMore = ref(false);
const sentinelRef = ref(null);
let observer = null;
function syncFromProp() {
const prop = getProp();
if (!prop) return;
lastPage.value = prop.last_page ?? 1;
}
function appendFromProp() {
const prop = getProp();
if (!prop?.data) return;
// append only new items (avoid duplicates by id)
const existingIds = new Set(items.value.map((i) => i.id));
const newItems = prop.data.filter((i) => !existingIds.has(i.id));
items.value.push(...newItems);
}
function reset(initialProp) {
items.value = initialProp?.data ?? [];
currentPage.value = initialProp?.current_page ?? 1;
lastPage.value = initialProp?.last_page ?? 1;
}
function loadMore() {
if (isLoadingMore.value) return;
if (currentPage.value >= lastPage.value) return;
const nextPage = currentPage.value + 1;
isLoadingMore.value = true;
const params = new URLSearchParams(window.location.search);
params.set(pageParam, nextPage);
router.reload({
url: `${window.location.pathname}?${params.toString()}`,
only: [propName],
preserveScroll: true,
preserveState: true,
onSuccess: () => {
appendFromProp();
currentPage.value = nextPage;
isLoadingMore.value = false;
},
onError: () => {
isLoadingMore.value = false;
},
});
}
onMounted(() => {
observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
loadMore();
}
},
{ rootMargin: "200px" }
);
if (sentinelRef.value) {
observer.observe(sentinelRef.value);
}
});
onUnmounted(() => {
observer?.disconnect();
});
return {
items,
currentPage,
lastPage,
isLoadingMore,
sentinelRef,
reset,
syncFromProp,
appendFromProp,
loadMore,
};
}