1import {CLONE_CLASS, REAL_INDEX_ATTR, ACTIVE_CLASS, DOT_CLASS, DOT_SELECTED_CLASS} from './constants.js'; 2import {fadeTo, swapText} from './utils.js'; 3import {CarouselBase} from "./base-carousel.js"; 4 5let SwiperLib = null; 6 7/** Dynamically load Swiper ES module */ 8async function loadSwiper() { 9 if (!SwiperLib) { 10 const mod = await import('https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.mjs'); 11 SwiperLib = mod.Swiper; 12 } 13 return SwiperLib; 14} 15 16/** Swiper wrapper implementing CarouselBase interface */ 17export class Carousel extends CarouselBase { 18 constructor(element, opts = {}) { 19 super(element); 20 this.element = element; 21 this.opts = opts; 22 this.swiper = null; 23 this.slides = []; 24 this.fadeTo = fadeTo; 25 this.swapText = swapText; 26 } 27 28 async init() { 29 if (this.element.__initialized) return; 30 this.element.__initialized = true; 31 32 const Swiper = await loadSwiper(); 33 const container = this.element.querySelector('.carousel__viewport'); 34 if (!Swiper || !container) return; 35 36 this.slides = [...container.querySelectorAll('.carousel__slide')]; 37 38 const swiperOpts = { 39 centeredSlides: false, 40 //loop: this.opts.loop ?? true, 41 loop: true, 42 slidesPerView: 'auto', 43 //slidesPerView: 4.5, 44 //loopFillGroupWithBlank: true, 45 loopAddBlankSlides: true, 46 loopAdditionalSlides: 10, 47 slidesPerGroup: 1, 48 //loopedSlides: 5, 49 //speed: this.opts.speed ?? 500, 50 wrapperClass: 'carousel__container', 51 slideClass: 'carousel__slide', 52 navigation: { 53 nextEl: this.element.querySelector('.carousel__next'), 54 prevEl: this.element.querySelector('.carousel__prev'), 55 }, 56 pagination: { 57 el: this.element.querySelector('.carousel__dots'), 58 clickable: true, 59 bulletClass: 'carousel__dot', 60 bulletActiveClass: 'carousel__dot--selected', 61 renderBullet: (index, className) => 62 `<button class="${className}" type="button"><span>${index + 1}</span></button>`, 63 }, 64 on: { 65 slideChange: () => this._emit('select', this.getActiveIndex()), 66 update: () => this._emit(('reInit')), 67 slidesLengthChange: () => this._emit(('reInit')), 68 }, 69 ...this.opts, 70 }; 71 72 console.log(swiperOpts); 73 74 this.swiper = new Swiper(container, swiperOpts); 75 76 this.updateActiveClass(); 77 this.initDots(); 78 79 // updateSlideIndex(this); 80 // this.on('reInit', () => updateSlideIndex(this)) 81 } 82 83 updateActiveClass() { 84 const activeIndex = this.getActiveIndex(); 85 this.slides.forEach((slide, i) => 86 slide.classList.toggle(ACTIVE_CLASS, i === activeIndex) 87 ); 88 } 89 90 initDots() { 91 const dotsNode = this.element.querySelector('.carousel__dots'); 92 if (!dotsNode) return; 93 94 const updateDots = () => { 95 const activeIndex = this.getActiveIndex(); 96 const dots = [...dotsNode.querySelectorAll(`.${DOT_CLASS}`)]; 97 dots.forEach((dot, i) => 98 dot.classList.toggle(DOT_SELECTED_CLASS, i === activeIndex) 99 ); 100 }; 101 102 this.swiper.on('slideChange', updateDots); 103 updateDots(); 104 } 105 106 next() { 107 this.swiper?.slideNext(); 108 } 109 110 prev() { 111 this.swiper?.slidePrev(); 112 } 113 114// goTo(index) { 115// if (!this.swiper) return; 116// 117// const slidesCount = this.slides.length; 118// const current = this.getActiveIndex(); 119// console.log(current, index) 120// if (index === current) return; 121// 122// let targetIndex = index; 123// 124// if (this.swiper.params.loop) { 125// // Calculate forward-only distance in loop 126// const distance = (index - current + slidesCount) % slidesCount; 127// targetIndex = current + distance; 128// } 129// console.log(targetIndex) 130// // Use slideTo for smooth animation 131// this.swiper.slideTo(targetIndex, this.swiper.params.speed, true); 132// } 133 134 goTo(index) { 135 console.log(index); 136 137 //scrollLeftAlign(this.swiper, index); 138 //scrollLeftAlign(swiper, 0, 0); 139 140 this.swiper?.slideToLoop(index); 141 //this.swiper?.slideTo(index); 142 //this.goToDirectional(index, 'forward'); 143 } 144 145 /** 146 * Scroll Swiper so the given real slide index becomes the left-most visible slide. 147 * Works with loop:true (handles duplicated slides) and non-loop mode. 148 * 149 * @param {Swiper} swiper - Swiper instance 150 * @param {number} index - real slide index (0..N-1) 151 * @param {number}
151 [speed] - animation speed (ms). Default uses swiper.params.speed 152 */ 153 scrollLeftAlign(swiper, index, speed = swiper?.params?.speed ?? 300) { 154 if (!swiper) return; 155 156 // determine real slides count 157 // prefer swiper.slides.length - looped duplicates if loop is on 158 // but we compute from swiper.slides and duplicate marker class for robustness 159 const allSlides = Array.from(swiper.slides || []); 160 // Swiper duplicate slides commonly have class 'swiper-slide-duplicate' 161 const realSlides = allSlides.filter(s => !s.classList.contains('swiper-slide-duplicate')); 162 const realTotal = realSlides.length || allSlides.length; 163 164 if (realTotal === 0) return; 165 166 // normalize index into range [0, realTotal-1] 167 const normalizedIndex = ((index % realTotal) + realTotal) % realTotal; 168 169 // If not looped, just go to that index 170 if (!swiper.params.loop) { 171 swiper.slideTo(normalizedIndex, speed); 172 return; 173 } 174 175 // When loop: find the internal index that corresponds to the real slide. 176 // Swiper creates loopedSlides copies at start and end; swiper.loopedSlides is available. 177 const loopedSlides = swiper.loopedSlides || Math.ceil(swiper.params.slidesPerView) || 0; 178 179 // target internal index: offset by loopedSlides 180 const target = loopedSlides + normalizedIndex; 181 182 // Use slideTo to force the exact internal index (not slideToLoop which picks shortest route). 183 swiper.slideTo(target, speed); 184 } 185 186 goToDirectional(index, direction = 'forward', speed = this.swiper.params.speed) { 187 const swiper = this.swiper; 188 if (!swiper) return; 189 190 const total = swiper.slides.length; 191 const realTotal = swiper.slides.filter(s => !s.classList.contains('swiper-slide-duplicate')).length; 192 const current = swiper.realIndex; 193 194 let target; 195 196 if (direction === 'forward') { 197 target = index >= current 198 ? swiper.loopedSlides + index 199 : swiper.loopedSlides + index + realTotal; 200 } else { 201 target = index <= current 202 ? swiper.loopedSlides + index 203 : swiper.loopedSlides + index - realTotal; 204 } 205 206 swiper.slideTo(target, speed); 207 } 208 209 getActiveIndex() { 210 return this.swiper?.realIndex ?? this.swiper?.activeIndex ?? 0; 211 } 212 213 getSlides() { 214 return this.slides; 215 } 216 217 destroy() { 218 this.swiper?.destroy(true, true); 219 this.swiper = null; 220 } 221} 222 223function updateSlideIndex(carouselInstance) { 224 const slides = carouselInstance.getSlides(); 225 const active = carouselInstance.getActiveIndex(); 226 227 slides.forEach((slide, i) => { 228 slide.dataset.carouselSlideIndex = i; 229 }); 230} 231 232/** 233 * Scroll Swiper so the given real slide index becomes the left-most visible slide. 234 * Works with loop:true (handles duplicated slides) and non-loop mode. 235 * 236 * @param {Swiper} swiper - Swiper instance 237 * @param {number} index - real slide index (0..N-1) 238 * @param {number} [speed] - animation speed (ms). Default uses swiper.params.speed 239 */ 240function scrollLeftAlign(swiper, index, speed = swiper?.params?.speed ?? 300) { 241 if (!swiper) return; 242 243 // determine real slides count 244 // prefer swiper.slides.length - looped duplicates if loop is on 245 // but we compute from swiper.slides and duplicate marker class for robustness 246 const allSlides = Array.from(swiper.slides || []); 247 // Swiper duplicate slides commonly have class 'swiper-slide-duplicate' 248 const realSlides = allSlides.filter(s => !s.classList.contains('swiper-slide-duplicate')); 249 const realTotal = realSlides.length || allSlides.length; 250 251 if (realTotal === 0) return; 252 253 // normalize index into range [0, realTotal-1] 254 const normalizedIndex = ((index % realTotal) + realTotal) % realTotal; 255 256 // If not looped, just go to that index 257 if (!swiper.params.loop) { 258 swiper.slideTo(normalizedIndex, speed); 259 return; 260 } 261 262 // When loop: find the internal index that corresponds to the real slide. 263 // Swiper creates loopedSlides copies at start and end; swiper.loopedSlides is available. 264 const loopedSlides = swiper.loopedSlides || Math.ceil(swiper.params.slidesPerView) || 0; 265 266 // target internal index: offset by loopedSlides 267 const target = loopedSlides + normalizedIndex; 268 269 // Use slideTo to force the exact internal index (not slideToLoop which picks shortest route). 270 swiper.slideTo(target, speed); 271} 272 273function loadSwiperStylesheet() { 274 const href = 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css'; 275 // Check if already loaded 276 if (![...document.styleSheets].some(ss => ss.href === href)) { 277 const link = document.createElement('link'); 278 link.rel = 'stylesheet'; 279 link.href = href; 280 document.head.appendChild(link); 281 } 282}
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.