InfiniteScroll
Calls on_more() when the end of its list scrolls into view, with Loading more… and That's all states and a Load more button.
Demo
- Entry 1
- Entry 2
- Entry 3
- Entry 4
- Entry 5
- Entry 6
- Entry 7
- Entry 8
- Entry 9
- Entry 10
10 of 40 entries, asked for more 0 times.
Scroll the box to its end: ten more come each time, until forty. The Load more button does the same.
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/infinite_scroll.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/infinite_scroll.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/infinite_scroll.qwe
component InfiniteScrollDemoLive() { state items = range(1, 11) state asked = 0 let limit = 40 <div.demo-stack> <div id="infinite-scroll-demo-box" tabindex="0" aria-label="Entries" style="height: 240px; overflow: auto; border: 1px solid var(--ui-border); border-radius: var(--ui-radius)"> <InfiniteScroll scroller="infinite-scroll-demo-box" margin="40px" done={len(items) >= limit} on_more={() => { asked += 1 items = items + range(len(items) + 1, min([limit, len(items) + 10]) + 1) }}> <ul style="margin: 0; padding: 0; list-style: none"> {for i in items => <li key={i} style="padding: 10px 16px; border-bottom: 1px solid var(--ui-border)">Entry {i}</li>} </ul> </InfiniteScroll> </div> <p.demo-note>{len(items)} of {limit} entries, asked for more {asked} {asked == 1 ? "time" : "times"}.</p> </div>}view InfiniteScrollDemo() = <div.demo-stack> <InfiniteScrollDemoLive/> <p.demo-note>Scroll the box to its end: ten more come each time, until forty. The Load more button does the same.</p> </div>Source
ui/infinite_scroll.qwe
// InfiniteScroll: a list that asks for more as its end comes into view. It// wraps the items; under them is a sentinel that, when scrolled into view (in// the window, or in a scrolling box), calls on_more(). A "Load more" button is// there too, for the keyboard and for when the observer cannot run.//// <InfiniteScroll loading={busy} done={len(items) >= total} on_more={() => { busy = true; items += fetch_page(len(items)); busy = false }}>// {for it in items => <li key={it.id}>{it.name}</li>}// </InfiniteScroll>//// children: the items (any markup). on_more(): asked for the next page, with// nothing; it is not asked again while loading is true, nor once done is true.// loading: show "Loading more…" and hold off. done: there is no more; it says// done_text ("That's all") and stops asking. margin: how far before the end it// asks, a CSS length ("200px"). scroller: the id of the scrolling box the list// is in, when it is not the window. loading_text, done_text, button: the words// (button: "Load more"). label: what screen readers call the loading status.// Inside one of your components, since it needs the callback.import ui/theme.qwe use ui_clslet infinite_scroll_css = css`.ui-infscroll { display: grid; gap: 0; }.ui-infscroll-end { display: grid; justify-items: center; gap: 8px; padding: 14px; font: 13px/1.4 var(--ui-font); color: var(--ui-muted); text-align: center; }.ui-infscroll-spin { width: 18px; height: 18px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--ui-accent) 20%, transparent); border-top-color: var(--ui-accent); animation: ui-infscroll-turn .75s linear infinite; }.ui-infscroll-btn { height: 32px; padding: 0 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-accent); font: 500 13px/1 var(--ui-font); cursor: pointer; }.ui-infscroll-btn:hover { border-color: var(--ui-accent); background: var(--ui-accent-soft); }.ui-infscroll-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }@keyframes ui-infscroll-turn { to { transform: rotate(360deg); } }@media (prefers-reduced-motion: reduce) { .ui-infscroll-spin { animation: none; border-color: var(--ui-accent); } }`component InfiniteScroll(on_more = null, loading: bool = false, done: bool = false, margin: str = "200px", scroller: str? = null, loading_text: str = "Loading more…", done_text: str = "That's all", button: str = "Load more", label: str = "More items", class: str? = null, children) { style global infinite_scroll_css state watcher = null state sentinel = null let ask = () => { if !loading && !done && on_more != null { on_more() } } // Once loading ends (or done is lifted), look again: the end may still be in view. if watcher != null && sentinel != null && !loading && !done { watcher.unobserve(sentinel) watcher.observe(sentinel) } <div class={ui_cls(["ui-infscroll", class])}> {children} <div.ui-infscroll-end role="status" aria-label={label} @mount={(el) => { sentinel = el let root = scroller != null ? js("document.getElementById", scroller) : null let seen = (entries, obs) => { if any(entries, (en) => en.isIntersecting) { ask() // Still in view after the new items came? Then ask again. js("requestAnimationFrame", (_t = null) => { obs.unobserve(el); obs.observe(el) }) } } let w = js_new("IntersectionObserver", seen, { root, rootMargin: margin }) w.observe(el) watcher = w () => w.disconnect() }}> {done ? <span>{done_text}</span> : (loading ? [<span.ui-infscroll-spin aria-hidden="true"></span>, <span>{loading_text}</span>] : <button.ui-infscroll-btn type="button" @click={ask}>{button}</button>)} </div> </div>}Related
LazyLoadPuts its content on the page only once it is scrolled near, holding its height until then.6 views · 7 downloadsLoadMoreButtonThe button at the end of a list that says how many are left, spins while loading, and says when there are none.5 views · 7 downloadsSkeletonTableA table of grey bars in rows and columns, with a header row, while a table's data loads.4 views · 7 downloadsActivityFeedA stream of what people did, with their pictures, an icon for each kind of action, when, quotes and day headings.5 views · 7 downloadsChangelogReleases with version, date and label, their changes grouped by kind: Added, Changed, Fixed, Security and the rest.6 views · 7 downloadsDataListRecords as rows with a title, a line of facts, a status badge and actions as links or buttons.6 views · 8 downloads
Thread
Thread 0 posts
On InfiniteScroll: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.