export type PagerState = | { kind: "at-page"; page: number } | { kind: "loading"; page: number } | { kind: "failed"; page: number }; export interface OffsetPager { readonly state: PagerState; readonly items: T[]; readonly limit: number; readonly hasNext: boolean; select: (target: number) => Promise; } export { discoveredPageCount, pageCount } from "$lib/pagination"; // size the last page to its remainder, or a full page when total is unknown export const pageRows = (page: number, limit: number, total?: number): number => total === undefined ? limit : Math.min(limit, Math.max(1, total - (page - 1) * limit)); export const createOffsetPager = ( initial: T[], limit: number, load: (offset: number) => Promise ): OffsetPager => { let state = $state({ kind: "at-page", page: 1 }); let pages = $state>({ 1: initial }); // point to the target page immediately so the skeleton and footer follow the click const select = async (target: number) => { if (state.kind === "loading" || target < 1 || target === state.page) return; const currentPage = state.page; state = { kind: "loading", page: target }; try { if (!pages[target]) { pages = { ...pages, [target]: await load((target - 1) * limit) }; } state = { kind: "at-page", page: target }; } catch { state = { kind: "failed", page: currentPage }; } }; return { get state() { return state; }, get items() { return pages[state.page] ?? []; }, get limit() { return limit; }, get hasNext() { // assume an in-flight page is full so the footer keeps its shape return (pages[state.page]?.length ?? limit) >= limit; }, select }; };