file.js

import { queryOne } from '@ecl/dom-utils';

/**
 * @param {HTMLElement} element DOM element for component instantiation and scope
 * @param {Object} options
 * @param {String} options.translationToggleSelector Selector for toggling translatoins section
 * @param {Boolean} options.attachClickListener Whether or not to bind click events on toggle
 */
export class FileDownload {
  /**
   * @static
   * Shorthand for instance creation and initialisation.
   *
   * @param {HTMLElement} root DOM element for component instantiation and scope
   *
   * @return {FileDownload} An instance of FileDownload.
   */
  static autoInit(root, { FILE_DOWNLOAD: defaultOptions = {} } = {}) {
    const fileDownload = new FileDownload(root, defaultOptions);
    fileDownload.init();
    root.ECLFileDownload = fileDownload;
    return fileDownload;
  }

  constructor(
    element,
    {
      translationToggleSelector = '[data-ecl-file-translation-toggle]',
      attachClickListener = true,
    } = {},
  ) {
    // Check element
    if (!element || element.nodeType !== Node.ELEMENT_NODE) {
      throw new TypeError(
        'DOM element should be given to initialize this widget.',
      );
    }

    this.element = element;

    // Options
    this.translationToggleSelector = translationToggleSelector;
    this.attachClickListener = attachClickListener;

    // Private variables
    this.translationToggle = null;
    this.translationContainer = null;

    // Bind `this` for use in callbacks
    this.handleClickOnToggle = this.handleClickOnToggle.bind(this);
    this.handleKeyboard = this.handleKeyboard.bind(this);
  }

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

    this.translationToggle = queryOne(
      this.translationToggleSelector,
      this.element,
    );

    // Get target element
    if (this.translationToggle) {
      this.translationContainer = document.querySelector(
        `#${this.translationToggle.getAttribute('aria-controls')}`,
      );
    }

    // Bind click event on toggle
    if (this.attachClickListener && this.translationToggle) {
      this.translationToggle.addEventListener(
        'click',
        this.handleClickOnToggle,
      );
      this.element.addEventListener('keyup', this.handleKeyboard);
    }

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

  /**
   * Destroy component.
   */
  destroy() {
    if (this.attachClickListener && this.translationToggle) {
      this.translationToggle.removeEventListener(
        'click',
        this.handleClickOnToggle,
      );
      this.element.removeEventListener('keyup', this.handleKeyboard);
    }
    if (this.element) {
      this.element.removeAttribute('data-ecl-auto-initialized');
      ECL.components.delete(this.element);
    }
  }

  /**
   * Adjusts translation item padding to keep actions aligned with the footer
   * when the list is tall enough to show a scrollbar.
   */
  alignTranslationActions() {
    const scrollbarWidth =
      this.translationContainer.offsetWidth -
      this.translationContainer.clientWidth;
    const items = this.translationContainer.querySelectorAll(
      '.ecl-file__translation-item',
    );

    if (items.length === 0) return;

    if (scrollbarWidth > 0) {
      const naturalPadding = parseFloat(
        getComputedStyle(items[0]).paddingInlineEnd,
      );
      const newPadding = Math.max(0, naturalPadding - scrollbarWidth);
      items.forEach((item) => {
        item.style.paddingInlineEnd = `${newPadding}px`;
      });
    } else {
      items.forEach((item) => {
        item.style.paddingInlineEnd = '';
      });
    }
  }

  /**
   * @param {Event} e
   */
  handleClickOnToggle(e) {
    e.preventDefault();

    // Exit if no target found
    if (!this.translationContainer) {
      throw new TypeError(
        'Target has to be provided for file download (aria-controls)',
      );
    }

    if (this.translationToggle.getAttribute('aria-expanded') === 'true') {
      this.closeTranslation();
    } else {
      this.openTranslation();
    }

    return this;
  }

  /**
   * Open the translation dropdown.
   */
  openTranslation() {
    this.element.classList.add('ecl-file--open');
    this.translationContainer.hidden = false;
    this.translationToggle.setAttribute('aria-expanded', 'true');
    this.alignTranslationActions();
  }

  /**
   * Close the translation dropdown.
   */
  closeTranslation() {
    this.element.classList.remove('ecl-file--open');
    this.translationContainer.hidden = true;
    this.translationToggle.setAttribute('aria-expanded', 'false');
    this.translationContainer
      .querySelectorAll('.ecl-file__translation-item')
      .forEach((item) => {
        item.style.paddingInlineEnd = '';
      });
  }

  /**
   * Handles keyboard events.
   *
   * @param {Event} e
   */
  handleKeyboard(e) {
    if (!this.translationToggle) return;

    // When pressing Esc close the translation dropdown, if open
    if (
      (e.key === 'Escape' || e.key === 'Esc') &&
      this.translationToggle.getAttribute('aria-expanded') === 'true'
    ) {
      this.closeTranslation();
      this.translationToggle.focus();
    }
  }
}

export default FileDownload;