feat: carousel core (count, navigation, scrollToIndex)
This commit is contained in:
+72
-1
@@ -1 +1,72 @@
|
||||
export {};
|
||||
export type CarouselEvent = 'change';
|
||||
|
||||
export type Carousel = {
|
||||
next(): void;
|
||||
prev(): void;
|
||||
scrollToIndex(i: number): void;
|
||||
readonly index: number;
|
||||
readonly count: number;
|
||||
on(evt: CarouselEvent, cb: (index: number) => void): () => void;
|
||||
destroy(): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap a scroll-snap track element and return a carousel controller.
|
||||
* @param track - the overflow-x scroll container whose children are slides
|
||||
*/
|
||||
export function createCarousel(track: HTMLElement): Carousel {
|
||||
const slides = () => Array.from(track.children) as HTMLElement[];
|
||||
let index = 0;
|
||||
const listeners = new Set<(i: number) => void>();
|
||||
|
||||
const clamp = (i: number) => Math.max(0, Math.min(i, slides().length - 1));
|
||||
|
||||
function scrollToIndex(i: number) {
|
||||
const target = slides()[clamp(i)];
|
||||
if (target) {
|
||||
track.scrollTo({ left: target.offsetLeft, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
// index tracking: the most-intersecting slide is current. Picking max ratio
|
||||
// (not the first intersecting) avoids a transient wrong index mid-swipe when
|
||||
// two slides cross the threshold in one callback.
|
||||
const io = new IntersectionObserver(
|
||||
(entries) => {
|
||||
let best: IntersectionObserverEntry | undefined;
|
||||
for (const e of entries) {
|
||||
if (e.isIntersecting && (!best || e.intersectionRatio > best.intersectionRatio)) {
|
||||
best = e;
|
||||
}
|
||||
}
|
||||
if (!best) return;
|
||||
const i = slides().indexOf(best.target as HTMLElement);
|
||||
if (i !== -1 && i !== index) {
|
||||
index = i;
|
||||
for (const cb of listeners) cb(index);
|
||||
}
|
||||
},
|
||||
{ root: track, threshold: 0.6 },
|
||||
);
|
||||
for (const s of slides()) io.observe(s);
|
||||
|
||||
return {
|
||||
next: () => scrollToIndex(index + 1),
|
||||
prev: () => scrollToIndex(index - 1),
|
||||
scrollToIndex,
|
||||
get index() {
|
||||
return index;
|
||||
},
|
||||
get count() {
|
||||
return slides().length;
|
||||
},
|
||||
on(_evt, cb) {
|
||||
listeners.add(cb);
|
||||
return () => listeners.delete(cb);
|
||||
},
|
||||
destroy() {
|
||||
io.disconnect();
|
||||
listeners.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user