fiddy/apps/web/features/entries/components/use-infinite-visible-count.ts

98 lines
3.2 KiB
TypeScript

"use client";
import { useEffect } from "react";
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
import { isEditableTarget } from "@/features/entries/components/entries-panel.utils";
type UseInfiniteVisibleCountParams = {
enabled: boolean;
hasMore: boolean;
totalCount: number;
pageSize: number;
sentinelRef: MutableRefObject<HTMLDivElement | null>;
setVisibleCount: Dispatch<SetStateAction<number>>;
};
export default function useInfiniteVisibleCount({
enabled,
hasMore,
totalCount,
pageSize,
sentinelRef,
setVisibleCount
}: UseInfiniteVisibleCountParams) {
useEffect(() => {
if (!enabled || !hasMore) return;
let touchY: number | null = null;
let lastLoadAt = 0;
let lastScrollY = window.scrollY;
function shouldLoadMore() {
const sentinel = sentinelRef.current;
if (!sentinel) return false;
const rect = sentinel.getBoundingClientRect();
return rect.top <= window.innerHeight + 48;
}
function tryLoadMore() {
if (!shouldLoadMore()) return;
const now = Date.now();
if (now - lastLoadAt < 150) return;
lastLoadAt = now;
setVisibleCount(prev => {
if (prev >= totalCount) return prev;
return Math.min(prev + pageSize, totalCount);
});
}
function onWheel(event: WheelEvent) {
if (event.deltaY <= 0) return;
tryLoadMore();
}
function onScroll() {
const nextY = window.scrollY;
if (nextY <= lastScrollY) {
lastScrollY = nextY;
return;
}
lastScrollY = nextY;
tryLoadMore();
}
function onKeyDown(event: KeyboardEvent) {
if (isEditableTarget(event.target)) return;
if (event.key === "ArrowDown" || event.key === "PageDown" || event.key === "End" || (event.key === " " && !event.shiftKey)) {
tryLoadMore();
}
}
function onTouchStart(event: TouchEvent) {
touchY = event.touches[0]?.clientY ?? null;
}
function onTouchMove(event: TouchEvent) {
const nextY = event.touches[0]?.clientY;
if (touchY == null || nextY == null) return;
const delta = touchY - nextY;
touchY = nextY;
if (delta > 10) tryLoadMore();
}
window.addEventListener("wheel", onWheel, { passive: true });
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("keydown", onKeyDown);
window.addEventListener("touchstart", onTouchStart, { passive: true });
window.addEventListener("touchmove", onTouchMove, { passive: true });
return () => {
window.removeEventListener("wheel", onWheel);
window.removeEventListener("scroll", onScroll);
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("touchstart", onTouchStart);
window.removeEventListener("touchmove", onTouchMove);
};
}, [enabled, hasMore, pageSize, sentinelRef, setVisibleCount, totalCount]);
}