1(function ($, Drupal, drupalSettings, once) { 2 /* 3 Carousel Prototype 4 Eric Eggert for W3C 5 */ 6 const myCarousel = (function () { 7 8 'use strict'; 9 10 // Initial variables 11 let carousel, slides, index, slidenav, settings, timer, setFocus, animationSuspended; 12 13 const visuallyHiddenClass = 'govuk-visually-hidden'; 14 const carouselClass = 'carousel'; 15 const carouselActiveClass = 'carousel--active'; 16 const currentClass = 'current'; 17 const slideClass = 'carousel__slide'; 18 const slideNavClass = 'slidenav'; 19 const withSlideNavClass = 'with-slidenav'; 20 const inTransitionClass = 'in-transition'; 21 22 const prevClass = 'prev'; 23 const nextClass = 'next'; 24 25 26 // Helper function: Iterates over an array of elements 27 function forEachElement(elements, fn) { 28 for (let i = 0; i < elements.length; i++) 29 fn(elements[i], i); 30 } 31 32 // Helper function: Remove Class 33 function addClass(el, className) { 34 if (!el) { 35 return; 36 } 37 if (el.classList) { 38 el.classList.add(...className.split(' ')); 39 } else if (!hasClass(el, className)) { 40 el.className += ` ${className}`; 41 } 42 } 43 44 // Helper function: Remove Class 45 function removeClass(el, className) { 46 if (!el) { 47 return; 48 } 49 if (el.classList) { 50 el.classList.remove(...className.split(' ')); 51 } else { 52 el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); 53 } 54 } 55 56 // Helper function: Test if element has a specific class 57 function hasClass(el, className) { 58 if (!el) { 59 return false; 60 } 61 if (el.classList) { 62 return el.classList.contains(className); 63 } else { 64 return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); 65 } 66 } 67 68 // Initialization for the carousel 69 // Argument: set = an object of settings 70 // Possible settings: 71 // id <string> ID of the carousel wrapper element (required). 72 // slidenav <bool> If true, a list of slides is shown. 73 // animate <bool> If true, the slides can be animated. 74 // startAnimated <bool> If true, the animation begins 75 // immediately. 76 // If false, the animation needs 77 // to be initiated by clicking 78 // the play button. 79 function init(set) { 80 81 // Make settings available to all functions 82 settings = set; 83 84 // Select the element and the individual slides 85 carousel = set.carousel || document.getElementById(settings.id); 86 slides = carousel.querySelectorAll('.blocks-gallery-item'); 87 88 addClass(carousel, `${carouselClass} ${carouselActiveClass}`); 89 90 // Create unordered list for controls, and attach click events to previous and next slide 91 const ctrls = document.createElement('ul'); 92 93 ctrls.className = 'controls'; 94 ctrls.innerHTML = '<li>' + 95 '<button type="button" class="btn-prev"><i class="far fa-arrow-alt-circle-left" title="' + Drupal.t('Previous Item') + '"></i></button>' + 96 '</li>' + 97 '<li>' + 98 '<button type="button" class="btn-next"><i class="far fa-arrow-alt-circle-right" title="' + Drupal.t('Next Item') + '"></i></button>' + 99 '</li>'; 100 101 ctrls.querySelector('.btn-prev') 102 .addEventListener('click', function () { 103 prevSlide(true); 104 }); 105 ctrls.querySelector('.btn-next') 106 .addEventListener('click', function () { 107 nextSlide(true); 108 }); 109 110 carousel.appendChild(ctrls); 111 112 // If the carousel is animated or a slide navigation is requested in the settings, another unordered list that contains those elements is added. (Note that you cannot supress the navigation when it is animated.) 113 if (settings.slidenav || settings.animate) { 114 slidenav = document.createElement('ul'); 115 116 slidenav.className = slideNavClass; 117 118 if (settings.animate) { 119 const li = document.createElement('li'); 120 121 if (settings.startAnimated) { 122 li.innerHTML = `<button data-action="stop"><span class="${visuallyHiddenClass}">${ Drupal.t('Stop Animation') } </span>ï¿</button>`; 123 } else { 124 li.innerHTML = `<button data-action="start"><span class="${visuallyHiddenClass}">${ + Drupal.t('Start Animation') } </span>â¶</button>`; 125 } 126 127 slidenav.appendChild(li); 128 } 129 130 if (settings.slidenav) {
131 forEachElement(slides, function (el, i) { 132 const li = document.createElement('li'); 133 const klass = (i === 0) ? ` class="${currentClass}"` : ''; 134 const kurrent = (i === 0) ? ` <span class="${visuallyHiddenClass}">${ Drupal.t('(Current Item)') }</span>` : ''; 135 136 li.innerHTML = `<button${klass} data-slide="${ i }"><span class="${visuallyHiddenClass}">${ Drupal.t('News') }</span> ${ (i + 1) + kurrent }</button>`; 137 slidenav.appendChild(li); 138 }); 139 } 140 141 slidenav.addEventListener('click', function (event) { 142 const button = event.target; 143 if (button.localName == 'button') { 144 if (button.getAttribute('data-slide')) { 145 stopAnimation(); 146 setSlides(button.getAttribute('data-slide'), true); 147 } else if (button.getAttribute('data-action') == 'stop') { 148 stopAnimation(); 149 } else if (button.getAttribute('data-action') == 'start') { 150 startAnimation(); 151 } 152 } 153 }, true); 154 155 addClass(carousel, `${carouselClass} ${carouselActiveClass} ${withSlideNavClass}`); 156 carousel.appendChild(slidenav); 157 158 const resolveSlideNavPadding = () => { 159 // Add the padding variable to be referenced. Ensuring it works with an obscenely large number of media items. 160 carousel.style.setProperty( 161 '--gallery-slidenav-padding', 162 `${slidenav.offsetHeight}px`, 163 ); 164 } 165 // Event listeners for when the scrollbar is likely to change. 166 window.addEventListener('resize', resolveSlideNavPadding); 167 window.addEventListener('orientationchange', resolveSlideNavPadding); 168 resolveSlideNavPadding(); 169 } 170 171 // Add a live region to announce the slide number when using the previous/next buttons 172 const liveregion = document.createElement('div'); 173 liveregion.setAttribute('aria-live', 'polite'); 174 liveregion.setAttribute('aria-atomic', 'true'); 175 liveregion.setAttribute('class', `liveregion ${visuallyHiddenClass}`); 176 carousel.appendChild(liveregion); 177 178 // After the slide transitioned, remove the in-transition class, if focus should be set, set the tabindex attribute to -1 and focus the slide. 179 slides[0].parentNode.addEventListener('transitionend', function (event) { 180 const slide = event.target; 181 removeClass(slide, inTransitionClass); 182 if (hasClass(slide, currentClass)) { 183 if (setFocus) { 184 slide.setAttribute('tabindex', '-1'); 185 slide.focus(); 186 setFocus = false; 187 } 188 } 189 }); 190 191 // When the mouse enters the carousel, suspend the animation. 192 carousel.addEventListener('mouseenter', suspendAnimation); 193 194 // When the mouse leaves the carousel, and the animation is suspended, start the animation. 195 carousel.addEventListener('mouseleave', function (event) { 196 if (animationSuspended) { 197 startAnimation(); 198 } 199 }); 200 201 // When the focus enters the carousel, suspend the animation 202 carousel.addEventListener('focusin', function (event) { 203 if (!hasClass(event.target, slideClass)) { 204 suspendAnimation(); 205 } 206 }); 207 208 // When the focus leaves the carousel, and the animation is suspended, start the animation 209 carousel.addEventListener('focusout', function (event) { 210 if (!hasClass(event.target, slideClass) && animationSuspended) { 211 startAnimation(); 212 } 213 }); 214 215 // Set the index (=current slide) to 0 â the first slide 216 index = 0; 217 setSlides(index); 218 219 // If the carousel is animated, advance to the 220 // next slide after 5s 221 if (settings.animate && settings.startAnimated) { 222 timer = setTimeout(nextSlide, settings.autoplaySpeed); 223 } 224 } 225 226 // Function to set a slide the current slide 227 function setSlides(new_current, setFocusHere, transition, announceItemHere) { 228 // Focus, transition and announce Item are optional parameters. 229 // focus denotes if the focus should be set after the 230 // carousel advanced to slide number new_current. 231 // transition denotes if the transition is going into the 232 // next or previous direction. 233 // If announceItem is set to true, the live regionâs text is changed (and announced) 234 // Here defaults are set: 235 236 setFocus = typeof setFocusHere !== 'undefined' ? setFocusHere : false;
237 transition = typeof transition !== 'undefined' ? transition : 'none'; 238 const announceItem = typeof announceItemHere !== 'undefined' ? announceItemHere : false; 239 240 new_current = parseFloat(new_current); 241 242 const length = slides.length; 243 let new_next = new_current + 1; 244 let new_prev = new_current - 1; 245 246 // If the next slide number is equal to the length, 247 // the next slide should be the first one of the slides. 248 // If the previous slide number is less than 0. 249 // the previous slide is the last of the slides. 250 if (new_next === length) { 251 new_next = 0; 252 } else if (new_prev < 0) { 253 new_prev = length - 1; 254 } 255 256 // Reset slide classes 257 for (let i = slides.length - 1; i >= 0; i--) { 258 removeClass(slides[i], `${prevClass} ${nextClass} ${currentClass} ${inTransitionClass}`); 259 addClass(slides[i], slideClass); 260 slides[i].setAttribute('aria-hidden', 'true'); 261 } 262 263 // Add classes to the previous, next and current slide 264 addClass(slides[new_next], `${nextClass} ${slideClass}` + ((transition == 'next') ? ` ${inTransitionClass}` : '')); 265 266 addClass(slides[new_prev], `${prevClass} ${slideClass}` + ((transition == 'prev') ? ` ${inTransitionClass}` : '')) 267 268 addClass(slides[new_current], `${currentClass} ${slideClass}`); 269 slides[new_current].removeAttribute('aria-hidden'); 270 271 // Update the text in the live region which is then announced by screen readers. 272 if (announceItem) { 273 carousel.querySelector('.liveregion').textContent = Drupal.t('Item @item of @total', { 274 '@item': new_current + 1, 275 '@total': slides.length, 276 }); 277 } 278 279 // Update the buttons in the slider navigation to match the currently displayed item 280 if (settings.slidenav) { 281 const buttons = carousel.querySelectorAll('.slidenav button[data-sli
281de]'); 282 for (let j = buttons.length - 1; j >= 0; j--) { 283 buttons[j].className = ''; 284 buttons[j].innerHTML = `<span class="${visuallyHiddenClass}">${ Drupal.t('News') }</span> ${ (j + 1) }`; 285 } 286 buttons[new_current].className = currentClass; 287 buttons[new_current].innerHTML = `<span class="${visuallyHiddenClass}">${ Drupal.t('News') }</span> ${ (new_current + 1) } <span class="${visuallyHiddenClass}">${ Drupal.t('(Current Item)') }</span>`; 288 } 289 290// Set the global index to the new current value 291 index = new_current; 292 293 } 294 295 // Function to advance to the next slide 296 function nextSlide(announceItem) { 297 announceItem = typeof announceItem !== 'undefined' ? announceItem : false; 298 299 const length = slides.length; 300 let new_current = index + 1; 301 302 if (new_current === length) { 303 new_current = 0; 304 } 305 306 // If we advance to the next slide, the previous needs to be 307 // visible to the user, so the third parameter is 'prev', not 308 // next. 309 setSlides(new_current, false, 'prev', announceItem); 310 311 // If the carousel is animated, advance to the next 312 // slide after 5s 313 if (settings.animate) { 314 timer = setTimeout(nextSlide, settings.autoplaySpeed); 315 } 316 317 } 318 319 // Function to advance to the previous slide 320 function prevSlide(announceItem) { 321 announceItem = typeof announceItem !== 'undefined' ? announceItem : false; 322 323 const length = slides.length; 324 let new_current = index - 1; 325 326 // If we are already on the first slide, show the last slide instead. 327 if (new_current < 0) { 328 new_current = length - 1; 329 } 330 331 // If we advance to the previous slide, the next needs to be 332 // visible to the user, so the third parameter is 'next', not 333 // prev. 334 setSlides(new_current, false, 'next', announceItem); 335 336 } 337 338 // Function to stop the animation 339 function stopAnimation() { 340 clearTimeout(timer); 341 settings.animate = false; 342 animationSuspended = false; 343 const _this = carousel.querySelector('[data-action]'); 344 if (!_this) { 345 return; 346 } 347 _this.innerHTML = `<span class="${visuallyHiddenClass}">${ Drupal.t('Start Animation') } </span>â¶`; 348 _this.setAttribute('data-action', 'start'); 349 } 350 351 // Function to start the animation 352 function startAnimation() { 353 settings.animate = true; 354 animationSuspended = false; 355 timer = setTimeout(nextSlide, settings.autoplaySpeed); 356 const _this = carousel.querySelector('[data-action]'); 357 if (!_this) { 358 return; 359 } 360 _this.innerHTML = `<span class="${visuallyHiddenClass}">${ Drupal.t('Stop Animation') } </span>ï¿`; 361 _this.setAttribute('data-action', 'stop'); 362 } 363 364 // Function to suspend the animation 365 function suspendAnimation() { 366 if (settings.animate) { 367 clearTimeout(timer); 368 settings.animate = false; 369 animationSuspended = true; 370 } 371 } 372 373 // Making some functions public 374 return { 375 init: init, 376 next: nextSlide, 377 prev: prevSlide, 378 goto: setSlides, 379 stop: stopAnimation, 380 start: startAnimation 381 }; 382 }); 383 384 Drupal.behaviors.councilPlatformGutenbergGalleryCarousel = { 385 attach: function (context, settings) { 386 const $galleries = $(once('council-platform-gutenberg-gallery-carousel', '.wp-block-gallery.is-style-carousel', context)); 387 $galleries.each((i, gallery) => { 388 const $gallery = $(gallery); 389 const carousel = new myCarousel(); 390 carousel.init({ 391 carousel: gallery, 392 slidenav: !$gallery.hasClass('carousel--no-slidenav'), 393 animate: $gallery.hasClass('carousel--controls'), 394 startAnimated: !$gallery.hasClass('carousel--no-autoplay'), 395 // @todo make this configurable. 396 autoplaySpeed: $gallery.attr('data-autoplay-speed') || 5000, 397 }); 398 }); 399 } 400 } 401})(jQuery, Drupal, drupalSettings, window.once);
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.