1import {getHorizontalItemStep} from '../core/measure.js'; 2 3const DESKTOP_BREAKPOINT = 1024; 4const VISIBLE_CARD_COUNT = 3; 5const ANIMATION_DURATION = 300; 6 7const createArticleCarousel = (container) => { 8 const track = container.querySelector('.article-carousel__track'); 9 const prevButton = container.querySelector('[data-article-carousel-prev]'); 10 const nextButton = container.querySelector('[data-article-carousel-next]'); 11 12 if (!(track instanceof HTMLElement)) { 13 return {syncToViewport() {}}; 14 } 15 16 const originalCards = Array.from(track.children); 17 const canLoop = originalCards.length > VISIBLE_CARD_COUNT; 18 const pendingDirections = []; 19 let isEnabled = false; 20 let activeMove = null; 21 22 for (const button of [prevButton, nextButton]) { 23 if (button) { 24 button.hidden = !canLoop; 25 } 26 } 27 28 const startMove = (direction) => { 29 if (!isEnabled) { 30 return; 31 } 32 if (activeMove) { 33 pendingDirections.push(direction); 34 return; 35 } 36 37 const step = getHorizontalItemStep(track, '.article-carousel__card'); 38 if (step <= 0) { 39 return; 40 } 41 42 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 43 if (direction > 0) { 44 track.append(track.firstElementChild); 45 } else { 46 track.prepend(track.lastElementChild); 47 } 48 return; 49 } 50 51 if (direction < 0) { 52 track.prepend(track.lastElementChild); 53 track.style.transform = `translateX(-${step}px)`; 54 } 55 56 const animation = track.animate([ 57 {transform: `translateX(${direction > 0 ? 0 : -step}px)`}, 58 {transform: `translateX(${direction > 0 ? -step : 0}px)`} 59 ], { 60 duration: ANIMATION_DURATION, 61 easing: 'ease', 62 fill: 'forwards' 63 }); 64 65 activeMove = {animation, direction}; 66 animation.onfinish = finishMove; 67 }; 68 69 const finishMove = () => { 70 if (!activeMove) { 71 return; 72 } 73 74 const {animation, direction} = activeMove; 75 activeMove = null; 76 animation.onfinish = null; 77 animation.cancel(); 78 79 if (direction > 0) { 80 track.append(track.firstElementChild); 81 } 82 track.style.transform = ''; 83 84 if (isEnabled && pendingDirections.length > 0) { 85 startMove(pendingDirections.shift()); 86 } 87 }; 88 89 prevButton?.addEventListener('click', () => startMove(-1)); 90 nextButton?.addEventListener('click', () => startMove(1)); 91 92 return { 93 syncToViewport() { 94 const shouldEnable = canLoop && window.innerWidth >= DESKTOP_BREAKPOINT; 95 96 if (!shouldEnable) { 97 isEnabled = false; 98 pendingDirections.length = 0; 99 finishMove(); 100 track.replaceChildren(...originalCards); 101 return; 102 } 103 104 isEnabled = true; 105 finishMove(); 106 } 107 }; 108}; 109 110export function initArticleCarouselNavigation() { 111 const containers = document.querySelectorAll('[data-article-carousel]'); 112 if (containers.length === 0) { 113 return; 114 } 115 116 const instances = Array.from(containers, createArticleCarousel); 117 const refresh = () => instances.forEach((instance) => instance.syncToViewport()); 118 refresh(); 119 window.addEventListener('resize', refresh); 120}
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.