1/** 2 * DACOR Shop - Carousel Component 3 * @package DACOR_Shop 4 */ 5 6(function($) { 7 'use strict'; 8 9 const DacorCarousel = { 10 currentIndex: 0, 11 itemsPerView: 3, 12 totalItems: 0, 13 enableSwipe: true, 14 $carousel: null, 15 $track: null, 16 $items: null, 17 $prevBtn: null, 18 $nextBtn: null, 19 $indicators: null, 20 touchStartX: 0, 21 touchEndX: 0, 22 isDragging: false, 23 startOffset: 0, 24 25 init: function(options) { 26 const width = $(window).width(); 27 if (width < 768) { 28 this.itemsPerView = 1; 29 } else if (width < 1024) { 30 this.itemsPerView = 2; 31 } else { 32 this.itemsPerView = options.itemsPerView || 3; 33 } 34 35 this.enableSwipe = options.enableSwipe !== false; 36 this.onSelect = options.onSelect || function() {}; 37 38 this.$carousel = $('#dacor-tarif-carousel'); 39 if (this.$carousel.length === 0) return; 40 41 this.$track = this.$carousel.find('.carousel-track'); 42 this.$items = this.$track.find('.dacor-tarif-card, .dacor-modem-card'); 43 this.$prevBtn = this.$carousel.find('.carousel-prev'); 44 this.$nextBtn = this.$carousel.find('.carousel-next'); 45 this.$indicators = $('.carousel-indicators .indicator'); 46 47 this.totalItems = this.$items.length; 48 this.currentIndex = 0; 49 50 this.setupLayout(); 51 this.setupControls(); 52 if (this.enableSwipe) { 53 this.setupTouchEvents(); 54 } 55 56 this.updateView(); 57 }, 58 59 setupLayout: function() {}, 60 61 setupControls: function() { 62 const self = this; 63 64 this.$prevBtn.off('click').on('click', function() { 65 self.prev(); 66 }); 67 68 this.$nextBtn.off('click').on('click', function() { 69 self.next(); 70 }); 71 72 this.$indicators.off('click').on('click', function() { 73 const index = $(this).data('index'); 74 self.goTo(index); 75 }); 76 77 // Only handle arrow keys when carousel or its children have focus 78 self.$carousel.off('keydown.carousel').on('keydown.carousel', function(e) { 79 if (e.key === 'ArrowLeft') { 80 e.preventDefault(); 81 self.prev(); 82 } else if (e.key === 'ArrowRight') { 83 e.preventDefault(); 84 self.next(); 85 } 86 }); 87 }, 88 89 setupTouchEvents: function() { 90 const self = this; 91 const viewport = this.$carousel.find('.carousel-viewport')[0]; 92 93 viewport.addEventListener('mousedown', function(e) { 94 // Don't intercept clicks on buttons or links 95 if (e.target.closest('button, a, input, label')) return; 96 self.handleDragStart(e.clientX); 97 }); 98 99 viewport.addEventListener('mousemove', function(e) { 100 if (self.isDragging) { 101 self.handleDragMove(e.clientX); 102 } 103 }); 104 105 viewport.addEventListener('mouseup', function(e) { 106 if (self.isDragging) { 107 self.handleDragEnd(e.clientX); 108 } 109 }); 110 111 viewport.addEventListener('mouseleave', function() { 112 if (self.isDragging) { 113 self.isDragging = false; 114 self.$track.css('transition', 'transform 0.4s cubic-bezier(0.4, 0, 0.2, 1)'); 115 } 116 }); 117 118 viewport.addEventListener('touchstart', function(e) { 119 self.handleDragStart(e.touches[0].clientX); 120 }, { passive: true }); 121 122 viewport.addEventListener('touchmove', function(e) { 123 if (self.isDragging) { 124 self.handleDragMove(e.touches[0].clientX); 125 } 126 }, { passive: true });
127 128 viewport.addEventListener('touchend', function(e) { 129 if (self.isDragging) { 130 self.handleDragEnd(e.changedTouches[0].clientX); 131 } 132 }); 133 }, 134 135 handleDragStart: function(clientX) { 136 this.isDragging = true; 137 this.touchStartX = clientX; 138 this.startOffset = this.getCurrentOffset(); 139 this.$track.css('transition', 'none'); 140 this.$carousel.addClass('dragging'); 141 }, 142 143 handleDragMove: function(clientX) { 144 if (!this.isDragging) return; 145 146 const diff = clientX - this.touchStartX; 147 const $firstCard = this.$items.first(); 148 const cardWidth = $firstCard.length ? $firstCard.outerWidth() : 327; 149 const gap = $(window).width() < 768 ? 40 : 59; 150 const itemWidth = cardWidth + gap; 151 const maxOffset = 0; 152 const maxIndex = Math.max(0, this.totalItems - this.itemsPerView); 153 const minOffset = -(maxIndex * itemWidth); 154 155 let newOffset = this.startOffset + diff; 156 157 // Edge resistance 158 if (newOffset > maxOffset) { 159 newOffset = maxOffset + (newOffset - maxOffset) * 0.3; 160 } else if (newOffset < minOffset) { 161 newOffset = minOffset + (newOffset - minOffset) * 0.3; 162 } 163 164 this.$track.css('transform', `translateX(${newOffset}px)`); 165 }, 166 167 handleDragEnd: function(clientX) { 168 this.isDragging = false; 169 this.touchEndX = clientX; 170 this.$track.css('transition', 'transform 0.4s cubic-bezier(0.4, 0, 0.2, 1)'); 171 this.$carousel.removeClass('dragging'); 172 173 const diff = this.touchEndX - this.touchStartX; 174 const threshold = 50; 175 176 if (Math.abs(diff) > threshold) { 177 if (diff > 0) { 178 this.prev(); 179 } else { 180 this.next(); 181 } 182 } else { 183 this.updateView(); 184 } 185 }, 186 187 getCurrentOffset: function() { 188 const transform = this.$track.css('transform'); 189 if (transform === 'none') return 0; 190 191 const matrix = transform.match(/matrix\(([^)]+)\)/); 192 if (!matrix) return 0; 193 194 const values = matrix[1].split(','); 195 return parseFloat(values[4]); 196 }, 197 198 next: function() { 199 const maxIndex = this.totalItems - this.itemsPerView; 200 if (this.currentIndex < maxIndex) { 201 this.currentIndex++; 202 this.updateView(); 203 } 204 }, 205 206 prev: function() { 207 if (this.currentIndex > 0) { 208 this.currentIndex--; 209 this.updateView(); 210 } 211 }, 212 213 goTo: function(index) { 214 const maxIndex = this.totalItems - this.itemsPerView; 215 if (index >= 0) { 216 this.currentIndex = Math.min(index, maxIndex); 217 this.updateView(); 218 } 219 }, 220 221 updateView: function() { 222 const $firstCard = this.$items.first(); 223 const cardWidth = $firstCard.length ? $firstCard.outerWidth() : 327; 224 const gap = $(window).width() < 768 ? 40 : 59; 225 const offsetPx = -(this.currentIndex * (cardWidth + gap)); 226 227 this.$track.css('transform', `translateX(${offsetPx}px)`); 228 this.updateButtons(); 229 this.updateIndicators(); 230 this.updateARIA(); 231 }, 232 233 updateButtons: function() { 234 const maxIndex = this.totalItems - this.itemsPerView; 235 236 if (this.currentIndex === 0) { 237 this.$prevBtn.prop('disabled', true).attr('aria-disabled', 'true'); 238 } else { 239 this.$prevBtn.prop('disabled', false).attr('aria-disabled', 'false'); 240 } 241 242 if (this.currentIndex >= maxIndex) { 243 this.$nextBtn.prop('disabled', true).attr('aria-disabled', 'true'); 244 } else { 245 this.$nextBtn.prop('disabled', false).attr('aria-disabled', 'false'); 246 } 247 }, 248 249 updateIndicators: function() { 250 this.$indicators.removeClass('active'); 251 252 for (let i = 0;
252 i < this.itemsPerView; i++) { 253 const index = this.currentIndex + i; 254 if (index < this.totalItems) { 255 this.$indicators.eq(index).addClass('active'); 256 } 257 } 258 }, 259 260 updateARIA: function() { 261 const currentItem = this.currentIndex + 1; 262 const lastVisible = Math.min(this.currentIndex + this.itemsPerView, this.totalItems); 263 this.$carousel.find('.carousel-viewport').attr('aria-label', `Zeige Tarif ${currentItem} bis ${lastVisible} von ${this.totalItems}`); 264 }, 265 266 handleResize: function() { 267 const self = this; 268 let resizeTimer; 269 const initialWidth = $(window).width(); 270 if (initialWidth < 768) { 271 self.itemsPerView = 1; 272 } else if (initialWidth < 1024) { 273 self.itemsPerView = 2; 274 } else { 275 self.itemsPerView = 3; 276 } 277 self.updateView(); 278 279 $(window).on('resize', function() { 280 clearTimeout(resizeTimer); 281 resizeTimer = setTimeout(function() { 282 const width = $(window).width(); 283 284 if (width < 768) { 285 self.itemsPerView = 1; 286 } else if (width < 1024) { 287 self.itemsPerView = 2; 288 } else { 289 self.itemsPerView = 3; 290 } 291 292 self.setupLayout(); 293 self.updateView(); 294 }, 250); 295 }); 296 }, 297 298 destroy: function() { 299 if (this.$carousel) { 300 this.$carousel.off('keydown.carousel'); 301 } 302 $(window).off('resize'); 303 304 if (this.$carousel) { 305 this.$carousel.find('*').off(); 306 } 307 } 308 }; 309 310 window.DacorCarousel = DacorCarousel; 311 312 $(document).ready(function() { 313 if ($('#dacor-tarif-carousel').length > 0) { 314 DacorCarousel.init({ 315 itemsPerView: 3, 316 enableSwipe: true 317 }); 318 DacorCarousel.handleResize(); 319 } 320 }); 321 322})(jQuery);
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.