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.