PageSourceSearch

https://www.boost.org/static/js/carousel.js

js boost.org collected 2026-10-01 08:58:50 UTC 7,035 bytes, 219 lines download raw bytes

1
2(function () {
3
4  const CAROUSEL_STEP_PX_FALLBACK = 320;
5  const SCROLL_RESET_EPSILON = 2;
6  const SCROLL_END_EPSILON = 1;
7  const DEFAULT_AUTOPLAY_MS = 4000;
8  const CAROUSEL_ITEM_SELECTOR = '[data-carousel-item]';
9
10  function setDisabled(btn, disabled) {
11    if (!btn) return;
12    if (btn.tagName === 'BUTTON') {
13      btn.disabled = disabled;
14    } else {
15      if (disabled) {
16        btn.setAttribute('aria-disabled', 'true');
17      } else {
18        btn.removeAttribute('aria-disabled');
19      }
20    }
21  }
22
23  function isDisabled(btn) {
24    if (!btn) return true;
25    if (btn.tagName === 'BUTTON') return btn.disabled;
26    return btn.getAttribute('aria-disabled') === 'true';
27  }
28
29  function updateArrowState(track, prevBtns, nextBtns) {
30    if (!track) return;
31    const maxScroll = track.scrollWidth - track.clientWidth;
32    const noScroll = maxScroll <= 0;
33    const atStart = !noScroll && track.scrollLeft <= 0;
34    const atEnd = !noScroll && track.scrollLeft >= maxScroll - SCROLL_END_EPSILON;
35    prevBtns.forEach(function (b) { setDisabled(b, noScroll || atStart); });
36    nextBtns.forEach(function (b) { setDisabled(b, noScroll || atEnd); });
37  }
38
39  function getStepPx(track) {
40    const first = track.querySelector(CAROUSEL_ITEM_SELECTOR);
41    if (first) {
42      const itemStyle = window.getComputedStyle(first);
43      const width = first.offsetWidth;
44      const marginRight = parseFloat(itemStyle.marginRight) || 0;
45      const trackStyle = window.getComputedStyle(track);
46      const gap = parseFloat(trackStyle.gap) || 0;
47      return width + marginRight + gap;
48    }
49    return CAROUSEL_STEP_PX_FALLBACK;
50  }
51
52  function scrollCarousel(track, direction, smooth) {
53    if (!track) return;
54    const step = getStepPx(track);
55    const maxScroll = track.scrollWidth - track.clientWidth;
56    const current = track.scrollLeft;
57    const next = direction === 'next'
58      ? Math.min(current + step, maxScroll)
59      : Math.max(current - step, 0);
60    track.scrollTo({ left: next, behavior: smooth ? 'smooth' : 'auto' });
61  }
62
63  function setupAutoplay(root, onTick, delayMs) {
64    const ms = delayMs || DEFAULT_AUTOPLAY_MS;
65    let timer = null;
66    const start = function () {
67      timer = setInterval(onTick, ms);
68    };
69    const stop = function () {
70      if (timer) clearInterval(timer);
71      timer = null;
72    };
73    root.addEventListener('mouseenter', stop);
74    root.addEventListener('mouseleave', start);
75    root.addEventListener('focusin', stop);
76    root.addEventListener('focusout', function (e) {
77      if (!root.contains(e.relatedTarget)) start();
78    });
79    start();
80  }
81
82  function setupInfiniteCarousel(root, track) {
83    const items = track.querySelectorAll(CAROUSEL_ITEM_SELECTOR);
84    if (items.length === 0) return;
85
86    let setCount = 1;
87    const appendCloneSet = () => {
88      const list = track.querySelectorAll(CAROUSEL_ITEM_SELECTOR);
89      const n = list.length / setCount;
90      for (let i = 0; i < n; i++) {
91        track.appendChild(list[i].cloneNode(true));
92      }
93      setCount++;
94    };
95
96    appendCloneSet();
97    while (track.scrollWidth / setCount < track.clientWidth) {
98      appendCloneSet();
99    }
100
101    const setWidth = track.scrollWidth / setCount;
102    const step = getStepPx(track);
103    let scrollResetInProgress = false;
104
105    track.addEventListener('scroll', function () {
106      if (scrollResetInProgress) return;
107      const left = track.scrollLeft;
108      if (left >= setWidth - SCROLL_RESET_EPSILON) {
109        scrollResetInProgress = true;
110        track.scrollLeft = left - setWidth;
111        requestAnimationFrame(function () {
112          scrollResetInProgress = false;
113        });
114      }
115    });
116
117    const controls = document.getElementById(root.id + '-control
117s');
118    const prevBtns = controls ? controls.querySelectorAll('[data-carousel-prev]') : [];
119    const nextBtns = controls ? controls.querySelectorAll('[data-carousel-next]') : [];
120
121    prevBtns.forEach(function (prevBtn) {
122      prevBtn.addEventListener('click', function (e) {
123        e.preventDefault();
124        if (track.scrollLeft < step) {
125          scrollResetInProgress = true;
126          track.scrollLeft = setWidth;
127          requestAnimationFrame(function () {
128            scrollResetInProgress = false;
129            scrollCarousel(track, 'prev', true);
130          });
131        } else {
132          scrollCarousel(track, 'prev', true);
133        }
134      });
135    });
136
137    nextBtns.forEach(function (nextBtn) {
138      nextBtn.addEventListener('click', function (e) {
139        e.preventDefault();
140        scrollCarousel(track, 'next', true);
141      });
142    });
143
144    const autoplayDelay = root.getAttribute('data-carousel-autoplay');
145    if (autoplayDelay) {
146      setupAutoplay(root, () => {
147        if (track.scrollLeft >= setWidth - SCROLL_RESET_EPSILON) {
148          scrollResetInProgress = true;
149          track.scrollLeft = track.scrollLeft - setWidth;
150          requestAnimationFrame(function () {
151            scrollResetInProgress = false;
152          });
153        }
154        scrollCarousel(track, 'next', true);
155      }, parseInt(autoplayDelay, 10));
156    }
157  }
158
159  function initCarousel(root) {
160    if (!root || !root.id) return;
161    const track = root.querySelector('[data-carousel-track]');
162    const controls = document.getElementById(root.id + '-control
162s');
163    if (!track || !controls) return;
164
165    if (root.hasAttribute('data-carousel-infinite')) {
166      setupInfiniteCarousel(root, track);
167      return;
168    }
169
170    const prevBtns = controls.querySelectorAll('[data-carousel-prev]');
171    const nextBtns = controls.querySelectorAll('[data-carousel-next]');
172    prevBtns.forEach(function (prevBtn) {
173      prevBtn.addEventListener('click', function (e) {
174        e.preventDefault();
175        if (isDisabled(prevBtn)) return;
176        scrollCarousel(track, 'prev', true);
177      });
178    });
179    nextBtns.forEach(function (nextBtn) {
180      nextBtn.addEventListener('click', function (e) {
181        e.preventDefault();
182        if (isDisabled(nextBtn)) return;
183        scrollCarousel(track, 'next', true);
184      });
185    });
186
187    const syncArrows = function () {
188      updateArrowState(track, prevBtns, nextBtns);
189    };
190    requestAnimationFrame(syncArrows);
191    track.addEventListener('scroll', syncArrows, { passive: true });
192    if (typeof ResizeObserver !== 'undefined') {
193      const ro = new ResizeObserver(syncArrows);
194      ro.observe(track);
195    }
196
197    const autoplayDelay = root.getAttribute('data-carousel-autoplay');
198    if (autoplayDelay) {
199      setupAutoplay(root,  () => {
200        const maxScroll = track.scrollWidth - track.clientWidth;
201        if (track.scrollLeft >= maxScroll - 1) {
202          track.scrollTo({ left: 0, behavior: 'smooth' });
203        } else {
204          scrollCarousel(track, 'next', true);
205        }
206      }, parseInt(autoplayDelay, 10));
207    }
208  }
209
210  function init() {
211    document.querySelectorAll('[data-carousel]').forEach(initCarousel);
212  }
213
214  if (document.readyState === 'loading') {
215    document.addEventListener('DOMContentLoaded', init);
216  } else {
217    init();
218  }
219})();

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.