// @ts-check /** * Infinite-scroll for cursor-paginated views. * * Each section that paginates (Files, Favorites, Recent, Trash, * SharedWithMe, MyShares) injects a "Load more" button wrapped in a * `
` below `.files-container`. The wrapper toggles `.hidden` to * reflect whether a next page exists. * * This utility wires an [`IntersectionObserver`] onto the same wrapper * so it auto-fires the load-more action when the user scrolls near it. * The visible button stays as a fallback (accessibility, browsers * without IntersectionObserver support, the user's reflex from * before infinite-scroll landed). * * Wire it once per wrapper at creation time. The observer is idempotent * across re-renders because the wrapper itself is re-used; appended * items push it down the document and trigger fresh intersection * events when the user scrolls again. * * Re-entrancy: every existing `_loadPage` implementation has its own * in-flight guard (a `_loading` flag returning early on re-entry), so * this utility doesn't add another. If a future view doesn't, add a * `_loading` field on the view object before adopting infinite-scroll. */ /** * Auto-fire `onLoadMore()` when `wrapper` scrolls within * `rootMargin` of the viewport AND is visible (the `.hidden` class is * the cursor-exhausted signal — see `_setLoadMoreVisible` in each view). * * @param {HTMLElement} wrapper The `.swm-load-more-wrapper` element. * @param {() => void} onLoadMore Called on each intersection-enter. * @param {{rootMargin?: string}} [opts] * @returns {() => void} Teardown — disconnects the observer. */ export function attachInfiniteScroll(wrapper, onLoadMore, opts = {}) { if (!('IntersectionObserver' in window)) { // Older browser — degrade to the button-only experience. return () => {}; } const observer = new IntersectionObserver( (entries) => { for (const entry of entries) { if (!entry.isIntersecting) continue; // No next page → wrapper hidden → skip. if (wrapper.classList.contains('hidden')) continue; onLoadMore(); } }, // Trigger a bit before the wrapper actually enters the viewport // so the next page is on its way by the time the user reaches // the bottom — smoother than a perceptible "click… wait…" beat. { rootMargin: opts.rootMargin ?? '200px' } ); observer.observe(wrapper); return () => observer.disconnect(); }