PageSourceSearch

https://www.north-herts.gov.uk/profiles/contrib/council_platform/m…/js/gallery-carousel.js?tlx2c5

js north-herts.gov.uk collected 2026-10-02 04:39:44 UTC 14,970 bytes, 401 lines download raw bytes

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.