datepicker.js

/**
 * @param {HTMLElement} element DOM element for component instantiation and scope
 * @param {Object} options
 * @param {String} options.datepickerFormat Format for dates
 */
export class Datepicker {
  /**
   * @static
   * Shorthand for instance creation and initialisation.
   *
   * @param {HTMLElement} root DOM element for component instantiation and scope
   *
   * @return {Datepicker} An instance of Datepicker.
   */
  static autoInit(root, { DATEPICKER: defaultOptions = {} } = {}) {
    const datepicker = new Datepicker(root, defaultOptions);
    datepicker.init();
    root.ECLDatepicker = datepicker;
    return datepicker;
  }

  constructor(
    element,
    {
      format = '',
      theme = 'ecl-datepicker-theme',
      yearRange = 40,
      reposition = false,
      i18n = {
        previousMonth: 'Previous Month',
        nextMonth: 'Next Month',
        months: [
          'January',
          'February',
          'March',
          'April',
          'May',
          'June',
          'July',
          'August',
          'September',
          'October',
          'November',
          'December',
        ],
        weekdays: [
          'Sunday',
          'Monday',
          'Tuesday',
          'Wednesday',
          'Thursday',
          'Friday',
          'Saturday',
        ],
        weekdaysShort: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
      },
      showDaysInNextAndPreviousMonths = true,
      enableSelectionDaysInNextAndPreviousMonths = 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.picker = null;
    this.format = format;
    this.theme = theme;
    this.yearRange = yearRange;
    this.i18n = i18n;
    this.showDaysInNextAndPreviousMonths = showDaysInNextAndPreviousMonths;
    this.enableSelectionDaysInNextAndPreviousMonths =
      enableSelectionDaysInNextAndPreviousMonths;
    this.reposition = reposition;
    this.direction = 'ltr';
  }

  /**
   * Initialise component.
   */
  init() {
    if (typeof window.Pikaday === 'undefined') {
      throw new TypeError(
        'Pikaday is not available. Make sure to include Pikaday in your project if you want to use the ECL datepicker',
      );
    }
    if (!ECL) {
      throw new TypeError('Called init but ECL is not present');
    }
    ECL.components = ECL.components || new Map();

    this.direction = getComputedStyle(this.element).direction;

    const options = {
      field: this.element,
      yearRange: this.yearRange,
      firstDay: 1,
      i18n: this.i18n,
      theme: this.theme,
      reposition: this.reposition,
      isRTL: this.direction === 'rtl',
      position: this.direction === 'rtl' ? 'bottom right' : 'bottom left',
      showDaysInNextAndPreviousMonths: this.showDaysInNextAndPreviousMonths,
      enableSelectionDaysInNextAndPreviousMonths:
        this.enableSelectionDaysInNextAndPreviousMonths,
    };

    if (this.format !== '') {
      options.format = this.format;
    } else {
      options.toString = (date) => {
        const day = `0${date.getDate()}`.slice(-2);
        const month = `0${date.getMonth() + 1}`.slice(-2);
        const year = date.getFullYear();

        return `${day}-${month}-${year}`;
      };
    }

    // eslint-disable-next-line no-undef
    this.picker = new Pikaday({
      ...options,
      onOpen() {
        this.direction = getComputedStyle(this.el).direction;

        // Extend picker size on mobile
        const vw = Math.max(
          document.documentElement.clientWidth || 0,
          window.innerWidth || 0,
        );
        const elRect = this.el.getBoundingClientRect();

        if (this.direction === 'rtl') {
          const pickerMargin = vw - elRect.right;
          if (vw < 768) {
            this.el.style.left = `${pickerMargin}px`;
          } else {
            this.el.style.left = 'auto';
          }
        } else {
          const pickerMargin = elRect.left;
          if (vw < 768) {
            this.el.style.right = `${pickerMargin}px`;
          } else {
            this.el.style.right = 'auto';
          }
        }
      },
    });

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

    return this.picker;
  }

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

export default Datepicker;