Source: drawer/modules/drawer.js

/**
 * @summary aofl-drawer
 * @version 3.0.0
 * @since 3.0.0
 * @author Arian Khosravi <arian.khosravi@aofl.com>
 */
import {AoflElement} from '@aofl/element';
import styles from './drawer.css';
/**
 * @memberof module:@aofl/drawer
 * @extends {AoflElement}
 */
class AoflDrawer extends AoflElement {
  /**
   * Creates an instance of AoflDrawer.
   */
  constructor() {
    super();
    this.open = null;
    this.transitionEndCount = 0;
    this['transition-count'] = 1;
  }

  /**
   * @readonly
   * @type {String}
   */
  static get is() {
    return 'aofl-drawer';
  }

  /**
   * @readonly
   * @property {Boolean} open - state of drawer
   * @property {String} trigger - className that triggers animations
   * @property {String} opening - className of opening animation
   * @property {String} closing - className of closing animation
   */
  static get properties() {
    return {
      'open': {type: Boolean},
      'trigger': {type: String},
      'opening': {type: String},
      'closing': {type: String},
      'transition-count': {type: Number}
    };
  }

  /**
   *
   */
  connectedCallback(...args) {
    super.connectedCallback(...args);
    // Set defaults
    this.cancelOpen = null;
    this.trigger = this.trigger || 'animate';
    this.animated = typeof this.opening !== 'undefined' && typeof this.closing !== 'undefined';

    // Initialize class to prepare for next animation
    if (this.animated) {
      if (this.open) {
        this.classList.remove(this.opening);
        this.classList.add(this.closing);
      } else {
        this.classList.remove(this.closing);
        this.classList.add(this.opening);
      }

      this.addEventListener('animationend', this.animationEndHandler);
      this.addEventListener('transitionend', this.animationEndHandler);
    }
  }

  /**
   * @return {Object}
   */
  render() {
    return super.render((ctx, html) => html`<slot></slot>`, [styles]);
  }

  /**
   *
   * @param {*} name
   * @param {*} oldValue
   * @param {*} newValue
   */
  attributeChangedCallback(name, oldValue, newValue) {
    super.attributeChangedCallback(name, oldValue, newValue);
    /* istanbul ignore else */
    if (name === 'open') {
      if (this.animated) {
        this.openChanged(this.open);
      } else {
        this.dispatchEvent(new CustomEvent('drawer-toggle', {
          composed: true,
          bubbles: true
        }));
      }
    }
  }

  /**
   * @param {Boolean} newVal
   * @private
   */
  openChanged(newVal) {
    /* istanbul ignore else */
    if (newVal === true) {
      this.cancelOpen = this.startOpeningAnimation(() => {
        this.transitionEndCount = 0;
        this.classList.remove(this.closing);
        this.classList.add(this.opening);
        this.classList.add(this.trigger);
      });
    } else if (this.classList.contains(this.closing) || this.classList.contains(this.trigger)) {
      if (typeof this.cancelOpen === 'function') {
        this.cancelOpen();
        this.cancelOpen = null;
      }
      this.classList.remove(this.opening);
      this.classList.add(this.closing);
      this.classList.add(this.trigger);
      this.classList.add('closing');
    }
  }

  /**
   * Waits for display block to be present before adding animation
   *
   * @param {Function} callback
   * @return {Function}
   * @private
   */
  startOpeningAnimation(callback) {
    let cancel = false;
    let max = 100;

    const testDisplayBlock = () => {
      const display = window.getComputedStyle(this).getPropertyValue('display');
      /* istanbul ignore next */
      if (display === 'block') {
        callback();
      } else if (!cancel && max-- > 0) {
        window.requestAnimationFrame(testDisplayBlock);
      }
    };

    testDisplayBlock();
    return /* istanbul ignore next */ () => {
      cancel = true;
    };
  }

  /**
   * Removes closed after drawer closes so display: none can be applied
   *
   * @private
   */
  animationEndHandler(e) {
    /* istanbul ignore next */
    if (e.target !== this) {
      return;
    }
    this.transitionEndCount += 1;
    if (this.transitionEndCount === this['transition-count']) {
      this.transitionEndCount = 0;
      this.classList.remove(this.trigger);
      if (this.classList.contains(this.opening)) {
        this.classList.remove(this.opening);
        this.classList.add(this.closing);
        this.cancelOpen = null;
      } else {
        this.classList.add(this.opening);
        this.classList.remove(this.closing);
        this.classList.remove('closing');
      }

      setTimeout(() => { // queue micro task
        this.dispatchEvent(new CustomEvent('drawer-toggle', {
          composed: true,
          bubbles: true
        }));
      });
    }
  }

  /**
   *
   */
  disconnectedCallback(...args) {
    super.disconnectedCallback(...args);
    if (this.animated) {
      this.removeEventListener('animationend', this.animationEndHandler);
      this.removeEventListener('transitionend', this.animationEndHandler);
    }
  }
}

/* istanbul ignore else */
if (window.customElements.get(AoflDrawer.is) === void 0) {
  window.customElements.define(AoflDrawer.is, AoflDrawer);
}

export default AoflDrawer;