| /** | |
| * @license | |
| * Copyright 2021 The Go Authors. All rights reserved. | |
| * Use of this source code is governed by a BSD-style | |
| * license that can be found in the LICENSE file. | |
| */ | |
| /** | |
| * Carousel Controller adds event listeners, accessibility enhancements, and | |
| * control elements to a carousel component. | |
| */ | |
| export class CarouselController { | |
| /** | |
| * slides is a collection of slides in the carousel. | |
| */ | |
| private slides: HTMLLIElement[]; | |
| /** | |
| * dots is a collection of dot navigation controls, added to the carousel | |
| * by this controller. | |
| */ | |
| private dots: HTMLElement[]; | |
| /** | |
| * liveRegion is a visually hidden element that notifies assitive devices | |
| * of visual changes to the carousel. They are added to the carousel by | |
| * this controller. | |
| */ | |
| private liveRegion: HTMLElement; | |
| /** | |
| * activeIndex is the 0-index of the currently active slide. | |
| */ | |
| private activeIndex: number; | |
| constructor(private el: HTMLElement) { | |
| this.slides = Array.from(el.querySelectorAll('.go-Carousel-slide')); | |
| this.dots = []; | |
| this.liveRegion = document.createElement('div'); | |
| this.activeIndex = Number(el.getAttribute('data-slide-index') ?? 0); | |
| this.initSlides(); | |
| this.initArrows(); | |
| this.initDots(); | |
| this.initLiveRegion(); | |
| } | |
| private initSlides() { | |
| for (const [i, v] of this.slides.entries()) { | |
| if (i === this.activeIndex) continue; | |
| v.setAttribute('aria-hidden', 'true'); | |
| } | |
| } | |
| private initArrows() { | |
| const arrows = document.createElement('ul'); | |
| arrows.classList.add('go-Carousel-arrows'); | |
| arrows.innerHTML = ` | |
| <li> | |
| <button class="go-Carousel-prevSlide" aria-label="Go to previous slide"> | |
| <img class="go-Icon" height="24" width="24" src="/static/shared/icon/arrow_left_gm_grey_24dp.svg" alt=""> | |
| </button> | |
| </li> | |
| <li> | |
| <button class="go-Carousel-nextSlide" aria-label="Go to next slide"> | |
| <img class="go-Icon" height="24" width="24" src="/static/shared/icon/arrow_right_gm_grey_24dp.svg" alt=""> | |
| </button> | |
| </li> | |
| `; | |
| arrows | |
| .querySelector('.go-Carousel-prevSlide') | |
| ?.addEventListener('click', () => this.setActive(this.activeIndex - 1)); | |
| arrows | |
| .querySelector('.go-Carousel-nextSlide') | |
| ?.addEventListener('click', () => this.setActive(this.activeIndex + 1)); | |
| this.el.append(arrows); | |
| } | |
| private initDots() { | |
| const dots = document.createElement('ul'); | |
| dots.classList.add('go-Carousel-dots'); | |
| for (let i = 0; i < this.slides.length; i++) { | |
| const li = document.createElement('li'); | |
| const button = document.createElement('button'); | |
| button.classList.add('go-Carousel-dot'); | |
| if (i === this.activeIndex) { | |
| button.classList.add('go-Carousel-dot--active'); | |
| } | |
| button.innerHTML = `<span class="go-Carousel-obscured">Slide ${i + 1}</span>`; | |
| button.addEventListener('click', () => this.setActive(i)); | |
| li.append(button); | |
| dots.append(li); | |
| this.dots.push(button); | |
| } | |
| this.el.append(dots); | |
| } | |
| private initLiveRegion() { | |
| this.liveRegion.setAttribute('aria-live', 'polite'); | |
| this.liveRegion.setAttribute('aria-atomic', 'true'); | |
| this.liveRegion.setAttribute('class', 'go-Carousel-obscured'); | |
| this.liveRegion.textContent = `Slide ${this.activeIndex + 1} of ${this.slides.length}`; | |
| this.el.appendChild(this.liveRegion); | |
| } | |
| private setActive = (index: number) => { | |
| this.activeIndex = (index + this.slides.length) % this.slides.length; | |
| this.el.setAttribute('data-slide-index', String(this.activeIndex)); | |
| for (const d of this.dots) { | |
| d.classList.remove('go-Carousel-dot--active'); | |
| } | |
| this.dots[this.activeIndex].classList.add('go-Carousel-dot--active'); | |
| for (const s of this.slides) { | |
| s.setAttribute('aria-hidden', 'true'); | |
| } | |
| this.slides[this.activeIndex].removeAttribute('aria-hidden'); | |
| this.liveRegion.textContent = 'Slide ' + (this.activeIndex + 1) + ' of ' + this.slides.length; | |
| }; | |
| } |