PageSourceSearch

https://www.rovalra.com/static/js/components/carousel.js

js rovalra.com collected 2026-10-01 14:02:17 UTC 2,873 bytes, 83 lines download raw bytes

1import { escapeHtml, prefersReducedMotion } from '../core/dom.js';
2
3const INTERVAL = 4500;
4
5export function mountCarousel(root, items) {
6    if (!root || items.length < 2) return;
7
8    const track = root.querySelector('[data-carousel-track]');
9    const existing = new Set([...track.querySelectorAll('img')].map((img) => img.getAttribute('src')));
10
11    items.filter((item) => !existing.has(item.image)).forEach((item) => {
12        track.insertAdjacentHTML('beforeend', `
13            <div class="carousel__slide">
14                <img src="${item.image}" alt="${escapeHtml(item.alt)}" loading="lazy" decoding="async">
15            </div>`);
16    });
17
18    const slides = [...track.children];
19    const dots = root.querySelector('[data-carousel-dots]');
20    dots.innerHTML = slides.map((slide, i) =>
21        `<button class="carousel__dot" type="button" aria-label="Show image ${i + 1} of ${slides.length}"></button>`,
22    ).join('');
23    root.style.setProperty('--carousel-interval', `${INTERVAL}ms`);
24
25    const autoplay = !prefersReducedMotion();
26    let index = 0;
27    let timer = null;
28    let remaining = INTERVAL;
29    let startedAt = 0;
30    let paused = false;
31
32    const show = (next) => {
33        index = (next + slides.length) % slides.length;
34        track.style.transform = `translateX(-${index * 100}%)`;
35        slides.forEach((slide, i) => slide.setAttribute('aria-hidden', String(i !== index)));
36        [...dots.children].forEach((dot, i) => {
37            dot.setAttribute('aria-current', String(i === index));
38            dot.classList.remove('is-progress');
39        });
40
41        if (!autoplay) return;
42        const active = dots.children[index];
43        void active.offsetWidth;
44        active.classList.add('is-progress');
45        remaining = INTERVAL;
46        schedule();
47    };
48
49    function schedule() {
50        clearTimeout(timer);
51        if (paused) return;
52        startedAt = performance.now();
53        timer = setTimeout(() => show(index + 1), remaining);
54    }
55
56    const pause = () => {
57        if (!autoplay || paused) return;
58        paused = true;
59        clearTimeout(timer);
60        remaining = Math.max(0, remaining - (performance.now() - startedAt));
61        root.classList.add('is-paused');
62    };
63
64    const resume = () => {
65        if (!autoplay || !paused) return;
66        paused = false;
67        root.classList.remove('is-paused');
68        schedule();
69    };
70
71    dots.addEventListener('click', (event) => {
72        const dot = event.target.closest('.carousel__dot');
73        if (dot) show([...dots.children].indexOf(dot));
74    });
75
76    root.addEventListener('mouseenter', pause);
77    root.addEventListener('mouseleave', resume);
78    root.addEventListener('focusin', pause);
79    root.addEventListener('focusout', resume);
80    document.addEventListener('visibilitychange', () => (document.hidden ? pause() : resume()));
81
82    show(0);
83}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.