1(function () { 2 'use strict'; 3 let sliderCounter = 0; 4 5 function Slider(el) { 6 // Identify slider and slides container 7 const slider = el; 8 const slides = el.querySelector('ul'); 9 if (slides === null) 10 return; 11 if (!slides.classList.contains('slides')) 12 slides.classList.add('slides'); 13 14 // Language strings 15 const lang = (document.documentElement.lang || 'de').toLowerCase(); 16 const isDE = lang.startsWith('de'); 17 const strings = { 18 next: isDE ? 'Nächste Folie' : 'Next', 19 prev: isDE ? 'Vorherige Folie' : 'Previous', 20 item: isDE ? 'Folie' : 'Item', 21 slide: isDE ? 'Folie' : 'Slide' 22 }; 23 24 // Options from data-options (no autoplay) 25 const opts = (el.dataset.options || '').toLowerCase(); 26 const hasOpt = (name) => opts.indexOf(name) !== -1; 27 28 const thumbs = hasOpt('thumbs') || hasOpt('thumbnails'); 29 const bubbles = hasOpt('bubbles') || hasOpt('dots'); 30 const arrows = hasOpt('arrows') || hasOpt('directionnav'); 31 const direction = el.classList.contains('vertical') ? 'vertical' : 'horizontal'; 32 const fade = el.classList.contains('fade'); 33 34 // Items 35 const items = Array.from(slides.children); 36 if (!items.length) 37 return; 38 39 // Give this carousel an id if none present (for generating slide ids) 40 if (!slider.id) { 41 sliderCounter += 1; 42 slider.id = 'carousel-' + sliderCounter; 43 } 44 45 // Ensure region semantics recommended (do not overwrite if author set) 46 if (!slider.hasAttribute('role')) 47 slider.setAttribute('role', 'region'); 48 49 // Live region for announcements 50 const liveRegion = document.createElement('div'); 51 liveRegion.setAttribute('aria-live', 'polite'); 52 liveRegion.setAttribute('aria-atomic', 'true'); 53 liveRegion.className = 'sr-only'; 54 slider.appendChild(liveRegion); 55 56 // State 57 let active = 0; 58 let controlsUL = null; 59 let controlTabs = []; // buttons with role=tab 60 61 // CORRECTION: Also find elements that we previously set to -1 62 function findFocusables(root) { 63 return root.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]'); 64 } 65 66 function ensureSlideIdentityAndRoles() { 67 const total = items.length; 68 items.forEach((slide, i) => { 69 setAttributeIfNotExists(slide,'role','group'); 70 setAttributeIfNotExists(slide,'aria-roledescription',isDE ? 'Folie' : 'Slide'); 71 setAttributeIfNotExists(slide,'tabindex','-1'); 72 73 if (!slide.id) 74 slide.id = slider.id + '-slide-' + (i + 1); 75 76 const title = getSlideTitle(slide); 77 let label =''; 78 if (title) { 79 if (isDE) { 80 label = `${title} (${i + 1} von ${total})`; 81 } else { 82 label = `${title} (${i + 1} of ${total})`; 83 } 84 } else { 85 if (isDE) { 86 label = `Folie ${i + 1} von ${total}`; 87 } 88 else { 89 label = `Slide ${i + 1} of ${total}`; 90 } 91 } 92 slide.setAttribute('aria-label', label); 93 if (slide.dataset.backgroundimage) { 94 slide.style.backgroundImage = "url('" + slide.dataset.backgroundimage + "')"; 95 } 96 }); 97 } 98 99 function setAttributeIfNotExists(el,attribute,value){ 100 if(!el.hasAttribute(attribute)) { 101 el.setAttribute(attribute,value); 102 } 103 } 104 105 function getSlideTitle(slide) { 106 let title = slide.dataset.title || ''; 107 if (!title) { 108 const heading = slide.querySelector('h1, h2, h3, h4, h5, h6'); 109 if (heading) 110 title = heading.textContent.trim(); 111 } 112 if (!title) { 113 const img = slide.querySelector('img[alt]'); 114 if (img && img.alt) 115 title = img.alt.trim(); 116 } 117 return title; 118 } 119 120 function announce(index) { 121 const total = items.length; 122 const slide = items[index]; 123 const title = getSlideTitle(slide); 124 let txt = ''; 125 if(title){ 126 if(isDE){ 127 txt = `${strings.slide} ${index + 1} von ${total}: ${title}`; 128 } 129 else { 130 txt = `${strings.slide} ${index + 1} of ${total}: ${title}`; 131 } 132 } 133 else { 134 if(isDE){ 135 txt = `${strings.slide} ${index + 1} von ${total}`; 136 } 137 else { 138 txt = `${strings.slide} ${index + 1} of ${total}`; 139 } 140 } 141 liveRegion.textContent = txt; 142 } 143 144 function setSlideState(activeIndex) {
145 items.forEach((slide, i) => { 146 const isActive = i === activeIndex; 147 slide.classList.toggle('active', isActive); 148 slide.setAttribute('aria-hidden', isActive ? 'false' : 'true'); 149 150 // Manage inert and focusability inside slides 151 if (!isActive) { 152 slide.setAttribute('inert', ''); 153 findFocusables(slide).forEach(el => { 154 if (el.hasAttribute('tabindex') && !el.hasAttribute('data-prev-tabindex')) { 155 el.setAttribute('data-prev-tabindex', el.getAttribute('tabindex')); 156 } 157 el.setAttribute('tabindex', '-1'); 158 }); 159 } else { 160 slide.removeAttribute('inert'); 161 findFocusables(slide).forEach(el => { 162 if (el.hasAttribute('data-prev-tabindex')) { 163 el.setAttribute('tabindex', el.getAttribute('data-prev-tabindex')); 164 el.removeAttribute('data-prev-tabindex'); 165 } else if (el.getAttribute('tabindex') === '-1') { 166 el.removeAttribute('tabindex'); 167 } 168 }); 169 170 const total = items.length; 171 const title = getSlideTitle(slide); 172 let label = ''; 173 if(title){ 174 if(isDE){ 175 label = `${title} (${activeIndex + 1} von ${total})`; 176 } 177 else { 178 label = `${title} (${activeIndex + 1} of ${total})`; 179 } 180 } 181 else { 182 if(isDE){ 183 label = `${strings.slide} ${activeIndex + 1} von ${total}`; 184 } 185 else { 186 label = `${strings.slide} ${activeIndex + 1} of ${total}`; 187 } 188 } 189 slide.setAttribute('aria-label', label); 190 } 191 }); 192 } 193 194 // CORRECTION: Search extended to include .inner-wrapper 195 function focusFirstInSlide(slide) { 196 const prefer = slide.querySelector('[data-focus], .news-link, #newslink, .inner-wrapper'); 197 const first = prefer || slide.querySelector('a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]'); 198 if (first) 199 first.focus(); 200 else 201 slide.focus(); 202 } 203 204 function setActiveIndicator(index) { 205 controlTabs.forEach((btn, i) => { 206 const isActive = i === index; 207 btn.classList.toggle('active', isActive); 208 btn.setAttribute('aria-selected', isActive ? 'true' : 'false'); 209 btn.setAttribute('tabindex', isActive ? '0' : '-1'); 210 }); 211 } 212 213 function scrollToSlide(index, smooth = true) { 214 const target = items[index]; 215 if (!target) 216 return; 217 if (fade) 218 return; 219 const behavior = smooth && 'scrollBehavior' in document.documentElement.style ? 'smooth' : 'auto'; 220 if (direction === 'vertical') { 221 slides.scrollTo({top: target.offsetTop, behavior}); 222 } else { 223 slides.scrollTo({left: target.offsetLeft, behavior}); 224 } 225 } 226 227 // CORRECTION: Timing delay for focus, if fade is active 228 function activate(index, options = {smooth: true, focusContent: false}) { 229 if (index >= items.length) 230 index = 0; 231 if (index < 0) 232 index = items.length - 1; 233 active = index; 234 scrollToSlide(active, options.smooth); 235 setSlideState(active); 236 if (controlTabs.length) 237 setActiveIndicator(active); 238 announce(active); 239 240 if (options.focusContent) { 241 // Add a small delay for fade transitions to ensure element is ready to be focused 242 setTimeout(() => { 243 focusFirstInSlide(items[active]); 244 }, 50); 245 } 246 } 247 248 function buildControls() { 249 if (!(bubbles || thumbs)) return; 250 controlsUL = document.createElement('ul'); 251 controlsUL.classList.add('controls', thumbs ? 'thumbs' : 'bubbles'); 252 controlsUL.setAttribute('role', 'tablist'); 253 slider.appendChild(controlsUL); 254 255 items.forEach((slide, i) => { 256 if (!slide.id) 257 slide.id = slider.id + '-slide-' + (i + 1); 258 const li = document.createElement('li'); 259 260 const btn = document.createElement('button'); 261 btn.type = 'button'; 262 btn.className = 'control'; 263 btn.setAttribute('role', 'tab'); 264 btn.setAttribute('aria-controls', slide.id); 265 btn.setAttribute('aria-selected', i === active ? 'true' : 'false'); 266 btn.setAttribute('tabindex', i === active ? '0' : '-1'); 267 btn.setAttribute('aria-label', (isDE ? strings.item : strings.item) + ' ' + (i + 1)); 268 269 if (thumbs) { 270 const img = slide.querySelector('img'); 271 const file = slide.dataset.backgroundimage || (img && (img.src || img.dataset.src)); 272 if (file) 273 btn.style.backgroundImage = "url('" + file + "')"; 274 } else { 275 btn.textContent = String(i + 1); 276 } 277 btn.addEventListener('click', () => { 278 activate(i, {smooth: true, focusContent: true}); 279 }); 280 281 btn.addEventListener('keydown', (e) => { 282 if (e.altKey || e.ctrlKey || e.metaKey) return; 283 let currentButtonIndex = i; 284 let nextFocusIndex = null; 285 let shouldActivateSlide = false;
286 287 if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { 288 e.preventDefault(); 289 nextFocusIndex = (currentButtonIndex - 1 + items.length) % items.length; 290 } 291 else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { 292 e.preventDefault(); 293 nextFocusIndex = (currentButtonIndex + 1) % items.length; 294 } 295 else if (e.key === 'Home') { 296 e.preventDefault(); 297 nextFocusIndex = 0; 298 } 299 else if (e.key === 'End') { 300 e.preventDefault(); 301 nextFocusIndex = items.length - 1; 302 } 303 else if (e.key === 'Enter' || e.key === ' ') { 304 e.preventDefault(); 305 shouldActivateSlide = true; 306 } 307 308 if (nextFocusIndex !== null) { 309 controlTabs.forEach((tabBtn, index) => { 310 const isNewlyFocused = index === nextFocusIndex; 311 tabBtn.setAttribute('aria-selected', isNewlyFocused ? 'true' : 'false'); 312 tabBtn.setAttribute('tabindex', isNewlyFocused ? '0' : '-1'); 313 }); 314 controlTabs[nextFocusIndex].focus(); 315 } 316 if (shouldActivateSlide) { 317 activate(currentButtonIndex, {smooth: true, focusContent: true}); 318 } 319 }); 320 li.appendChild(btn); 321 controlsUL.appendChild(li); 322 }); 323 controlTabs = Array.from(controlsUL.querySelectorAll('button[role="tab"]')); 324 setActiveIndicator(active); 325 } 326 327 function buildArrows() { 328 if (!arrows) 329 return; 330 331 const btnPrev = document.createElement('button'); 332 btnPrev.type = 'button'; 333 btnPrev.className = 'prev'; 334 btnPrev.setAttribute('aria-label', strings.prev); 335 btnPrev.addEventListener('click', () => activate(active - 1, {smooth: true, focusContent: false})); 336 slider.appendChild(btnPrev); 337 const btnNext = document.createElement('button'); 338 btnNext.type = 'button'; 339 btnNext.className = 'next'; 340 btnNext.setAttribute('aria-label', strings.next); 341 btnNext.addEventListener('click', () => activate(active + 1, {smooth: true, focusContent: false})); 342 slider.appendChild(btnNext); 343 } 344 345 function setupGlobalKeydown() { 346 slider.addEventListener('keydown', (e) => { 347 if (e.altKey || e.ctrlKey || e.metaKey) 348 return; 349 const target = e.target; 350 const role = target?.getAttribute('role'); 351 if (role === 'tab' || ['INPUT', 'TEXTAREA', 'SELECT', 'A'].includes(target?.tagName?.toUpperCase())) { 352 return; 353 } 354 if (e.key === 'ArrowLeft') { 355 e.preventDefault(); 356 activate(active - 1, {smooth: true, focusContent: false}); 357 } else if (e.key === 'ArrowRight') { 358 e.preventDefault(); 359 activate(active + 1, {smooth: true, focusContent: false}); 360 } else if (e.key === 'Home') { 361 e.preventDefault(); 362 activate(0, {smooth: true, focusContent: false}); 363 } else if (e.key === 'End') { 364 e.preventDefault(); 365 activate(items.length - 1, {smooth: true, focusContent: false}); 366 } 367 }); 368 } 369 370 function setupScrollHandlers() { 371 let scrollTimer = null; 372 function startScroll() {
373 items.forEach(slide => slide.classList.remove('active')); 374 if (controlTabs.length) 375 controlTabs.forEach(btn => btn.classList.remove('active')); 376 } 377 function stopScroll() { 378 clearTimeout(scrollTimer); 379 scrollTimer = null; 380 const center = { 381 y: slides.scrollTop + slides.clientHeight / 2, 382 x: slides.scrollLeft + slides.clientWidth / 2 383 }; 384 for (let i = 0; i < items.length; i++) { 385 const it = items[i]; 386 const top = it.offsetTop; 387 const left = it.offsetLeft; 388 const bottom = top + it.offsetHeight; 389 const right = left + it.offsetWidth; 390 if (top < center.y && bottom > center.y && left < center.x && right > center.x) { 391 if (active !== i) { 392 active = i; 393 setSlideState(active); 394 if (controlTabs.length) 395 setActiveIndicator(active); 396 announce(active); 397 } else { 398 it.classList.add('active'); 399 if (controlTabs.length) setActiveIndicator(active); 400 } 401 break; 402 } 403 } 404 } 405 function onScroll() { 406 if (scrollTimer) 407 clearTimeout(scrollTimer); 408 else 409 startScroll(); 410 scrollTimer = setTimeout(stopScroll, 100); 411 } 412 slides.addEventListener('scroll', onScroll); 413 414 // Mouse drag logic 415 let posInitial = 0, posLast = 0, diff = 0; 416 const threshold = 100; 417 function dragStart(event) { 418 diff = 0; 419 event.preventDefault(); 420 if (direction === 'horizontal') { 421 posInitial = slides.scrollLeft; 422 posLast = event.clientX; 423 } else { 424 posInitial = slides.scrollTop; 425 posLast = event.clientY; 426 } 427 slides.style.scrollSnapType = 'none'; 428 document.addEventListener('mouseup', dragEnd); 429 slides.addEventListener('mousemove', dragAction); 430 } 431 function dragAction(event) { 432 if (direction === 'horizontal') { 433 diff += (posLast - event.clientX); 434 posLast = event.clientX; 435 slides.scroll(posInitial + diff, 0); 436 } else { 437 diff += (posLast - event.clientY); 438 posLast = event.clientY; 439 slides.scroll(0, posInitial + diff); 440 } 441 } 442 function dragEnd() { 443 slides.style.scrollSnapType = ''; 444 if (diff < -threshold) { 445 activate(active - 1, {smooth: true, focusContent: false}); 446 } else if (diff > threshold) { 447 activate(active + 1, {smooth: true, focusContent: false}); 448 } else { 449 if (direction === 'horizontal') 450 slides.scroll(posInitial, 0); 451 else 452 slides.scroll(0, posInitial); 453 setSlideState(active); 454 } 455 document.removeEventListener('mouseup', dragEnd); 456 slides.removeEventListener('mousemove', dragAction); 457 } 458 slides.addEventListener('mousedown', dragStart, true); 459 } 460 461 ensureSlideIdentityAndRoles(); 462 buildControls(); 463 buildArrows(); 464 setupGlobalKeydown(); 465 setupScrollHandlers(); 466 467 if (fade) 468 items[active].classList.add('active'); 469 setSlideState(active); 470 announce(active); 471 } 472 473 document.querySelectorAll('div[data-type="slider"]').forEach(el => new Slider(el)); 474 window.Slider = Slider; 475})();
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.