@blossom-carousel/svelte
Version:
A native-scroll-first carousel component for Svelte.
62 lines (61 loc) • 2.48 kB
JavaScript
import { getSnapPositions, isRtl, logicalScrollLeft, } from "./snap.js";
import { getSnapCache } from "./cache.js";
/** ::scroll-button() pages ~85% of the scrollport. */
const PAGE_RATIO = 0.85;
const EPSILON = 1;
/**
* Mirrors `::scroll-button():disabled`: a direction is scrollable only when the
* scroller hasn't reached the corresponding edge. `scrollLeft` is normalized via
* `Math.abs` so this also holds in RTL where it is negative.
*/
export function canScroll(scroller, dir) {
const max = Math.max(scroller.scrollWidth - scroller.clientWidth, 0);
const position = Math.abs(scroller.scrollLeft);
return dir === "prev"
? position > EPSILON
: position < max - EPSILON;
}
function adjacentSnap(points, current, dir) {
// Skip cached points whose element was detached (a slide removed before the
// cache refresh runs), mirroring the staleness guard in the goto command —
// `scrollIntoView` on a detached element would silently no-op.
if (dir === "next") {
for (let i = 0; i < points.length; i++) {
if (points[i].x > current + EPSILON && points[i].el.isConnected)
return points[i];
}
}
else {
for (let i = points.length - 1; i >= 0; i--) {
if (points[i].x < current - EPSILON && points[i].el.isConnected)
return points[i];
}
}
return null;
}
/**
* Mirrors `::scroll-button()` activation: advance to the next/previous snap
* point when scroll-snap is in use (brought into view with its own inline
* alignment), otherwise page by ~85% of the scrollport.
*/
export function pageScroll(scroller, dir) {
// Prefer the host-maintained cache; fall back to a live read for plain usage.
const cache = getSnapCache(scroller);
const points = cache?.pages ?? getSnapPositions(scroller);
const rtl = cache?.rtl ?? isRtl(scroller);
if (points.length) {
const target = adjacentSnap(points, logicalScrollLeft(scroller, rtl), dir);
if (!target)
return;
target.el.scrollIntoView({
block: "nearest",
inline: target.align,
behavior: "smooth",
});
return;
}
const delta = scroller.clientWidth * PAGE_RATIO * (dir === "next" ? 1 : -1);
// `scrollBy` takes a physical delta; flip it so "next" advances towards the
// inline-end in RTL too.
scroller.scrollBy({ left: rtl ? -delta : delta, behavior: "smooth" });
}