accordion.js

/**
 * @param {HTMLElement} element DOM element for component instantiation and scope
 * @param {Object} options
 * @param {String} options.sidebarItemSelector Selector for sidebar items that open on desktop
 * @param {String} options.sidebarBreakpoint Media query for the desktop breakpoint
 */
export class Accordion {
  /**
   * @static
   * Shorthand for instance creation and initialisation.
   *
   * @param {HTMLElement} root DOM element for component instantiation and scope
   *
   * @return {Accordion} An instance of Accordion.
   */
  static autoInit(root, { ACCORDION: defaultOptions = {} } = {}) {
    const accordion = new Accordion(root, defaultOptions);
    accordion.init();
    root.ECLAccordion = accordion;
    return accordion;
  }

  constructor(
    element,
    {
      sidebarItemSelector = '[data-desktop-open]',
      sidebarBreakpoint = '(min-width: 996px)',
    } = {},
  ) {
    if (!element || element.nodeType !== Node.ELEMENT_NODE) {
      throw new TypeError(
        'DOM element should be given to initialize this widget.',
      );
    }

    this.element = element;
    this.sidebarItemSelector = sidebarItemSelector;
    this.sidebarBreakpoint = sidebarBreakpoint;
    this.mediaQuery = null;
    this.sidebarItems = null;

    this.syncSidebarItems = this.syncSidebarItems.bind(this);
  }

  /**
   * Initialise component.
   */
  init() {
    if (!ECL) {
      throw new TypeError('Called init but ECL is not present');
    }
    ECL.components = ECL.components || new Map();

    // Sidebar variant: keep items open on desktop, collapsed on mobile
    this.sidebarItems = this.element.querySelectorAll(this.sidebarItemSelector);
    if (this.sidebarItems.length > 0) {
      this.mediaQuery = window.matchMedia(this.sidebarBreakpoint);
      this.mediaQuery.addEventListener('change', this.syncSidebarItems);
      this.syncSidebarItems();
    }

    this.element.setAttribute('data-ecl-auto-initialized', 'true');
    ECL.components.set(this.element, this);
  }

  /**
   * Destroy component.
   */
  destroy() {
    if (this.mediaQuery) {
      this.mediaQuery.removeEventListener('change', this.syncSidebarItems);
      this.mediaQuery = null;
    }
    if (this.element) {
      this.element.removeAttribute('data-ecl-auto-initialized');
      ECL.components.delete(this.element);
    }
  }

  /**
   * Set each sidebar item's open state based on the current breakpoint.
   */
  syncSidebarItems() {
    this.sidebarItems.forEach((item) => {
      item.open = this.mediaQuery.matches;
    });
  }
}

export default Accordion;