1// Calcula los bullets visibles: siempre 3, agrupados de 3 en 3, aun si el total no es multiplo de 3. 2function updateCarouselBullets(swiper, carouselElement) { 3 const bullets = carouselElement.querySelectorAll('.swiper-pagination-bullet'); 4 if (bullets.length === 0) return; 5 6 const total = bullets.length; 7 const realIndex = swiper.realIndex; 8 const totalReal = swiper.slides.length - (swiper.loopedSlides * 2); 9 10 // Cuenta las vueltas (loop fwd y back) para mantener el grupo de 3 correcto. 11 if (!carouselElement._swiperProgressiveData) { 12 carouselElement._swiperProgressiveData = { lastRealIndex: realIndex, totalLoops: 0 }; 13 } 14 const data = carouselElement._swiperProgressiveData; 15 if (data.lastRealIndex === totalReal - 1 && realIndex === 0) data.totalLoops++; 16 if (data.lastRealIndex === 0 && realIndex === totalReal - 1) data.totalLoops--; 17 data.lastRealIndex = realIndex; 18 19 let loops = data.totalLoops; 20 if (loops < 0) loops += total + 2; // ajuste para loop inverso 21 const counter = realIndex + loops * total; 22 23 // Inicio del grupo de 3 que contiene al slide actual (con wrap modulo total). 24 const start = ((realIndex - (counter % 3)) % total + total) % total; 25 const order = { [start]: '-3', [(start + 1) % total]: '-2', [(start + 2) % total]: '-1' }; 26 27 bullets.forEach((bullet, index) => { 28 const pos = order[index]; 29 bullet.style.display = pos ? 'inline-block' : 'none'; 30 bullet.style.order = pos || 'initial'; 31 }); 32} 33 34// Indica si un carousel es visible (no esta dentro de un tab-pane oculto). 35// Swiper no puede medir slides con display:none, por lo que hay que evitar 36// inicializarlo mientras este oculto. 37function isCarouselVisible(carousel) { 38 if (carousel.offsetParent === null) return false; 39 const pane = carousel.closest('.tab-pane'); 40 if (pane && !pane.classList.contains('active')) return false; 41 return true; 42} 43 44function initCarousel(carousel) { 45 if (carousel._swiperInit) return; 46 carousel._swiperInit = true; 47 48 const swiperEl = carousel.querySelector('.swiper'); 49 const slidesCount = swiperEl.querySelectorAll('.swiper-slide').length; 50 51 const defaults = { 52 slidesPerView: 1, 53 pagination: false, 54 loop: true 55 }; 56 57 let customOptions = {}; 58 if (carousel.dataset.carouselOptions) { 59 try { 60 customOptions = JSON.parse(carousel.dataset.carouselOptions); 61 } catch (e) { 62 console.warn('Error de data-carousel-options', e); 63 } 64 } 65 66 const config = { ...defaults, ...customOptions }; 67 68 const navNext = carousel.querySelector('.swiper-button-next'); 69 const navPrev = carousel.querySelector('.swiper-button-prev'); 70 71 const swiperOptions = { 72 ...config, 73 pagination: config.pagination 74 ? { 75 el: swiperEl.querySelector('.swiper-pagination'), 76 clickable: true, 77 ...(typeof config.pagination === 'object' ? config.pagination : {}) 78 } 79 : false, 80 navigation: config.navigation 81 ? { 82 ...(typeof config.navigation === 'object' ? config.navigation : {}), 83 nextEl: navNext, 84 prevEl: navPrev, 85 } 86 : false 87 }; 88 89 if (customOptions.autoplay === true && slidesCount > 1) { 90 swiperOptions.autoplay = { delay: 5000, disableOnInteraction: false }; 91 } else if (typeof customOptions.autoplay === 'object') { 92 swiperOptions.autoplay = { delay: 5000, ...customOptions.autoplay }; 93 } else if (customOptions.autoplay === false) { 94 swiperOptions.autoplay = false; 95 } 96 97 const swiper = new Swiper(swiperEl, swiperOptions); 98 carousel._swiper = swiper; 99 carousel._swiperEl = swiperEl; 100 carousel._hasBullets = !!(config.pagination && slidesCount > 3); 101 102 // Inicializar bullets dinámicos, 103 if (carousel._hasBullets) { 104 updateCarouselBullets(swiper, swiperEl); 105 106 swiper.on('slideChange', () => { 107 updateCarouselBullets(swiper, swiperEl); 108 }); 109 } 110} 111 112function initCarousels() { 113 // Solo se inicializan los carouseles visibles en este momento. 114 document.querySelectorAll('[data-carousel]').forEach(carousel => { 115 if (isCarouselVisible(carousel)) { 116 initCarousel(carousel); 117 } 118 }); 119 120 // Los carouseles dentro de tab-panes ocultos se inicializan recien cuando
121 // su pestaña se muestra (shown.bs.tab), momento en el que ya tienen 122 // dimensiones reales y Swiper puede calcular slides/loop/autoplay/bullets. 123 document.querySelectorAll('[data-bs-toggle="tab"]').forEach(triggerEl => { 124 triggerEl.addEventListener('shown.bs.tab', event => { 125 const target = event.target.getAttribute('data-bs-target'); 126 const pane = target ? document.querySelector(target) : null; 127 if (!pane) return; 128 129 pane.querySelectorAll('[data-carousel]').forEach(carousel => { 130 if (!isCarouselVisible(carousel)) return; 131 if (carousel._swiper) { 132 if (carousel._swiperEl) carousel._swiperEl._swiperProgressiveData = null; 133 carousel._swiper.destroy(true, true); 134 carousel._swiper = null; 135 carousel._swiperInit = false; 136 } 137 initCarousel(carousel); 138 if (carousel._hasBullets) { 139 updateCarouselBullets(carousel._swiper, carousel._swiperEl); 140 } 141 }); 142 }); 143 }); 144}
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.